515 lines
21 KiB
TypeScript
515 lines
21 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
closeLocalPostgresDataPool,
|
|
createLocalPostgresDataClient,
|
|
} from "../src/lib/db/local-postgres-client-core.ts";
|
|
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
|
|
|
const runnerPath = fileURLToPath(
|
|
new URL("../scripts/db-migrate.mjs", import.meta.url),
|
|
);
|
|
const migrationSql = readFileSync(
|
|
new URL("../supabase/migrations/20260814030000_rectification_event_decision_contract_v2.sql", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
function dockerAvailable(): boolean {
|
|
return spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], {
|
|
encoding: "utf8",
|
|
stdio: "ignore",
|
|
}).status === 0;
|
|
}
|
|
|
|
const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on this host";
|
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
function rpcError(error: unknown): string {
|
|
if (!error || typeof error !== "object") return "";
|
|
const value = error as { message?: unknown };
|
|
return typeof value.message === "string" ? value.message : "";
|
|
}
|
|
|
|
function candidateIds(value: unknown): string[] {
|
|
if (!value || typeof value !== "object") return [];
|
|
const rows = (value as { candidates?: unknown }).candidates;
|
|
if (!Array.isArray(rows)) return [];
|
|
return rows.flatMap((row) => {
|
|
if (!row || typeof row !== "object") return [];
|
|
const id = (row as { candidate_id?: unknown }).candidate_id;
|
|
return typeof id === "string" ? [id] : [];
|
|
});
|
|
}
|
|
|
|
function objectField(value: unknown, field: string): Record<string, unknown> {
|
|
if (!value || typeof value !== "object") return {};
|
|
const nested = (value as Record<string, unknown>)[field];
|
|
return nested && typeof nested === "object" && !Array.isArray(nested)
|
|
? (nested as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
test("PR-4 migration binds receipts to server candidate UUIDs and restores V2 result fields", () => {
|
|
const persistStart = migrationSql.indexOf(
|
|
"create or replace function public.persist_agentic_rectification_candidate_v2(",
|
|
);
|
|
const caseStart = migrationSql.indexOf(
|
|
"create or replace function public.get_agentic_rectification_case(",
|
|
);
|
|
const dossierStart = migrationSql.indexOf(
|
|
"create or replace function public.get_agentic_rectification_case_dossier(",
|
|
);
|
|
const acceptStart = migrationSql.indexOf(
|
|
"create or replace function public.accept_agentic_rectification_candidate_for_case_v2(",
|
|
);
|
|
assert.ok(persistStart >= 0 && caseStart > persistStart && dossierStart > caseStart && acceptStart > dossierStart);
|
|
|
|
const persistFunction = migrationSql.slice(persistStart, caseStart);
|
|
assert.match(persistFunction, /p_decision_receipt - 'representative_candidate_id'/);
|
|
assert.match(persistFunction, /v_representative_candidate_id := v_candidate_id/);
|
|
assert.match(persistFunction, /\{representative_candidate_id\}/);
|
|
assert.match(persistFunction, /decision_receipt = v_saved_decision_receipt/);
|
|
assert.match(persistFunction, /'decision_receipt', v_saved_decision_receipt/);
|
|
assert.match(persistFunction, /'decision_receipt', v_cached\.decision_receipt/);
|
|
|
|
for (const functionSql of [
|
|
migrationSql.slice(caseStart, dossierStart),
|
|
migrationSql.slice(dossierStart, acceptStart),
|
|
]) {
|
|
for (const field of [
|
|
"event_contract_version",
|
|
"decision_policy_version",
|
|
"decision_receipt",
|
|
"execution_ledger",
|
|
"selected_candidate_id",
|
|
]) {
|
|
assert.match(functionSql, new RegExp(`'${field}', v_result\.${field}`));
|
|
}
|
|
}
|
|
const dossierFunction = migrationSql.slice(dossierStart, acceptStart);
|
|
assert.match(dossierFunction, /\(1, 'user'::text, recent\.user_message\)/);
|
|
assert.match(dossierFunction, /\(2, 'assistant'::text, recent\.assistant_message\)/);
|
|
});
|
|
|
|
test("PR-4 candidate decisions use server UUIDs, receipt-derived gates and separate acceptance/confirmation", { skip: skipWithoutDocker }, async () => {
|
|
const fixture = startPostgresFixture();
|
|
const schemaUrl = fixture.connectionUrl("schema_owner", "schema-owner-test-password");
|
|
const serviceUrl = fixture.connectionUrl("service_runtime", "service-runtime-test-password");
|
|
try {
|
|
const migration = spawnSync(process.execPath, [runnerPath], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, SCHEMA_DATABASE_URL: schemaUrl },
|
|
});
|
|
assert.equal(migration.status, 0, migration.stderr);
|
|
assert.match(
|
|
migration.stdout,
|
|
/applied 20260814030000_rectification_event_decision_contract_v2\.sql/,
|
|
);
|
|
|
|
const userId = "10000000-0000-4000-8000-000000000001";
|
|
const sessionId = "20000000-0000-4000-8000-000000000001";
|
|
const secondSessionId = "20000000-0000-4000-8000-000000000002";
|
|
const caseId = "30000000-0000-4000-8000-000000000001";
|
|
const secondCaseId = "30000000-0000-4000-8000-000000000002";
|
|
const consentTurnId = "40000000-0000-4000-8000-000000000001";
|
|
const fakeCallerCandidateId = "50000000-0000-4000-8000-000000000001";
|
|
const fingerprint = "a".repeat(64);
|
|
const snapshot = {
|
|
birth_date: "1997-08-08",
|
|
latitude: 36.420487,
|
|
longitude: 114.209936,
|
|
timezone_offset: 8,
|
|
birth_time_source: "family_exact",
|
|
reported_birth_time: "05:00",
|
|
active_birth_time: null,
|
|
birth_time_period: null,
|
|
uncertainty_before_minutes: 10,
|
|
uncertainty_after_minutes: 10,
|
|
};
|
|
const range = { start_time: "04:50", end_time: "05:10" };
|
|
|
|
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
|
insert into identity.users (id, name, email, email_verified, email_verified_at)
|
|
values ('${userId}', 'PR4 Candidate Fixture', 'pr4-candidate@example.com', true, now())
|
|
`);
|
|
fixture.psql(`
|
|
update public.profiles
|
|
set birth_date = '1997-08-08',
|
|
reported_birth_time = '05:00',
|
|
active_birth_time = null,
|
|
birth_time = '05:00',
|
|
birth_time_status = 'reported',
|
|
birth_time_source = 'family_exact',
|
|
birth_time_period = null,
|
|
uncertainty_before_minutes = 10,
|
|
uncertainty_after_minutes = 10,
|
|
birth_place_label = '河北省邯郸市武安市',
|
|
latitude = 36.420487,
|
|
longitude = 114.209936,
|
|
timezone_id = 'Asia/Shanghai',
|
|
timezone_offset = 8
|
|
where id = '${userId}';
|
|
|
|
insert into public.chat_sessions (id, user_id, title, theme, session_type, messages)
|
|
values
|
|
('${sessionId}', '${userId}', 'PR4 candidate case', 'general', 'birth_time_rectification', '[]'::jsonb),
|
|
('${secondSessionId}', '${userId}', 'PR4 other case', 'general', 'birth_time_rectification', '[]'::jsonb);
|
|
|
|
insert into public.agentic_rectification_cases (
|
|
id, user_id, session_id, status, skill_name, skill_version,
|
|
baseline_profile_fingerprint, baseline_birth_snapshot, candidate_range
|
|
) values
|
|
(
|
|
'${caseId}', '${userId}', '${sessionId}', 'candidate_ready',
|
|
'jyotish-birth-time-rectification', '9.0.0', '${fingerprint}',
|
|
'${JSON.stringify(snapshot)}'::jsonb, '${JSON.stringify(range)}'::jsonb
|
|
),
|
|
(
|
|
'${secondCaseId}', '${userId}', '${secondSessionId}', 'candidate_ready',
|
|
'jyotish-birth-time-rectification', '9.0.0', '${"b".repeat(64)}',
|
|
'${JSON.stringify(snapshot)}'::jsonb, '${JSON.stringify(range)}'::jsonb
|
|
);
|
|
|
|
insert into public.agentic_rectification_turns (
|
|
id, case_id, request_id, user_message, assistant_message, status, model_name, completed_at
|
|
) values (
|
|
'${consentTurnId}', '${caseId}', '41000000-0000-4000-8000-000000000001',
|
|
'我确认使用05:07作为出生时间', '已记录确认请求', 'completed', 'fixture-model', now()
|
|
);
|
|
`);
|
|
|
|
const service = createLocalPostgresDataClient(serviceUrl, null, "service_role");
|
|
const candidates = [
|
|
{
|
|
candidate_id: fakeCallerCandidateId,
|
|
rank: 1,
|
|
time: "05:07",
|
|
relative_support: 60,
|
|
tied_minute_count: 1,
|
|
},
|
|
{ rank: 2, time: "05:08", relative_support: 40, tied_minute_count: 1 },
|
|
];
|
|
const decisionReceipt = {
|
|
display_allowed: true,
|
|
accept_allowed: true,
|
|
confirm_allowed: true,
|
|
representative_time: "05:07",
|
|
overall_confidence: "high",
|
|
margin_percent: 20,
|
|
representative_candidate_id: fakeCallerCandidateId,
|
|
};
|
|
const executionLedger = [
|
|
{ phase: "candidate.score", status: "completed", engine: "fixture-engine" },
|
|
{ phase: "decision.evaluate", status: "completed", policy: "decision-policy-v1" },
|
|
];
|
|
const persist = (engineResultId: string, policyVersion: string) =>
|
|
service.rpc("persist_agentic_rectification_candidate_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_engine_result_id: engineResultId,
|
|
p_evidence_ledger_fingerprint: "c".repeat(64),
|
|
p_candidate_range_fingerprint: "d".repeat(64),
|
|
p_skill_version: "9.0.0",
|
|
p_algorithm_version: "rectification-pr4-fixture-v1",
|
|
p_event_contract_version: "rectification-event-contract-v2",
|
|
p_decision_policy_version: policyVersion,
|
|
p_candidate_range: range,
|
|
p_candidates: candidates,
|
|
p_decision_receipt: decisionReceipt,
|
|
p_execution_ledger: executionLedger,
|
|
});
|
|
|
|
const first = await persist("pr4-engine-result-1", "decision-policy-v1");
|
|
assert.equal(first.error, null, rpcError(first.error));
|
|
const firstRow = first.data as Record<string, unknown>;
|
|
const firstResultId = String(firstRow.result_id);
|
|
const firstCandidateIds = candidateIds(firstRow);
|
|
assert.equal(firstRow.cached, false);
|
|
assert.equal(firstCandidateIds.length, 2);
|
|
assert.ok(firstCandidateIds.every((id) => UUID_PATTERN.test(id)));
|
|
assert.ok(!firstCandidateIds.includes(fakeCallerCandidateId));
|
|
assert.equal(
|
|
objectField(firstRow, "decision_receipt").representative_candidate_id,
|
|
firstCandidateIds[0],
|
|
);
|
|
|
|
const cached = await persist("pr4-engine-result-cache-ignored", "decision-policy-v1");
|
|
assert.equal(cached.error, null, rpcError(cached.error));
|
|
const cachedRow = cached.data as Record<string, unknown>;
|
|
assert.equal(cachedRow.cached, true);
|
|
assert.equal(cachedRow.result_id, firstResultId);
|
|
assert.deepEqual(candidateIds(cachedRow), firstCandidateIds);
|
|
assert.equal(
|
|
objectField(cachedRow, "decision_receipt").representative_candidate_id,
|
|
firstCandidateIds[0],
|
|
);
|
|
|
|
const changedPolicy = await persist("pr4-engine-result-2", "decision-policy-v2");
|
|
assert.equal(changedPolicy.error, null, rpcError(changedPolicy.error));
|
|
const changedPolicyRow = changedPolicy.data as Record<string, unknown>;
|
|
const resultId = String(changedPolicyRow.result_id);
|
|
const ids = candidateIds(changedPolicyRow);
|
|
const representativeCandidateId = ids[0]!;
|
|
const nonRepresentativeCandidateId = ids[1]!;
|
|
assert.equal(changedPolicyRow.cached, false);
|
|
assert.notEqual(resultId, firstResultId);
|
|
assert.equal(
|
|
objectField(changedPolicyRow, "decision_receipt").representative_candidate_id,
|
|
representativeCandidateId,
|
|
);
|
|
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select (r.decision_receipt ->> 'representative_candidate_id') || ':' || c.id
|
|
from public.agentic_rectification_results r
|
|
join public.agentic_rectification_candidates c
|
|
on c.result_id = r.id and c.is_representative
|
|
where r.id = '${resultId}'
|
|
`),
|
|
`${representativeCandidateId}:${representativeCandidateId}`,
|
|
);
|
|
|
|
const restoredCase = await service.rpc("get_agentic_rectification_case", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
});
|
|
assert.equal(restoredCase.error, null, rpcError(restoredCase.error));
|
|
const restoredCaseResult = objectField(restoredCase.data, "latest_result");
|
|
assert.deepEqual(
|
|
{
|
|
event_contract_version: restoredCaseResult.event_contract_version,
|
|
decision_policy_version: restoredCaseResult.decision_policy_version,
|
|
representative_candidate_id: objectField(
|
|
restoredCaseResult,
|
|
"decision_receipt",
|
|
).representative_candidate_id,
|
|
execution_ledger: restoredCaseResult.execution_ledger,
|
|
selected_candidate_id: restoredCaseResult.selected_candidate_id,
|
|
},
|
|
{
|
|
event_contract_version: "rectification-event-contract-v2",
|
|
decision_policy_version: "decision-policy-v2",
|
|
representative_candidate_id: representativeCandidateId,
|
|
execution_ledger: executionLedger,
|
|
selected_candidate_id: null,
|
|
},
|
|
);
|
|
|
|
const restoredDossier = await service.rpc("get_agentic_rectification_case_dossier", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
});
|
|
assert.equal(restoredDossier.error, null, rpcError(restoredDossier.error));
|
|
const restoredDossierResult = objectField(restoredDossier.data, "latest_result");
|
|
assert.equal(restoredDossierResult.event_contract_version, "rectification-event-contract-v2");
|
|
assert.equal(restoredDossierResult.decision_policy_version, "decision-policy-v2");
|
|
assert.equal(
|
|
objectField(restoredDossierResult, "decision_receipt").representative_candidate_id,
|
|
representativeCandidateId,
|
|
);
|
|
assert.deepEqual(restoredDossierResult.execution_ledger, executionLedger);
|
|
assert.equal(restoredDossierResult.selected_candidate_id, null);
|
|
assert.deepEqual(
|
|
(restoredDossier.data as { turns?: Array<{ role?: unknown }> }).turns?.map(
|
|
(turn) => turn.role,
|
|
),
|
|
["user", "assistant"],
|
|
);
|
|
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select event_contract_version || ':' || decision_policy_version || ':' ||
|
|
display_allowed || ':' || selection_allowed || ':' || confirmation_allowed || ':' ||
|
|
(decision_receipt ->> 'confirm_allowed') || ':' || jsonb_array_length(execution_ledger)
|
|
from public.agentic_rectification_results
|
|
where id = '${resultId}'
|
|
`),
|
|
"rectification-event-contract-v2:decision-policy-v2:true:true:true:true:2",
|
|
);
|
|
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select
|
|
has_function_privilege('service_role', 'public.persist_agentic_rectification_candidate(uuid,uuid,text,text,text,text,text,jsonb,jsonb,text,numeric,boolean,boolean,time without time zone)', 'EXECUTE') || ':' ||
|
|
has_function_privilege('service_role', 'public.accept_agentic_rectification_candidate(uuid,uuid,uuid,time without time zone)', 'EXECUTE') || ':' ||
|
|
has_function_privilege('service_role', 'public.accept_agentic_rectification_candidate_for_case(uuid,uuid,uuid,time without time zone)', 'EXECUTE') || ':' ||
|
|
has_function_privilege('service_role', 'public.confirm_agentic_rectification_birth_time(uuid,uuid,uuid,time without time zone,text,uuid)', 'EXECUTE')
|
|
`),
|
|
"f:f:f:f",
|
|
);
|
|
|
|
const crossResult = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: firstResultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: "60000000-0000-4000-8000-000000000001",
|
|
});
|
|
assert.match(rpcError(crossResult.error), /agentic_rectification_candidate_not_found/);
|
|
|
|
const crossCase = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: secondCaseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: "60000000-0000-4000-8000-000000000002",
|
|
});
|
|
assert.match(rpcError(crossCase.error), /agentic_rectification_candidate_not_found/);
|
|
|
|
const forged = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: "50000000-0000-4000-8000-000000000099",
|
|
p_request_id: "60000000-0000-4000-8000-000000000003",
|
|
});
|
|
assert.match(rpcError(forged.error), /agentic_rectification_candidate_not_found/);
|
|
|
|
const inexactConfirmation = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: nonRepresentativeCandidateId,
|
|
p_request_id: "70000000-0000-4000-8000-000000000001",
|
|
p_consent_quote: "确认使用05:07",
|
|
p_source_turn_id: consentTurnId,
|
|
});
|
|
assert.match(
|
|
rpcError(inexactConfirmation.error),
|
|
/agentic_rectification_confirmation_exact_gate_blocked/,
|
|
);
|
|
|
|
const acceptRequestId = "80000000-0000-4000-8000-000000000001";
|
|
const accepted = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: acceptRequestId,
|
|
});
|
|
assert.equal(accepted.error, null, rpcError(accepted.error));
|
|
assert.deepEqual(
|
|
{
|
|
status: (accepted.data as Record<string, unknown>).status,
|
|
case_status: (accepted.data as Record<string, unknown>).case_status,
|
|
saved_time: (accepted.data as Record<string, unknown>).saved_time,
|
|
candidate_id: (accepted.data as Record<string, unknown>).candidate_id,
|
|
},
|
|
{
|
|
status: "accepted",
|
|
case_status: "candidate_accepted",
|
|
saved_time: "05:07",
|
|
candidate_id: representativeCandidateId,
|
|
},
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select active_birth_time || ':' || birth_time_status || ':' || selection_kind || ':' || selected_candidate_id
|
|
from public.profiles p
|
|
join public.agentic_rectification_results r on r.user_id = p.id
|
|
where r.id = '${resultId}'
|
|
`),
|
|
`05:07:00:accepted:user_accepted:${representativeCandidateId}`,
|
|
);
|
|
|
|
for (const rpcName of [
|
|
"get_agentic_rectification_case",
|
|
"get_agentic_rectification_case_dossier",
|
|
]) {
|
|
const restoredAfterAccept = await service.rpc(rpcName, {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
});
|
|
assert.equal(restoredAfterAccept.error, null, rpcError(restoredAfterAccept.error));
|
|
const latestResult = objectField(restoredAfterAccept.data, "latest_result");
|
|
assert.equal(latestResult.selected_candidate_id, representativeCandidateId);
|
|
}
|
|
|
|
const acceptReplay = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: acceptRequestId,
|
|
});
|
|
assert.equal(acceptReplay.error, null, rpcError(acceptReplay.error));
|
|
assert.equal((acceptReplay.data as Record<string, unknown>).idempotent, true);
|
|
|
|
const requestConflict = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: nonRepresentativeCandidateId,
|
|
p_request_id: acceptRequestId,
|
|
});
|
|
assert.match(
|
|
rpcError(requestConflict.error),
|
|
/agentic_rectification_candidate_request_conflict/,
|
|
);
|
|
|
|
const ungrounded = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: "90000000-0000-4000-8000-000000000001",
|
|
p_consent_quote: "这段话没有出现",
|
|
p_source_turn_id: consentTurnId,
|
|
});
|
|
assert.match(rpcError(ungrounded.error), /agentic_rectification_consent_not_grounded/);
|
|
|
|
const confirmRequestId = "90000000-0000-4000-8000-000000000002";
|
|
const confirmed = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: confirmRequestId,
|
|
p_consent_quote: "确认使用05:07",
|
|
p_source_turn_id: consentTurnId,
|
|
});
|
|
assert.equal(confirmed.error, null, rpcError(confirmed.error));
|
|
assert.deepEqual(
|
|
{
|
|
status: (confirmed.data as Record<string, unknown>).status,
|
|
case_status: (confirmed.data as Record<string, unknown>).case_status,
|
|
saved_time: (confirmed.data as Record<string, unknown>).saved_time,
|
|
},
|
|
{ status: "confirmed", case_status: "confirmed", saved_time: "05:07" },
|
|
);
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select p.birth_time_status || ':' || r.selection_kind || ':' || c.status || ':' ||
|
|
c.confirmed_time || ':' || r.selected_candidate_id
|
|
from public.profiles p
|
|
join public.agentic_rectification_results r on r.user_id = p.id
|
|
join public.agentic_rectification_cases c on c.id = r.case_id
|
|
where r.id = '${resultId}'
|
|
`),
|
|
`confirmed:engine_confirmed:confirmed:05:07:00:${representativeCandidateId}`,
|
|
);
|
|
|
|
const confirmReplay = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
|
|
p_user_id: userId,
|
|
p_case_id: caseId,
|
|
p_result_id: resultId,
|
|
p_candidate_id: representativeCandidateId,
|
|
p_request_id: confirmRequestId,
|
|
p_consent_quote: "确认使用05:07",
|
|
p_source_turn_id: consentTurnId,
|
|
});
|
|
assert.equal(confirmReplay.error, null, rpcError(confirmReplay.error));
|
|
assert.equal((confirmReplay.data as Record<string, unknown>).idempotent, true);
|
|
} finally {
|
|
try {
|
|
await closeLocalPostgresDataPool(serviceUrl);
|
|
} finally {
|
|
fixture.stop();
|
|
}
|
|
}
|
|
});
|