814c924e4a
Keep askable cards after exhaustion, explain each probe, read the adopted credible range in reports and chat, and compare declared periods before the minute grid when the clock is unknown. Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
7.7 KiB
TypeScript
205 lines
7.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import test from "node:test";
|
|
|
|
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
|
|
|
const runnerPath = fileURLToPath(
|
|
new URL("../scripts/db-migrate.mjs", import.meta.url),
|
|
);
|
|
|
|
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 ids = {
|
|
user: "a1111111-1111-4111-8111-111111111111",
|
|
otherUser: "a2222222-2222-4222-8222-222222222222",
|
|
session: "b1111111-1111-4111-8111-111111111111",
|
|
otherSession: "b2222222-2222-4222-8222-222222222222",
|
|
caseId: "c1111111-1111-4111-8111-111111111111",
|
|
otherCase: "c2222222-2222-4222-8222-222222222222",
|
|
legacyCase: "d1111111-1111-4111-8111-111111111111",
|
|
};
|
|
|
|
function sqlLiteral(value: string): string {
|
|
return `'${value.replaceAll("'", "''")}'`;
|
|
}
|
|
|
|
test("read_report_candidate_range is service_role-only, returns only the window, and stays empty without rows", { skip: skipWithoutDocker }, () => {
|
|
const fixture = startPostgresFixture();
|
|
const migrate = () =>
|
|
spawnSync(process.execPath, [runnerPath], {
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
SCHEMA_DATABASE_URL: fixture.connectionUrl(
|
|
"schema_owner",
|
|
"schema-owner-test-password",
|
|
),
|
|
},
|
|
});
|
|
const serviceSql = (statement: string) =>
|
|
fixture
|
|
.psqlAs(
|
|
"service_runtime",
|
|
"service-runtime-test-password",
|
|
`set role service_role; ${statement}`,
|
|
)
|
|
.replace(/^SET\n/, "");
|
|
|
|
try {
|
|
const first = migrate();
|
|
assert.equal(first.status, 0, `${first.stdout}${first.stderr}`);
|
|
assert.match(first.stdout, /applied 20260904010000_read_report_candidate_range\.sql/);
|
|
|
|
const second = migrate();
|
|
assert.equal(second.status, 0, `${second.stdout}${second.stderr}`);
|
|
assert.match(second.stdout, /already applied 20260904010000_read_report_candidate_range\.sql/);
|
|
|
|
assert.equal(
|
|
fixture.psql(`
|
|
select concat_ws(':',
|
|
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'SELECT'),
|
|
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'INSERT'),
|
|
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'UPDATE'),
|
|
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'DELETE'),
|
|
has_function_privilege('service_role', 'public.read_report_candidate_range(uuid, uuid)', 'EXECUTE'),
|
|
has_function_privilege('anon', 'public.read_report_candidate_range(uuid, uuid)', 'EXECUTE'),
|
|
has_function_privilege('authenticated', 'public.read_report_candidate_range(uuid, uuid)', 'EXECUTE')
|
|
)
|
|
`),
|
|
"f:f:f:f:t:f:f",
|
|
);
|
|
|
|
const catalog = fixture.psql(`
|
|
select concat_ws(':',
|
|
pg_catalog.pg_get_userbyid(p.proowner),
|
|
p.prosecdef::text,
|
|
coalesce(array_to_string(p.proconfig, ','), ''),
|
|
p.provolatile::text
|
|
)
|
|
from pg_catalog.pg_proc p
|
|
join pg_catalog.pg_namespace n on n.oid = p.pronamespace
|
|
where n.nspname = 'public' and p.proname = 'read_report_candidate_range'
|
|
`);
|
|
assert.match(catalog, /^schema_owner:true:search_path="":s$/);
|
|
|
|
fixture.psqlAs(
|
|
"identity_runtime",
|
|
"identity-runtime-test-password",
|
|
`insert into identity.users (id, name, email, email_verified, email_verified_at)
|
|
values (${sqlLiteral(ids.user)}, 'Range User', 'range-user@example.com', true, now()),
|
|
(${sqlLiteral(ids.otherUser)}, 'Other User', 'range-other@example.com', true, now());`,
|
|
);
|
|
|
|
assert.equal(
|
|
serviceSql(`select public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid) is null`),
|
|
"t",
|
|
);
|
|
|
|
fixture.psql(`
|
|
insert into public.chat_sessions (id, user_id, title, theme, session_type, messages)
|
|
values (
|
|
${sqlLiteral(ids.session)}, ${sqlLiteral(ids.user)}, '生时校正', 'general',
|
|
'birth_time_rectification', '[]'::jsonb
|
|
), (
|
|
${sqlLiteral(ids.otherSession)}, ${sqlLiteral(ids.otherUser)}, '生时校正', 'general',
|
|
'birth_time_rectification', '[]'::jsonb
|
|
);
|
|
insert into public.agentic_rectification_cases (
|
|
id, user_id, session_id, status, skill_name, skill_version,
|
|
skill_sha256, skill_source_commit, baseline_profile_fingerprint,
|
|
baseline_birth_snapshot, candidate_range
|
|
) values (
|
|
${sqlLiteral(ids.caseId)}, ${sqlLiteral(ids.user)}, ${sqlLiteral(ids.session)},
|
|
'candidate_accepted', 'jyotish-birth-time-rectification', '9.0.0',
|
|
${sqlLiteral("a".repeat(64))}, ${sqlLiteral("0".repeat(40))}, ${sqlLiteral("a".repeat(64))},
|
|
'{"birth_date":"1997-08-08"}'::jsonb,
|
|
'{"start_time":"10:00","end_time":"10:04","secret":"must-not-leak"}'::jsonb
|
|
), (
|
|
${sqlLiteral(ids.otherCase)}, ${sqlLiteral(ids.otherUser)}, ${sqlLiteral(ids.otherSession)},
|
|
'candidate_accepted', 'jyotish-birth-time-rectification', '9.0.0',
|
|
${sqlLiteral("b".repeat(64))}, ${sqlLiteral("1".repeat(40))}, ${sqlLiteral("b".repeat(64))},
|
|
'{"birth_date":"1997-08-08"}'::jsonb,
|
|
'{"start_time":"11:00","end_time":"11:20"}'::jsonb
|
|
);
|
|
`);
|
|
|
|
assert.throws(
|
|
() => serviceSql(`select candidate_range from public.agentic_rectification_cases limit 1`),
|
|
/permission denied for table agentic_rectification_cases/,
|
|
);
|
|
|
|
assert.equal(
|
|
serviceSql(
|
|
`select public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid) = '{"start_time":"10:00","end_time":"10:04"}'::jsonb`,
|
|
),
|
|
"t",
|
|
);
|
|
|
|
const keys = serviceSql(`
|
|
select array_to_string(array(
|
|
select jsonb_object_keys(public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid))
|
|
order by 1
|
|
), ',')
|
|
`);
|
|
assert.equal(keys, "end_time,start_time");
|
|
|
|
assert.equal(
|
|
serviceSql(
|
|
`select public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid) ? 'secret'`,
|
|
),
|
|
"f",
|
|
);
|
|
|
|
fixture.psql(`
|
|
update public.agentic_rectification_cases
|
|
set adopted_credible_range = '{"start_time":"05:00","end_time":"05:26","representative_time":"05:12","width_minutes":27,"source":"inference_credible_range"}'::jsonb
|
|
where id = ${sqlLiteral(ids.caseId)};
|
|
`);
|
|
assert.equal(
|
|
serviceSql(
|
|
`select public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid) = '{"start_time":"05:00","end_time":"05:26"}'::jsonb`,
|
|
),
|
|
"t",
|
|
);
|
|
const opening = fixture.psql(`
|
|
select (candidate_range->>'start_time') || '-' || (candidate_range->>'end_time')
|
|
from public.agentic_rectification_cases
|
|
where id = ${sqlLiteral(ids.caseId)};
|
|
`);
|
|
assert.match(opening, /10:00-10:04/);
|
|
|
|
try {
|
|
fixture.psql(`
|
|
insert into public.birth_time_rectification_cases (
|
|
id, user_id, status, reported_date, source, candidate_start, candidate_end
|
|
) values (
|
|
${sqlLiteral(ids.legacyCase)}, ${sqlLiteral(ids.user)}, 'confirmed',
|
|
'1997-08-08', 'family_exact', '08:16', '08:20'
|
|
);
|
|
`);
|
|
assert.equal(
|
|
serviceSql(
|
|
`select public.read_report_candidate_range(${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid) = '{"start_time":"08:16","end_time":"08:20"}'::jsonb`,
|
|
),
|
|
"t",
|
|
);
|
|
} catch (error) {
|
|
assert.ok(
|
|
false,
|
|
`legacy birth_time_rectification_cases seed failed: ${error instanceof Error ? error.message : error}`,
|
|
);
|
|
}
|
|
} finally {
|
|
fixture.stop();
|
|
}
|
|
});
|