feat: include history in export/import backup
Export now returns version 2 with a history array alongside instances. Import accepts the history array and restores all audit events. v1 backups without a history key still import cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -275,6 +275,18 @@ describe('GET /api/export', () => {
|
||||
const res = await request(app).get('/api/export')
|
||||
expect(res.body.instances).toEqual([])
|
||||
})
|
||||
|
||||
it('returns version 2', async () => {
|
||||
const res = await request(app).get('/api/export')
|
||||
expect(res.body.version).toBe(2)
|
||||
})
|
||||
|
||||
it('includes a history array', async () => {
|
||||
await request(app).post('/api/instances').send(base)
|
||||
const res = await request(app).get('/api/export')
|
||||
expect(res.body.history).toBeInstanceOf(Array)
|
||||
expect(res.body.history.some(e => e.field === 'created')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ── POST /api/import ──────────────────────────────────────────────────────────
|
||||
@@ -309,6 +321,26 @@ describe('POST /api/import', () => {
|
||||
.send({ instances: [{ ...base, name: undefined, vmid: 1 }] })
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('restores history when history array is provided', async () => {
|
||||
await request(app).post('/api/instances').send(base)
|
||||
const exp = await request(app).get('/api/export')
|
||||
await request(app).post('/api/instances').send({ ...base, vmid: 999, name: 'other' })
|
||||
const res = await request(app).post('/api/import').send({
|
||||
instances: exp.body.instances,
|
||||
history: exp.body.history,
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const hist = await request(app).get('/api/instances/100/history')
|
||||
expect(hist.body.some(e => e.field === 'created')).toBe(true)
|
||||
})
|
||||
|
||||
it('succeeds with a v1 backup that has no history key', async () => {
|
||||
const res = await request(app).post('/api/import')
|
||||
.send({ instances: [{ ...base, vmid: 1, name: 'legacy' }] })
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.imported).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Static assets & SPA routing ───────────────────────────────────────────────
|
||||
|
||||
@@ -202,6 +202,15 @@ describe('importInstances', () => {
|
||||
importInstances([{ ...base, name: 'new', vmid: 2 }]);
|
||||
expect(getInstanceHistory(1)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('restores history rows when provided', () => {
|
||||
importInstances(
|
||||
[{ ...base, name: 'a', vmid: 1 }],
|
||||
[{ vmid: 1, field: 'created', old_value: null, new_value: null, changed_at: '2026-01-01 00:00:00' }]
|
||||
);
|
||||
const h = getInstanceHistory(1);
|
||||
expect(h.some(e => e.field === 'created')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── instance history ─────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user