import { describe, expect, it, vi } from "vitest"; import { buildInstalledSourceMap, mergeUpdateSources, parseApmUpgradableOutput, parseAptssUpgradableOutput, parsePrintUrisOutput, } from "../../../../electron/main/backend/update-center/query"; describe("update-center query", () => { it("parses aptss upgradable output into normalized aptss items", () => { const output = [ "Listing...", "spark-weather/stable 2.0.0 amd64 [upgradable from: 1.9.0]", "spark-same/stable 1.0.0 amd64 [upgradable from: 1.0.0]", "", ].join("\n"); expect(parseAptssUpgradableOutput(output)).toEqual([ { pkgname: "spark-weather", source: "aptss", currentVersion: "1.9.0", nextVersion: "2.0.0", arch: "amd64", }, ]); }); it("parses the legacy from variant in upgradable output", () => { const aptssOutput = "spark-clock/stable 1.2.0 amd64 [from: 1.1.0]"; const apmOutput = "spark-player/main 2.0.0 amd64 [from: 1.5.0]"; expect(parseAptssUpgradableOutput(aptssOutput)).toEqual([ { pkgname: "spark-clock", source: "aptss", currentVersion: "1.1.0", nextVersion: "1.2.0", arch: "amd64", }, ]); expect(parseApmUpgradableOutput(apmOutput)).toEqual([ { pkgname: "spark-player", source: "apm", currentVersion: "1.5.0", nextVersion: "2.0.0", arch: "amd64", }, ]); }); it("parses apt print-uris output into download metadata", () => { const output = "'https://example.invalid/pool/main/s/spark-weather_2.0.0_amd64.deb' spark-weather_2.0.0_amd64.deb 123456 SHA512:deadbeef"; expect(parsePrintUrisOutput(output)).toEqual({ downloadUrl: "https://example.invalid/pool/main/s/spark-weather_2.0.0_amd64.deb", fileName: "spark-weather_2.0.0_amd64.deb", size: 123456, sha512: "deadbeef", }); }); it("marks an apm item as migration when the same package is only installed in aptss", () => { const merged = mergeUpdateSources( [ { pkgname: "spark-weather", source: "aptss", currentVersion: "1.9.0", nextVersion: "2.0.0", }, ], [ { pkgname: "spark-weather", source: "apm", currentVersion: "1.8.0", nextVersion: "3.0.0", }, ], new Map([["spark-weather", { aptss: true, apm: false }]]), ); expect(merged).toEqual([ { pkgname: "spark-weather", source: "apm", currentVersion: "1.8.0", nextVersion: "3.0.0", isMigration: true, migrationSource: "aptss", migrationTarget: "apm", aptssVersion: "2.0.0", }, { pkgname: "spark-weather", source: "aptss", currentVersion: "1.9.0", nextVersion: "2.0.0", }, ]); }); it("uses Debian-style version ordering for migration decisions", () => { const merged = mergeUpdateSources( [ { pkgname: "spark-browser", source: "aptss", currentVersion: "9.0", nextVersion: "10.0", }, ], [ { pkgname: "spark-browser", source: "apm", currentVersion: "1:0.9", nextVersion: "2:1.0", }, ], new Map([["spark-browser", { aptss: true, apm: false }]]), ); expect(merged[0]).toMatchObject({ pkgname: "spark-browser", source: "apm", isMigration: true, aptssVersion: "10.0", }); expect(merged[1]).toMatchObject({ pkgname: "spark-browser", source: "aptss", nextVersion: "10.0", }); }); it("uses Debian epoch ordering in the fallback when dpkg is unavailable", async () => { vi.resetModules(); vi.doMock("node:child_process", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, default: actual, spawnSync: vi.fn(() => ({ status: null, error: new Error("dpkg unavailable"), output: null, pid: 0, signal: null, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), })), }; }); const { mergeUpdateSources: mergeWithFallback } = await import("../../../../electron/main/backend/update-center/query"); const merged = mergeWithFallback( [ { pkgname: "spark-reader", source: "aptss", currentVersion: "2.5.0", nextVersion: "2.9.0", }, ], [ { pkgname: "spark-reader", source: "apm", currentVersion: "2.0.0", nextVersion: "3.0.0", }, ], new Map([["spark-reader", { aptss: true, apm: false }]]), ); expect(merged[0]).toMatchObject({ pkgname: "spark-reader", source: "apm", isMigration: true, aptssVersion: "2.9.0", }); vi.doUnmock("node:child_process"); vi.resetModules(); }); it("uses Debian tilde ordering in the fallback when dpkg is unavailable", async () => { vi.resetModules(); vi.doMock("node:child_process", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, default: actual, spawnSync: vi.fn(() => ({ status: null, error: new Error("dpkg unavailable"), output: null, pid: 0, signal: null, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), })), }; }); const { mergeUpdateSources: mergeWithFallback } = await import("../../../../electron/main/backend/update-center/query"); const merged = mergeWithFallback( [ { pkgname: "spark-tilde", source: "aptss", currentVersion: "0.9", nextVersion: "1.0~rc1", }, ], [ { pkgname: "spark-tilde", source: "apm", currentVersion: "0.9", nextVersion: "1.0", }, ], new Map([["spark-tilde", { aptss: true, apm: false }]]), ); expect(merged[0]).toMatchObject({ pkgname: "spark-tilde", source: "apm", isMigration: true, aptssVersion: "1.0~rc1", }); vi.doUnmock("node:child_process"); vi.resetModules(); }); it("parses apm list output into normalized apm items", () => { const output = [ "Listing...", "spark-music/main 5.0.0 arm64 [upgradable from: 4.5.0]", "spark-same/main 1.0.0 arm64 [upgradable from: 1.0.0]", "", ].join("\n"); expect(parseApmUpgradableOutput(output)).toEqual([ { pkgname: "spark-music", source: "apm", currentVersion: "4.5.0", nextVersion: "5.0.0", arch: "arm64", }, ]); }); it("builds installed-source map from dpkg-query and apm list output", () => { const dpkgOutput = [ "spark-weather\tinstall ok installed", "spark-weather-data\tdeinstall ok config-files", "spark-notes\tinstall ok installed", "", ].join("\n"); const apmInstalledOutput = [ "Listing...", "spark-weather/main,stable 3.0.0 amd64 [installed]", "spark-player/main 1.0.0 amd64 [installed,automatic]", "", ].join("\n"); expect( Array.from( buildInstalledSourceMap(dpkgOutput, apmInstalledOutput).entries(), ), ).toEqual([ ["spark-weather", { aptss: true, apm: true }], ["spark-notes", { aptss: true, apm: false }], ["spark-player", { aptss: false, apm: true }], ]); }); });