mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: E2E test isolation and analytics consent bugs
- Isolate test web server on port 2349 so E2E tests run alongside Docker - Use fresh SQLite database per test run via DB_PATH, fixing missing roles/audit_log tables from stale migration state - Fix analytics consent redirect loop when ANALYTICS_ENABLED=false by auto-declining consent instead of bare redirect - Dismiss analytics consent via API in auth setup and RBAC test user creation so the consent page never blocks UI tests - Make Vite port and proxy target configurable via env vars
This commit is contained in:
@@ -8,7 +8,8 @@ const t = en.analytics;
|
||||
|
||||
export function AnalyticsConsentPage() {
|
||||
const navigate = useNavigate();
|
||||
const { config, configLoaded, fetchConfig, acceptAnalytics, remindLater } = useAnalyticsStore();
|
||||
const { config, configLoaded, fetchConfig, acceptAnalytics, declineAnalytics, remindLater } =
|
||||
useAnalyticsStore();
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig();
|
||||
@@ -16,9 +17,9 @@ export function AnalyticsConsentPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (configLoaded && !config?.enabled) {
|
||||
navigate("/", { replace: true });
|
||||
declineAnalytics().then(() => navigate("/", { replace: true }));
|
||||
}
|
||||
}, [configLoaded, config, navigate]);
|
||||
}, [configLoaded, config, navigate, declineAnalytics]);
|
||||
|
||||
if (!configLoaded) {
|
||||
return (
|
||||
|
||||
@@ -12,9 +12,9 @@ export default defineConfig({
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
server: {
|
||||
port: 1349,
|
||||
port: Number(process.env.PORT) || 1349,
|
||||
proxy: {
|
||||
"/api": "http://localhost:13490",
|
||||
"/api": process.env.VITE_API_URL || "http://localhost:13490",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
+12
-4
@@ -2,6 +2,9 @@ import path from "node:path";
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const authFile = path.join(__dirname, "test-results", ".auth", "user.json");
|
||||
const testDbPath = path.join(__dirname, "test-results", ".e2e-db", "ashim.db");
|
||||
|
||||
const TEST_WEB_PORT = 2349;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests/e2e",
|
||||
@@ -20,7 +23,7 @@ export default defineConfig({
|
||||
workers: 1,
|
||||
reporter: "html",
|
||||
use: {
|
||||
baseURL: "http://localhost:1349",
|
||||
baseURL: `http://localhost:${TEST_WEB_PORT}`,
|
||||
screenshot: "only-on-failure",
|
||||
trace: "retain-on-failure",
|
||||
},
|
||||
@@ -40,23 +43,28 @@ export default defineConfig({
|
||||
],
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm --filter @ashim/api dev",
|
||||
command: `rm -f "${testDbPath}" "${testDbPath}-shm" "${testDbPath}-wal" && mkdir -p "${path.dirname(testDbPath)}" && pnpm --filter @ashim/api dev`,
|
||||
port: 13490,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
env: {
|
||||
PORT: "13490",
|
||||
AUTH_ENABLED: "true",
|
||||
DEFAULT_USERNAME: "admin",
|
||||
DEFAULT_PASSWORD: "admin",
|
||||
RATE_LIMIT_PER_MIN: "50000",
|
||||
SKIP_MUST_CHANGE_PASSWORD: "true",
|
||||
ANALYTICS_ENABLED: "false",
|
||||
DB_PATH: testDbPath,
|
||||
},
|
||||
timeout: 30_000,
|
||||
},
|
||||
{
|
||||
command: "pnpm --filter @ashim/web dev",
|
||||
port: 1349,
|
||||
port: TEST_WEB_PORT,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
env: {
|
||||
PORT: String(TEST_WEB_PORT),
|
||||
VITE_API_URL: "http://localhost:13490",
|
||||
},
|
||||
timeout: 30_000,
|
||||
},
|
||||
],
|
||||
|
||||
+15
-2
@@ -14,8 +14,21 @@ setup("authenticate", async ({ page }) => {
|
||||
await page.getByLabel("Password").fill("admin");
|
||||
await page.getByRole("button", { name: /login/i }).click();
|
||||
|
||||
// Wait for the full-page redirect to "/"
|
||||
await page.waitForURL("/", { timeout: 15_000 });
|
||||
// Wait for login to complete and grab the token in one step
|
||||
const handle = await page.waitForFunction(() => localStorage.getItem("ashim-token"), null, {
|
||||
timeout: 15_000,
|
||||
});
|
||||
const token = await handle.jsonValue();
|
||||
|
||||
// Dismiss analytics consent via API so it won't block any test
|
||||
const apiBase = process.env.API_URL || "http://localhost:13490";
|
||||
await page.request.put(`${apiBase}/api/v1/user/analytics`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
data: { enabled: false },
|
||||
});
|
||||
|
||||
// Now navigate to "/" - consent guard is satisfied
|
||||
await page.goto("/");
|
||||
await expect(page).toHaveURL("/");
|
||||
|
||||
// Save storage state (includes localStorage with the token)
|
||||
|
||||
@@ -85,6 +85,19 @@ async function createUserWithRole(
|
||||
if (!changeRes.ok) {
|
||||
throw new Error(`Failed to clear mustChangePassword for ${username}: ${changeRes.status}`);
|
||||
}
|
||||
|
||||
// Re-login (change-password invalidates sessions) and dismiss analytics consent
|
||||
const reLogin = await fetch(`${API}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
const reLoginData = await reLogin.json();
|
||||
await fetch(`${API}/api/v1/user/analytics`, {
|
||||
method: "PUT",
|
||||
headers: authJson(reLoginData.token),
|
||||
body: JSON.stringify({ enabled: false }),
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete a user by username if it exists. */
|
||||
|
||||
@@ -68,6 +68,19 @@ async function ensureTestUser(adminToken: string): Promise<void> {
|
||||
if (!changeRes.ok) {
|
||||
throw new Error(`Failed to clear mustChangePassword: ${changeRes.status}`);
|
||||
}
|
||||
|
||||
// Re-login (change-password invalidates sessions) and dismiss analytics consent
|
||||
const reLogin = await fetch(`${API}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: TEST_USER, password: TEST_PASSWORD }),
|
||||
});
|
||||
const reLoginData = await reLogin.json();
|
||||
await fetch(`${API}/api/v1/user/analytics`, {
|
||||
method: "PUT",
|
||||
headers: authJson(reLoginData.token),
|
||||
body: JSON.stringify({ enabled: false }),
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete the test user if it exists. */
|
||||
@@ -204,6 +217,19 @@ base.describe("RBAC - Editor sees collaborative tabs", () => {
|
||||
newPassword: "EditorTest1",
|
||||
}),
|
||||
});
|
||||
|
||||
// Re-login and dismiss analytics consent
|
||||
const reLogin = await fetch(`${API}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: "editortest", password: "EditorTest1" }),
|
||||
});
|
||||
const reLoginData = await reLogin.json();
|
||||
await fetch(`${API}/api/v1/user/analytics`, {
|
||||
method: "PUT",
|
||||
headers: authJson(reLoginData.token),
|
||||
body: JSON.stringify({ enabled: false }),
|
||||
});
|
||||
});
|
||||
|
||||
base.afterAll(async () => {
|
||||
|
||||
Reference in New Issue
Block a user