import { EnvironmentId, type VcsListRefsResult } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import { type ClientCacheKind, MobileDatabase } from "../persistence/mobile-database"; import { make } from "./environment-cache-store"; const ENVIRONMENT_ID = EnvironmentId.make("environment-1"); const REFS: VcsListRefsResult = { refs: [ { name: "main", current: true, isDefault: true, worktreePath: "/repo", }, ], isRepo: true, hasPrimaryRemote: true, nextCursor: null, totalCount: 1, }; function cacheId(environmentId: EnvironmentId, kind: ClientCacheKind, cacheKey: string) { return `${environmentId}:${kind}:${cacheKey}`; } function makeDatabase() { const values = new Map(); const removed: Array = []; const database = MobileDatabase.of({ loadCache: (environmentId, kind, cacheKey) => Effect.succeed(Option.fromUndefinedOr(values.get(cacheId(environmentId, kind, cacheKey)))), listCache: (kind) => Effect.sync(() => [...values.entries()] .filter(([key]) => key.split(":")[1] === kind) .map(([, payload]) => payload), ), saveCache: (environmentId, kind, cacheKey, _schemaVersion, payload) => Effect.sync(() => { values.set(cacheId(environmentId, kind, cacheKey), payload); }), removeCache: (environmentId, kind, cacheKey) => Effect.sync(() => { const id = cacheId(environmentId, kind, cacheKey); removed.push(id); values.delete(id); }), clearCacheKind: (environmentId, kind) => Effect.sync(() => { for (const key of values.keys()) { if (key.startsWith(`${environmentId}:${kind}:`)) values.delete(key); } }), clearEnvironmentCache: (environmentId) => Effect.sync(() => { for (const key of values.keys()) { if (key.startsWith(`${environmentId}:`)) values.delete(key); } }), clearAllCaches: Effect.sync(() => values.clear()), inspectCaches: Effect.succeed([]), loadPreferencesJson: Effect.succeed(Option.none()), savePreferencesJson: () => Effect.void, }); return { database, removed, values }; } describe("mobile SQLite environment cache store", () => { it.effect("round-trips schema-validated VCS refs", () => Effect.gen(function* () { const memory = makeDatabase(); const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database)); yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo")).toEqual(Option.some(REFS)); }), ); it.effect("deletes a corrupt cache record and treats it as a miss", () => Effect.gen(function* () { const memory = makeDatabase(); const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database)); const id = cacheId(ENVIRONMENT_ID, "vcs-refs", "/repo"); memory.values.set(id, "{not-json"); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo")).toEqual(Option.none()); expect(memory.removed).toEqual([id]); }), ); it.effect("removes one persisted VCS ref snapshot", () => Effect.gen(function* () { const memory = makeDatabase(); const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database)); yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS); yield* store.removeVcsRefs(ENVIRONMENT_ID, "/repo"); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo")).toEqual(Option.none()); expect(memory.removed).toContain(cacheId(ENVIRONMENT_ID, "vcs-refs", "/repo")); }), ); it.effect("clears every persisted VCS ref snapshot in one environment", () => Effect.gen(function* () { const memory = makeDatabase(); const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database)); const otherEnvironmentId = EnvironmentId.make("environment-2"); yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS); yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo-worktree", REFS); yield* store.saveVcsRefs(otherEnvironmentId, "/repo", REFS); yield* store.clearVcsRefs(ENVIRONMENT_ID); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo")).toEqual(Option.none()); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo-worktree")).toEqual(Option.none()); expect(yield* store.loadVcsRefs(otherEnvironmentId, "/repo")).toEqual(Option.some(REFS)); }), ); it.effect("clears one environment without touching another", () => Effect.gen(function* () { const memory = makeDatabase(); const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database)); const otherEnvironmentId = EnvironmentId.make("environment-2"); yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS); yield* store.saveVcsRefs(otherEnvironmentId, "/repo", REFS); yield* store.clear(ENVIRONMENT_ID); expect(yield* store.loadVcsRefs(ENVIRONMENT_ID, "/repo")).toEqual(Option.none()); expect(yield* store.loadVcsRefs(otherEnvironmentId, "/repo")).toEqual(Option.some(REFS)); }), ); });