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