142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
import fs from "fs";
|
||
import os from "os";
|
||
import path from "path";
|
||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
|
||
const TEMP_DIRS: string[] = [];
|
||
const FLAG_KEYS = [
|
||
"FEATURE_ASSISTANT_BROAD_GUARD_V1",
|
||
"FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1"
|
||
] as const;
|
||
const ORIGINAL_FLAGS: Record<string, string | undefined> = Object.fromEntries(FLAG_KEYS.map((key) => [key, process.env[key]]));
|
||
|
||
function restoreFlags(): void {
|
||
for (const key of FLAG_KEYS) {
|
||
const original = ORIGINAL_FLAGS[key];
|
||
if (original === undefined) {
|
||
delete process.env[key];
|
||
} else {
|
||
process.env[key] = original;
|
||
}
|
||
}
|
||
}
|
||
|
||
function cleanupTempDirs(): void {
|
||
for (const dir of TEMP_DIRS.splice(0)) {
|
||
fs.rmSync(dir, { recursive: true, force: true });
|
||
}
|
||
}
|
||
|
||
function createSnapshotRoot(keyFields: Array<Record<string, unknown>>): string {
|
||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-wave11-recovery-"));
|
||
TEMP_DIRS.push(root);
|
||
const write = (fileName: string, records: Array<Record<string, unknown>>) => {
|
||
fs.writeFileSync(path.resolve(root, fileName), JSON.stringify({ records }, null, 2), "utf-8");
|
||
};
|
||
|
||
write("09_samples_key_fields_Recorder_Ref_Supplier_Buyer_Responsible.json", keyFields);
|
||
write("03_snapshot_fragment_problem_cases.json", []);
|
||
write("07_samples_DocumentJournals.json", []);
|
||
write("08_samples_NDS_registers.json", []);
|
||
write("04_samples_SpisanieSRaschetnogoScheta.json", []);
|
||
write("05_samples_RealizaciyaTovarovUslug.json", []);
|
||
write("06_samples_PostuplenieTovarovUslug.json", []);
|
||
|
||
return root;
|
||
}
|
||
|
||
function buildDrilldownAnchorRecord(): Record<string, unknown> {
|
||
return {
|
||
source_entity: "Document",
|
||
source_id: "DOC-SETTLE-4",
|
||
display_name: "Оплата по счету 4",
|
||
unknown_link_count: 0,
|
||
attributes: {
|
||
Number: "4",
|
||
Date: "2020-07-07T00:00:00",
|
||
Amount: 276873.6,
|
||
Description: "Оплата по счету 4 от 07.07.20",
|
||
Account: "62.02"
|
||
},
|
||
links: [
|
||
{
|
||
relation: "document_has_counterparty",
|
||
target_entity: "Counterparty",
|
||
target_id: "CP-4",
|
||
source_field: "Counterparty"
|
||
}
|
||
]
|
||
};
|
||
}
|
||
|
||
function buildSettlementRecoveryRecord(): Record<string, unknown> {
|
||
return {
|
||
source_entity: "Document",
|
||
source_id: "DOC-SETTLE-RECOVERY-1",
|
||
display_name: "Payment settlement tail",
|
||
unknown_link_count: 1,
|
||
attributes: {
|
||
Period: "2020-07-15T00:00:00",
|
||
Description: "payment linked to contract and buyer with unresolved closure"
|
||
},
|
||
links: [
|
||
{
|
||
relation: "document_has_counterparty",
|
||
target_entity: "Counterparty",
|
||
target_id: "CP-RECOVERY",
|
||
source_field: "Counterparty"
|
||
},
|
||
{
|
||
relation: "document_refers_to_document",
|
||
target_entity: "Document",
|
||
target_id: "DOC-CHAIN-1",
|
||
source_field: "Recorder"
|
||
}
|
||
]
|
||
};
|
||
}
|
||
|
||
describe.sequential("wave11 data-layer recovery", () => {
|
||
afterEach(() => {
|
||
cleanupTempDirs();
|
||
restoreFlags();
|
||
vi.resetModules();
|
||
});
|
||
|
||
it("settlement_object_trace_with_number_date_amount_must_not_require_guid_by_default", async () => {
|
||
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
|
||
const dataLayer = new AssistantDataLayer(createSnapshotRoot([buildDrilldownAnchorRecord()]));
|
||
const result = dataLayer.executeRoute(
|
||
"live_mcp_drilldown",
|
||
"Оплата по счету № 4 от 07.07.20 на 276 873,60 пришла 13 июля по счету 62.02."
|
||
);
|
||
|
||
expect(result.status).toBe("ok");
|
||
const summary = result.summary as Record<string, unknown>;
|
||
expect(summary.reason).toBe("business_anchor_trace");
|
||
expect(summary.reason).not.toBe("guid_not_provided");
|
||
expect(Array.isArray(result.items)).toBe(true);
|
||
expect(result.items.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("broad_settlement_query_must_not_drop_all_retrieval_due_to_strict_purity_if_in_scope", async () => {
|
||
process.env.FEATURE_ASSISTANT_BROAD_GUARD_V1 = "0";
|
||
process.env.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = "0";
|
||
vi.resetModules();
|
||
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
|
||
const dataLayer = new AssistantDataLayer(createSnapshotRoot([buildSettlementRecoveryRecord()]));
|
||
const result = dataLayer.executeRoute(
|
||
"hybrid_store_plus_live",
|
||
"Почему по поставщику деньги ушли, а долг остался по счетам 60.01/62.02 в июле?"
|
||
);
|
||
|
||
expect(result.status).toBe("ok");
|
||
expect(result.items.length).toBeGreaterThan(0);
|
||
const summary = result.summary as Record<string, unknown>;
|
||
const guard = (summary.domain_purity_guard ?? {}) as Record<string, unknown>;
|
||
expect(Number(guard.source_selection_allowed ?? 0)).toBeGreaterThan(0);
|
||
expect(Boolean(guard.settlement_source_recovery) || Boolean(guard.settlement_narrowing_recovery)).toBe(true);
|
||
});
|
||
});
|
||
|