Initial Commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import type { PluginManifest } from "@suaveplan/api-types/manifest";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { LifecycleError } from "../error-codes/error-codes.js";
|
||||
import { compareSemver, requiresReinstall } from "./reinstall-criteria.js";
|
||||
|
||||
function manifest(overrides: Partial<PluginManifest> = {}): PluginManifest {
|
||||
return {
|
||||
name: "billing",
|
||||
version: "0.1.0",
|
||||
apiHost: "1.x",
|
||||
isolation: "trusted",
|
||||
trustLevel: "first-party",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("reinstall-criteria", () => {
|
||||
describe("compareSemver", () => {
|
||||
it("orders patch versions", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("1.0.0", "1.0.1")).toBe(-1);
|
||||
expect(compareSemver("1.0.1", "1.0.0")).toBe(1);
|
||||
expect(compareSemver("1.0.0", "1.0.0")).toBe(0);
|
||||
});
|
||||
|
||||
it("orders minor versions", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("1.1.0", "1.2.0")).toBe(-1);
|
||||
expect(compareSemver("1.2.0", "1.1.0")).toBe(1);
|
||||
});
|
||||
|
||||
it("orders major versions", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("1.0.0", "2.0.0")).toBe(-1);
|
||||
});
|
||||
|
||||
it("ignores prerelease and build metadata", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("1.0.0-rc.1", "1.0.0")).toBe(0);
|
||||
expect(compareSemver("1.0.0+build.1", "1.0.0")).toBe(0);
|
||||
});
|
||||
|
||||
it("handles missing segments as zeros", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("1", "1.0.0")).toBe(0);
|
||||
expect(compareSemver("1.0", "1.0.1")).toBe(-1);
|
||||
// Reverse: b shorter than a — exercises the `bp[i] ?? 0` branch.
|
||||
expect(compareSemver("1.0.0", "1")).toBe(0);
|
||||
expect(compareSemver("1.0.1", "1.0")).toBe(1);
|
||||
});
|
||||
|
||||
it("treats non-numeric segments as zero", { timeout: 5000 }, () => {
|
||||
expect(compareSemver("a.b.c", "0.0.0")).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("requiresReinstall", () => {
|
||||
it("identical manifest returns false", { timeout: 5000 }, () => {
|
||||
const m = manifest({ scopes: [{ name: "x" }] });
|
||||
expect(requiresReinstall(m, m)).toBe(false);
|
||||
});
|
||||
|
||||
it("schemaVersion bump returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({
|
||||
database: { required: true, schemaVersion: 1 },
|
||||
});
|
||||
const next = manifest({
|
||||
database: { required: true, schemaVersion: 2 },
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("schemaVersion unchanged returns false", { timeout: 5000 }, () => {
|
||||
const prev = manifest({
|
||||
database: { required: true, schemaVersion: 1 },
|
||||
});
|
||||
const next = manifest({
|
||||
database: { required: true, schemaVersion: 1 },
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(false);
|
||||
});
|
||||
|
||||
it("new scope returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ scopes: [{ name: "read" }] });
|
||||
const next = manifest({
|
||||
scopes: [{ name: "read" }, { name: "write" }],
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("new publish topic returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ events: { publishes: ["a"] } });
|
||||
const next = manifest({ events: { publishes: ["a", "b"] } });
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("new subscribe topic returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ events: { subscribes: ["a"] } });
|
||||
const next = manifest({ events: { subscribes: ["a", "b"] } });
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("new cron job returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({
|
||||
jobs: { cron: [{ name: "x", expression: "* * * * *" }] },
|
||||
});
|
||||
const next = manifest({
|
||||
jobs: {
|
||||
cron: [
|
||||
{ name: "x", expression: "* * * * *" },
|
||||
{ name: "y", expression: "* * * * *" },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("new queued job returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({
|
||||
jobs: { queued: [{ name: "a" }] },
|
||||
});
|
||||
const next = manifest({
|
||||
jobs: { queued: [{ name: "a" }, { name: "b" }] },
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("new secret returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({
|
||||
secrets: [{ name: "STRIPE", required: true }],
|
||||
});
|
||||
const next = manifest({
|
||||
secrets: [
|
||||
{ name: "STRIPE", required: true },
|
||||
{ name: "PAYPAL", required: false },
|
||||
],
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("dependsOn change returns true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ dependsOn: { core: "1.x" } });
|
||||
const next = manifest({ dependsOn: { core: "2.x" } });
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("dependsOn additions return true", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ dependsOn: { core: "1.x" } });
|
||||
const next = manifest({
|
||||
dependsOn: { core: "1.x", auth: "1.x" },
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(true);
|
||||
});
|
||||
|
||||
it("version downgrade throws", { timeout: 5000 }, () => {
|
||||
const prev = manifest({ version: "1.0.0" });
|
||||
const next = manifest({ version: "0.9.0" });
|
||||
try {
|
||||
requiresReinstall(prev, next);
|
||||
expect.fail("expected throw");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(LifecycleError);
|
||||
const e = err as LifecycleError;
|
||||
expect(e.code).toBe(
|
||||
"LIFECYCLE.REINSTALL_REQUIRED_VERSION_DOWNGRADE"
|
||||
);
|
||||
expect(e.context).toEqual({ prev: "1.0.0", next: "0.9.0" });
|
||||
}
|
||||
});
|
||||
|
||||
it("no schemaVersion in either manifest treats as zero", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(requiresReinstall(manifest(), manifest())).toBe(false);
|
||||
});
|
||||
|
||||
it("manifest with empty events block returns false", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const prev = manifest({
|
||||
events: { publishes: [], subscribes: [] },
|
||||
});
|
||||
const next = manifest({
|
||||
events: { publishes: [], subscribes: [] },
|
||||
});
|
||||
expect(requiresReinstall(prev, next)).toBe(false);
|
||||
});
|
||||
|
||||
it("removing an item is NOT a reinstall trigger", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const prev = manifest({
|
||||
scopes: [{ name: "read" }, { name: "write" }],
|
||||
});
|
||||
const next = manifest({ scopes: [{ name: "read" }] });
|
||||
expect(requiresReinstall(prev, next)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user