screenshot-storage.test.js
1 /** 2 * Integration Tests for Screenshot Storage Utility 3 */ 4 5 import { describe, it, before, after } from 'node:test'; 6 import assert from 'node:assert'; 7 import fs from 'fs/promises'; 8 import path from 'path'; 9 import os from 'os'; 10 11 // Set test environment with unique temp directory 12 const testDir = path.join(os.tmpdir(), `screenshot-test-${Date.now()}`); 13 process.env.SCREENSHOT_BASE_PATH = testDir; 14 15 // Now import the module 16 const { 17 loadScreenshot, 18 screenshotsExist, 19 allScreenshotsExist, 20 deleteScreenshots, 21 loadScreenshots, 22 saveScreenshots, 23 SCREENSHOT_FILES, 24 } = await import('../../src/utils/screenshot-storage.js'); 25 26 describe('Screenshot Storage Module', () => { 27 before(async () => { 28 await fs.mkdir(testDir, { recursive: true }); 29 }); 30 31 after(async () => { 32 try { 33 await fs.rm(testDir, { recursive: true, force: true }); 34 } catch { 35 // Ignore cleanup errors 36 } 37 }); 38 39 describe('loadScreenshot', () => { 40 it('should throw error when screenshot path is missing', async () => { 41 await assert.rejects(async () => await loadScreenshot('', 'desktop_above'), { 42 message: 'Screenshot path is required', 43 }); 44 }); 45 46 it('should throw error for invalid screenshot type', async () => { 47 await assert.rejects(async () => await loadScreenshot('screenshots/123', 'invalid_type'), { 48 message: /Invalid screenshot type/, 49 }); 50 }); 51 52 it('should load screenshot with valid path and type', async () => { 53 const siteId = 'load-test-123'; 54 const siteDir = path.join(testDir, siteId); 55 await fs.mkdir(siteDir, { recursive: true }); 56 57 const expectedBuffer = Buffer.from('test image data'); 58 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), expectedBuffer); 59 60 const result = await loadScreenshot(`screenshots/${siteId}`, 'desktop_above'); 61 62 assert.ok(Buffer.isBuffer(result)); 63 assert.deepStrictEqual(result, expectedBuffer); 64 65 await fs.rm(siteDir, { recursive: true }); 66 }); 67 68 it('should handle file read errors', async () => { 69 await assert.rejects( 70 async () => await loadScreenshot('screenshots/nonexistent', 'mobile_above'), 71 { 72 code: 'ENOENT', 73 } 74 ); 75 }); 76 }); 77 78 describe('screenshotsExist', () => { 79 it('should return false when screenshot path is missing', async () => { 80 const result = await screenshotsExist(''); 81 assert.strictEqual(result, false); 82 }); 83 84 it('should return false when screenshot path is null', async () => { 85 const result = await screenshotsExist(null); 86 assert.strictEqual(result, false); 87 }); 88 89 it('should return true when desktop_above screenshot exists', async () => { 90 const siteId = 'exist-test-123'; 91 const siteDir = path.join(testDir, siteId); 92 await fs.mkdir(siteDir, { recursive: true }); 93 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), Buffer.from('test')); 94 95 const result = await screenshotsExist(`screenshots/${siteId}`); 96 97 assert.strictEqual(result, true); 98 99 await fs.rm(siteDir, { recursive: true }); 100 }); 101 102 it('should return false when desktop_above screenshot does not exist', async () => { 103 const result = await screenshotsExist('screenshots/nonexistent'); 104 assert.strictEqual(result, false); 105 }); 106 }); 107 108 describe('allScreenshotsExist', () => { 109 it('should return all missing when screenshot path is empty', async () => { 110 const result = await allScreenshotsExist(''); 111 112 assert.strictEqual(result.exists, false); 113 assert.strictEqual(result.missing.length, 6); 114 assert.deepStrictEqual(result.missing, Object.keys(SCREENSHOT_FILES)); 115 }); 116 117 it('should return exists=true when all screenshots are present', async () => { 118 const siteId = 'all-exist-123'; 119 const siteDir = path.join(testDir, siteId); 120 await fs.mkdir(siteDir, { recursive: true }); 121 122 // Create all 6 screenshots 123 for (const filename of Object.values(SCREENSHOT_FILES)) { 124 await fs.writeFile(path.join(siteDir, filename), Buffer.from('test')); 125 } 126 127 const result = await allScreenshotsExist(`screenshots/${siteId}`); 128 129 assert.strictEqual(result.exists, true); 130 assert.strictEqual(result.missing.length, 0); 131 132 await fs.rm(siteDir, { recursive: true }); 133 }); 134 135 it('should return exists=false with missing list when some are missing', async () => { 136 const siteId = 'partial-exist-123'; 137 const siteDir = path.join(testDir, siteId); 138 await fs.mkdir(siteDir, { recursive: true }); 139 140 // Only create 4 out of 6 screenshots 141 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), Buffer.from('test')); 142 await fs.writeFile(path.join(siteDir, 'desktop_above_uncropped.jpg'), Buffer.from('test')); 143 await fs.writeFile(path.join(siteDir, 'mobile_above.jpg'), Buffer.from('test')); 144 await fs.writeFile(path.join(siteDir, 'mobile_above_uncropped.jpg'), Buffer.from('test')); 145 146 const result = await allScreenshotsExist(`screenshots/${siteId}`); 147 148 assert.strictEqual(result.exists, false); 149 assert.strictEqual(result.missing.length, 2); 150 assert.ok(result.missing.includes('desktop_below')); 151 assert.ok(result.missing.includes('desktop_below_uncropped')); 152 153 await fs.rm(siteDir, { recursive: true }); 154 }); 155 }); 156 157 describe('deleteScreenshots', () => { 158 it('should return early when screenshot path is empty', async () => { 159 // Should not throw 160 await deleteScreenshots(''); 161 }); 162 163 it('should return early when screenshot path is null', async () => { 164 // Should not throw 165 await deleteScreenshots(null); 166 }); 167 168 it('should delete screenshots directory successfully', async () => { 169 const siteId = 'delete-test-123'; 170 const siteDir = path.join(testDir, siteId); 171 await fs.mkdir(siteDir, { recursive: true }); 172 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), Buffer.from('test')); 173 174 await deleteScreenshots(`screenshots/${siteId}`); 175 176 // Verify directory was deleted 177 await assert.rejects(async () => await fs.access(siteDir), { 178 code: 'ENOENT', 179 }); 180 }); 181 182 it('should handle ENOENT errors gracefully', async () => { 183 // Should not throw 184 await deleteScreenshots('screenshots/nonexistent'); 185 }); 186 }); 187 188 describe('loadScreenshots', () => { 189 it('should throw error when screenshot path is missing', async () => { 190 await assert.rejects(async () => await loadScreenshots(''), { 191 message: 'Screenshot path is required', 192 }); 193 }); 194 195 it('should load only cropped screenshots by default', async () => { 196 const siteId = 'load-cropped-123'; 197 const siteDir = path.join(testDir, siteId); 198 await fs.mkdir(siteDir, { recursive: true }); 199 200 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), Buffer.from('d_above')); 201 await fs.writeFile(path.join(siteDir, 'desktop_below.jpg'), Buffer.from('d_below')); 202 await fs.writeFile(path.join(siteDir, 'mobile_above.jpg'), Buffer.from('m_above')); 203 await fs.writeFile( 204 path.join(siteDir, 'desktop_above_uncropped.jpg'), 205 Buffer.from('uncropped') 206 ); 207 208 const result = await loadScreenshots(`screenshots/${siteId}`); 209 210 assert.ok(result.desktop_above); 211 assert.ok(result.desktop_below); 212 assert.ok(result.mobile_above); 213 assert.strictEqual(result.desktop_above_uncropped, undefined); 214 215 await fs.rm(siteDir, { recursive: true }); 216 }); 217 218 it('should load uncropped screenshots when requested', async () => { 219 const siteId = 'load-uncropped-123'; 220 const siteDir = path.join(testDir, siteId); 221 await fs.mkdir(siteDir, { recursive: true }); 222 223 for (const filename of Object.values(SCREENSHOT_FILES)) { 224 await fs.writeFile(path.join(siteDir, filename), Buffer.from('test')); 225 } 226 227 const result = await loadScreenshots(`screenshots/${siteId}`, { includeUncropped: true }); 228 229 assert.ok(result.desktop_above); 230 assert.ok(result.desktop_above_uncropped); 231 assert.ok(result.mobile_above_uncropped); 232 233 await fs.rm(siteDir, { recursive: true }); 234 }); 235 236 it('should handle missing optional files gracefully', async () => { 237 const siteId = 'load-optional-123'; 238 const siteDir = path.join(testDir, siteId); 239 await fs.mkdir(siteDir, { recursive: true }); 240 241 // Only create desktop_above and mobile_above (desktop_below is optional) 242 await fs.writeFile(path.join(siteDir, 'desktop_above.jpg'), Buffer.from('d_above')); 243 await fs.writeFile(path.join(siteDir, 'mobile_above.jpg'), Buffer.from('m_above')); 244 245 const result = await loadScreenshots(`screenshots/${siteId}`); 246 247 assert.ok(result.desktop_above); 248 assert.strictEqual(result.desktop_below, undefined); 249 assert.ok(result.mobile_above); 250 251 await fs.rm(siteDir, { recursive: true }); 252 }); 253 }); 254 255 describe('saveScreenshots', () => { 256 it('should create directory and save all screenshots', async () => { 257 const siteId = 123; 258 const screenshots = { 259 desktop_above: Buffer.from('d_above'), 260 desktop_below: Buffer.from('d_below'), 261 mobile_above: Buffer.from('m_above'), 262 desktop_above_uncropped: Buffer.from('d_above_unc'), 263 desktop_below_uncropped: Buffer.from('d_below_unc'), 264 mobile_above_uncropped: Buffer.from('m_above_unc'), 265 }; 266 267 const result = await saveScreenshots(siteId, screenshots); 268 269 assert.strictEqual(result, 'screenshots/123'); 270 271 // Verify all files were created 272 const siteDir = path.join(testDir, '123'); 273 for (const filename of Object.values(SCREENSHOT_FILES)) { 274 const filePath = path.join(siteDir, filename); 275 await assert.doesNotReject(async () => await fs.access(filePath)); 276 } 277 278 await fs.rm(siteDir, { recursive: true }); 279 }); 280 281 it('should skip missing screenshots', async () => { 282 const siteId = 456; 283 const screenshots = { 284 desktop_above: Buffer.from('d_above'), 285 mobile_above: Buffer.from('m_above'), 286 }; 287 288 await saveScreenshots(siteId, screenshots); 289 290 const siteDir = path.join(testDir, '456'); 291 292 // Verify only 2 files were created 293 await assert.doesNotReject( 294 async () => await fs.access(path.join(siteDir, 'desktop_above.jpg')) 295 ); 296 await assert.doesNotReject( 297 async () => await fs.access(path.join(siteDir, 'mobile_above.jpg')) 298 ); 299 await assert.rejects(async () => await fs.access(path.join(siteDir, 'desktop_below.jpg')), { 300 code: 'ENOENT', 301 }); 302 303 await fs.rm(siteDir, { recursive: true }); 304 }); 305 }); 306 });