fix(archives): write a real timestamp on restored photos (#1257)
Brings this branch in line with main, which fixed it in passing. The archive restore inserted photos with a bare Date for uploaded_at. Inside jest the sqlite3 binding's type dispatch misses sandbox-created Dates and stores the literal string "[object Object]", so every restored photo got a garbage timestamp. Verified on this branch rather than assumed: bare Date -> "[object Object]" toISOString -> "2026-09-01T06:48:41.915Z" Production writes Dates as ms-numbers and is unaffected, which is exactly why it survives unnoticed — it only corrupts what tests read back, so a future test asserting on a restored photo's date would have believed it. The regression test fails against the previous line. Not touched: the category insert a few lines up has the same shape, but it is identical on main, so fixing it here alone would re-open the divergence this commit closes. Worth one small PR against both branches. Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
a01731d986
commit
fed99ac03d
@@ -141,6 +141,27 @@ describe('archive restore restores categories (flat archives included)', () => {
|
|||||||
expect(await categoryOf('b.jpg')).toBe('Ceremony');
|
expect(await categoryOf('b.jpg')).toBe('Ceremony');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stores a real timestamp on restored photos, not "[object Object]"', async () => {
|
||||||
|
// The jest+sqlite landmine: a Date handed to knex inside jest stores as
|
||||||
|
// the literal string "[object Object]". Production writes ms-numbers and
|
||||||
|
// is unaffected, so this only ever corrupts what tests read back — which
|
||||||
|
// is how it survives unnoticed.
|
||||||
|
const archiveRelPath = await writeArchive('timestamp.zip', {
|
||||||
|
'individual/STAMPED.jpg': PIXEL,
|
||||||
|
'photos_manifest.json': Buffer.from(JSON.stringify([
|
||||||
|
{ filename: 'STAMPED.jpg', original_filename: 'STAMPED.jpg', category_name: 'Ceremony' },
|
||||||
|
]), 'utf8'),
|
||||||
|
});
|
||||||
|
const eventId = await seedArchivedEvent(archiveRelPath, 'timestamp-event');
|
||||||
|
|
||||||
|
const res = await request(app).post(`/admin/archives/${eventId}/restore`).send({});
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
|
const photo = await db('photos').where({ event_id: eventId, filename: 'STAMPED.jpg' }).first();
|
||||||
|
expect(String(photo.uploaded_at)).not.toBe('[object Object]');
|
||||||
|
expect(Number.isNaN(new Date(photo.uploaded_at).getTime())).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('reuses an existing category row instead of creating a duplicate', async () => {
|
it('reuses an existing category row instead of creating a duplicate', async () => {
|
||||||
const archiveRelPath = await writeArchive('reuse.zip', {
|
const archiveRelPath = await writeArchive('reuse.zip', {
|
||||||
'c.jpg': PIXEL,
|
'c.jpg': PIXEL,
|
||||||
|
|||||||
@@ -464,7 +464,13 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
|||||||
type: path.extname(filename).substring(1).toLowerCase(),
|
type: path.extname(filename).substring(1).toLowerCase(),
|
||||||
size_bytes: stats.size,
|
size_bytes: stats.size,
|
||||||
category_id: categoryId,
|
category_id: categoryId,
|
||||||
uploaded_at: new Date()
|
// .toISOString(), not a Date: inside jest the sqlite3 binding's
|
||||||
|
// type dispatch misses sandbox-created Dates and stores the
|
||||||
|
// literal string "[object Object]", so every restored photo
|
||||||
|
// gets a garbage timestamp that any test reading it would
|
||||||
|
// believe. Production stores Dates as ms-numbers and is
|
||||||
|
// unaffected — which is exactly why this survives unnoticed.
|
||||||
|
uploaded_at: new Date().toISOString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (statError) {
|
} catch (statError) {
|
||||||
|
|||||||
Reference in New Issue
Block a user