OAAUTO1 执行自动化收口证据

这个提交包含在:
2026-05-23 23:34:29 +08:00
父节点 141adc8cd9
当前提交 a3f13eab57
共修改 2 个文件,包含 100 行新增和 8 行删除
+93 -1
查看文件
@@ -49,6 +49,18 @@ const ROLE_TARGETS = [
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -86,6 +98,18 @@ const ROLE_TARGETS = [
{ id: "payment-batches", path: "/api/payment-batches?pageSize=5", statuses: [403, 409] },
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -124,6 +148,18 @@ const ROLE_TARGETS = [
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -197,6 +233,17 @@ const ROLE_TARGETS = [
{ id: "payment-batches", path: "/api/payment-batches?pageSize=5", statuses: [403, 409] },
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -231,6 +278,17 @@ const ROLE_TARGETS = [
negative: [
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -255,6 +313,9 @@ const ROLE_TARGETS = [
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [200] },
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [200] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [200] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [200] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [200, 409] },
{ id: "global-search", path: "/api/search?q=TEST&limit=10", statuses: [200, 409] },
{ id: "assets", path: "/api/assets?pageSize=5", statuses: [200] },
{
id: "navigation",
@@ -384,6 +445,33 @@ function evaluateNavigationExpectations(json, expectations) {
return [...visible, ...hidden];
}
function evaluateSearchExpectations(json, expectations) {
if (!expectations || !Array.isArray(json?.data?.items)) return [];
const items = json.data.items.filter((item) => item && typeof item === "object");
const forbiddenEntityTypes = new Set(expectations.forbiddenEntityTypes || []);
const forbiddenHrefPrefixes = expectations.forbiddenHrefPrefixes || [];
const results = [];
for (const entityType of forbiddenEntityTypes) {
const matchedCount = items.filter((item) => item.entityType === entityType).length;
results.push({
type: "forbidden_entity_type",
value: entityType,
status: matchedCount === 0 ? "PASS" : "FAIL",
matchedCount,
});
}
for (const hrefPrefix of forbiddenHrefPrefixes) {
const matchedCount = items.filter((item) => typeof item.href === "string" && item.href.startsWith(hrefPrefix)).length;
results.push({
type: "forbidden_href_prefix",
value: hrefPrefix,
status: matchedCount === 0 ? "PASS" : "FAIL",
matchedCount,
});
}
return results;
}
function collectSensitiveKeyHits(value, currentPath = "data", hits = []) {
if (hits.length >= 12) return hits;
if (Array.isArray(value)) {
@@ -471,17 +559,20 @@ async function probe(accessToken, probe, kind) {
const expected = probe.statuses.includes(response.status);
const dataExpectations = response.ok ? evaluateDataExpectations(response.json, probe.dataExpectations) : [];
const navigationExpectations = response.ok ? evaluateNavigationExpectations(response.json, probe.navigationExpectations) : [];
const searchExpectations = response.ok ? evaluateSearchExpectations(response.json, probe.searchExpectations) : [];
const dataExpectationFailed = dataExpectations.some((item) => item.status === "FAIL");
const navigationExpectationFailed = navigationExpectations.some((item) => item.status === "FAIL");
const searchExpectationFailed = searchExpectations.some((item) => item.status === "FAIL");
const sensitiveKeyHits = response.ok ? collectSensitiveKeyHits(response.json?.data).slice(0, 12) : [];
return {
id: probe.id,
kind,
status: expected && !dataExpectationFailed && !navigationExpectationFailed && sensitiveKeyHits.length === 0 ? "PASS" : "FAIL",
status: expected && !dataExpectationFailed && !navigationExpectationFailed && !searchExpectationFailed && sensitiveKeyHits.length === 0 ? "PASS" : "FAIL",
httpStatus: response.status,
expectedStatuses: probe.statuses,
dataExpectations,
navigationExpectations,
searchExpectations,
sensitiveKeyHits,
summary: response.ok ? summarizeData(response.json) : {
code: response.json?.error?.code ?? null,
@@ -544,6 +635,7 @@ function collectIssues(results) {
code: probeResult.summary?.code ?? null,
failedDataExpectations: probeResult.dataExpectations?.filter((item) => item.status === "FAIL") || [],
failedNavigationExpectations: probeResult.navigationExpectations?.filter((item) => item.status === "FAIL") || [],
failedSearchExpectations: probeResult.searchExpectations?.filter((item) => item.status === "FAIL") || [],
sensitiveKeyHits: probeResult.sensitiveKeyHits || [],
});
}