|
12 | 12 | */ |
13 | 13 |
|
14 | 14 | import { beforeAll, beforeEach, describe, expect, test } from 'vitest'; |
15 | | -import type { ReadFileResult } from '@repo/shared'; |
| 15 | +import type { FileInfo, ListFilesResult, ReadFileResult } from '@repo/shared'; |
16 | 16 | import type { ErrorResponse } from './test-worker/types'; |
17 | 17 | import { |
18 | 18 | getSharedSandbox, |
@@ -135,4 +135,128 @@ describe('File Operations Error Handling', () => { |
135 | 135 | const wrongTypeData = (await wrongTypeResponse.json()) as ErrorResponse; |
136 | 136 | expect(wrongTypeData.error).toMatch(/not a directory/i); |
137 | 137 | }, 90000); |
| 138 | + |
| 139 | + // Regression test for #196: hidden files in hidden directories |
| 140 | + test('should list files in hidden directories with includeHidden flag', async () => { |
| 141 | + const hiddenDir = `${testDir}/.hidden/foo`; |
| 142 | + |
| 143 | + // Create hidden directory structure |
| 144 | + await fetch(`${workerUrl}/api/file/mkdir`, { |
| 145 | + method: 'POST', |
| 146 | + headers, |
| 147 | + body: JSON.stringify({ path: `${hiddenDir}/bar`, recursive: true }) |
| 148 | + }); |
| 149 | + |
| 150 | + // Write visible files in hidden directory |
| 151 | + await fetch(`${workerUrl}/api/file/write`, { |
| 152 | + method: 'POST', |
| 153 | + headers, |
| 154 | + body: JSON.stringify({ |
| 155 | + path: `${hiddenDir}/visible1.txt`, |
| 156 | + content: 'Visible 1' |
| 157 | + }) |
| 158 | + }); |
| 159 | + await fetch(`${workerUrl}/api/file/write`, { |
| 160 | + method: 'POST', |
| 161 | + headers, |
| 162 | + body: JSON.stringify({ |
| 163 | + path: `${hiddenDir}/visible2.txt`, |
| 164 | + content: 'Visible 2' |
| 165 | + }) |
| 166 | + }); |
| 167 | + |
| 168 | + // Write hidden file in hidden directory |
| 169 | + await fetch(`${workerUrl}/api/file/write`, { |
| 170 | + method: 'POST', |
| 171 | + headers, |
| 172 | + body: JSON.stringify({ |
| 173 | + path: `${hiddenDir}/.hiddenfile.txt`, |
| 174 | + content: 'Hidden' |
| 175 | + }) |
| 176 | + }); |
| 177 | + |
| 178 | + // List WITHOUT includeHidden - should NOT show .hiddenfile.txt |
| 179 | + const listResponse = await fetch(`${workerUrl}/api/list-files`, { |
| 180 | + method: 'POST', |
| 181 | + headers, |
| 182 | + body: JSON.stringify({ path: hiddenDir }) |
| 183 | + }); |
| 184 | + |
| 185 | + expect(listResponse.status).toBe(200); |
| 186 | + const listData = (await listResponse.json()) as ListFilesResult; |
| 187 | + expect(listData.success).toBe(true); |
| 188 | + |
| 189 | + const visibleFiles = listData.files.filter( |
| 190 | + (f: FileInfo) => !f.name.startsWith('.') |
| 191 | + ); |
| 192 | + expect(visibleFiles.length).toBe(3); // visible1.txt, visible2.txt, bar/ |
| 193 | + |
| 194 | + const hiddenFile = listData.files.find( |
| 195 | + (f: FileInfo) => f.name === '.hiddenfile.txt' |
| 196 | + ); |
| 197 | + expect(hiddenFile).toBeUndefined(); |
| 198 | + |
| 199 | + // List WITH includeHidden - should show all files |
| 200 | + const listWithHiddenResponse = await fetch(`${workerUrl}/api/list-files`, { |
| 201 | + method: 'POST', |
| 202 | + headers, |
| 203 | + body: JSON.stringify({ |
| 204 | + path: hiddenDir, |
| 205 | + options: { includeHidden: true } |
| 206 | + }) |
| 207 | + }); |
| 208 | + |
| 209 | + expect(listWithHiddenResponse.status).toBe(200); |
| 210 | + const listWithHiddenData = |
| 211 | + (await listWithHiddenResponse.json()) as ListFilesResult; |
| 212 | + |
| 213 | + expect(listWithHiddenData.success).toBe(true); |
| 214 | + expect(listWithHiddenData.files.length).toBe(4); // +.hiddenfile.txt |
| 215 | + |
| 216 | + const hiddenFileWithFlag = listWithHiddenData.files.find( |
| 217 | + (f: FileInfo) => f.name === '.hiddenfile.txt' |
| 218 | + ); |
| 219 | + expect(hiddenFileWithFlag).toBeDefined(); |
| 220 | + }, 90000); |
| 221 | + |
| 222 | + test('should read binary files with base64 encoding', async () => { |
| 223 | + // 1x1 PNG - smallest valid PNG |
| 224 | + const pngBase64 = |
| 225 | + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jYlkKQAAAABJRU5ErkJggg=='; |
| 226 | + |
| 227 | + // Create binary file via base64 decode |
| 228 | + await fetch(`${workerUrl}/api/file/mkdir`, { |
| 229 | + method: 'POST', |
| 230 | + headers, |
| 231 | + body: JSON.stringify({ path: testDir, recursive: true }) |
| 232 | + }); |
| 233 | + |
| 234 | + await fetch(`${workerUrl}/api/execute`, { |
| 235 | + method: 'POST', |
| 236 | + headers, |
| 237 | + body: JSON.stringify({ |
| 238 | + command: `echo '${pngBase64}' | base64 -d > ${testDir}/test.png` |
| 239 | + }) |
| 240 | + }); |
| 241 | + |
| 242 | + // Read the binary file |
| 243 | + const readResponse = await fetch(`${workerUrl}/api/file/read`, { |
| 244 | + method: 'POST', |
| 245 | + headers, |
| 246 | + body: JSON.stringify({ path: `${testDir}/test.png` }) |
| 247 | + }); |
| 248 | + |
| 249 | + expect(readResponse.status).toBe(200); |
| 250 | + const readData = (await readResponse.json()) as ReadFileResult; |
| 251 | + |
| 252 | + expect(readData.success).toBe(true); |
| 253 | + expect(readData.encoding).toBe('base64'); |
| 254 | + expect(readData.isBinary).toBe(true); |
| 255 | + expect(readData.mimeType).toMatch(/image\/png/); |
| 256 | + expect(readData.content).toBeTruthy(); |
| 257 | + expect(readData.size).toBeGreaterThan(0); |
| 258 | + |
| 259 | + // Verify the content is valid base64 |
| 260 | + expect(readData.content).toMatch(/^[A-Za-z0-9+/=]+$/); |
| 261 | + }, 90000); |
138 | 262 | }); |
0 commit comments