// One version, three files, and the rule that they agree.
//
// package.json, manifest/chrome.json and manifest/firefox.json each declare a
// version and none is derived from another. Before this, nothing compared
// them: the manifests were hardcoded at 0.1.0 and copied to dist/ verbatim
// while package.json fed the About screen separately, so the number the
// browser reported and the number the extension displayed could drift apart
// with no check anywhere failing.
//
// The build enforces the agreement (build.js calls resolveVersion before it
// emits anything) and script/lib/package.js names the artifacts from it. This
// asserts both halves: that the tree as committed agrees, and that a tree that
// does not is refused rather than resolved to one of the answers.

const fs = require("fs");
const os = require("os");
const path = require("path");

const {
    VERSION_SOURCES,
    declaredVersions,
    resolveVersion,
} = require("../script/lib/version");

const ROOT = path.join(__dirname, "..");

// A tree containing only the three version files, with the given versions.
function fixture(versions) {
    const dir = fs.mkdtempSync(path.join(os.tmpdir(), "autistmask-version-"));
    fs.mkdirSync(path.join(dir, "manifest"));
    VERSION_SOURCES.forEach((source, i) => {
        fs.writeFileSync(
            path.join(dir, source),
            JSON.stringify({ version: versions[i] }),
        );
    });
    return dir;
}

describe("the declared version", () => {
    test("all three sources agree in this tree", () => {
        const declared = declaredVersions(ROOT);
        expect(declared.map((d) => d.source)).toEqual(VERSION_SOURCES);
        expect([...new Set(declared.map((d) => d.version))]).toHaveLength(1);
        expect(resolveVersion(ROOT)).toBe(declared[0].version);
    });

    // Semver-shaped, because it names every release artifact and is what the
    // browser compares when deciding whether an install is an upgrade.
    test("is semver-shaped", () => {
        expect(resolveVersion(ROOT)).toMatch(/^\d+\.\d+\.\d+$/);
    });

    // The load-bearing case: each of the three, disagreeing on its own, has to
    // fail. A check that only looked at two of them would pass one of these.
    test.each([
        ["package.json", ["9.9.9", "0.1.0", "0.1.0"]],
        ["manifest/chrome.json", ["0.1.0", "9.9.9", "0.1.0"]],
        ["manifest/firefox.json", ["0.1.0", "0.1.0", "9.9.9"]],
    ])("a disagreeing %s fails rather than resolving", (source, versions) => {
        const dir = fixture(versions);
        try {
            expect(() => resolveVersion(dir)).toThrow(
                /the declared versions disagree/,
            );
            expect(() => resolveVersion(dir)).toThrow(new RegExp(source));
        } finally {
            fs.rmSync(dir, { recursive: true, force: true });
        }
    });

    test("agreeing sources resolve", () => {
        const dir = fixture(["1.2.3", "1.2.3", "1.2.3"]);
        try {
            expect(resolveVersion(dir)).toBe("1.2.3");
        } finally {
            fs.rmSync(dir, { recursive: true, force: true });
        }
    });

    // A missing or empty version is not agreement. Left unchecked, three files
    // that all declared nothing would "agree" on undefined and the artifacts
    // would be named after it.
    test.each([[undefined], [""], ["   "], [3]])(
        "a version of %p is refused",
        (bad) => {
            const dir = fixture([bad, bad, bad]);
            try {
                expect(() => resolveVersion(dir)).toThrow(
                    /declares no usable "version"/,
                );
            } finally {
                fs.rmSync(dir, { recursive: true, force: true });
            }
        },
    );
});
