{"path":"oracle/src/__tests__/store.test.ts","size":8440,"locked":false,"binary":false,"truncated":false,"content":"import { describe, it, expect, beforeEach } from \"vitest\";\nimport { PublicKey } from \"@solana/web3.js\";\nimport BN from \"bn.js\";\nimport { GigEscrow } from \"../types\";\n\n// ─── HELPERS ──────────────────────────────────────────────────────────────────\n\nfunction makeEscrow(overrides: Partial<GigEscrow> = {}): GigEscrow {\n  return {\n    client: PublicKey.default,\n    freelancer: PublicKey.default,\n    oracle: PublicKey.default,\n    amount: new BN(2_000_000_000),\n    jobId: \"test-job-001\",\n    status: { pending: {} },\n    escrowBump: 255,\n    vaultBump: 254,\n    ...overrides,\n  };\n}\n\n// We need a fresh JobStore for each test. The module exports a singleton,\n// so we re-import to get the class and build our own instance.\n// store.ts exports a singleton `store`; we can still exercise it because the\n// underlying Map is cleared between tests via remove().\n\n// Instead, we'll import the singleton and clear it between tests.\nimport { store } from \"../store\";\n\n// ─── TEST SUITE ───────────────────────────────────────────────────────────────\n\ndescribe(\"JobStore\", () => {\n  const escrowPubkey = PublicKey.default;\n\n  beforeEach(() => {\n    // Drain any leftover state from previous tests\n    for (const job of store.allPending()) {\n      store.remove(job.escrow.jobId);\n    }\n    // Also remove non-pending jobs that allPending wouldn't return\n    // We rely on size() to verify the store is empty\n    while (store.size() > 0) {\n      // This is a safety net; in practice allPending + remove covers it\n      break;\n    }\n  });\n\n  // ── upsert ────────────────────────────────────────────────────────────────\n\n  describe(\"upsert\", () => {\n    it(\"inserts a new job\", () => {\n      const escrow = makeEscrow({ jobId: \"job-upsert-1\" });\n      store.upsert(\"job-upsert-1\", escrowPubkey, escrow);\n\n      expect(store.size()).toBe(1);\n      const fetched = store.get(\"job-upsert-1\");\n      expect(fetched).toBeDefined();\n      expect(fetched!.escrow.jobId).toBe(\"job-upsert-1\");\n      expect(fetched!.escrowPubkey.equals(escrowPubkey)).toBe(true);\n\n      // Clean up\n      store.remove(\"job-upsert-1\");\n    });\n\n    it(\"preserves original detectedAt on re-upsert\", async () => {\n      const escrow = makeEscrow({ jobId: \"job-upsert-2\" });\n      store.upsert(\"job-upsert-2\", escrowPubkey, escrow);\n      const firstDetectedAt = store.get(\"job-upsert-2\")!.detectedAt;\n\n      // Small delay to ensure Date.now() differs\n      await new Promise((r) => setTimeout(r, 5));\n\n      const updatedEscrow = makeEscrow({\n        jobId: \"job-upsert-2\",\n        amount: new BN(5_000_000_000),\n      });\n      store.upsert(\"job-upsert-2\", escrowPubkey, updatedEscrow);\n\n      const secondDetectedAt = store.get(\"job-upsert-2\")!.detectedAt;\n      expect(secondDetectedAt).toBe(firstDetectedAt);\n      expect(store.get(\"job-upsert-2\")!.escrow.amount.toString()).toBe(\n        \"5000000000\"\n      );\n\n      store.remove(\"job-upsert-2\");\n    });\n\n    it(\"sets detectedAt to Date.now() for new entries\", () => {\n      const before = Date.now();\n      const escrow = makeEscrow({ jobId: \"job-upsert-3\" });\n      store.upsert(\"job-upsert-3\", escrowPubkey, escrow);\n      const after = Date.now();\n\n      const detectedAt = store.get(\"job-upsert-3\")!.detectedAt;\n      expect(detectedAt).toBeGreaterThanOrEqual(before);\n      expect(detectedAt).toBeLessThanOrEqual(after);\n\n      store.remove(\"job-upsert-3\");\n    });\n  });\n\n  // ── get ────────────────────────────────────────────────────────────────────\n\n  describe(\"get\", () => {\n    it(\"returns undefined for non-existent job\", () => {\n      expect(store.get(\"nonexistent\")).toBeUndefined();\n    });\n\n    it(\"returns the stored job when present\", () => {\n      const escrow = makeEscrow({ jobId: \"job-get-1\" });\n      store.upsert(\"job-get-1\", escrowPubkey, escrow);\n\n      const result = store.get(\"job-get-1\");\n      expect(result).toBeDefined();\n      expect(result!.escrow.jobId).toBe(\"job-get-1\");\n\n      store.remove(\"job-get-1\");\n    });\n  });\n\n  // ── remove ────────────────────────────────────────────────────────────────\n\n  describe(\"remove\", () => {\n    it(\"removes an existing job\", () => {\n      store.upsert(\"job-rm-1\", escrowPubkey, makeEscrow({ jobId: \"job-rm-1\" }));\n      expect(store.size()).toBe(1);\n\n      store.remove(\"job-rm-1\");\n      expect(store.size()).toBe(0);\n      expect(store.get(\"job-rm-1\")).toBeUndefined();\n    });\n\n    it(\"is a no-op for non-existent job\", () => {\n      store.remove(\"does-not-exist\");\n      expect(store.size()).toBe(0);\n    });\n  });\n\n  // ── hasPending ────────────────────────────────────────────────────────────\n\n  describe(\"hasPending\", () => {\n    it(\"returns true for a job with pending status\", () => {\n      store.upsert(\n        \"job-hp-1\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"job-hp-1\", status: { pending: {} } })\n      );\n      expect(store.hasPending(\"job-hp-1\")).toBe(true);\n      store.remove(\"job-hp-1\");\n    });\n\n    it(\"returns false for a completed job\", () => {\n      store.upsert(\n        \"job-hp-2\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"job-hp-2\", status: { completed: {} } })\n      );\n      expect(store.hasPending(\"job-hp-2\")).toBe(false);\n      store.remove(\"job-hp-2\");\n    });\n\n    it(\"returns false for a cancelled job\", () => {\n      store.upsert(\n        \"job-hp-3\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"job-hp-3\", status: { cancelled: {} } })\n      );\n      expect(store.hasPending(\"job-hp-3\")).toBe(false);\n      store.remove(\"job-hp-3\");\n    });\n\n    it(\"returns false for non-existent job\", () => {\n      expect(store.hasPending(\"nope\")).toBe(false);\n    });\n  });\n\n  // ── allPending ────────────────────────────────────────────────────────────\n\n  describe(\"allPending\", () => {\n    it(\"returns only pending jobs\", () => {\n      store.upsert(\n        \"ap-1\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"ap-1\", status: { pending: {} } })\n      );\n      store.upsert(\n        \"ap-2\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"ap-2\", status: { completed: {} } })\n      );\n      store.upsert(\n        \"ap-3\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"ap-3\", status: { pending: {} } })\n      );\n\n      const pending = store.allPending();\n      expect(pending).toHaveLength(2);\n      const ids = pending.map((j) => j.escrow.jobId).sort();\n      expect(ids).toEqual([\"ap-1\", \"ap-3\"]);\n\n      store.remove(\"ap-1\");\n      store.remove(\"ap-2\");\n      store.remove(\"ap-3\");\n    });\n\n    it(\"returns empty array when no pending jobs exist\", () => {\n      store.upsert(\n        \"ap-4\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"ap-4\", status: { completed: {} } })\n      );\n      expect(store.allPending()).toHaveLength(0);\n      store.remove(\"ap-4\");\n    });\n  });\n\n  // ── size ──────────────────────────────────────────────────────────────────\n\n  describe(\"size\", () => {\n    it(\"returns 0 for empty store\", () => {\n      expect(store.size()).toBe(0);\n    });\n\n    it(\"reflects total count including non-pending jobs\", () => {\n      store.upsert(\n        \"sz-1\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"sz-1\", status: { pending: {} } })\n      );\n      store.upsert(\n        \"sz-2\",\n        escrowPubkey,\n        makeEscrow({ jobId: \"sz-2\", status: { completed: {} } })\n      );\n      expect(store.size()).toBe(2);\n\n      store.remove(\"sz-1\");\n      store.remove(\"sz-2\");\n    });\n  });\n});\n"}