-
+
{job.job_id}
diff --git a/apps/web/src/app/(workspace)/workspace/audits/page.tsx b/apps/web/src/app/(workspace)/workspace/audits/page.tsx
index f4a75d9..5b46698 100644
--- a/apps/web/src/app/(workspace)/workspace/audits/page.tsx
+++ b/apps/web/src/app/(workspace)/workspace/audits/page.tsx
@@ -8,9 +8,7 @@ export const dynamic = "force-dynamic";
import { resolveLocaleFromHeaderStore } from "@/lib/locale";
import { WORKSPACE_COPY } from "@/lib/workspace-copy";
import { WorkspacePageFrame } from "@/components/workspace-frame";
-import { sanitizeAuditJobPayload } from "@/lib/audit-job-payload";
-import { isDemoModeEnabledServer } from "@/lib/demo-mode";
-import { listDemoJobs } from "@/lib/demo-jobs-store";
+import { getWorkspaceAuditJobsData } from "@/lib/workspace-source";
import { AuditsPageClient } from "./AuditsPageClient";
type WorkspaceAuditsPageOptions = {
@@ -20,9 +18,7 @@ type WorkspaceAuditsPageOptions = {
async function renderWorkspaceAuditsPage({ locale }: WorkspaceAuditsPageOptions = {}) {
const resolvedLocale = locale ?? resolveLocaleFromHeaderStore(await headers());
const copy = WORKSPACE_COPY[resolvedLocale].audits;
- const initialJobs = await isDemoModeEnabledServer()
- ? sanitizeAuditJobPayload(listDemoJobs())
- : [];
+ const initialJobs = await getWorkspaceAuditJobsData();
return (
{
const { default: WorkspaceHomePage } = await import("./start/page");
const markup = await renderMarkup(await WorkspaceHomePage());
- expect(markup).toContain("建议操作");
- expect(markup).toContain("最近结果");
+ expect(markup).toContain("工作台总览");
+ expect(markup).toContain("AUC 风险分布");
+ expect(markup).toContain("近期任务");
expect(markup).toContain("PIA");
expect(markup).toContain("stable-diffusion-v1-4");
- expect(markup).toContain("可审计模型");
- expect(markup).toContain("已防御结果");
+ expect(markup).toContain("可审计合同");
+ expect(markup).toContain("已评估防御");
});
it("renders en-US copy with forced demo data", async () => {
@@ -44,9 +45,10 @@ describe("WorkspaceHomePage", () => {
const { default: WorkspaceHomePage } = await import("./start/page");
const markup = await renderMarkup(await WorkspaceHomePage());
- expect(markup).toContain("Suggested actions");
- expect(markup).toContain("Recent results");
+ expect(markup).toContain("Workspace Overview");
+ expect(markup).toContain("AUC Risk Distribution");
+ expect(markup).toContain("Recent tasks");
expect(markup).toContain("stable-diffusion-v1-4");
- expect(markup).toContain("Auditable models");
+ expect(markup).toContain("Auditable contracts");
});
});
diff --git a/apps/web/src/app/(workspace)/workspace/reports/page.test.tsx b/apps/web/src/app/(workspace)/workspace/reports/page.test.tsx
index d5823c3..1900eb3 100644
--- a/apps/web/src/app/(workspace)/workspace/reports/page.test.tsx
+++ b/apps/web/src/app/(workspace)/workspace/reports/page.test.tsx
@@ -25,11 +25,11 @@ describe("WorkspaceReportsPage", () => {
const { default: WorkspaceReportsPage } = await import("./page");
const markup = await renderMarkup(await WorkspaceReportsPage());
- expect(markup).toContain("报告生成");
- expect(markup).toContain("按审计模式生成");
- expect(markup).toContain("已生成报告");
- expect(markup).toContain("综合分析");
- expect(markup).toContain("导出选项");
+ expect(markup).toContain("审计结果和覆盖缺口");
+ expect(markup).toContain("任务报告");
+ expect(markup).toContain("任务报告表");
+ expect(markup).toContain("photo-real-xl");
+ expect(markup).toContain("查看审计报告");
});
it("renders en-US copy with forced demo data", async () => {
@@ -37,11 +37,10 @@ describe("WorkspaceReportsPage", () => {
const { default: WorkspaceReportsPage } = await import("./page");
const markup = await renderMarkup(await WorkspaceReportsPage());
- expect(markup).toContain("Reports");
- expect(markup).toContain("Report Generation");
- expect(markup).toContain("Generate by Audit Mode");
- expect(markup).toContain("Generated Reports");
- expect(markup).toContain("Comprehensive Analysis");
- expect(markup).toContain("Export Options");
+ expect(markup).toContain("Audit results and coverage gaps");
+ expect(markup).toContain("Task reports");
+ expect(markup).toContain("Task reports table");
+ expect(markup).toContain("photo-real-xl");
+ expect(markup).toContain("View Report");
});
});
diff --git a/apps/web/src/app/(workspace)/workspace/reports/page.tsx b/apps/web/src/app/(workspace)/workspace/reports/page.tsx
index 8d1b031..77d86d2 100644
--- a/apps/web/src/app/(workspace)/workspace/reports/page.tsx
+++ b/apps/web/src/app/(workspace)/workspace/reports/page.tsx
@@ -3,9 +3,7 @@ import { headers } from "next/headers";
import { resolveLocaleFromHeaderStore } from "@/lib/locale";
import { WORKSPACE_COPY } from "@/lib/workspace-copy";
import { WorkspacePageFrame } from "@/components/workspace-frame";
-import { sanitizeAuditJobPayload } from "@/lib/audit-job-payload";
-import { isDemoModeEnabledServer } from "@/lib/demo-mode";
-import { listDemoJobs } from "@/lib/demo-jobs-store";
+import { getWorkspaceAuditJobsData } from "@/lib/workspace-source";
import { ReportsPageClient } from "./ReportsPageClient";
export const dynamic = "force-dynamic";
@@ -17,9 +15,7 @@ type WorkspaceReportsPageOptions = {
async function renderWorkspaceReportsPage({ locale }: WorkspaceReportsPageOptions = {}) {
const resolvedLocale = locale ?? resolveLocaleFromHeaderStore(await headers());
const copy = WORKSPACE_COPY[resolvedLocale].reports;
- const initialJobs = await isDemoModeEnabledServer()
- ? sanitizeAuditJobPayload(listDemoJobs())
- : [];
+ const initialJobs = await getWorkspaceAuditJobsData();
return (
diff --git a/apps/web/src/app/api/auth/me/route.test.ts b/apps/web/src/app/api/auth/me/route.test.ts
new file mode 100644
index 0000000..69e525f
--- /dev/null
+++ b/apps/web/src/app/api/auth/me/route.test.ts
@@ -0,0 +1,62 @@
+import { NextRequest } from "next/server";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const getCurrentUserProfile = vi.fn();
+const isDemoModeEnabledServer = vi.fn();
+
+vi.mock("@/lib/auth", () => ({
+ getCurrentUserProfile,
+ SESSION_COOKIE_NAME: "diffaudit_session",
+}));
+
+vi.mock("@/lib/demo-mode", () => ({
+ isDemoModeEnabledServer,
+}));
+
+describe("auth me route", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ getCurrentUserProfile.mockReset();
+ isDemoModeEnabledServer.mockReset();
+ });
+
+ it("returns anonymous profile without a 401 response when demo mode is enabled", async () => {
+ isDemoModeEnabledServer.mockResolvedValue(true);
+ const route = await import("./route");
+
+ const response = await route.GET(new NextRequest("http://localhost/api/auth/me"));
+ const payload = await response.json();
+
+ expect(response.status).toBe(200);
+ expect(payload).toEqual({ user: null });
+ expect(getCurrentUserProfile).not.toHaveBeenCalled();
+ });
+
+ it("keeps the unauthenticated 401 response outside demo mode", async () => {
+ isDemoModeEnabledServer.mockResolvedValue(false);
+ const route = await import("./route");
+
+ const response = await route.GET(new NextRequest("http://localhost/api/auth/me"));
+ const payload = await response.json();
+
+ expect(response.status).toBe(401);
+ expect(payload).toEqual({ user: null });
+ });
+
+ it("returns the current user when a valid session exists", async () => {
+ const user = { id: "user-1", username: "demo-reviewer" };
+ getCurrentUserProfile.mockReturnValue(user);
+ const route = await import("./route");
+
+ const response = await route.GET(
+ new NextRequest("http://localhost/api/auth/me", {
+ headers: { cookie: "diffaudit_session=session-token" },
+ }),
+ );
+ const payload = await response.json();
+
+ expect(response.status).toBe(200);
+ expect(payload).toEqual({ user });
+ expect(getCurrentUserProfile).toHaveBeenCalledWith("session-token");
+ });
+});
diff --git a/apps/web/src/app/api/auth/me/route.ts b/apps/web/src/app/api/auth/me/route.ts
index 30e917a..94ff7fa 100644
--- a/apps/web/src/app/api/auth/me/route.ts
+++ b/apps/web/src/app/api/auth/me/route.ts
@@ -1,16 +1,25 @@
import { type NextRequest } from "next/server";
import { getCurrentUserProfile, SESSION_COOKIE_NAME } from "@/lib/auth";
+import { isDemoModeEnabledServer } from "@/lib/demo-mode";
+
+async function anonymousResponse(request: NextRequest) {
+ if (await isDemoModeEnabledServer(request)) {
+ return Response.json({ user: null });
+ }
+
+ return Response.json({ user: null }, { status: 401 });
+}
export async function GET(request: NextRequest) {
const token = request.cookies.get(SESSION_COOKIE_NAME)?.value;
if (!token) {
- return Response.json({ user: null }, { status: 401 });
+ return anonymousResponse(request);
}
const user = getCurrentUserProfile(token);
if (!user) {
- return Response.json({ user: null }, { status: 401 });
+ return anonymousResponse(request);
}
return Response.json({ user });
diff --git a/apps/web/src/app/health/route.test.ts b/apps/web/src/app/health/route.test.ts
new file mode 100644
index 0000000..193427a
--- /dev/null
+++ b/apps/web/src/app/health/route.test.ts
@@ -0,0 +1,46 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const isDemoModeEnabledServer = vi.fn();
+const proxyToBackend = vi.fn();
+
+vi.mock("@/lib/demo-mode", () => ({
+ isDemoModeEnabledServer,
+}));
+
+vi.mock("@/lib/api-proxy", () => ({
+ proxyToBackend,
+}));
+
+describe("health route", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ isDemoModeEnabledServer.mockReset();
+ proxyToBackend.mockReset();
+ proxyToBackend.mockResolvedValue(Response.json({ upstream: true }));
+ });
+
+ it("returns demo health without proxying when demo mode is enabled", async () => {
+ isDemoModeEnabledServer.mockResolvedValue(true);
+ const route = await import("./route");
+
+ const response = await route.GET(new Request("http://localhost/health"));
+ const payload = await response.json();
+
+ expect(response.status).toBe(200);
+ expect(payload).toMatchObject({
+ demo_mode: true,
+ snapshot_available: true,
+ build: { revision: "demo-snapshot" },
+ });
+ expect(proxyToBackend).not.toHaveBeenCalled();
+ });
+
+ it("proxies backend health outside demo mode", async () => {
+ isDemoModeEnabledServer.mockResolvedValue(false);
+ const route = await import("./route");
+
+ await route.GET(new Request("http://localhost/health"));
+
+ expect(proxyToBackend).toHaveBeenCalledWith("/health");
+ });
+});
diff --git a/apps/web/src/app/health/route.ts b/apps/web/src/app/health/route.ts
index c5f5598..ebde043 100644
--- a/apps/web/src/app/health/route.ts
+++ b/apps/web/src/app/health/route.ts
@@ -1,5 +1,16 @@
import { proxyToBackend } from "@/lib/api-proxy";
+import { isDemoModeEnabledServer } from "@/lib/demo-mode";
+
+export async function GET(request: Request) {
+ if (await isDemoModeEnabledServer(request)) {
+ return Response.json({
+ demo_mode: true,
+ snapshot_available: true,
+ build: {
+ revision: "demo-snapshot",
+ },
+ });
+ }
-export async function GET() {
return proxyToBackend("/health");
}
diff --git a/apps/web/src/components/chart-risk-donut.tsx b/apps/web/src/components/chart-risk-donut.tsx
index b8aa790..0e52867 100644
--- a/apps/web/src/components/chart-risk-donut.tsx
+++ b/apps/web/src/components/chart-risk-donut.tsx
@@ -27,17 +27,20 @@ export function ChartRiskDonut({ data, totalLabel = "总结果", height = 160 }:
const router = useRouter();
const total = data.reduce((acc, item) => acc + item.count, 0);
const chartSize = Math.max(106, Math.min(124, height - 46));
- let cursor = 0;
+ const segments = data.map((item, index) => {
+ const start = data
+ .slice(0, index)
+ .reduce((acc, previous) => acc + (total > 0 ? (previous.count / total) * 360 : 0), 0);
+ const sweep = total > 0 ? (item.count / total) * 360 : 0;
+ return { item, start, sweep };
+ });
return (
|