feat(p10): archivage automatique des sessions terminées (rétention configurable)
- DB: migration #10 (sessions.archived_at + index), helpers archive/unarchive/archiveExpired (soft-archive, jamais de DELETE) - core/retention-settings.ts: session_retention_days (défaut 30, 0=jamais, 1-3650) + session_purge_days (off) - core/session-archive.ts: scheduler start/stop/sweep (calqué DiscoveryService), câblé dans runDaemon - pty-manager.list({includeArchived}) + SessionSummary.archived + emitHistoricalUpdate - routes: GET /sessions?includeArchived, POST/DELETE /sessions/:id/archive, POST /sessions/archive-now - protocole additif: message WS session_archived (relayé par la gateway, abonnés 'sessions') - settings: retentionDays/purgeDays exposés et validés dans PATCH /settings - web: store sessions (showArchived + archive/unarchive), SessionsListView (toggle/badge/actions), SettingsView (slider rétention), i18n EN+FR - tests: retention-settings + session-archive (vitest) + settings-routes étendu ; acceptance-p10.mjs (sweep, event WS, resume d'une session archivée → 201, rétention=0)
This commit is contained in:
@@ -84,6 +84,9 @@ describe('GET /api/v1/settings', () => {
|
||||
expect(body.settings.claudeHome).toBeNull();
|
||||
expect(body.server.claudeHome).toBe(join(dir, 'claude'));
|
||||
expect(body.server.claudeBin).toEqual({ path: '/usr/bin/claude', source: 'path', ok: true });
|
||||
// rétention des sessions (P10) : défaut 30 j d'archivage, purge désactivée (0).
|
||||
expect(body.settings.retentionDays).toBe(30);
|
||||
expect(body.settings.purgeDays).toBe(0);
|
||||
});
|
||||
|
||||
it('n’expose AUCUN secret (server_secret, clé privée VAPID)', async () => {
|
||||
@@ -187,3 +190,24 @@ describe('PATCH /api/v1/settings — Claude CLI', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — rétention des sessions (P10)', () => {
|
||||
it('enregistre une rétention valide et l’expose', async () => {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { retentionDays: 7 } });
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect((res.json() as SettingsResponse).settings.retentionDays).toBe(7);
|
||||
});
|
||||
|
||||
it('accepte 0 (= jamais archiver)', async () => {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { retentionDays: 0 } });
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect((res.json() as SettingsResponse).settings.retentionDays).toBe(0);
|
||||
});
|
||||
|
||||
it('rejette une rétention hors borne ou non-entière (400)', async () => {
|
||||
for (const retentionDays of [-1, 3651, 1.5]) {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { retentionDays } });
|
||||
expect(res.statusCode).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user