mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
Feature/ssl integration (#1)
* feat: update E2E testing configuration to use ssl * fix: add IPv6 loopback support to mkcert generation command in CI workflow * feat: centralize SSL certificate generation into a reusable workflow job * fix: use static ip in mkcert command * fix: correct mkcert command args * feat: implement certificate caching in CI workflow to persist SSL files across jobs * refactor: optimize CI workflow caching * ci: implement certificate caching in workflow * fix: correct certificate caching keys and fix environment file paths in CI workflows * fix: correct environment file paths and parallelize frontend dependency installation in CI workflow * test: set TZ environment variable to Asia/Kolkata in vitest configuration * fix: force en-US locale in Intl formatters to ensure consistent date and time output * fix: update MAILPIT_API_URL protocol from https to http in e2e environment example * chore: set test timezone to Asia/Kolkata * ci: add sll support and enhance e2e workflow * ci: improve compatibility for docker-compose execution * refactor: improve container orchestration detection and fallback logic in e2e test script * feat: add container runtime validation and force docker usage in CI environment * feat: add caching for Playwright dependencies in CI workflow * chore: update restart policy to unless-stopped for postgres and mailpit services in e2e docker-compose --------- Co-authored-by: ramvignesh-b <ramvignesh-b@github.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^4.1.4",
|
||||
"dotenv": "^17.4.2",
|
||||
@@ -257,6 +258,8 @@
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.3.0", "", { "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||
|
||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="],
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^4.1.4",
|
||||
"dotenv": "^17.4.2",
|
||||
|
||||
@@ -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,13 +34,17 @@ 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 */
|
||||
trace: "on-first-retry",
|
||||
/* Capture screenshot on failure */
|
||||
screenshot: "only-on-failure",
|
||||
/* Capture video on failure */
|
||||
video: "retain-on-failure",
|
||||
/* Ignore HTTPS errors */
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
@@ -49,8 +61,13 @@ 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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const timeFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
const dateTimeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(undefined, {
|
||||
const rtf = new Intl.RelativeTimeFormat("en-US", {
|
||||
numeric: "auto",
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,16 +1,41 @@
|
||||
import fs from "node:fs";
|
||||
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 sslCerts: { key: Buffer; cert: Buffer } | undefined;
|
||||
|
||||
if (isSslEnabled) {
|
||||
sslCerts = {
|
||||
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: isSslEnabled ? sslCerts : undefined,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ export default defineConfig({
|
||||
test: {
|
||||
env: {
|
||||
VITE_API_URL: "http://piku-server",
|
||||
TZ: "Asia/Kolkata",
|
||||
},
|
||||
include: ["**/*.test.ts"],
|
||||
environment: "jsdom",
|
||||
|
||||
Reference in New Issue
Block a user