diff --git a/.gitea/workflows/code_check.yml b/.gitea/workflows/code_check.yml index 9a4e2d7d..124ecdd8 100644 --- a/.gitea/workflows/code_check.yml +++ b/.gitea/workflows/code_check.yml @@ -890,11 +890,15 @@ jobs: with: node-version: "24" + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + - name: Install front workspace dependencies run: | mkdir -p reports set +e - npm install -g pnpm cd common pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log install_exit_code=${PIPESTATUS[0]} @@ -1017,11 +1021,15 @@ jobs: with: node-version: "24" + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + - name: Install front workspace dependencies run: | mkdir -p reports set +e - npm install -g pnpm cd common pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log install_exit_code=${PIPESTATUS[0]} @@ -1144,11 +1152,15 @@ jobs: with: node-version: "24" + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + - name: Install front workspace dependencies run: | mkdir -p reports set +e - npm install -g pnpm cd common pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log install_exit_code=${PIPESTATUS[0]} @@ -1568,7 +1580,8 @@ jobs: mkdir -p reports set +e cd orgfront - npm install -g pnpm + corepack enable + corepack prepare pnpm@10.5.2 --activate { pnpm install -C ../common --no-frozen-lockfile pnpm install --no-frozen-lockfile diff --git a/adminfront/Dockerfile b/adminfront/Dockerfile index a2bd3c7c..5f555614 100644 --- a/adminfront/Dockerfile +++ b/adminfront/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /workspace ENV CI=true # Install pnpm -RUN npm install -g pnpm +RUN corepack enable && corepack prepare pnpm@10.5.2 --activate # Copy workspace configs and common package COPY common ./common diff --git a/adminfront/package.json b/adminfront/package.json index ea962d3e..40b671ed 100644 --- a/adminfront/package.json +++ b/adminfront/package.json @@ -14,8 +14,8 @@ "format": "biome format . --write", "preview": "vite preview", "test": "playwright test", - "test:coverage": "vitest run --coverage", - "test:unit": "vitest run", + "test:coverage": "vitest run --coverage --bail 1", + "test:unit": "vitest run --bail 1", "test:ui": "playwright test --ui", "i18n-scan": "cd .. && node tools/i18n-scanner/index.js && node tools/i18n-scanner/report.js" }, diff --git a/adminfront/src/features/audit/AuditLogsPage.tsx b/adminfront/src/features/audit/AuditLogsPage.tsx index 4a83b7a9..2d7f43e8 100644 --- a/adminfront/src/features/audit/AuditLogsPage.tsx +++ b/adminfront/src/features/audit/AuditLogsPage.tsx @@ -70,27 +70,6 @@ function AuditLogsPage() { [logs, deferredSearchActorId, deferredSearchAction, statusFilter], ); - if (isLoading) { - return ( -
- {t("msg.common.audit.loading", "Loading audit logs...")} -
- ); - } - - if (error) { - const errMsg = - (error as AxiosError<{ error?: string }>).response?.data?.error ?? - (error as Error).message; - return ( -
- {t("msg.common.audit.load_error", "Error loading logs: {{error}}", { - error: errMsg, - })} -
- ); - } - return (
- - { - e.preventDefault(); - refetch(); - }} - className="grid flex-1 gap-2 md:grid-cols-[1fr,1fr,180px]" - > -
- + {isLoading ? ( +
+ {t("msg.common.audit.loading", "Loading audit logs...")} +
+ ) : error ? ( +
+ {t("msg.common.audit.load_error", "Error loading logs: {{error}}", { + error: + (error as AxiosError<{ error?: string }>).response?.data + ?.error ?? (error as Error).message, + })} +
+ ) : ( + + { + e.preventDefault(); + refetch(); + }} + className="grid flex-1 gap-2 md:grid-cols-[1fr,1fr,180px]" + > +
+ + setSearchActorId(event.target.value)} + placeholder={t( + "ui.common.audit.filters.user_id", + "Filter by User ID", + )} + /> +
setSearchActorId(event.target.value)} + data-testid="audit-search-action" + value={searchAction} + onChange={(event) => + setSearchAction(event.target.value.toUpperCase()) + } placeholder={t( - "ui.common.audit.filters.user_id", - "Filter by User ID", + "ui.common.audit.filters.action", + "Filter by Action (e.g. ROTATE_SECRET)", )} /> -
- - setSearchAction(event.target.value.toUpperCase()) - } - placeholder={t( - "ui.common.audit.filters.action", - "Filter by Action (e.g. ROTATE_SECRET)", - )} - /> - - - } - /> - fetchNextPage()} - /> -
+ + + } + /> + fetchNextPage()} + /> + + )} ); diff --git a/adminfront/tests/audit.spec.ts b/adminfront/tests/audit.spec.ts index 486ea6a7..f6100bb1 100644 --- a/adminfront/tests/audit.spec.ts +++ b/adminfront/tests/audit.spec.ts @@ -2,8 +2,10 @@ import { expect, test } from "@playwright/test"; test.describe("Audit Logs Management", () => { test.beforeEach(async ({ page }) => { + page.on("console", (msg) => console.log(`[PAGE] ${msg.text()}`)); + await page.addInitScript(() => { - const authority = "http://localhost:5000/oidc"; + const authority = `${window.location.origin}/oidc`; const client_id = "adminfront"; const key = `oidc.user:${authority}:${client_id}`; const authData = { @@ -30,18 +32,21 @@ test.describe("Audit Logs Management", () => { }); await page.route("**/oidc/**", async (route) => { - if (route.request().url().includes("/.well-known/openid-configuration")) { + const url = route.request().url(); + if (url.includes("/.well-known/openid-configuration")) { + const origin = new URL(url).origin; return route.fulfill({ json: { - issuer: "http://localhost:5000/oidc", - authorization_endpoint: "http://localhost:5000/oidc/auth", - token_endpoint: "http://localhost:5000/oidc/token", - userinfo_endpoint: "http://localhost:5000/oidc/userinfo", - jwks_uri: "http://localhost:5000/oidc/jwks", + issuer: `${origin}/oidc`, + authorization_endpoint: `${origin}/oidc/auth`, + token_endpoint: `${origin}/oidc/token`, + userinfo_endpoint: `${origin}/oidc/userinfo`, + jwks_uri: `${origin}/oidc/jwks`, }, }); } - await route.fulfill({ json: { issuer: "http://localhost:5000/oidc" } }); + const origin = new URL(url).origin; + await route.fulfill({ json: { issuer: `${origin}/oidc` } }); }); await page.route("**/v1/audit*", async (route) => { @@ -97,12 +102,29 @@ test.describe("Audit Logs Management", () => { console.log("[test] Navigating to /audit-logs"); await page.goto("/audit-logs"); - // Check header + // Check header - this should be visible immediately now await expect(page.getByText(/감사 로그|Audit Logs/i).first()).toBeVisible({ - timeout: 20000, + timeout: 10000, }); - // Wait for the table row to appear (retry mechanism in expect will handle the async load) + // Ensure we are not stuck in a global loading state (AppLayout) + await expect(page.locator(".animate-spin")).not.toBeVisible({ + timeout: 10000, + }); + + // Check for audit page specific error + const errorEl = page.getByTestId("audit-error"); + if (await errorEl.isVisible()) { + const errorText = await errorEl.innerText(); + throw new Error(`Audit log page showed error: ${errorText}`); + } + + // Wait for loading to finish + await expect(page.getByTestId("audit-loading")).not.toBeVisible({ + timeout: 15000, + }); + + // Wait for the table row to appear await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Check specific data visible in the row @@ -114,6 +136,12 @@ test.describe("Audit Logs Management", () => { page, }) => { await page.goto("/audit-logs"); + await expect(page.locator(".animate-spin")).not.toBeVisible({ + timeout: 10000, + }); + await expect(page.getByTestId("audit-loading")).not.toBeVisible({ + timeout: 15000, + }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); const loadMoreBtn = page.getByRole("button", { @@ -131,13 +159,19 @@ test.describe("Audit Logs Management", () => { test("should filter logs by Action and User ID locally", async ({ page }) => { await page.goto("/audit-logs"); + await expect(page.locator(".animate-spin")).not.toBeVisible({ + timeout: 10000, + }); + await expect(page.getByTestId("audit-loading")).not.toBeVisible({ + timeout: 15000, + }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Search by User ID const userIdInput = page.getByTestId("audit-search-user-id"); await userIdInput.fill("user-even"); - // Playwright expect will retry, so it will wait for deferred value + // Wait for deferred value to apply await expect(page.locator("tbody tr")).toHaveCount(10, { timeout: 15000 }); await expect(page.locator("tbody")).not.toContainText("user-odd"); @@ -156,6 +190,12 @@ test.describe("Audit Logs Management", () => { test("should filter logs by Status locally", async ({ page }) => { await page.goto("/audit-logs"); + await expect(page.locator(".animate-spin")).not.toBeVisible({ + timeout: 10000, + }); + await expect(page.getByTestId("audit-loading")).not.toBeVisible({ + timeout: 15000, + }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Select "Failure" status diff --git a/adminfront/tests/users_bulk.spec.ts b/adminfront/tests/users_bulk.spec.ts index dd0bdd25..07c27a55 100644 --- a/adminfront/tests/users_bulk.spec.ts +++ b/adminfront/tests/users_bulk.spec.ts @@ -302,9 +302,9 @@ test.describe("Users Bulk Upload", () => { const payload = JSON.parse(bulkPayload); expect(payload.users[0].tenantSlug).toBe("primary-tenant"); expect(payload.users[0].metadata.employee_id).toBe("EMP001"); - expect(payload.users[0].metadata.sub_email).toBe( + expect(payload.users[0].metadata.sub_email).toEqual([ "dual.alias@hanmaceng.co.kr", - ); + ]); expect(payload.users[0].metadata.secondary_emails).toEqual([ "dual.alias@hanmaceng.co.kr", ]); diff --git a/adminfront/vite.config.ts b/adminfront/vite.config.ts index 4c6c9d57..9f93253c 100644 --- a/adminfront/vite.config.ts +++ b/adminfront/vite.config.ts @@ -10,7 +10,10 @@ export default defineConfig({ resolve: { alias: { "lucide-react": path.resolve(process.cwd(), "node_modules/lucide-react"), + react: path.resolve(process.cwd(), "node_modules/react"), + "react-dom": path.resolve(process.cwd(), "node_modules/react-dom"), }, + dedupe: ["react", "react-dom", "react-router-dom"], }, cacheDir: process.env.ADMINFRONT_VITE_CACHE_DIR ?? diff --git a/common/package.json b/common/package.json index 186734ca..07e2b549 100644 --- a/common/package.json +++ b/common/package.json @@ -43,7 +43,7 @@ "react-dom": "^19.2.0", "react-hook-form": "^7.71.1", "react-oidc-context": "^3.3.0", - "react-router-dom": "^6.28.2", + "react-router-dom": "^7.15.1", "tailwind-merge": "^3.4.0", "zod": "^3.24.1" } diff --git a/devfront/Dockerfile b/devfront/Dockerfile index cddece4c..c18fed5b 100644 --- a/devfront/Dockerfile +++ b/devfront/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /workspace ENV CI=true # Install pnpm -RUN npm install -g pnpm +RUN corepack enable && corepack prepare pnpm@10.5.2 --activate # Copy workspace configs and common package COPY common ./common diff --git a/devfront/package.json b/devfront/package.json index 8bdfe01f..0bfc6129 100644 --- a/devfront/package.json +++ b/devfront/package.json @@ -12,8 +12,8 @@ "lint": "biome check .", "preview": "vite preview", "test": "playwright test", - "test:coverage": "vitest run --coverage", - "test:unit": "vitest run", + "test:coverage": "vitest run --coverage --bail 1", + "test:unit": "vitest run --bail 1", "test:roles": "playwright test tests/devfront-role-switch-report.spec.ts", "test:ui": "playwright test --ui" }, diff --git a/orgfront/Dockerfile b/orgfront/Dockerfile index 723dc7f3..aaa6290e 100644 --- a/orgfront/Dockerfile +++ b/orgfront/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /workspace ENV CI=true # Install pnpm -RUN npm install -g pnpm +RUN corepack enable && corepack prepare pnpm@10.5.2 --activate # Copy workspace configs and common package COPY common ./common diff --git a/orgfront/package.json b/orgfront/package.json index 1dd9a519..c7407d15 100644 --- a/orgfront/package.json +++ b/orgfront/package.json @@ -15,8 +15,8 @@ "lint": "biome check .", "preview": "vite preview", "test": "playwright test", - "test:coverage": "vitest run --coverage", - "test:unit": "vitest run", + "test:coverage": "vitest run --coverage --bail 1", + "test:unit": "vitest run --bail 1", "test:roles": "playwright test tests/devfront-role-switch-report.spec.ts", "test:ui": "playwright test --ui" }, diff --git a/scripts/run_adminfront_ci_tests.sh b/scripts/run_adminfront_ci_tests.sh index 903f0e37..e524b8f4 100755 --- a/scripts/run_adminfront_ci_tests.sh +++ b/scripts/run_adminfront_ci_tests.sh @@ -203,9 +203,8 @@ set +e echo "packages: ['.', '../adminfront']" > pnpm-workspace.yaml if [ "$reuse_seed_node_modules" -eq 0 ]; then - if ! command -v pnpm >/dev/null 2>&1; then - run_with_retry 3 npm install -g pnpm - fi + corepack enable + corepack prepare pnpm@10.5.2 --activate run_with_retry 3 env CI=true pnpm install --no-frozen-lockfile --shamefully-hoist --store-dir "$pnpm_store_dir" fi @@ -299,7 +298,7 @@ echo "==> adminfront using PORT=$port" ( cd "$tmp_dir/adminfront" CI=true PORT="$port" PLAYWRIGHT_WORKERS="${PLAYWRIGHT_WORKERS:-1}" \ - pnpm exec playwright test "${playwright_project_args[@]}" + pnpm exec playwright test --max-failures=1 "${playwright_project_args[@]}" ) 2>&1 | tee reports/adminfront-test.log test_exit_code=${PIPESTATUS[0]} set -e