From af3a0cc05a80f8ef085d81c075b62cc4cd667517 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 15 Apr 2025 09:31:02 +0200 Subject: [PATCH 1/5] test(routes/login): add initial test via supertest for failed login --- src/routes/login.spec.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/routes/login.spec.ts diff --git a/src/routes/login.spec.ts b/src/routes/login.spec.ts new file mode 100644 index 000000000..395119de7 --- /dev/null +++ b/src/routes/login.spec.ts @@ -0,0 +1,36 @@ +import { beforeAll, describe, it } from "vitest"; +import supertest from "supertest"; +import { initializeTranslations } from "../services/i18n.js"; +import type { Application, Request, Response, NextFunction } from "express"; + +let app: Application; + +describe("Login Route test", () => { + + beforeAll(async () => { + initializeTranslations(); + app = (await import("../app.js")).default; + }); + + it("return a 401 status, when login fails with wrong password", async () => { + + await supertest(app) + .post("/login") + .send({ password: "fakePassword" }) + .expect(401) + + }); + + // TriliumNextTODO: how to handle different configs here? e.g. TOTP, or different cookieMaxAge from config.ini + + /* + + it("sets correct Expires, when 'Remember Me' is ticked", async () => { + await supertest(app) + .post("/login") + .expect(302) + .expect("Set-Cookie", "trilium.sid=trilium.sid; Path=/; Expires=TODO"); + }); + + */ +}); \ No newline at end of file From a2fc327cdb0227718df91f4d24824fb3b2bd1bce Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 16 Apr 2025 10:00:09 +0200 Subject: [PATCH 2/5] test(routes/login): add more tests for the login route --- src/routes/login.spec.ts | 68 ++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/src/routes/login.spec.ts b/src/routes/login.spec.ts index 395119de7..29b314647 100644 --- a/src/routes/login.spec.ts +++ b/src/routes/login.spec.ts @@ -1,18 +1,33 @@ -import { beforeAll, describe, it } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import supertest from "supertest"; import { initializeTranslations } from "../services/i18n.js"; import type { Application, Request, Response, NextFunction } from "express"; +import dayjs from "dayjs"; let app: Application; describe("Login Route test", () => { - beforeAll(async () => { + beforeEach(async () => { initializeTranslations(); app = (await import("../app.js")).default; }); - it("return a 401 status, when login fails with wrong password", async () => { + it("should return the login page, when using a GET request", async () => { + + // RegExp for login page specific string in HTML: e.g. "assets/v0.92.7/app/login.css" + const loginCssRegexp = /assets\/v[0-9.a-z]+\/app\/login\.css/; + + const res = await supertest(app) + .get("/login") + .expect(200) + + + expect(loginCssRegexp.test(res.text)).toBe(true); + + }); + + it("returns a 401 status, when login fails with wrong password", async () => { await supertest(app) .post("/login") @@ -21,16 +36,51 @@ describe("Login Route test", () => { }); - // TriliumNextTODO: how to handle different configs here? e.g. TOTP, or different cookieMaxAge from config.ini - - /* it("sets correct Expires, when 'Remember Me' is ticked", async () => { - await supertest(app) + + // TriliumNextTODO: make setting cookieMaxAge via env variable work + // => process.env.TRILIUM_SESSION_COOKIEMAXAGE + // the custom cookieMaxAge is currently hardocded in the test data dir's config.ini + + const CUSTOM_MAX_AGE_SECONDS = 86400; + const expectedExpiresDate = dayjs().utc().add(CUSTOM_MAX_AGE_SECONDS, "seconds").toDate().toUTCString(); + + const res = await supertest(app) .post("/login") + .send({ password: "demo1234", rememberMe: 1 }) .expect(302) - .expect("Set-Cookie", "trilium.sid=trilium.sid; Path=/; Expires=TODO"); + + 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); + const actualExpiresDate = new Date(expiresCookieMatch?.groups?.date || "").toUTCString() + + expect(actualExpiresDate).to.not.eql("Invalid Date"); + + // ignore the seconds in the comparison, just to avoid flakiness in tests, + // if for some reason execution is slow between calculation of expected and actual + expect(actualExpiresDate.slice(0,23)).toBe(expectedExpiresDate.slice(0,23)) }); - */ + + it("does not set Expires, when 'Remember Me' is not ticked", async () => { + + const res = await supertest(app) + .post("/login") + .send({ password: "demo1234" }) + .expect(302) + + 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(); + + }); + + }); \ No newline at end of file From 55e2abc7decb076089c9d87f8cc56372f7c8e493 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 16 Apr 2025 10:00:38 +0200 Subject: [PATCH 3/5] test(config): add custom cookieMaxAge for now --- integration-tests/db/config.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/integration-tests/db/config.ini b/integration-tests/db/config.ini index ac5c26ff2..e43b459dd 100644 --- a/integration-tests/db/config.ini +++ b/integration-tests/db/config.ini @@ -27,3 +27,8 @@ keyPath= # once set, expressjs will use the X-Forwarded-For header set by the rev. proxy to determinate the real IPs of clients. # expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7) trustedReverseProxy=false + +[Session] +# Default value is 1814400 Seconds, which is 21 Days. +# For integration test we set the value to just 86400 seconds, which is 1 Day +cookieMaxAge=86400 \ No newline at end of file From 5462bdeef51e0856c59638435b50d03981ffdc43 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 16 Apr 2025 10:06:12 +0200 Subject: [PATCH 4/5] test(routes/login): use beforeAll for now --- src/routes/login.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/login.spec.ts b/src/routes/login.spec.ts index 29b314647..9053edc15 100644 --- a/src/routes/login.spec.ts +++ b/src/routes/login.spec.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it } from "vitest"; import supertest from "supertest"; import { initializeTranslations } from "../services/i18n.js"; import type { Application, Request, Response, NextFunction } from "express"; @@ -8,7 +8,7 @@ let app: Application; describe("Login Route test", () => { - beforeEach(async () => { + beforeAll(async () => { initializeTranslations(); app = (await import("../app.js")).default; }); From a149b56f9dfe7afc0ec70738bf7dac08063b7ed8 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Thu, 17 Apr 2025 22:58:11 +0200 Subject: [PATCH 5/5] test(routes/login): add temporary timeout to workaround failing on CI --- src/routes/login.spec.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/routes/login.spec.ts b/src/routes/login.spec.ts index 9053edc15..1c3f2bc46 100644 --- a/src/routes/login.spec.ts +++ b/src/routes/login.spec.ts @@ -63,7 +63,11 @@ describe("Login Route test", () => { // ignore the seconds in the comparison, just to avoid flakiness in tests, // if for some reason execution is slow between calculation of expected and actual expect(actualExpiresDate.slice(0,23)).toBe(expectedExpiresDate.slice(0,23)) - }); + + }, 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 it("does not set Expires, when 'Remember Me' is not ticked", async () => { @@ -80,7 +84,10 @@ describe("Login Route test", () => { const expiresCookieMatch = setCookieHeader.match(expiresCookieRegExp); expect(expiresCookieMatch).toBeNull(); - }); + }, 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 }); \ No newline at end of file