Add local and public QR sharing for training profiles
This commit is contained in:
@@ -69,6 +69,7 @@ test("backend hydration normalizes persisted entities and settings", () => {
|
||||
pitEntryWsUrl: "ws://decoder:9002",
|
||||
pitExitWsUrl: "ws://decoder:9003",
|
||||
backendUrl: "http://race:8081",
|
||||
publicBaseUrl: "https://training.example.test",
|
||||
language: "en",
|
||||
theme: "light",
|
||||
autoReconnect: false,
|
||||
@@ -107,6 +108,7 @@ test("backend hydration normalizes persisted entities and settings", () => {
|
||||
assert.equal(state.activeSessionId, "session_1");
|
||||
assert.deepEqual(state.audioEventLog, [{ id: "audio_1", event: "race_start" }]);
|
||||
assert.equal(state.settings.theme, "light");
|
||||
assert.equal(state.settings.publicBaseUrl, "https://training.example.test");
|
||||
assert.equal(state.settings.splitDecoderEnabled, true);
|
||||
assert.equal(state.settings.splitWsUrl, "ws://decoder:9001");
|
||||
assert.equal(state.settings.pitDecoderEnabled, true);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createServer } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
@@ -147,8 +148,14 @@ test("SQLite API persists state, passings, and overlay session results", { timeo
|
||||
|
||||
const initialState = {
|
||||
events: [{ id: "event_1", name: "Club race" }],
|
||||
classes: [{ id: "class_1", name: "Buggy" }],
|
||||
drivers: [{ id: "driver_1", name: "Driver One", classId: "class_1" }],
|
||||
sessions: [{ id: "session_1", eventId: "event_1", name: "A final" }],
|
||||
resultsBySession: {},
|
||||
settings: { publicBaseUrl: "https://racecore.example.org" },
|
||||
training: {
|
||||
shares: [{ driverId: "driver_1", token: "test-share-token-1234567890", enabled: true }],
|
||||
},
|
||||
};
|
||||
const saveResponse = await fetch(`${baseUrl}/api/state`, {
|
||||
method: "POST",
|
||||
@@ -160,6 +167,28 @@ test("SQLite API persists state, passings, and overlay session results", { timeo
|
||||
assert.equal(savePayload.ok, true);
|
||||
assert.equal(typeof savePayload.updatedAt, "string");
|
||||
|
||||
const shareToken = initialState.training.shares[0].token;
|
||||
const publicQrResponse = await fetch(
|
||||
`${baseUrl}/public-training/qr/${shareToken}?lang=en&baseUrl=${encodeURIComponent("https://public.example.test")}`,
|
||||
);
|
||||
const publicQrSvg = await publicQrResponse.text();
|
||||
const expectedPublicQrSvg = await QRCode.toString(
|
||||
`https://public.example.test/public-training/${shareToken}?lang=en`,
|
||||
{ type: "svg", errorCorrectionLevel: "M", margin: 2, width: 320 },
|
||||
);
|
||||
assert.equal(publicQrResponse.status, 200);
|
||||
assert.match(publicQrResponse.headers.get("content-type"), /^image\/svg\+xml/);
|
||||
assert.equal(publicQrSvg, expectedPublicQrSvg);
|
||||
|
||||
const localQrResponse = await fetch(`${baseUrl}/public-training/qr/${shareToken}?lang=sv&baseUrl=local`);
|
||||
const localQrSvg = await localQrResponse.text();
|
||||
assert.equal(localQrResponse.status, 200);
|
||||
assert.match(localQrResponse.headers.get("content-type"), /^image\/svg\+xml/);
|
||||
assert.notEqual(localQrSvg, publicQrSvg);
|
||||
|
||||
const missingQrResponse = await fetch(`${baseUrl}/public-training/qr/unknown-share-token-123456?baseUrl=local`);
|
||||
assert.equal(missingQrResponse.status, 404);
|
||||
|
||||
const backupResponse = await fetch(`${baseUrl}/api/backups`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
Reference in New Issue
Block a user