Files
Jyotisha/frontend/tests/database-session-list-visibility.test.ts
T
jesse-ux 5a4b87383f
Independent Staging Quality Gate / validate (push) Failing after 6m35s
Independent Staging Quality Gate / publish (push) Skipped
fix(web): retire session archive UI and API, restore archived rows
Remove archive/restore from the session menu and drop archived=1 listing. PATCH still accepts archived_at from old bundles then ignores it. A forward migration clears archived_at without bumping updated_at. Delete stays unchanged.
2026-09-21 18:49:40 +08:00

134 lines
6.7 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 {
closeLocalPostgresDataPools,
createLocalPostgresDataClient,
} from "../src/lib/db/local-postgres-client-core.ts";
import {
cloudListIncludesSession,
excludeEmptyConsultations,
} from "../src/lib/session-list-filter.ts";
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
const runner = fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url));
const retireSql = readFileSync(
new URL("../supabase/migrations/20260921010000_retire_chat_session_archive.sql", import.meta.url),
"utf8",
);
const docker = spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], { stdio: "ignore" }).status === 0;
const emptyConsultation = "11111111-1111-4111-8111-111111111111";
const emptyRectification = "22222222-2222-4222-8222-222222222222";
const filledConsultation = "33333333-3333-4333-8333-333333333333";
const archivedRectification = "44444444-4444-4444-8444-444444444444";
const archivedEmptyConsultation = "55555555-5555-4555-8555-555555555555";
test("session list query keeps empty rectification rows and drops empty consultations", {
skip: docker ? false : "docker unavailable",
}, async () => {
const fixture = startPostgresFixture();
try {
const migration = spawnSync(process.execPath, [runner], {
encoding: "utf8",
env: {
...process.env,
SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password"),
},
});
assert.equal(migration.status, 0, migration.stderr);
fixture.psqlAs(
"identity_runtime",
"identity-runtime-test-password",
`insert into identity.users(name,email,email_verified,email_verified_at) values ('Fictional List','list-visibility@example.com',true,now());`,
);
const userId = fixture.psql("select id from identity.users where email='list-visibility@example.com'");
fixture.psql(`
insert into public.chat_sessions (id, user_id, title, theme, model_id, messages, session_type, pinned, archived_at, updated_at)
values
('${emptyConsultation}', '${userId}', 'Empty consult', 'general', 'test-model', '[]', 'consultation', false, null, now()),
('${emptyRectification}', '${userId}', 'Empty rectification', 'general', 'test-model', '[]', 'birth_time_rectification', false, null, now()),
('${filledConsultation}', '${userId}', 'Filled consult', 'general', 'test-model', '[{"role":"user","text":"问一句"}]', 'consultation', false, null, now()),
('${archivedRectification}', '${userId}', 'Archived rectification', 'general', 'test-model', '[]', 'birth_time_rectification', false, '2026-09-08T00:00:00Z', now()),
('${archivedEmptyConsultation}', '${userId}', 'Archived empty consult', 'general', 'test-model', '[]', 'consultation', false, '2026-09-08T00:00:00Z', now());
`);
const local = createLocalPostgresDataClient(
fixture.connectionUrl("app_runtime", "app-runtime-test-password"),
{ id: userId, email: "list-visibility@example.com" },
);
const live = await excludeEmptyConsultations(
local.from("chat_sessions").select("id").eq("user_id", userId).eq("pinned", false).is("archived_at", null),
);
assert.equal(live.error, null, live.error?.message);
const liveIds = (live.data as { id: string }[]).map((row) => row.id).sort();
assert.deepEqual(liveIds, [emptyRectification, filledConsultation].sort());
assert.equal(liveIds.includes(archivedRectification), false);
assert.equal(liveIds.includes(archivedEmptyConsultation), false);
const rows = [
{ id: emptyConsultation, sessionType: "consultation" as const, messagesEmpty: true, archived: false },
{ id: emptyRectification, sessionType: "birth_time_rectification" as const, messagesEmpty: true, archived: false },
{ id: filledConsultation, sessionType: "consultation" as const, messagesEmpty: false, archived: false },
{ id: archivedRectification, sessionType: "birth_time_rectification" as const, messagesEmpty: true, archived: true },
{ id: archivedEmptyConsultation, sessionType: "consultation" as const, messagesEmpty: true, archived: true },
];
assert.deepEqual(
rows.filter((row) => cloudListIncludesSession(row)).map((row) => row.id).sort(),
liveIds,
);
} finally {
await closeLocalPostgresDataPools();
fixture.stop();
}
});
test("retire archive migration clears archived_at without bumping updated_at", {
skip: docker ? false : "docker unavailable",
}, async () => {
const fixture = startPostgresFixture();
try {
const migration = spawnSync(process.execPath, [runner], {
encoding: "utf8",
env: {
...process.env,
SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password"),
},
});
assert.equal(migration.status, 0, migration.stderr);
fixture.psqlAs(
"identity_runtime",
"identity-runtime-test-password",
`insert into identity.users(name,email,email_verified,email_verified_at) values ('Fictional Unarchive','unarchive@example.com',true,now());`,
);
const userId = fixture.psql("select id from identity.users where email='unarchive@example.com'");
fixture.psql(`
insert into public.chat_sessions (id, user_id, title, theme, model_id, messages, session_type, pinned, archived_at, updated_at)
values (
'${archivedRectification}', '${userId}', 'Archived rectification', 'general', 'test-model',
'[]', 'birth_time_rectification', true, '2026-09-08T00:00:00Z', '2026-09-10T08:00:00Z'
);
`);
const before = fixture.psql(
`select pinned::text || '|' || jsonb_array_length(messages)::text || '|' || updated_at::text from public.chat_sessions where id = '${archivedRectification}'`,
);
assert.match(retireSql, /set archived_at = null/);
assert.doesNotMatch(retireSql, /updated_at\s*=/);
fixture.psqlAs("schema_owner", "schema-owner-test-password", `
update public.chat_sessions set archived_at = null where archived_at is not null;
`);
assert.equal(fixture.psql("select count(*) from public.chat_sessions where archived_at is not null"), "0");
const after = fixture.psql(
`select pinned::text || '|' || jsonb_array_length(messages)::text || '|' || updated_at::text from public.chat_sessions where id = '${archivedRectification}'`,
);
assert.equal(after, before);
fixture.psqlAs("schema_owner", "schema-owner-test-password", `
update public.chat_sessions set archived_at = null where archived_at is not null;
`);
assert.equal(fixture.psql("select count(*) from public.chat_sessions where archived_at is not null"), "0");
} finally {
fixture.stop();
}
});