diff --git a/.dockerignore b/.dockerignore index 91f5648..62669bc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,8 @@ node_modules /package .env .env.* -!.env.example +.eslintcache vite.config.js.timestamp-* vite.config.ts.timestamp-* +vitest.config.*.js.timestamp-* +vitest.config.*.ts.timestamp-* diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 731a1f8..8989c51 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -16,9 +16,19 @@ jobs: env: POSTGRES_DB: "test" POSTGRES_PASSWORD: "1234" + oidc: + image: thetadev256/oidc-mock-server + env: + CLIENT_ID: visitenbuch + CLIENT_SECRET: supersecret + CLIENT_REDIRECT_URIS: http://localhost:4173/auth/callback/keycloak + CLIENT_LOGOUT_REDIRECT_URIS: http://localhost:4173/login?noAuto=1 + ISSUER_HOST: oidc:3000 env: DATABASE_URL: "postgresql://postgres:1234@postgres:5432/test?schema=public" TEST_DATABASE_URL: "postgresql://postgres:1234@postgres:5432/test?schema=public" + KEYCLOAK_ISSUER: http://oidc:3000 + KEYCLOAK_LOGOUT: http://oidc:3000/session/end steps: - name: 👁️ Checkout repository uses: actions/checkout@v4 @@ -36,6 +46,11 @@ jobs: run: | npx prisma migrate reset --force npm run test:integration + - name: 👨‍🔬 E2E test + run: | + npx playwright install --with-deps + npm run build + npm run test:e2e release: runs-on: cimaster-latest @@ -45,6 +60,8 @@ jobs: steps: - name: 👁️ Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: 📦 pnpm install run: pnpm install @@ -65,7 +82,7 @@ jobs: run: | { echo 'CHANGELOG<> "$GITHUB_ENV" - name: 🎉 Publish release diff --git a/.gitignore b/.gitignore index cdc9c6b..ffc5dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,8 @@ node_modules /.svelte-kit /package .env +.eslintcache vite.config.js.timestamp-* vite.config.ts.timestamp-* +vitest.config.*.js.timestamp-* +vitest.config.*.ts.timestamp-* diff --git a/eslint.config.js b/eslint.config.js index 9421a96..3d6696e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,7 +14,7 @@ import ts from "typescript-eslint"; export default [ js.configs.recommended, - ...ts.configs.recommended, + ...ts.configs.recommendedTypeChecked, ...svelte.configs["flat/recommended"], // TS-Svelte { @@ -585,10 +585,16 @@ export default [ }, ], "@typescript-eslint/return-await": "error", - "@typescript-eslint/no-shadow": ["error", { allow: ["i", "j"] }], "no-shadow-restricted-names": "error", - "@typescript-eslint/no-loss-of-precision": "error", + "@typescript-eslint/promise-function-async": "error", + "@typescript-eslint/no-base-to-string": "off", + + // these clash with Svelte generics + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-member-access": "off", }, }, @@ -812,7 +818,7 @@ export default [ ".svelte-kit/", "*.config.cjs", "vite.config.js.timestamp-*", - "vite.config.ts.timestamp-*", + "vitest.config.*.timestamp-*", ".tmp/", ], }, diff --git a/package.json b/package.json index 89c0900..61fcd29 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "vitest --run && vitest --config vitest.config.integration.js --run", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "eslint . --max-warnings=0", + "lint": "eslint . --max-warnings=0 --cache", "format": "eslint . --fix", "test:unit": "vitest", "test:integration": "vitest --config vitest.config.integration.js", diff --git a/playwright.config.js b/playwright.config.js index 3d145d0..afa7d21 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -2,9 +2,12 @@ import { defineConfig } from "@playwright/test"; export default defineConfig({ webServer: { - command: "npm run build && npm run preview", + command: "npm run preview -m test", port: 4173, + reuseExistingServer: true, }, - testDir: "tests", - testMatch: /(.+\.)?(test|spec)\.[jt]s/, + testDir: "tests/e2e", + testMatch: /\.[jt]s$/, + globalSetup: "tests/helpers/generate-mockdata.ts", + outputDir: ".svelte-kit/test-results", }); diff --git a/src/app.d.ts b/src/app.d.ts index 13a994c..b86d0aa 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -8,7 +8,9 @@ declare global { interface Locals { session: Session | null; } - // interface PageData {} + interface PageData { + session: Session | null; + } // interface Platform {} } diff --git a/src/app.pcss b/src/app.pcss index 244e176..801af2d 100644 --- a/src/app.pcss +++ b/src/app.pcss @@ -43,6 +43,7 @@ button { text-align: initial; + user-select: text; } .heading { @@ -72,10 +73,6 @@ button { .row { @apply flex; - } - - .row, - .rowb { @apply p-2; @apply border-t border-solid border-base-content/30; } @@ -109,3 +106,77 @@ button { transform: scale(0.99); } } + +.block { + display: block !important; +} + +@media print { + /* Make all text black for best look on B/W printers */ + :root { + --bc: 0% 0 0; + --pc: 0% 0 0; + --sc: 0% 0 0; + --ac: 0% 0 0; + --nc: 0% 0 0; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + } + + .btn:not(.btn-id) { + display: none !important; + } + + .btn-id { + font-size: 100%; + border: none; + background-color: transparent; + box-shadow: none; + padding: 0; + @apply rounded-none; + } + + .card2 { + @apply rounded-none; + background: none !important; + page-break-inside: avoid; + + .row:first-child { + @apply border-solid border-base-content/50 border-t-0 border-b-2; + @apply font-semibold; + } + + .row:last-child, + .row:first-child { + @apply rounded-none; + } + + .c-light, + .c-vlight, + .c-primary { + background: none !important; + } + } + + .card2, + .card2 > .row { + @apply border-none; + } + + .table { + td, th { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + @apply border-base-content/50 border; + } + } + + .badge { + background-color: transparent !important; + color: #000 !important; + padding: 0; + @apply rounded-none; + } +} diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 4aac5a8..4cc17f9 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -3,7 +3,7 @@ import { TRPCClientError } from "@trpc/client"; const CHECK_CONNECTION = "Die Seite konnte nicht geladen werden, prüfen sie ihre Verbindung"; -export const handleError: HandleClientError = async ({ error, message, status }) => { +export const handleError: HandleClientError = ({ error, message, status }) => { // If there are client-side errors, SvelteKit always returns the nondescript // "Internal error" message. The most common errors should be mapped to a more // detailed description diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 9a0c57d..a7fddf5 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -33,5 +33,5 @@ export const handle = sequence( ); // Allow server application to exit -process.on("SIGINT", process.exit); // Ctrl+C -process.on("SIGTERM", process.exit); // docker stop +process.on("SIGINT", () => process.exit()); // Ctrl+C +process.on("SIGTERM", () => process.exit()); // docker stop diff --git a/src/lib/components/entry/EntryBody.svelte b/src/lib/components/entry/EntryBody.svelte index 48b5383..d712f50 100644 --- a/src/lib/components/entry/EntryBody.svelte +++ b/src/lib/components/entry/EntryBody.svelte @@ -61,7 +61,7 @@
-
+
Zuletzt bearbeitet am {formatDate(entry.current_version.created_at, true)} von
diff --git a/src/lib/components/filter/Autocomplete.svelte b/src/lib/components/filter/Autocomplete.svelte index 9009450..f1a7885 100644 --- a/src/lib/components/filter/Autocomplete.svelte +++ b/src/lib/components/filter/Autocomplete.svelte @@ -1,8 +1,9 @@ -
+ + diff --git a/src/lib/components/filter/filters.ts b/src/lib/components/filter/filters.ts index 50dac8a..dfc6743 100644 --- a/src/lib/components/filter/filters.ts +++ b/src/lib/components/filter/filters.ts @@ -107,7 +107,7 @@ export const ENTRY_FILTERS: Record = { name: "Datum", icon: mdiCalendar, inputType: InputType.FilterList, - options: async () => weekFilterItems(), + options: () => weekFilterItems(), textToItem: (s) => { const parsed = DateRange.parseHuman(s); if (parsed) { diff --git a/src/lib/components/filter/types.ts b/src/lib/components/filter/types.ts index cc3eb6b..f3c64e1 100644 --- a/src/lib/components/filter/types.ts +++ b/src/lib/components/filter/types.ts @@ -1,3 +1,5 @@ +import type { MaybePromise } from "@sveltejs/kit"; + export enum InputType { None = 0, FreeText = 1, @@ -27,7 +29,7 @@ export type FilterDef = { name: string; icon?: string; }; - options?: () => Promise; + options?: () => MaybePromise; textToItem?: (s: string) => BaseItem | void; }; diff --git a/src/lib/components/table/EntryTable.svelte b/src/lib/components/table/EntryTable.svelte index 6ea8b98..42c2990 100644 --- a/src/lib/components/table/EntryTable.svelte +++ b/src/lib/components/table/EntryTable.svelte @@ -16,7 +16,7 @@
- +
@@ -42,7 +42,7 @@ > diff --git a/src/lib/components/table/FilteredPatientTable.svelte b/src/lib/components/table/FilteredPatientTable.svelte index eff3843..afb602d 100644 --- a/src/lib/components/table/FilteredPatientTable.svelte +++ b/src/lib/components/table/FilteredPatientTable.svelte @@ -44,7 +44,7 @@ if (browser) { // Update page URL const url = getQueryUrl(q, baseUrl); - goto(url, { replaceState: true, keepFocus: true }); + void goto(url, { replaceState: true, keepFocus: true }); } } diff --git a/src/lib/components/table/PatientTable.svelte b/src/lib/components/table/PatientTable.svelte index 21a3568..b4ecfed 100644 --- a/src/lib/components/table/PatientTable.svelte +++ b/src/lib/components/table/PatientTable.svelte @@ -17,7 +17,7 @@
-
{entry.id}
+
diff --git a/src/lib/components/table/SortHeader.svelte b/src/lib/components/table/SortHeader.svelte index c4007fe..eec0e9c 100644 --- a/src/lib/components/table/SortHeader.svelte +++ b/src/lib/components/table/SortHeader.svelte @@ -23,7 +23,7 @@ } else if (sorting === 2) { delete sortData[index]; } else if (index !== -1) { - sortData[index] = sortData[index].split(":", 1) + ":dsc"; + sortData[index] = sortData[index].split(":", 1)[0] + ":dsc"; } else { sortData.push(key); } diff --git a/src/lib/components/ui/LoadingBar.svelte b/src/lib/components/ui/LoadingBar.svelte index 36737a7..eb92688 100644 --- a/src/lib/components/ui/LoadingBar.svelte +++ b/src/lib/components/ui/LoadingBar.svelte @@ -1,5 +1,6 @@ -
+ -
+
-
+ + + diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index fadfee1..e840d93 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -35,7 +35,10 @@

Visite

Hier können sie Visitenbucheinträge abarbeiten.

-

Heute müssen {data.nTodo} Einträge erledigt werden.

+

Heute müssen + {data.nTodo} + Einträge erledigt werden. +

diff --git a/src/routes/(app)/categories/+page.svelte b/src/routes/(app)/categories/+page.svelte index 9ae3771..84de7c1 100644 --- a/src/routes/(app)/categories/+page.svelte +++ b/src/routes/(app)/categories/+page.svelte @@ -20,7 +20,7 @@
-
+
diff --git a/src/routes/(app)/entry/[id]/+page.svelte b/src/routes/(app)/entry/[id]/+page.svelte index 6828514..f93e323 100644 --- a/src/routes/(app)/entry/[id]/+page.svelte +++ b/src/routes/(app)/entry/[id]/+page.svelte @@ -25,7 +25,7 @@ {#if !data.entry.execution?.done} -
+ {#if version.text.length > 0} -
+
{#each version.text as change} {change.value} diff --git a/src/routes/(app)/logout/+page.svelte b/src/routes/(app)/logout/+page.svelte index d3c66f5..bc6647b 100644 --- a/src/routes/(app)/logout/+page.svelte +++ b/src/routes/(app)/logout/+page.svelte @@ -5,6 +5,6 @@

Möchten sie sich abmelden?

- +
diff --git a/src/routes/(app)/rooms/+page.svelte b/src/routes/(app)/rooms/+page.svelte index e3230f8..513a06f 100644 --- a/src/routes/(app)/rooms/+page.svelte +++ b/src/routes/(app)/rooms/+page.svelte @@ -19,7 +19,7 @@
-
Name Beschreibung
+
diff --git a/src/routes/(app)/stations/+page.svelte b/src/routes/(app)/stations/+page.svelte index 4b17c23..010080e 100644 --- a/src/routes/(app)/stations/+page.svelte +++ b/src/routes/(app)/stations/+page.svelte @@ -19,7 +19,7 @@
-
Name Station
+
diff --git a/src/routes/(app)/visit/+page.svelte b/src/routes/(app)/visit/+page.svelte index 3a3522f..23cbc5d 100644 --- a/src/routes/(app)/visit/+page.svelte +++ b/src/routes/(app)/visit/+page.svelte @@ -77,18 +77,21 @@ if (browser) { // Update page URL const url = getQueryUrl(q, URL_VISIT); - goto(url, { replaceState: true, keepFocus: true }); + void goto(url, { replaceState: true, keepFocus: true }); } } - Visite + Visite {dateRange.format()} -

Visite

+
+

Visite

+ +
-
+
@@ -118,7 +121,7 @@
{#each data.groups as group} {@const first = group.items[0] ?? group.prio[0]} -
+
{#if data.groupByStation} {#if first.patient.room} Station {first.patient.room?.station.name} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index bdc5805..6065cf5 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,5 +1,5 @@ import type { LayoutServerLoad } from "./$types"; -export const load: LayoutServerLoad = async (event) => ({ +export const load: LayoutServerLoad = (event) => ({ session: event.locals.session, }); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a18fc7e..acdea48 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,5 @@ - +
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 0d37816..0e1376f 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -5,6 +5,6 @@

Visitenbuch

- +
diff --git a/tests/e2e/login.test.ts b/tests/e2e/login.test.ts new file mode 100644 index 0000000..3150585 --- /dev/null +++ b/tests/e2e/login.test.ts @@ -0,0 +1,49 @@ +import { test, expect } from "@playwright/test"; + +import { + isLoggedIn, loginIfNecessary, loginWithToken, USERNAME, OIDC_BASE_URL, +} from "$tests/helpers/login"; + +test.describe.configure({ mode: "serial" }); // Parallel account creation may cause issues + +test("login", async ({ page }) => { + await page.goto("/"); + await loginIfNecessary(page); + await expect(page).toHaveTitle("Visitenbuch"); + await expect(page.locator("h1.heading")).toHaveText("Hallo, Lucy Login"); + // Test cases may create more entries + expect(parseInt(await page.getByTestId("n-entries-todo").innerText())) + .toBeGreaterThanOrEqual(193); +}); + +test("loginWithToken", async ({ page }) => { + await loginWithToken(page); + await page.goto("/"); + + await expect(page).toHaveTitle("Visitenbuch"); + await expect(page.locator("h1.heading")).toHaveText("Hallo, " + USERNAME); + // Test cases may create more entries + expect(parseInt(await page.getByTestId("n-entries-todo").innerText())) + .toBeGreaterThanOrEqual(193); +}); + +test("logout", async ({ page, baseURL }) => { + await page.goto("/"); + await loginIfNecessary(page); + + await page.goto("/logout"); + await page.getByTestId("btn-logout").click(); + await page.locator('button[value="yes"]').click(); + await page.waitForURL("/login?noAuto=1"); + await expect(page.getByTestId("btn-login")).toBeVisible(); + expect(await isLoggedIn(page)).toBe(false); + + // Check if application is not accessible if unauthorized + await page.goto("/plan"); + expect(page.url() === baseURL + "/login?returnURL=%2Fplan" || page.url().startsWith(OIDC_BASE_URL)).toBe(true); + + // Check if TRPC API is not accessible if unauthorized + const apiResponse = await page.context().request.get("/trpc/savedFilter.getAll"); + expect(apiResponse.status()).toBe(401); + expect(await apiResponse.json()).toMatchObject({ error: { message: "not logged in" } }); +}); diff --git a/tests/e2e/plan.test.ts b/tests/e2e/plan.test.ts new file mode 100644 index 0000000..3c6fcff --- /dev/null +++ b/tests/e2e/plan.test.ts @@ -0,0 +1,28 @@ +import { test, expect } from "@playwright/test"; + +import { loginWithToken } from "$tests/helpers/login"; + +test("filter", async ({ page }) => { + await loginWithToken(page); + await page.goto("/plan"); + await expect(page).toHaveTitle("Planung"); + + const filterbar = page.locator(".filterbar-outer"); + const filterIn = filterbar.getByPlaceholder("Filter"); + await filterIn.click(); + await filterbar.getByRole("option", { name: "Kategorie" }).click(); + await filterbar.getByRole("option", { name: "Laborabnahme" }).click(); + await filterIn.click(); + await filterbar.getByRole("option", { name: "Zimmer" }).click(); + await filterbar.getByRole("option", { name: "R1.5" }).click(); + await filterIn.click(); + await filterbar.getByRole("option", { name: "Autor" }).click(); + await filterbar.getByRole("option", { name: "Akeem Wisozk" }).click(); + + await expect(page).toHaveURL("http://localhost:4173/plan?filter%5Bcategory%5D%5B0%5D%5Bid%5D=1&filter%5Bcategory%5D%5B0%5D%5Bname%5D=Laborabnahme&filter%5Broom%5D%5B0%5D%5Bid%5D=5&filter%5Broom%5D%5B0%5D%5Bname%5D=R1.5&filter%5Bauthor%5D%5B0%5D%5Bid%5D=5&filter%5Bauthor%5D%5B0%5D%5Bname%5D=Akeem%20Wisozk"); + + const table = page.getByTestId("entry-table"); + const firstRow = table.locator("tbody > tr").first(); + + await expect(firstRow.locator("td > a").first()).toHaveText("275"); +}); diff --git a/tests/helpers/generate-mockdata.ts b/tests/helpers/generate-mockdata.ts index 260f1f9..412d09f 100644 --- a/tests/helpers/generate-mockdata.ts +++ b/tests/helpers/generate-mockdata.ts @@ -1,6 +1,7 @@ /* eslint-disable no-await-in-loop */ import fs from "fs"; import path from "path"; +import { performance } from "perf_hooks"; import { fileURLToPath } from "url"; import { faker } from "@faker-js/faker"; @@ -29,9 +30,17 @@ function randomId(len: number): number { return faker.number.int({ min: 1, max: len - 1 }); } +function randomUserId(): number { + return randomId(N_USERS) + 1; +} + +/** Reset database and create extensive test data for development and E2E tests */ export default async () => { + const startTime = performance.now(); + // Reset database await prisma.$transaction([ + prisma.savedFilter.deleteMany(), prisma.entryExecution.deleteMany(), prisma.entryVersion.deleteMany(), prisma.entry.deleteMany(), @@ -58,9 +67,16 @@ export default async () => { const entryMockdata: MockEntry[] = file .trim() .split("\n") - .map((l) => JSON.parse(l)); + .map((l) => JSON.parse(l) as MockEntry); - for (let i = 1; i <= N_USERS; i++) { + await prisma.user.create({ + data: { + id: 1, + name: "Tico Testboy", + email: "t.testboy@example.com", + }, + }); + for (let i = 2; i <= N_USERS + 1; i++) { const firstName = faker.person.firstName(); const lastName = faker.person.lastName(); @@ -109,7 +125,7 @@ export default async () => { await prisma.entryVersion.create({ data: { entry_id: entry.id, - author_id: randomId(N_USERS), + author_id: randomUserId(), category_id: CATEGORY_IDS[e.category], date: todo_date, priority, @@ -123,7 +139,7 @@ export default async () => { await prisma.entryVersion.create({ data: { entry_id: entry.id, - author_id: randomId(N_USERS), + author_id: randomUserId(), category_id: CATEGORY_IDS[e.category], date: todo_date, priority, @@ -137,7 +153,7 @@ export default async () => { await prisma.entryExecution.create({ data: { entry_id: entry.id, - author_id: randomId(N_USERS), + author_id: randomUserId(), text: e.result, created_at: faker.date.soon({ refDate: todo_date, days: 2 }), }, @@ -146,7 +162,7 @@ export default async () => { await prisma.$transaction([ prisma.$executeRawUnsafe( - `alter sequence users_id_seq restart with ${N_USERS + 1}`, + `alter sequence users_id_seq restart with ${N_USERS + 2}`, ), prisma.$executeRawUnsafe( `alter sequence categories_id_seq restart with ${CATEGORIES.length + 1}`, @@ -162,4 +178,6 @@ export default async () => { ), ]); } + // eslint-disable-next-line no-console + console.log(`Generated mock data in ${performance.now() - startTime} ms`); }; diff --git a/tests/helpers/login.ts b/tests/helpers/login.ts new file mode 100644 index 0000000..3521ee9 --- /dev/null +++ b/tests/helpers/login.ts @@ -0,0 +1,58 @@ +import { encode } from "@auth/core/jwt"; +import { expect, type Page } from "@playwright/test"; + +import { prisma } from "$lib/server/prisma"; + +const AUTH_COOKIE = "authjs.session-token"; + +export const OIDC_BASE_URL = "http://localhost:9090/interaction/"; +export const USERNAME = "Tico Testboy"; +export const USER_EMAIL = "t.testboy@example.org"; + +export async function loginIfNecessary(page: Page) { + if (page.url().startsWith(OIDC_BASE_URL)) { + await page.locator('input[name="login"]').fill("Lucy Login"); + await page.locator('input[name="password"]').fill("1234"); + await page.locator("button.login-submit").click(); + await page.getByRole("button", { name: "Continue" }).click(); + expect(await isLoggedIn(page)).toBe(true); + } +} + +export async function isLoggedIn(page: Page): Promise { + const cookies = await page.context().cookies(); + return cookies.findIndex((c) => c.name === AUTH_COOKIE) !== -1; +} + +/** + * Create a session token (Cookie: authjs.session-token) to use for E2E tests + * so we dont have to step through the login system every time + */ +export async function newSessionToken(user_id: number): Promise { + const user = await prisma.user.findUniqueOrThrow({ + select: { email: true, name: true }, + where: { id: user_id }, + }); + return encode({ + salt: AUTH_COOKIE, + secret: process.env.AUTH_SECRET!, + token: { + name: user.name, + email: user.email, + sub: user_id.toString(), + id: user_id.toString(), + }, + }); +} + +export async function loginWithToken(page: Page, user_id = 1) { + const token = await newSessionToken(user_id); + await page.context().addCookies([{ + name: AUTH_COOKIE, + value: token, + domain: "localhost", + path: "/", + httpOnly: true, + sameSite: "Lax", + }]); +} diff --git a/tests/helpers/reset-db.ts b/tests/helpers/reset-db.ts index a7d12f8..54889b7 100644 --- a/tests/helpers/reset-db.ts +++ b/tests/helpers/reset-db.ts @@ -4,6 +4,7 @@ import { CATEGORIES, ROOMS, STATIONS, USERS, } from "./testdata"; +/** Reset database and create basic test data for integration tests */ export default async () => { await prisma.$transaction([ prisma.savedFilter.deleteMany(), diff --git a/tests/integration/query/category.ts b/tests/integration/query/category.ts index 2e17793..b5fb658 100644 --- a/tests/integration/query/category.ts +++ b/tests/integration/query/category.ts @@ -26,7 +26,7 @@ test("get categories", async () => { test("delete categories", async () => { await deleteCategory(6); - expect(getCategory(6)).rejects.toThrowError("No Category found"); + await expect(getCategory(6)).rejects.toThrowError("No Category found"); }); test("hide category", async () => { diff --git a/tests/integration/query/entry.ts b/tests/integration/query/entry.ts index 78c87d4..9c2422d 100644 --- a/tests/integration/query/entry.ts +++ b/tests/integration/query/entry.ts @@ -149,7 +149,7 @@ test("create entry version (wrong old vid)", async () => { }); const entry = await getEntry(eId); - expect(async () => { + await expect(async () => { await newEntryVersion( 1, eId, @@ -202,7 +202,7 @@ test("create entry execution (wrong old xid)", async () => { }); const x1 = await newEntryExecution(1, eId, { text: "x1", done: true }, null); - expect(async () => newEntryExecution(1, eId, { text: "x2", done: true }, x1 + 1)).rejects.toThrowError(new ErrorConflict("old execution id does not match")); + await expect(async () => newEntryExecution(1, eId, { text: "x2", done: true }, x1 + 1)).rejects.toThrowError(new ErrorConflict("old execution id does not match")); }); test("get entries", async () => { diff --git a/tests/integration/query/patient.ts b/tests/integration/query/patient.ts index f039870..42fec28 100644 --- a/tests/integration/query/patient.ts +++ b/tests/integration/query/patient.ts @@ -49,7 +49,7 @@ test("update patient", async () => { test("delete patient", async () => { await deletePatient(1); - expect(async () => getPatient(1)).rejects.toThrowError("No Patient found"); + await expect(async () => getPatient(1)).rejects.toThrowError("No Patient found"); }); test("delete patient (restricted)", async () => { @@ -64,7 +64,7 @@ test("delete patient (restricted)", async () => { }, }); - expect(async () => deletePatient(pId)).rejects.toThrowError(); + await expect(async () => deletePatient(pId)).rejects.toThrowError(); }); test("hide patient", async () => { diff --git a/tests/integration/query/room.ts b/tests/integration/query/room.ts index b730a3f..5dd7695 100644 --- a/tests/integration/query/room.ts +++ b/tests/integration/query/room.ts @@ -37,7 +37,7 @@ test("update room", async () => { test("delete room", async () => { await deleteRoom(ROOMS[3].id); - expect(async () => getRoom(ROOMS[3].id)).rejects.toThrowError("No Room found"); + await expect(async () => getRoom(ROOMS[3].id)).rejects.toThrowError("No Room found"); }); test("hide room", async () => { diff --git a/tests/integration/query/savedFilter.ts b/tests/integration/query/savedFilter.ts index c7c02cc..8c27a64 100644 --- a/tests/integration/query/savedFilter.ts +++ b/tests/integration/query/savedFilter.ts @@ -96,7 +96,7 @@ test("update filter", async () => { }); test("update filter not found", async () => { - expect(updateSavedFilter(1, "Hello World", 1)).rejects.toThrowError(); + await expect(updateSavedFilter(1, "Hello World", 1)).rejects.toThrowError(); }); test("delete filter", async () => { diff --git a/tests/integration/query/station.ts b/tests/integration/query/station.ts index 65a8db9..b6b2270 100644 --- a/tests/integration/query/station.ts +++ b/tests/integration/query/station.ts @@ -25,7 +25,7 @@ test("update station", async () => { test("delete station", async () => { await deleteStation(S3.id); - expect(async () => getStation(S3.id)).rejects.toThrowError("No Station found"); + await expect(async () => getStation(S3.id)).rejects.toThrowError("No Station found"); }); test("hide station", async () => { diff --git a/tests/integration/trpc/entry.ts b/tests/integration/trpc/entry.ts index e609d21..c4bb8ab 100644 --- a/tests/integration/trpc/entry.ts +++ b/tests/integration/trpc/entry.ts @@ -16,7 +16,7 @@ test("create entry", async () => { }); expect(eID).gt(0); - const entry = await caller().entry.get(eID!); + const entry = await caller().entry.get(eID); expect(entry.patient.id).toBe(1); expect(entry.execution).toBeNull(); expect(entry.current_version.id).gt(0); diff --git a/vite.config.js b/vite.config.ts similarity index 79% rename from vite.config.js rename to vite.config.ts index 6539b16..9d34322 100644 --- a/vite.config.js +++ b/vite.config.ts @@ -2,12 +2,12 @@ import { exec } from "child_process"; import { promisify } from "util"; import { sveltekit } from "@sveltejs/kit/vite"; -import { createViteLicensePlugin } from "rollup-license-plugin"; +import { createViteLicensePlugin, type LicenseMeta } from "rollup-license-plugin"; import { defineConfig } from "vitest/config"; // Get current tag/commit and last commit date from git const pexec = promisify(exec); -let [version, lastmod] = ( +const [version, lastmod] = ( await Promise.allSettled([ pexec("git describe --tags || git rev-parse --short HEAD"), pexec('git log -1 --format=%cd --date=format:"%Y-%m-%d %H:%M"'), @@ -31,13 +31,17 @@ export default defineConfig({

Open-Source-Lizenzen

JSON-formatted license list `; - for (const p of packages) { - // @ts-expect-error repo not present in type definition - let rp = p.repository; - // @ts-expect-error author not present in type definition - let aut = p.author; - if (typeof aut === "object") { - aut = aut.name; + for (const _p of packages) { + type LicenseMetaExt = LicenseMeta & { + repository: string | null, + author: string | { name: string } | null + }; + const p = _p as LicenseMetaExt; + const rp = p.repository; + let aut = null; + if (p.author) { + if (typeof p.author === "object" && p.author.name) aut = p.author.name; + else if (typeof p.author === "string") aut = p.author; } let repoUrl = null;
Name