1import { describe, expect, it } from "@effect/vitest";
2import { EnvironmentId, ProjectId } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
6import { EnvironmentQueryStore, persistQuery } from "./queryPersistence.ts";
7
8import { persistPullRequestStats } from "./pullRequestStatsPersistence.ts";
9
10const env = EnvironmentId.make("one");
11const other = EnvironmentId.make("two");
12const persisted = persistQuery(Schema.Struct({ title: Schema.String }));
13
14function store(records = new Map<string, string>()) {
15 return {
16 load: (environmentId: EnvironmentId, key: string) =>
17 Effect.sync(() => Option.fromUndefinedOr(records.get(`${environmentId}:${key}`))),
18 save: (environmentId: EnvironmentId, key: string, payload: string) =>
19 Effect.sync(() => {
20 records.set(`${environmentId}:${key}`, payload);
21 }),
22 };
23}
24
25describe("query persistence", () => {
26 it.effect("round trips responses without mixing environments or query scopes", () =>
27 Effect.gen(function* () {
28 yield* persisted.save(env, "open", { title: "saved" });
29 expect(yield* persisted.load(env, "open")).toEqual(Option.some({ title: "saved" }));
30 expect(yield* persisted.load(other, "open")).toEqual(Option.none());
31 expect(yield* persisted.load(env, "closed")).toEqual(Option.none());
32 }).pipe(Effect.provideService(EnvironmentQueryStore, store())),
33 );
34
35 it.effect("ignores corrupt or incompatible records", () =>
36 Effect.gen(function* () {
37 expect(yield* persisted.load(env, "broken")).toEqual(Option.none());
38 expect(yield* persisted.load(env, "incompatible")).toEqual(Option.none());
39 }).pipe(
40 Effect.provideService(
41 EnvironmentQueryStore,
42 store(
43 new Map([
44 ["one:broken", "not json"],
45 ["one:incompatible", '{"title":42}'],
46 ]),
47 ),
48 ),
49 ),
50 );
51
52 it.effect("does not persist on platforms without a query store", () =>
53 Effect.gen(function* () {
54 yield* persisted.save(env, "open", { title: "saved" });
55 expect(yield* persisted.load(env, "open")).toEqual(Option.none());
56 }),
57 );
58});
59
60it.effect("restores line counts when visible PR batches change", () =>
61 Effect.gen(function* () {
62 const cache = persistPullRequestStats();
63 const first = { projectId: ProjectId.make("project"), repository: "owner/repo", number: 1 };
64 const second = { ...first, number: 2 };
65 const stat = { ...first, additions: 42, deletions: 3 };
66 yield* cache.save(env, JSON.stringify(["stats", { refs: [first, second] }]), { stats: [stat] });
67 const key = JSON.stringify(["stats", { refs: [first] }]);
68 expect(yield* cache.load(env, key)).toEqual(Option.some({ stats: [stat] }));
69 expect(yield* cache.load(other, key)).toEqual(Option.none());
70 }).pipe(Effect.provideService(EnvironmentQueryStore, store())),
71);