In den Kaesten steht jetzt neben dem offenen Stand, wie viel jemals in den Topf kam: "0 von 3" liest sich anders als "0 von 0" - das eine heisst aufgebraucht, das andere nie etwas gehabt. Gezaehlt werden dabei nur echte Zugaenge: Die Ruecknahme eines Verbrauchs zaehlt dorthin, wo die stornierte Zeile zaehlte, sonst saehe ein zurueckgenommenes Redesign wie ein zusaetzliches Paket aus. Neben jedem Plus steht ein Minus, das die zuletzt von Hand eingetragene Zugabe dieser Art zurueckholt - fuer den vertippten oder doppelten Klick, ohne in der Historie nach der richtigen Zeile zu suchen. Zurueckgenommen wird ueber eine Gegenbuchung; bei einem CC-Paket gehen beide Zeilen mit. Aus einem Import gebuchte Pakete bleiben aussen vor: die gehoeren zu einer Bestellung und werden dort storniert, wo der Zusammenhang sichtbar ist. Die Rechte folgen dem Eintragen - Kulanz bleibt bei den Admins. Nebenbei zwei wacklige Tests festgezurrt: Die Testdatenbanken weichen auf den naechsten Port aus, wenn einer aus einem frueheren Lauf noch haengt, und der Sperr-Test des Abgleichs klopft nicht mehr bei GExtension an, sondern laeuft gegen eine tote Adresse.
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
import { readFileSync, readdirSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { PGlite } from '@electric-sql/pglite';
|
|
import { PGLiteSocketServer } from '@electric-sql/pglite-socket';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const MIGRATIONS_DIR = join(here, '..', 'prisma', 'migrations');
|
|
|
|
/** Alle Migrationen in der Reihenfolge, in der Prisma sie anwenden würde. */
|
|
function migrationFiles(): string[] {
|
|
return readdirSync(MIGRATIONS_DIR)
|
|
.filter((entry) => /^\d/.test(entry))
|
|
.sort()
|
|
.map((entry) => join(MIGRATIONS_DIR, entry, 'migration.sql'));
|
|
}
|
|
|
|
export interface TestDatabase {
|
|
url: string;
|
|
stop: () => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Startet eine Postgres-Instanz im Speicher und stellt sie über einen lokalen
|
|
* Port bereit, damit Prisma sich wie an eine echte Datenbank verbindet.
|
|
* So laufen die Tests ohne Docker oder installiertes Postgres.
|
|
*/
|
|
export async function startTestDatabase(port: number): Promise<TestDatabase> {
|
|
const db = await PGlite.create();
|
|
for (const file of migrationFiles()) {
|
|
await db.exec(readFileSync(file, 'utf8'));
|
|
}
|
|
|
|
// Die Testdateien laufen nebeneinander und bekommen feste Ports. Hängt aus
|
|
// einem vorherigen Lauf noch einer fest, wäre der Start sonst ohne Grund
|
|
// fehlgeschlagen - deshalb ein paar Versuche mit dem nächsten Port.
|
|
let server: PGLiteSocketServer | null = null;
|
|
let benutzt = port;
|
|
|
|
for (let versuch = 0; versuch < 20; versuch += 1) {
|
|
const kandidat = new PGLiteSocketServer({ db, port: benutzt, host: '127.0.0.1' });
|
|
try {
|
|
await kandidat.start();
|
|
server = kandidat;
|
|
break;
|
|
} catch (error) {
|
|
const code = (error as NodeJS.ErrnoException).code;
|
|
if (code !== 'EADDRINUSE') throw error;
|
|
benutzt += 1;
|
|
}
|
|
}
|
|
|
|
if (!server) {
|
|
throw new Error(`Kein freier Port ab ${port} für die Testdatenbank gefunden`);
|
|
}
|
|
|
|
const belegterPort = benutzt;
|
|
|
|
return {
|
|
// connection_limit=1, weil die Testinstanz immer nur eine Verbindung
|
|
// gleichzeitig bedient. Echtes Postgres braucht die Einschränkung nicht.
|
|
url: `postgresql://postgres:postgres@127.0.0.1:${belegterPort}/postgres?connection_limit=1`,
|
|
stop: async () => {
|
|
await server.stop();
|
|
await db.close();
|
|
},
|
|
};
|
|
}
|