import { describe, expect, it, vi } from "vitest"; import { resolveNpmIntegrityDrift, resolveNpmIntegrityDriftWithDefaultMessage, } from "./npm-integrity.js"; describe("resolveNpmIntegrityDrift", () => { it("returns proceed=true when integrity is missing or unchanged", async () => { const createPayload = vi.fn(() => "unused"); const cases = [ { expectedIntegrity: undefined, resolution: { integrity: "sha512-same", resolvedAt: "2026-01-01T00:00:00.000Z" }, }, { expectedIntegrity: "sha512-same", resolution: { resolvedAt: "2026-01-01T00:00:00.000Z" }, }, { expectedIntegrity: "sha512-same", resolution: { integrity: "sha512-same", resolvedAt: "2026-01-01T00:00:00.000Z" }, }, ]; for (const testCase of cases) { await expect( resolveNpmIntegrityDrift({ spec: "@openclaw/test@1.0.0", expectedIntegrity: testCase.expectedIntegrity, resolution: testCase.resolution, createPayload, }), ).resolves.toEqual({ proceed: true }); } expect(createPayload).not.toHaveBeenCalled(); }); it("uses callback on integrity drift", async () => { const onIntegrityDrift = vi.fn(async () => false); const result = await resolveNpmIntegrityDrift({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedAt: "2026-01-01T00:00:00.000Z", }, createPayload: ({ expectedIntegrity, actualIntegrity }) => ({ expectedIntegrity, actualIntegrity, }), onIntegrityDrift, }); expect(onIntegrityDrift).toHaveBeenCalledWith({ expectedIntegrity: "sha512-old", actualIntegrity: "sha512-new", }); expect(result.proceed).toBe(false); expect(result.integrityDrift).toEqual({ expectedIntegrity: "sha512-old", actualIntegrity: "sha512-new", }); }); it("returns payload when the drift callback allows continuing", async () => { const result = await resolveNpmIntegrityDrift({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedAt: "2026-01-01T00:00:00.000Z", }, createPayload: ({ spec, actualIntegrity }) => ({ spec, actualIntegrity }), onIntegrityDrift: async () => true, }); expect(result).toEqual({ integrityDrift: { expectedIntegrity: "sha512-old", actualIntegrity: "sha512-new", }, payload: { spec: "@openclaw/test@1.0.0", actualIntegrity: "sha512-new", }, proceed: true, }); }); it("warns by default when no callback is provided", async () => { const warn = vi.fn(); const result = await resolveNpmIntegrityDrift({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedAt: "2026-01-01T00:00:00.000Z", }, createPayload: ({ spec }) => ({ spec }), warn, }); expect(warn).toHaveBeenCalledWith({ spec: "@openclaw/test@1.0.0" }); expect(result.proceed).toBe(true); }); it("formats default warning and abort error messages", async () => { const warn = vi.fn(); const warningResult = await resolveNpmIntegrityDriftWithDefaultMessage({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedSpec: "@openclaw/test@1.0.0", resolvedAt: "2026-01-01T00:00:00.000Z", }, warn, }); expect(warningResult.error).toBeUndefined(); expect(warn).toHaveBeenCalledWith( "Integrity drift detected for @openclaw/test@1.0.0: expected sha512-old, got sha512-new", ); const abortResult = await resolveNpmIntegrityDriftWithDefaultMessage({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedSpec: "@openclaw/test@1.0.0", resolvedAt: "2026-01-01T00:00:00.000Z", }, onIntegrityDrift: async () => false, }); expect(abortResult.error).toBe( "aborted: npm package integrity drift detected for @openclaw/test@1.0.0", ); }); it("falls back to the original spec when resolvedSpec is missing", async () => { const warn = vi.fn(); await resolveNpmIntegrityDriftWithDefaultMessage({ spec: "@openclaw/test@1.0.0", expectedIntegrity: "sha512-old", resolution: { integrity: "sha512-new", resolvedAt: "2026-01-01T00:00:00.000Z", }, warn, }); expect(warn).toHaveBeenCalledWith( "Integrity drift detected for @openclaw/test@1.0.0: expected sha512-old, got sha512-new", ); }); });