/ tests / capture / screenshot-storage.test.js
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  });