screenshot-storage-supplement.test.js
1 /** 2 * Screenshot Storage Supplement - Coverage for uncovered paths 3 * 4 * Targets: 5 * - Lines 94-95: non-ENOENT error in loadScreenshots (cropped files) 6 * - Lines 114-117: non-ENOENT error in loadScreenshots (uncropped files) 7 * - Lines 176-202: croppedScreenshotsExist function (entirely uncovered) 8 * - Lines 251-255: non-ENOENT error in deleteScreenshots 9 * 10 * CRITICAL: Do NOT use mock.module() - it causes V8 coverage interference. 11 * Use SCREENSHOT_BASE_PATH env var set BEFORE import to control the base dir. 12 */ 13 14 import { test, describe } from 'node:test'; 15 import assert from 'node:assert/strict'; 16 import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'; 17 import { tmpdir } from 'os'; 18 import { join } from 'path'; 19 20 // Create a temp base directory for screenshots 21 const tempBase = mkdtempSync(join(tmpdir(), 'screenshot-storage-sup-')); 22 23 // Must set before import so module picks it up 24 process.env.SCREENSHOT_BASE_PATH = tempBase; 25 26 const { loadScreenshots, croppedScreenshotsExist, deleteScreenshots } = 27 await import('../../src/utils/screenshot-storage.js'); 28 29 // Helper to create a fake screenshot directory with real files 30 function createFakeSite(siteId, files = []) { 31 const siteDir = join(tempBase, siteId.toString()); 32 mkdirSync(siteDir, { recursive: true }); 33 for (const filename of files) { 34 writeFileSync(join(siteDir, filename), Buffer.from('fake-img-data')); 35 } 36 return `screenshots/${siteId}`; 37 } 38 39 // ====================================================== 40 // loadScreenshots - non-ENOENT error path (lines 94-95) 41 // ====================================================== 42 43 describe('loadScreenshots - non-ENOENT error in cropped files', () => { 44 test('throws non-ENOENT errors from cropped file reads', async () => { 45 // Create a siteDir where desktop_above.jpg is a directory, not a file 46 // This causes EISDIR when readFile is called on it 47 const siteId = 'err-cropped-1'; 48 const siteDir = join(tempBase, siteId); 49 mkdirSync(siteDir, { recursive: true }); 50 // Create desktop_above.jpg as a directory to force a non-ENOENT error 51 mkdirSync(join(siteDir, 'desktop_above.jpg'), { recursive: true }); 52 53 const screenshotPath = `screenshots/${siteId}`; 54 await assert.rejects( 55 async () => await loadScreenshots(screenshotPath), 56 err => { 57 // Should throw EISDIR (or similar non-ENOENT FS error) 58 assert.ok(err.code !== 'ENOENT', `Expected non-ENOENT error but got: ${err.code}`); 59 return true; 60 } 61 ); 62 }); 63 }); 64 65 // ====================================================== 66 // loadScreenshots - non-ENOENT error in uncropped files (lines 114-117) 67 // ====================================================== 68 69 describe('loadScreenshots - non-ENOENT error in uncropped files', () => { 70 test('throws non-ENOENT errors from uncropped file reads', async () => { 71 // Create a siteDir where a cropped file exists, but uncropped file is a directory 72 const siteId = 'err-uncropped-1'; 73 const siteDir = join(tempBase, siteId); 74 mkdirSync(siteDir, { recursive: true }); 75 // Provide valid cropped files 76 writeFileSync(join(siteDir, 'desktop_above.jpg'), Buffer.from('img')); 77 writeFileSync(join(siteDir, 'desktop_below.jpg'), Buffer.from('img')); 78 writeFileSync(join(siteDir, 'mobile_above.jpg'), Buffer.from('img')); 79 // Make uncropped file a directory (forces EISDIR read error) 80 mkdirSync(join(siteDir, 'desktop_above_uncropped.jpg'), { recursive: true }); 81 82 const screenshotPath = `screenshots/${siteId}`; 83 await assert.rejects( 84 async () => await loadScreenshots(screenshotPath, { includeUncropped: true }), 85 err => { 86 assert.ok(err.code !== 'ENOENT', `Expected non-ENOENT error but got: ${err.code}`); 87 return true; 88 } 89 ); 90 }); 91 92 test('ENOENT in uncropped files is silently ignored', async () => { 93 // Cropped files present, uncropped files simply missing (ENOENT) - should not throw 94 const siteId = 'uncropped-enoent-1'; 95 const siteDir = join(tempBase, siteId); 96 mkdirSync(siteDir, { recursive: true }); 97 writeFileSync(join(siteDir, 'desktop_above.jpg'), Buffer.from('img')); 98 writeFileSync(join(siteDir, 'desktop_below.jpg'), Buffer.from('img')); 99 writeFileSync(join(siteDir, 'mobile_above.jpg'), Buffer.from('img')); 100 // No uncropped files - should just silently skip them 101 102 const screenshotPath = `screenshots/${siteId}`; 103 const result = await loadScreenshots(screenshotPath, { includeUncropped: true }); 104 assert.ok(result.desktop_above, 'Should have desktop_above'); 105 assert.equal( 106 result.desktop_above_uncropped, 107 undefined, 108 'Uncropped should be undefined when missing' 109 ); 110 }); 111 }); 112 113 // ====================================================== 114 // croppedScreenshotsExist - entirely uncovered (lines 176-202) 115 // ====================================================== 116 117 describe('croppedScreenshotsExist', () => { 118 test('returns exists=false with all three missing when screenshotPath is null', async () => { 119 const result = await croppedScreenshotsExist(null); 120 assert.equal(result.exists, false); 121 assert.deepEqual(result.missing, ['desktop_above', 'desktop_below', 'mobile_above']); 122 }); 123 124 test('returns exists=false with all three missing when screenshotPath is empty string', async () => { 125 const result = await croppedScreenshotsExist(''); 126 assert.equal(result.exists, false); 127 assert.deepEqual(result.missing, ['desktop_above', 'desktop_below', 'mobile_above']); 128 }); 129 130 test('returns exists=true when all three cropped screenshots are present', async () => { 131 const screenshotPath = createFakeSite('cropped-exist-1', [ 132 'desktop_above.jpg', 133 'desktop_below.jpg', 134 'mobile_above.jpg', 135 ]); 136 const result = await croppedScreenshotsExist(screenshotPath); 137 assert.equal(result.exists, true); 138 assert.deepEqual(result.missing, []); 139 }); 140 141 test('returns exists=false when desktop_below is missing', async () => { 142 const screenshotPath = createFakeSite('cropped-exist-2', [ 143 'desktop_above.jpg', 144 'mobile_above.jpg', 145 // No desktop_below.jpg 146 ]); 147 const result = await croppedScreenshotsExist(screenshotPath); 148 assert.equal(result.exists, false); 149 assert.ok(result.missing.includes('desktop_below')); 150 }); 151 152 test('returns exists=false when all three cropped files are missing', async () => { 153 const screenshotPath = createFakeSite('cropped-exist-3', [ 154 // Only uncropped files present, no cropped 155 'desktop_above_uncropped.jpg', 156 ]); 157 const result = await croppedScreenshotsExist(screenshotPath); 158 assert.equal(result.exists, false); 159 assert.equal(result.missing.length, 3); 160 }); 161 162 test('returns exists=false when only mobile_above is missing', async () => { 163 const screenshotPath = createFakeSite('cropped-exist-4', [ 164 'desktop_above.jpg', 165 'desktop_below.jpg', 166 // No mobile_above.jpg 167 ]); 168 const result = await croppedScreenshotsExist(screenshotPath); 169 assert.equal(result.exists, false); 170 assert.deepEqual(result.missing, ['mobile_above']); 171 }); 172 173 test('returns exists=false when only desktop_above is missing', async () => { 174 const screenshotPath = createFakeSite('cropped-exist-5', [ 175 'desktop_below.jpg', 176 'mobile_above.jpg', 177 // No desktop_above.jpg 178 ]); 179 const result = await croppedScreenshotsExist(screenshotPath); 180 assert.equal(result.exists, false); 181 assert.deepEqual(result.missing, ['desktop_above']); 182 }); 183 184 test('ignores uncropped files - only checks 3 essential cropped', async () => { 185 // Only uncropped files present, none of the 3 cropped ones 186 const screenshotPath = createFakeSite('cropped-exist-6', [ 187 'desktop_above_uncropped.jpg', 188 'desktop_below_uncropped.jpg', 189 'mobile_above_uncropped.jpg', 190 ]); 191 const result = await croppedScreenshotsExist(screenshotPath); 192 assert.equal(result.exists, false); 193 assert.equal(result.missing.length, 3); 194 }); 195 196 test('extracts site ID from path correctly for nested-style paths', async () => { 197 // screenshotPath like "screenshots/99" -> siteId "99" 198 const screenshotPath = createFakeSite('99', [ 199 'desktop_above.jpg', 200 'desktop_below.jpg', 201 'mobile_above.jpg', 202 ]); 203 assert.equal(screenshotPath, 'screenshots/99'); 204 const result = await croppedScreenshotsExist(screenshotPath); 205 assert.equal(result.exists, true); 206 }); 207 }); 208 209 // ====================================================== 210 // deleteScreenshots - non-ENOENT throw path (lines 251-255) 211 // ====================================================== 212 213 describe('deleteScreenshots - non-ENOENT error path', () => { 214 test('does not throw when ENOENT (directory does not exist)', async () => { 215 // A path pointing to a non-existent directory - should not throw 216 const screenshotPath = 'screenshots/nonexistent-999'; 217 await assert.doesNotReject(async () => await deleteScreenshots(screenshotPath)); 218 }); 219 220 test('returns early for null screenshotPath', async () => { 221 await assert.doesNotReject(async () => await deleteScreenshots(null)); 222 }); 223 224 test('returns early for empty screenshotPath', async () => { 225 await assert.doesNotReject(async () => await deleteScreenshots('')); 226 }); 227 228 test('successfully deletes existing directory', async () => { 229 const screenshotPath = createFakeSite('delete-test-1', ['desktop_above.jpg']); 230 // Directory should exist before 231 const siteDir = join(tempBase, 'delete-test-1'); 232 const { existsSync } = await import('fs'); 233 assert.ok(existsSync(siteDir), 'Directory should exist before delete'); 234 235 await deleteScreenshots(screenshotPath); 236 237 assert.ok(!existsSync(siteDir), 'Directory should not exist after delete'); 238 }); 239 }); 240 241 // Cleanup temp directory at end 242 process.on('exit', () => { 243 try { 244 rmSync(tempBase, { recursive: true, force: true }); 245 } catch (_) { 246 // ignore 247 } 248 });