1import { assert, it } from "@effect/vitest";
2import { EventId, ProjectId, ProviderInstanceId } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6import * as SqlClient from "effect/sql/SqlClient";
7
8import * as SqlitePersistence from "../persistence/Sqlite.ts";
9import * as ProjectStore from "./ProjectStore.ts";
10
11it.layer(ProjectStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)))(
12 "ProjectStoreV2",
13 (it) => {
14 it.effect("stores a model selection without options as JSON without an options key", () =>
15 Effect.gen(function* () {
16 const projects = yield* ProjectStore.ProjectStoreV2;
17 const sql = yield* SqlClient.SqlClient;
18 const projectId = ProjectId.make("project-null-options");
19 const modelSelection = { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" };
20 yield* projects.apply({
21 sequence: 1,
22 eventId: EventId.make("event-null-options"),
23 aggregateKind: "project",
24 aggregateId: projectId,
25 occurredAt: "2026-03-24T00:00:00.000Z",
26 commandId: null,
27 causationEventId: null,
28 correlationId: null,
29 metadata: {},
30 type: "project.created",
31 payload: {
32 projectId,
33 title: "Null options project",
34 workspaceRoot: "/tmp/project-null-options",
35 defaultModelSelection: modelSelection,
36 scripts: [],
37 createdAt: "2026-03-24T00:00:00.000Z",
38 updatedAt: "2026-03-24T00:00:00.000Z",
39 },
40 });
41
42 const rows = yield* sql<{ readonly defaultModelSelection: string | null }>`
43 SELECT default_model_selection_json AS "defaultModelSelection"
44 FROM projection_projects
45 WHERE project_id = ${projectId}
46 `;
47 assert.strictEqual(rows[0]?.defaultModelSelection, JSON.stringify(modelSelection));
48 assert.deepStrictEqual(
49 Option.getOrNull(yield* projects.get(projectId))?.defaultModelSelection,
50 modelSelection,
51 );
52 }),
53 );
54 },
55);