// Mock logger before importing docker module
jest.mock("./logger.js", () => ({
    logger: {
        debug: jest.fn(),
        info: jest.fn(),
        error: jest.fn(),
        warning: jest.fn(),
    },
}));
// Mock dockerode before importing docker module
const mockInspect = jest.fn();
const mockGetImage = jest.fn(() => ({ inspect: mockInspect }));
const mockFollowProgress = jest.fn();
const mockPull = jest.fn();
const mockModem = { followProgress: mockFollowProgress };
jest.mock("dockerode", () => jest.fn().mockImplementation(() => ({
    getImage: mockGetImage,
    pull: mockPull,
    modem: mockModem,
})));
import { dockerImageExistsLocally, pullDockerImage } from "./docker.js";
// Helper: make docker.pull succeed (stream ends without error)
function mockPullSuccess() {
    const fakeStream = {};
    mockPull.mockImplementation((_image, _opts, cb) => {
        cb(null, fakeStream);
    });
    mockFollowProgress.mockImplementation((_stream, onFinished) => {
        onFinished(null);
    });
}
// Helper: make docker.pull fail with the given error
function mockPullError(err) {
    mockPull.mockImplementation((_image, _opts, cb) => {
        cb(err);
    });
}
describe("dockerImageExistsLocally", () => {
    beforeEach(() => jest.clearAllMocks());
    it("returns true when inspect succeeds", async () => {
        mockInspect.mockResolvedValue({});
        await expect(dockerImageExistsLocally("img:tag")).resolves.toBe(true);
    });
    it("returns false on 404 (image not present locally)", async () => {
        mockInspect.mockRejectedValue({ statusCode: 404 });
        await expect(dockerImageExistsLocally("img:tag")).resolves.toBe(false);
    });
    it("throws and logs a warning when Docker daemon is unreachable", async () => {
        const { logger } = await import("./logger.js");
        const daemonErr = new Error("connect ECONNREFUSED /var/run/docker.sock");
        mockInspect.mockRejectedValue(daemonErr);
        await expect(dockerImageExistsLocally("img:tag")).rejects.toThrow(daemonErr);
        expect(logger.warning).toHaveBeenCalledWith("Could not check Docker image locally", expect.objectContaining({ image: "img:tag" }));
    });
});
describe("pullDockerImage", () => {
    const IMAGE = "skyramp/executor:v1.3.25";
    beforeEach(() => jest.clearAllMocks());
    describe("on amd64 host", () => {
        const originalArch = process.arch;
        beforeAll(() => Object.defineProperty(process, "arch", { value: "x64", configurable: true }));
        afterAll(() => Object.defineProperty(process, "arch", { value: originalArch, configurable: true }));
        it("pulls with linux/amd64 and succeeds", async () => {
            mockPullSuccess();
            await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
            expect(mockPull).toHaveBeenCalledTimes(1);
            expect(mockPull).toHaveBeenCalledWith(IMAGE, { platform: "linux/amd64" }, expect.any(Function));
        });
        it("rethrows any pull error without fallback", async () => {
            const err = new Error("unauthorized: access denied");
            mockPullError(err);
            await expect(pullDockerImage(IMAGE)).rejects.toThrow(err);
            expect(mockPull).toHaveBeenCalledTimes(1);
        });
    });
    describe("on arm64 host", () => {
        const originalArch = process.arch;
        beforeAll(() => Object.defineProperty(process, "arch", { value: "arm64", configurable: true }));
        afterAll(() => Object.defineProperty(process, "arch", { value: originalArch, configurable: true }));
        it("pulls with linux/arm64 and succeeds without fallback when image lands locally", async () => {
            mockPullSuccess();
            mockInspect.mockResolvedValue({}); // post-pull verify: image found
            await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
            expect(mockPull).toHaveBeenCalledTimes(1);
            expect(mockPull).toHaveBeenCalledWith(IMAGE, { platform: "linux/arm64" }, expect.any(Function));
        });
        it("falls back to linux/amd64 on manifest-mismatch error", async () => {
            const manifestErr = new Error("no matching manifest for linux/arm64 in the manifest list entries");
            const fakeStream = {};
            mockPull
                .mockImplementationOnce((_image, _opts, cb) => cb(manifestErr))
                .mockImplementationOnce((_image, _opts, cb) => cb(null, fakeStream));
            mockFollowProgress.mockImplementation((_stream, onFinished) => onFinished(null));
            await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
            expect(mockPull).toHaveBeenCalledTimes(2);
            expect(mockPull).toHaveBeenNthCalledWith(1, IMAGE, { platform: "linux/arm64" }, expect.any(Function));
            expect(mockPull).toHaveBeenNthCalledWith(2, IMAGE, { platform: "linux/amd64" }, expect.any(Function));
        });
        it("rethrows non-manifest errors (auth/network) without falling back", async () => {
            const authErr = new Error("unauthorized: access denied");
            mockPullError(authErr);
            await expect(pullDockerImage(IMAGE)).rejects.toThrow(authErr);
            expect(mockPull).toHaveBeenCalledTimes(1);
        });
        it("falls back to linux/amd64 when arm64 pull succeeds silently but image is not stored locally", async () => {
            mockPullSuccess();
            mockInspect.mockRejectedValueOnce({ statusCode: 404 }); // post-pull verify: not found
            await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
            expect(mockPull).toHaveBeenCalledTimes(2);
            expect(mockPull).toHaveBeenNthCalledWith(2, IMAGE, { platform: "linux/amd64" }, expect.any(Function));
        });
    });
});
