import { describe, it, expect, vi, beforeEach } from "vitest"; import { evaluateOracleSanity } from "../../../src/guards/oracleSanity.js"; import { PriceOracle } from "../../../src/pricing/index.js"; import { Guard } from "../../../src/strategy.schema.js"; describe("Oracle Sanity Guard", () => { let mockOracle: PriceOracle; let mockProvider: any; beforeEach(() => { mockProvider = { getNetwork: vi.fn().mockResolvedValue({ chainId: 1n }), call: vi.fn(), }; mockOracle = new PriceOracle("mainnet"); // @ts-ignore - access private property for testing mockOracle.provider = mockProvider; }); it("should pass when price is within bounds", async () => { const guard: Guard = { type: "oracleSanity", params: { token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", maxDeviationBps: 500, // 5% }, }; // Mock price fetch vi.spyOn(mockOracle, "getPrice").mockResolvedValue({ name: "chainlink", price: 1000000n, // $1.00 decimals: 8, timestamp: Date.now(), }); const result = await evaluateOracleSanity(guard, mockOracle, {}); expect(result.passed).toBe(true); }); it("should fail when price deviation is too high", async () => { const guard: Guard = { type: "oracleSanity", params: { token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", maxDeviationBps: 100, // 1% expectedPrice: "1000000", // $1.00 }, }; // Mock price that's 2% off vi.spyOn(mockOracle, "getPrice").mockResolvedValue({ name: "chainlink", price: 1020000n, // $1.02 (2% deviation) decimals: 8, timestamp: Date.now(), }); const result = await evaluateOracleSanity(guard, mockOracle, {}); expect(result.passed).toBe(false); expect(result.reason).toContain("deviation"); }); it("should handle missing oracle gracefully", async () => { const guard: Guard = { type: "oracleSanity", params: { token: "0xInvalid", maxDeviationBps: 500, }, }; vi.spyOn(mockOracle, "getPrice").mockRejectedValue( new Error("Oracle not found") ); const result = await evaluateOracleSanity(guard, mockOracle, {}); expect(result.passed).toBe(false); expect(result.reason).toBeDefined(); }); it("should check for stale price data", async () => { const guard: Guard = { type: "oracleSanity", params: { token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", maxDeviationBps: 500, maxAgeSeconds: 3600, // 1 hour }, }; // Mock stale price (2 hours old) const staleTimestamp = Date.now() - 2 * 60 * 60 * 1000; vi.spyOn(mockOracle, "getPrice").mockResolvedValue({ name: "chainlink", price: 1000000n, decimals: 8, timestamp: staleTimestamp, }); const result = await evaluateOracleSanity(guard, mockOracle, {}); expect(result.passed).toBe(false); expect(result.reason).toContain("stale"); }); });