| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
- type PersistTestingType = typeof import("./persist").PersistTesting
- class MemoryStorage implements Storage {
- private values = new Map<string, string>()
- readonly events: string[] = []
- readonly calls = { get: 0, set: 0, remove: 0 }
- clear() {
- this.values.clear()
- }
- get length() {
- return this.values.size
- }
- key(index: number) {
- return Array.from(this.values.keys())[index] ?? null
- }
- getItem(key: string) {
- this.calls.get += 1
- this.events.push(`get:${key}`)
- if (key.startsWith("opencode.throw")) throw new Error("storage get failed")
- return this.values.get(key) ?? null
- }
- setItem(key: string, value: string) {
- this.calls.set += 1
- this.events.push(`set:${key}`)
- if (key.startsWith("opencode.quota")) throw new DOMException("quota", "QuotaExceededError")
- if (key.startsWith("opencode.throw")) throw new Error("storage set failed")
- this.values.set(key, value)
- }
- removeItem(key: string) {
- this.calls.remove += 1
- this.events.push(`remove:${key}`)
- if (key.startsWith("opencode.throw")) throw new Error("storage remove failed")
- this.values.delete(key)
- }
- }
- const storage = new MemoryStorage()
- let persistTesting: PersistTestingType
- beforeAll(async () => {
- mock.module("@/context/platform", () => ({
- usePlatform: () => ({ platform: "web" }),
- }))
- const mod = await import("./persist")
- persistTesting = mod.PersistTesting
- })
- beforeEach(() => {
- storage.clear()
- storage.events.length = 0
- storage.calls.get = 0
- storage.calls.set = 0
- storage.calls.remove = 0
- Object.defineProperty(globalThis, "localStorage", {
- value: storage,
- configurable: true,
- })
- })
- describe("persist localStorage resilience", () => {
- test("does not cache values as persisted when quota write and eviction fail", () => {
- const storageApi = persistTesting.localStorageWithPrefix("opencode.quota.scope")
- storageApi.setItem("value", '{"value":1}')
- expect(storage.getItem("opencode.quota.scope:value")).toBeNull()
- expect(storageApi.getItem("value")).toBeNull()
- })
- test("disables only the failing scope when storage throws", () => {
- const bad = persistTesting.localStorageWithPrefix("opencode.throw.scope")
- bad.setItem("value", '{"value":1}')
- const before = storage.calls.set
- bad.setItem("value", '{"value":2}')
- expect(storage.calls.set).toBe(before)
- expect(bad.getItem("value")).toBeNull()
- const healthy = persistTesting.localStorageWithPrefix("opencode.safe.scope")
- healthy.setItem("value", '{"value":3}')
- expect(storage.getItem("opencode.safe.scope:value")).toBe('{"value":3}')
- })
- test("failing fallback scope does not poison direct storage scope", () => {
- const broken = persistTesting.localStorageWithPrefix("opencode.throw.scope2")
- broken.setItem("value", '{"value":1}')
- const direct = persistTesting.localStorageDirect()
- direct.setItem("direct-value", '{"value":5}')
- expect(storage.getItem("direct-value")).toBe('{"value":5}')
- })
- test("normalizer rejects malformed JSON payloads", () => {
- const result = persistTesting.normalize({ value: "ok" }, '{"value":"\\x"}')
- expect(result).toBeUndefined()
- })
- })
|