// 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 }); } }, ); });