补齐发票验真测试覆盖(05-28-oa-test-coverage-gap-fill)
这个提交包含在:
@@ -65,6 +65,21 @@ async function fulfillJson<T>(route: Route, data: T) {
|
||||
});
|
||||
}
|
||||
|
||||
async function fulfillApiError(route: Route, status: number, details: Record<string, unknown>) {
|
||||
await route.fulfill({
|
||||
status,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
requestId: "mock-pc-wave4",
|
||||
error: {
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
details,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function navigationItem(key: string, href: string, label: string, icon: string, placement = "main") {
|
||||
return { key, href, label, icon, placement, permissionCodes: [], featureFlagKey: null };
|
||||
}
|
||||
@@ -394,7 +409,41 @@ const notificationDeliveries = [
|
||||
},
|
||||
];
|
||||
|
||||
async function mockWave4Apis(page: Page) {
|
||||
type MockVerificationRecord = {
|
||||
id: string;
|
||||
provider: string;
|
||||
status: string;
|
||||
resultCode: string | null;
|
||||
resultMessage: string | null;
|
||||
errorCode: string | null;
|
||||
errorMessage: string | null;
|
||||
durationMs: number | null;
|
||||
requestedBy: string | null;
|
||||
requestedAt: string | null;
|
||||
completedAt: string | null;
|
||||
retryOfId: string | null;
|
||||
attempts: number;
|
||||
};
|
||||
|
||||
type Wave4MockOptions = {
|
||||
detailVerification?: MockVerificationRecord | null;
|
||||
verificationRecords?: MockVerificationRecord[];
|
||||
triggerVerificationError?: Record<string, unknown>;
|
||||
retryVerificationError?: Record<string, unknown>;
|
||||
onTriggerVerification?: (route: Route) => void;
|
||||
onRetryVerification?: (route: Route) => void;
|
||||
};
|
||||
|
||||
async function mockWave4Apis(page: Page, options: Wave4MockOptions = {}) {
|
||||
const defaultVerification = invoiceListItem.verification as MockVerificationRecord;
|
||||
const initialVerificationRecords =
|
||||
options.verificationRecords ??
|
||||
[options.detailVerification ?? defaultVerification].filter(
|
||||
(item): item is MockVerificationRecord => Boolean(item),
|
||||
);
|
||||
let currentVerificationRecords = initialVerificationRecords;
|
||||
let currentDetailVerification = options.detailVerification ?? currentVerificationRecords[0] ?? defaultVerification;
|
||||
|
||||
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
@@ -593,13 +642,20 @@ async function mockWave4Apis(page: Page) {
|
||||
}
|
||||
|
||||
if (pathName === "/api/invoice-pool") {
|
||||
await fulfillJson(route, { page: 1, pageSize: 20, total: 1, ownerScope: "own", items: [invoiceListItem] });
|
||||
await fulfillJson(route, {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 1,
|
||||
ownerScope: "own",
|
||||
items: [{ ...invoiceListItem, verification: currentDetailVerification }],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === `/api/invoice-pool/${invoiceListItem.id}`) {
|
||||
await fulfillJson(route, {
|
||||
...invoiceListItem,
|
||||
verification: currentDetailVerification,
|
||||
archivedReason: null,
|
||||
archivedAt: null,
|
||||
ownerEmployeeId: visualMe.id,
|
||||
@@ -616,8 +672,64 @@ async function mockWave4Apis(page: Page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === `/api/invoice-pool/${invoiceListItem.id}/verify`) {
|
||||
options.onTriggerVerification?.(route);
|
||||
if (options.triggerVerificationError) {
|
||||
await fulfillApiError(route, 409, options.triggerVerificationError);
|
||||
return;
|
||||
}
|
||||
const pendingRecord: MockVerificationRecord = {
|
||||
...defaultVerification,
|
||||
id: "verify-wave4-triggered",
|
||||
status: "pending",
|
||||
resultCode: null,
|
||||
resultMessage: null,
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
completedAt: null,
|
||||
attempts: 1,
|
||||
};
|
||||
currentDetailVerification = pendingRecord;
|
||||
currentVerificationRecords = [pendingRecord, ...currentVerificationRecords];
|
||||
await fulfillJson(route, {
|
||||
invoiceId: invoiceListItem.id,
|
||||
verificationId: pendingRecord.id,
|
||||
provider: "mock",
|
||||
triggerMode: "queued",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === `/api/invoice-pool/${invoiceListItem.id}/verification`) {
|
||||
await fulfillJson(route, { invoiceId: invoiceListItem.id, items: [invoiceListItem.verification] });
|
||||
await fulfillJson(route, { invoiceId: invoiceListItem.id, items: currentVerificationRecords });
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === `/api/invoice-pool/${invoiceListItem.id}/verification/retry`) {
|
||||
options.onRetryVerification?.(route);
|
||||
if (options.retryVerificationError) {
|
||||
await fulfillApiError(route, 409, options.retryVerificationError);
|
||||
return;
|
||||
}
|
||||
const retryRecord: MockVerificationRecord = {
|
||||
...defaultVerification,
|
||||
id: "verify-wave4-retry",
|
||||
status: "pending",
|
||||
resultCode: null,
|
||||
resultMessage: null,
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
completedAt: null,
|
||||
retryOfId: currentDetailVerification?.id ?? null,
|
||||
attempts: (currentDetailVerification?.attempts ?? 1) + 1,
|
||||
};
|
||||
currentDetailVerification = retryRecord;
|
||||
currentVerificationRecords = [retryRecord, ...currentVerificationRecords];
|
||||
await fulfillJson(route, {
|
||||
invoiceId: invoiceListItem.id,
|
||||
verificationId: retryRecord.id,
|
||||
status: "pending",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -796,3 +908,72 @@ test("PC 财务与管理页 Wave4 视觉冒烟", async ({ page }) => {
|
||||
await capture(page, target.screenshot);
|
||||
}
|
||||
});
|
||||
|
||||
test("PC 票据中心辅助核验动作可提交并重试", async ({ page }) => {
|
||||
const failedVerification: MockVerificationRecord = {
|
||||
...(invoiceListItem.verification as MockVerificationRecord),
|
||||
id: "verify-wave4-failed",
|
||||
status: "error",
|
||||
resultCode: "tax_provider_not_configured",
|
||||
resultMessage: null,
|
||||
errorCode: "tax_provider_not_configured",
|
||||
errorMessage: "tax provider is not configured in current environment",
|
||||
attempts: 1,
|
||||
};
|
||||
const triggerRequests: string[] = [];
|
||||
const retryRequests: string[] = [];
|
||||
|
||||
await mockWave4Apis(page, {
|
||||
detailVerification: failedVerification,
|
||||
verificationRecords: [failedVerification],
|
||||
onTriggerVerification: (route) => triggerRequests.push(route.request().method()),
|
||||
onRetryVerification: (route) => retryRequests.push(route.request().method()),
|
||||
});
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await login(page);
|
||||
|
||||
await page.goto("/invoice-pool");
|
||||
await expect(page.getByTestId("invoice-detail-panel")).toBeVisible();
|
||||
await expect(page.getByTestId("invoice-trigger-verification")).toBeVisible();
|
||||
await expect(page.getByTestId("invoice-retry-verification")).toBeVisible();
|
||||
await expect(page.getByTestId("invoice-verification-records")).toContainText("当前环境未配置自动核验服务");
|
||||
|
||||
await page.getByTestId("invoice-retry-verification").click();
|
||||
await expect(page.getByText("发票核验已重新提交")).toBeVisible();
|
||||
expect(retryRequests).toEqual(["POST"]);
|
||||
|
||||
await page.getByTestId("invoice-trigger-verification").click();
|
||||
await expect(page.getByText("发票核验已提交")).toBeVisible();
|
||||
expect(triggerRequests).toEqual(["POST"]);
|
||||
});
|
||||
|
||||
test("PC 票据中心辅助核验错误提示可理解", async ({ page }) => {
|
||||
const failedVerification: MockVerificationRecord = {
|
||||
...(invoiceListItem.verification as MockVerificationRecord),
|
||||
id: "verify-wave4-attempts",
|
||||
status: "error",
|
||||
resultCode: "mock_transport_error",
|
||||
resultMessage: null,
|
||||
errorCode: "mock_transport_error",
|
||||
errorMessage: "mock provider transport error",
|
||||
attempts: 3,
|
||||
};
|
||||
|
||||
await mockWave4Apis(page, {
|
||||
detailVerification: failedVerification,
|
||||
verificationRecords: [failedVerification],
|
||||
triggerVerificationError: { verification: "too_frequent" },
|
||||
retryVerificationError: { verification: "attempts_exceeded" },
|
||||
});
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await login(page);
|
||||
|
||||
await page.goto("/invoice-pool");
|
||||
await expect(page.getByTestId("invoice-detail-panel")).toBeVisible();
|
||||
|
||||
await page.getByTestId("invoice-trigger-verification").click();
|
||||
await expect(page.getByRole("main").getByText("VALID_001: 验真触发过于频繁,请稍后重试。")).toBeVisible();
|
||||
|
||||
await page.getByTestId("invoice-retry-verification").click();
|
||||
await expect(page.getByRole("main").getByText("VALID_001: 验真重试次数已达上限,请联系财务人工核验。")).toBeVisible();
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户