1import { assert, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as SqlClient from "effect/sql/SqlClient";
4import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
5
6import { runMigrations } from "../Migrations.ts";
7import migrateActiveOrderKey from "./049_ProjectionThreadsActiveOrderKey.ts";
8
9it.layer(NodeSqliteClient.layer({ filename: ":memory:" }))(
10 "049_ProjectionThreadsActiveOrderKey",
11 (it) => {
12 it.effect("migrates old threads without changing their timestamps or assigning an order", () =>
13 Effect.gen(function* () {
14 const sql = yield* SqlClient.SqlClient;
15 yield* runMigrations({ toMigrationInclusive: 48 });
16 const now = "2026-01-01T00:00:00.000Z";
17 yield* sql`
18 INSERT INTO projection_threads (
19 thread_id, project_id, title, model_selection_json, runtime_mode,
20 created_at, updated_at
21 ) VALUES (
22 'thread-1', 'project-1', 'Existing thread',
23 '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', ${now}, ${now}
24 )
25 `;
26 yield* runMigrations({ toMigrationInclusive: 49 });
27 const migrated = yield* sql<{ readonly activeOrderKey: string | null }>`
28 SELECT active_order_key AS "activeOrderKey" FROM projection_threads WHERE thread_id = 'thread-1'
29 `;
30 assert.deepEqual(migrated, [{ activeOrderKey: null }]);
31 // Recovery may run the same migration against a database that already
32 // has the column, including a placement written after the upgrade.
33 yield* sql`UPDATE projection_threads SET active_order_key = 'gm' WHERE thread_id = 'thread-1'`;
34 yield* migrateActiveOrderKey;
35 const rows = yield* sql<{
36 readonly activeOrderKey: string | null;
37 readonly createdAt: string;
38 readonly updatedAt: string;
39 }>`
40 SELECT active_order_key AS "activeOrderKey", created_at AS "createdAt", updated_at AS "updatedAt"
41 FROM projection_threads WHERE thread_id = 'thread-1'
42 `;
43 assert.deepEqual(rows, [{ activeOrderKey: "gm", createdAt: now, updatedAt: now }]);
44 }),
45 );
46 },
47);