syncova-backup/apps/web/src/features/auth/sessionStore.test.ts
Jerrit Fritzsche 77e61dc2b3
Some checks failed
CI / Backend (Go) (push) Failing after 30s
CI / Frontend (React/TypeScript) (push) Successful in 44s
CI / Sicherheitsprüfungen (push) Successful in 26s
Blackscreen auf /retention behoben, Aussehen ueberarbeitet
**Der schwarze Bildschirm ist gefunden.** `GET /retention-policies` liefert ein
Objekt `{ policies, predefined }` — als einziger von zehn geprueften
Listenendpunkten. Die Oberflaeche behandelte es als Array; `map` gibt es auf
einem Objekt nicht, React haengte den ganzen Baum aus, und uebrig blieb eine
leere Seite. Im dunklen Thema schwarz.

Der Regressionstest fuettert die **echte** Antwortform. Ein Test mit einem Array
haette den Fehler nie gefunden — und genau das war passiert. Durch Mutation als
fangend bestaetigt ("repositories.map is not a function").

Nebenbei ergab sich ein Modul: Der Endpunkt liefert **sechs mitgelieferte
Vorlagen** mit. Sie erscheinen jetzt als Kacheln, ein Klick legt daraus eine
eigene Regel an.

**Aussehen.** Kraeftigerer Akzent (Indigo statt gedecktem Blau), waermere
Neutraltoene, groessere Radien, Schatten statt Rahmen, wo Tiefe gemeint ist:
Ein Rahmen trennt, ein Schatten hebt — Karten sollen heben, Tabellenzeilen
trennen. Kompaktere Statusabzeichen und Tabellenkoepfe.

**Texte gekuerzt.** 33 Erlaeuterungen von Absaetzen auf einen Satz. Der Inhalt
bleibt richtig; er steht nur nicht mehr zwischen den Bedienelementen. Was
ausfuehrlich erklaert werden muss, gehoert in docs/.

**Schnellzugriffe auf der Uebersicht.** Acht Kacheln, die die Handlung nennen
statt des Bereichs ("Auftrag anlegen" statt "Sicherungsauftraege"). Gezeigt wird
nur, was die Rolle darf — eine Kachel, die in eine Fehlermeldung fuehrt, ist
schlimmer als keine. Als Schaltflaechen umgesetzt, nicht als div mit
Klick-Handler: Der Unterschied ist Tastaturbedienbarkeit.

84 Tests gruen, tsc sauber, eslint ohne Warnung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:56:51 +02:00

103 lines
3.1 KiB
TypeScript

/**
* Prüft die beiden Grenzen der Sitzung.
*
* Beide sind eine Zusage an den Betreiber, und beide lassen sich mit einer
* falschen Zeile still aushebeln: Wer beim Vermerken einer Interaktion die
* harte Obergrenze mitverschiebt, macht aus „30 Minuten" ein „unbegrenzt,
* solange die Maus wackelt".
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
SESSION_IDLE_MS,
SESSION_MAXIMUM_MS,
clearStoredSession,
readStoredSession,
remainingSessionMs,
touchStoredSession,
writeStoredSession,
} from './sessionStore';
const tokenPair = {
access_token: 'zugriff',
refresh_token: 'erneuerung', // secretscan:erlaubt — Testwert, kein Geheimnis
access_expires_at: '',
refresh_expires_at: '',
};
describe('Sitzungsablage', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-08-18T10:00:00Z'));
clearStoredSession();
});
afterEach(() => {
vi.useRealTimers();
});
it('überlebt ein Neuladen innerhalb der Frist', () => {
writeStoredSession(tokenPair);
// Ein Neuladen bedeutet: neuer Arbeitsspeicher, gleiche Ablage.
const resumed = readStoredSession();
expect(resumed).not.toBeNull();
expect(resumed?.accessToken).toBe('zugriff');
});
it('endet nach der harten Obergrenze, auch bei ständiger Interaktion', () => {
writeStoredSession(tokenPair);
// Alle fünf Minuten eine Interaktion — sieben Mal, also 35 Minuten.
for (let step = 0; step < 7; step += 1) {
vi.advanceTimersByTime(5 * 60 * 1000);
touchStoredSession();
}
// Die Untätigkeitsgrenze wäre nie erreicht worden. Die Obergrenze schon.
expect(readStoredSession()).toBeNull();
});
it('endet bei Untätigkeit vor der Obergrenze', () => {
writeStoredSession(tokenPair);
vi.advanceTimersByTime(SESSION_IDLE_MS + 1000);
expect(readStoredSession()).toBeNull();
});
it('verschiebt die Obergrenze durch eine Interaktion nicht', () => {
const created = writeStoredSession(tokenPair);
vi.advanceTimersByTime(60 * 1000);
const touched = touchStoredSession();
expect(touched?.absoluteExpiryMs).toBe(created.absoluteExpiryMs);
expect(touched?.lastActivityMs).toBeGreaterThan(created.lastActivityMs);
});
it('nennt die frühere der beiden Grenzen als Restzeit', () => {
const created = writeStoredSession(tokenPair);
// Direkt nach der Anmeldung fallen beide Grenzen zusammen.
expect(remainingSessionMs(created)).toBe(SESSION_MAXIMUM_MS);
vi.advanceTimersByTime(20 * 60 * 1000);
const stillValid = readStoredSession();
expect(stillValid).not.toBeNull();
// Zehn Minuten bis zur Obergrenze, dreißig bis zur Untätigkeitsgrenze —
// maßgeblich sind die zehn.
expect(remainingSessionMs(stillValid!)).toBe(10 * 60 * 1000);
});
it('verwirft einen unlesbaren Eintrag, statt ihn zu raten', () => {
window.sessionStorage.setItem('syncova.session', '{kein gültiges json');
expect(readStoredSession()).toBeNull();
expect(window.sessionStorage.getItem('syncova.session')).toBeNull();
});
});