From e003ec3b6fdb3b72247365628c4a4d3cb34a5760 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 7 Jun 2025 09:55:55 +0300 Subject: [PATCH] test(server): ensure session info exists --- apps/server/src/routes/login.spec.ts | 47 ++++++++++++++++--- apps/server/src/routes/session_parser.spec.ts | 14 ++++++ apps/server/src/routes/session_parser.ts | 6 ++- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 apps/server/src/routes/session_parser.spec.ts diff --git a/apps/server/src/routes/login.spec.ts b/apps/server/src/routes/login.spec.ts index 99984dbda..5e27cca43 100644 --- a/apps/server/src/routes/login.spec.ts +++ b/apps/server/src/routes/login.spec.ts @@ -2,14 +2,19 @@ import { beforeAll, describe, expect, it } from "vitest"; import supertest from "supertest"; import type { Application } from "express"; import dayjs from "dayjs"; +import type { SQLiteSessionStore } from "./session_parser.js"; +import { promisify } from "util"; +import { SessionData } from "express-session"; let app: Application; +let sessionStore: SQLiteSessionStore; describe("Login Route test", () => { beforeAll(async () => { const buildApp = (await import("../app.js")).default; app = await buildApp(); + sessionStore = (await import("./session_parser.js")).sessionStore; }); it("should return the login page, when using a GET request", async () => { @@ -52,7 +57,7 @@ describe("Login Route test", () => { // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;" const expiresCookieRegExp = /Expires=(?[\w\s,:]+)/; const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp); - const actualExpiresDate = new Date(expiresCookieMatch?.groups?.date || "").toUTCString() + const actualExpiresDate = new Date(expiresCookieMatch?.groups?.date || "").toUTCString(); expect(actualExpiresDate).to.not.eql("Invalid Date"); @@ -60,6 +65,13 @@ describe("Login Route test", () => { // if for some reason execution is slow between calculation of expected and actual expect(actualExpiresDate.slice(0,23)).toBe(expectedExpiresDate.slice(0,23)) + // Check the session is stored in the database. + const session = await getSessionFromCookie(setCookieHeader); + expect(session!).toBeTruthy(); + expect(session!.cookie.expires).toBeTruthy(); + expect(new Date(session!.cookie.expires!).toUTCString().substring(0, 23)) + .toBe(expectedExpiresDate.substring(0, 23)); + expect(session!.loggedIn).toBe(true); }, 10_000); // use 10 sec (10_000 ms) timeout for now, instead of default 5 sec to work around // failing CI, because for some reason it currently takes approx. 6 secs to run @@ -67,7 +79,6 @@ describe("Login Route test", () => { it("does not set Expires, when 'Remember Me' is not ticked", async () => { - const res = await supertest(app) .post("/login") .send({ password: "demo1234" }) @@ -76,14 +87,38 @@ describe("Login Route test", () => { const setCookieHeader = res.headers["set-cookie"][0]; // match for e.g. "Expires=Wed, 07 May 2025 07:02:59 GMT;" - const expiresCookieRegExp = /Expires=(?[\w\s,:]+)/; - const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp); - expect(expiresCookieMatch).toBeNull(); + expect(setCookieHeader).not.toMatch(/Expires=(?[\w\s,:]+)/) + // Check the session is stored in the database. + const session = await getSessionFromCookie(setCookieHeader); + expect(session!).toBeTruthy(); + expect(session!.cookie.expires).toBeUndefined(); + expect(session!.loggedIn).toBe(true); }, 10_000); // use 10 sec (10_000 ms) timeout for now, instead of default 5 sec to work around // failing CI, because for some reason it currently takes approx. 6 secs to run // TODO: actually identify what is causing this and fix the flakiness - }); + +async function getSessionFromCookie(setCookieHeader: string) { + // Extract the session ID from the cookie. + const sessionIdMatch = setCookieHeader.match(/trilium.sid=(?[^;]+)/)?.[1]; + expect(sessionIdMatch).toBeTruthy(); + + // Check the session is stored in the database. + const sessionId = decodeURIComponent(sessionIdMatch!).slice(2).split(".")[0]; + return await getSessionFromStore(sessionId); +} + +function getSessionFromStore(sessionId: string) { + return new Promise((resolve, reject) => { + sessionStore.get(sessionId, (err, session) => { + if (err) { + reject(err); + } else { + resolve(session); + } + }); + }); +} diff --git a/apps/server/src/routes/session_parser.spec.ts b/apps/server/src/routes/session_parser.spec.ts new file mode 100644 index 000000000..fe9340599 --- /dev/null +++ b/apps/server/src/routes/session_parser.spec.ts @@ -0,0 +1,14 @@ +import { beforeAll, describe, expect, it } from "vitest"; +import supertest from "supertest"; +import type { Application } from "express"; +import dayjs from "dayjs"; +let app: Application; + +describe("Session parser", () => { + + beforeAll(async () => { + const buildApp = (await import("../app.js")).default; + app = await buildApp(); + }); + +}); diff --git a/apps/server/src/routes/session_parser.ts b/apps/server/src/routes/session_parser.ts index 240341ef7..8ad8cd5f5 100644 --- a/apps/server/src/routes/session_parser.ts +++ b/apps/server/src/routes/session_parser.ts @@ -5,7 +5,7 @@ import config from "../services/config.js"; import log from "../services/log.js"; import type express from "express"; -class SQLiteSessionStore extends Store { +export class SQLiteSessionStore extends Store { get(sid: string, callback: (err: any, session?: session.SessionData | null) => void): void { try { @@ -52,6 +52,8 @@ class SQLiteSessionStore extends Store { } +export const sessionStore = new SQLiteSessionStore(); + const sessionParser: express.RequestHandler = session({ secret: sessionSecret, resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request. @@ -62,7 +64,7 @@ const sessionParser: express.RequestHandler = session({ maxAge: config.Session.cookieMaxAge * 1000 // needs value in milliseconds }, name: "trilium.sid", - store: new SQLiteSessionStore() + store: sessionStore }); setInterval(() => {