ci: add sll support and enhance e2e workflow

This commit is contained in:
ramvignesh-b
2026-04-17 01:22:03 +05:30
parent f5757b47de
commit c40e3d20cb
21 changed files with 368 additions and 162 deletions
+1
View File
@@ -34,6 +34,7 @@ export async function registerAndLogin(
// 2. Activation via Mailpit
logger.info(`[Auth] Polling Mailpit for activation email...`);
const activationLink = await MailpitHelper.getActivationLink(email);
await page.goto(activationLink);
await expect(page.getByText(/account activated/i)).toBeVisible();
+1 -2
View File
@@ -7,7 +7,7 @@ export interface MailpitMessage {
To: { Address: string }[];
}
const MAILPIT_API_URL = process.env.MAILPIT_API_URL;
const MAILPIT_API_URL = `http://${process.env.EMAIL_HOST}:${process.env.EMAIL_API_PORT}/api/v1`;
export const MailpitHelper = {
getActivationLink: async (
@@ -18,7 +18,6 @@ export const MailpitHelper = {
const requestContext = await request.newContext();
while (Date.now() - startTime < timeout) {
// Search specifically for the recipient to reduce data transfer
const response = await requestContext.get(`${MAILPIT_API_URL}/search`, {
params: { query: `to:${email}`, limit: 1 },
});
+16 -4
View File
@@ -1,12 +1,20 @@
import path from "node:path";
import process from "node:process";
import process, { env } from "node:process";
import { defineConfig, devices } from "@playwright/test";
import dotenv from "dotenv";
import { getBaseUrl } from "./utils/url-builder";
/**
* Read environment variables from file.
*/
dotenv.config({ path: path.resolve(process.cwd(), "../.env.e2e") });
const baseUrl = getBaseUrl(
env.SSL_ENABLED === "true",
env.FRONTEND_DOMAIN,
env.FRONTEND_PORT,
);
console.log(baseUrl);
export default defineConfig({
timeout: 60000,
expect: {
@@ -26,7 +34,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.FRONTEND_URL,
baseURL: baseUrl,
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 20000,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
@@ -53,8 +61,12 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: "bun run dev",
url: process.env.FRONTEND_URL,
command: "bun run dev -- --mode e2e",
url: getBaseUrl(
process.env.SSL_ENABLED === "true",
process.env.FRONTEND_DOMAIN,
process.env.FRONTEND_PORT,
),
reuseExistingServer: !process.env.CI,
ignoreHTTPSErrors: true,
},
+10 -10
View File
@@ -13,7 +13,7 @@ import { server } from "../../test/mocks/server";
import { useAuthStore } from "../store/useAuthStore";
import { api } from "./apiClient";
const API_URL = "http://piku-server";
const VITE_API_URL = "http://piku-server";
beforeEach(() => {
useAuthStore.setState({
@@ -24,7 +24,7 @@ beforeEach(() => {
});
beforeAll(() => {
vi.stubEnv("API_URL", API_URL);
vi.stubEnv("VITE_API_URL", VITE_API_URL);
});
afterAll(() => {
@@ -37,7 +37,7 @@ describe("request interceptor", () => {
let capturedAuthHeader = "";
server.use(
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
capturedAuthHeader = request.headers.get("Authorization") ?? "";
return HttpResponse.json(mockUser);
}),
@@ -51,7 +51,7 @@ describe("request interceptor", () => {
it("should not send Authorization header when the store has no token", async () => {
let capturedAuthHeader: string | null = "";
server.use(
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
capturedAuthHeader = request.headers.get("Authorization");
return HttpResponse.json({});
}),
@@ -70,14 +70,14 @@ describe("response interceptor", () => {
let _refreshApiCallCount = 0;
server.use(
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
meApiCallCount++;
if (request.headers.get("Authorization") === "Bearer expired-token") {
return new HttpResponse(null, { status: 401 });
}
return HttpResponse.json(mockUser);
}),
http.post(`${API_URL}/api/auth/refresh/`, () => {
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
_refreshApiCallCount++;
return HttpResponse.json({ access: "refreshed-token" });
}),
@@ -94,13 +94,13 @@ describe("response interceptor", () => {
useAuthStore.getState().setAuth("expired-token", mockUser);
server.use(
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
if (request.headers.get("Authorization") === "Bearer expired-token") {
return new HttpResponse(null, { status: 401 });
}
return HttpResponse.json(mockUser);
}),
http.post(`${API_URL}/api/auth/refresh/`, () =>
http.post(`${VITE_API_URL}/api/auth/refresh/`, () =>
HttpResponse.json({ access: "refreshed-token" }),
),
);
@@ -115,14 +115,14 @@ describe("response interceptor", () => {
server.use(
http.get(
`${API_URL}/api/auth/me/`,
`${VITE_API_URL}/api/auth/me/`,
() =>
new HttpResponse(JSON.stringify({ detail: "Invalid token" }), {
status: 401,
}),
),
http.post(
`${API_URL}/api/auth/refresh/`,
`${VITE_API_URL}/api/auth/refresh/`,
() =>
new HttpResponse(JSON.stringify({ detail: "Refresh failed" }), {
status: 401,
+5 -5
View File
@@ -17,7 +17,7 @@ import { useAuth } from "./useAuth";
vi.mock("../utils/crypto");
vi.mock("../utils/keystore");
const API_URL = "http://piku-server";
const VITE_API_URL = "http://piku-server";
beforeEach(() => {
vi.clearAllMocks();
@@ -112,7 +112,7 @@ describe("logout", () => {
it("should call the logout API endpoint", async () => {
let logoutCalled = false;
server.use(
http.post(`${API_URL}/api/auth/logout/`, () => {
http.post(`${VITE_API_URL}/api/auth/logout/`, () => {
logoutCalled = true;
return HttpResponse.json({});
}),
@@ -139,7 +139,7 @@ describe("logout", () => {
it("should clear the auth store even if the API call fails", async () => {
server.use(
http.post(
`${API_URL}/api/auth/logout/`,
`${VITE_API_URL}/api/auth/logout/`,
() => new HttpResponse(null, { status: 500 }),
),
);
@@ -163,7 +163,7 @@ describe("initialize", () => {
});
let refreshCalled = false;
server.use(
http.post(`${API_URL}/api/auth/refresh/`, () => {
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
refreshCalled = true;
return HttpResponse.json({ access: "new-token" });
}),
@@ -194,7 +194,7 @@ describe("initialize", () => {
it("should preserve the master key even if the refresh attempt fails", async () => {
server.use(
http.post(
`${API_URL}/api/auth/refresh/`,
`${VITE_API_URL}/api/auth/refresh/`,
() => new HttpResponse(null, { status: 401 }),
),
);
+9
View File
@@ -0,0 +1,9 @@
export const getBaseUrl = (
isSslEnabled: boolean,
domain: string | undefined,
port: string | undefined,
): string => {
const uriScheme = isSslEnabled ? "https" : "http";
const baseURL = `${uriScheme}://${domain}${port ? `:${port}` : ""}`;
return baseURL;
};
+23 -8
View File
@@ -3,24 +3,39 @@ import path from "node:path";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig, loadEnv } from "vite";
import { getBaseUrl } from "./utils/url-builder";
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, "../", "");
const isSslEnabled = env.SSL_ENABLED === "true";
let ssl_certs: { key: Buffer; cert: Buffer };
if (isSslEnabled) {
ssl_certs = {
key: fs.readFileSync(
path.resolve(__dirname, "../certs/localhost-key.pem"),
),
cert: fs.readFileSync(path.resolve(__dirname, "../certs/localhost.pem")),
};
}
const baseApiUrl = getBaseUrl(
isSslEnabled,
env.BACKEND_DOMAIN,
env.BACKEND_PORT,
);
console.log(baseApiUrl);
return {
envDir: "../",
plugins: [react(), tailwindcss()],
define: {
"import.meta.env.VITE_API_URL": JSON.stringify(baseApiUrl),
},
server: {
port: Number(env.FRONTEND_PORT),
host: env.FRONTEND_DOMAIN,
https: {
key: fs.readFileSync(
path.resolve(__dirname, "../certs/localhost-key.pem"),
),
cert: fs.readFileSync(
path.resolve(__dirname, "../certs/localhost.pem"),
),
},
https: isSslEnabled ? ssl_certs : undefined,
},
};
});