local createLibTest = require("/apis/libtest") local createCcpm = require("/apis/libccpm") local testlib = createLibTest({ ... }) local counter = 0 -- Fresh, isolated state + install sandbox per case (never touches real /trapos). local function freshDirs() counter = counter + 1 local stateDir = "/ccpm-test/state-" .. counter local installRoot = "/ccpm-test/root-" .. counter fs.delete(stateDir) fs.delete(installRoot) return stateDir, installRoot end -- Minimal stub of the CC `http` API backed by a url -> body map. local function fakeHttp(routes) return { get = function(url) local body = routes[url] if not body then return nil end return { readAll = function() return body end, close = function() end, } end, } end local function giteaBase(name, branch) return "https://git.trapcloud.fr/" .. name .. "/raw/branch/" .. branch .. "/" end testlib.test("registryBaseUrl resolves a gitea branch", function() local ccpm = createCcpm({ stateDir = freshDirs() }) testlib.assertEquals( ccpm.registryBaseUrl({ name = "guillaumearm/cc-libs", type = "gitea", branch = "next" }), "https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/" ) testlib.assertEquals( ccpm.descriptorUrl({ name = "guillaumearm/cc-libs", type = "gitea", branch = "next" }, "trapos-net"), "https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/packages/trapos-net/ccpm.json" ) end) testlib.test("registry urls resolve an http base", function() local ccpm = createCcpm({ stateDir = freshDirs() }) testlib.assertEquals( ccpm.registryBaseUrl({ name = "http://example.com/repo", type = "http" }), "http://example.com/repo/" ) testlib.assertEquals( ccpm.descriptorUrl({ name = "http://example.com/repo/", type = "http" }, "trapos-net"), "http://example.com/repo/packages/trapos-net/ccpm.json" ) end) testlib.test("resolve orders dependencies before dependents", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/a/ccpm.json"] = textutils.serializeJSON({ name = "a", version = "1", dependencies = { "b", "c" }, files = {}, }), [base .. "packages/b/ccpm.json"] = textutils.serializeJSON({ name = "b", version = "1", dependencies = { "c" }, files = {}, }), [base .. "packages/c/ccpm.json"] = textutils.serializeJSON({ name = "c", version = "1", dependencies = {}, files = {}, }), } local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) local ordered, err = ccpm.resolve("a") testlib.assertTrue(ordered, tostring(err)) testlib.assertEquals(#ordered, 3) testlib.assertEquals(ordered[1].name, "c") testlib.assertEquals(ordered[2].name, "b") testlib.assertEquals(ordered[3].name, "a") end) testlib.test("resolve reports a missing package", function() local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp({}) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) local ordered, err = ccpm.resolve("ghost") testlib.assertTrue(not ordered) testlib.assertTrue(string.find(err, "not found", 1, true)) end) testlib.test("resolve detects a dependency cycle", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/a/ccpm.json"] = textutils.serializeJSON({ name = "a", version = "1", dependencies = { "b" }, }), [base .. "packages/b/ccpm.json"] = textutils.serializeJSON({ name = "b", version = "1", dependencies = { "a" }, }), } local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) local ordered, err = ccpm.resolve("a") testlib.assertTrue(not ordered) testlib.assertTrue(string.find(err, "cycle", 1, true)) end) testlib.test("install rejects an already-installed package", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeLock({ packages = { foo = { version = "1", files = {} } } }) local ok, msg = ccpm.install("foo", {}) testlib.assertTrue(not ok) testlib.assertTrue(string.find(msg, "already installed", 1, true)) testlib.assertTrue(string.find(msg, "reinstall foo", 1, true)) end) testlib.test("install downloads files and records the lock", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({ name = "trapos-core", version = "1", dependencies = {}, files = { "apis/eventloop.lua" }, }), [base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({ name = "trapos-net", version = "1", dependencies = { "trapos-core" }, files = { "apis/net.lua" }, }), [base .. "apis/eventloop.lua"] = "eventloop-body", [base .. "apis/net.lua"] = "net-body", } local sd, root = freshDirs() local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) local ok = ccpm.install("trapos-net", {}) testlib.assertTrue(ok) testlib.assertTrue(fs.exists(root .. "/apis/net.lua")) testlib.assertTrue(fs.exists(root .. "/apis/eventloop.lua")) local f = fs.open(root .. "/apis/net.lua", "r") local body = f.readAll() f.close() testlib.assertEquals(body, "net-body") local lock = ccpm.readLock() testlib.assertTrue(lock.packages["trapos-net"]) testlib.assertTrue(lock.packages["trapos-core"]) testlib.assertEquals(lock.packages["trapos-net"].registry, "me/repo") end) testlib.test("install downloads files from a gitea registry", function() local base = giteaBase("guillaumearm/cc-libs", "next") local routes = { [base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({ name = "trapos-core", version = "1", dependencies = {}, files = { "apis/eventloop.lua" }, }), [base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({ name = "trapos-net", version = "1", dependencies = { "trapos-core" }, files = { "apis/net.lua" }, }), [base .. "apis/eventloop.lua"] = "eventloop-body", [base .. "apis/net.lua"] = "net-body", } local sd, root = freshDirs() local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "guillaumearm/cc-libs", type = "gitea", branch = "next" } } }) local ok = ccpm.install("trapos-net", {}) testlib.assertTrue(ok) testlib.assertTrue(fs.exists(root .. "/apis/net.lua")) testlib.assertTrue(fs.exists(root .. "/apis/eventloop.lua")) local f = fs.open(root .. "/apis/net.lua", "r") local body = f.readAll() f.close() testlib.assertEquals(body, "net-body") local lock = ccpm.readLock() testlib.assertEquals(lock.packages["trapos-net"].registry, "guillaumearm/cc-libs") end) testlib.test("installing trapos writes aggregated os state", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/trapos/ccpm.json"] = textutils.serializeJSON({ name = "trapos", version = "1", dependencies = { "trapos-net" }, files = {}, }), [base .. "packages/trapos-core/ccpm.json"] = textutils.serializeJSON({ name = "trapos-core", version = "1", dependencies = {}, files = { "programs/ccpm.lua" }, }), [base .. "packages/trapos-net/ccpm.json"] = textutils.serializeJSON({ name = "trapos-net", version = "1", dependencies = { "trapos-core" }, files = { "apis/net.lua" }, autostart = { "servers/ping-server" }, }), [base .. "programs/ccpm.lua"] = "ccpm-body", [base .. "apis/net.lua"] = "net-body", } local sd, root = freshDirs() local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) testlib.assertTrue(ccpm.install("trapos", {})) local f = fs.open(sd .. "/manifest.json", "r") local manifest = textutils.unserializeJSON(f.readAll()) f.close() testlib.assertEquals(manifest.version, "1") testlib.assertEquals(#manifest.files, 2) testlib.assertEquals(manifest.autostart[1], "servers/ping-server") end) testlib.test("registry add and remove round-trip", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeConfig({ registries = {} }) testlib.assertTrue(ccpm.addRegistry("foo/bar", { branch = "next" })) local regs = ccpm.listRegistries() testlib.assertEquals(#regs, 1) testlib.assertEquals(regs[1].name, "foo/bar") testlib.assertEquals(regs[1].branch, "next") local dupOk = ccpm.addRegistry("foo/bar", {}) testlib.assertTrue(not dupOk) testlib.assertTrue(ccpm.removeRegistry("foo/bar")) testlib.assertEquals(#ccpm.listRegistries(), 0) local rmOk = ccpm.removeRegistry("nope") testlib.assertTrue(not rmOk) end) testlib.test("addRegistry defaults to a gitea registry", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeConfig({ registries = {} }) local ok, registry = ccpm.addRegistry("foo/bar", {}) testlib.assertTrue(ok) testlib.assertEquals(registry.type, "gitea") testlib.assertEquals(registry.branch, "master") local regs = ccpm.listRegistries() testlib.assertEquals(regs[1].type, "gitea") testlib.assertEquals(ccpm.registryBaseUrl(regs[1]), "https://git.trapcloud.fr/foo/bar/raw/branch/master/") -- An explicit type is still honored. testlib.assertTrue(ccpm.addRegistry("http://example.com/repo", { type = "http" })) local explicit = ccpm.listRegistries()[2] testlib.assertEquals(explicit.type, "http") end) testlib.test("compareVersions treats padded zeros as equal", function() -- compareVersions is internal; probe via available() status local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeCache({ packages = { pkg = { version = "1.0.0", registry = "r" } } }) ccpm.writeLock({ packages = { pkg = { version = "1.0", registry = "r", files = {}, dependencies = {} } } }) local avail = ccpm.available() testlib.assertEquals(avail[1].status, "up-to-date") end) testlib.test("update writes a package cache from registries", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/index.json"] = textutils.serializeJSON({ packages = { foo = "1.0.0", bar = "2.0.0" } }), } local ccpm = createCcpm({ stateDir = freshDirs(), http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) local cache = ccpm.update() testlib.assertEquals(cache.packages.foo.version, "1.0.0") testlib.assertEquals(cache.packages.foo.registry, "me/repo") local search = ccpm.search("ba") testlib.assertEquals(#search, 1) testlib.assertEquals(search[1].name, "bar") end) testlib.test("available marks cached packages by install status", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeCache({ packages = { alpha = { version = "1.0.0", registry = "me/repo" }, beta = { version = "2.0.0", registry = "me/repo" }, gamma = { version = "1.0.0", registry = "me/repo" }, }, }) ccpm.writeLock({ packages = { alpha = { version = "1.0.0", files = {}, dependencies = {} }, beta = { version = "1.0.0", files = {}, dependencies = {} }, }, }) local available = ccpm.available() testlib.assertEquals(#available, 3) testlib.assertEquals(available[1].name, "alpha") testlib.assertEquals(available[1].status, "up-to-date") testlib.assertEquals(available[2].name, "beta") testlib.assertEquals(available[2].status, "updatable") testlib.assertEquals(available[3].name, "gamma") testlib.assertEquals(available[3].status, "available") end) testlib.test("upgrade reinstalls outdated packages from cache", function() local base = giteaBase("me/repo", "master") local routes = { [base .. "packages/foo/ccpm.json"] = textutils.serializeJSON({ name = "foo", version = "2.0.0", dependencies = {}, files = { "programs/foo.lua" }, }), [base .. "programs/foo.lua"] = "foo-v2", } local sd, root = freshDirs() local ccpm = createCcpm({ stateDir = sd, installRoot = root, http = fakeHttp(routes) }) ccpm.writeConfig({ registries = { { name = "me/repo", type = "gitea", branch = "master" } } }) ccpm.writeCache({ packages = { foo = { version = "2.0.0", registry = "me/repo" } } }) ccpm.writeLock({ packages = { foo = { version = "1.0.0", registry = "me/repo", files = { "programs/foo.lua" }, dependencies = {} }, }, }) local ok, upgraded = ccpm.upgrade({}) testlib.assertTrue(ok) testlib.assertEquals(#upgraded, 1) testlib.assertEquals(upgraded[1], "foo") testlib.assertEquals(ccpm.readLock().packages.foo.version, "2.0.0") local f = fs.open(root .. "/programs/foo.lua", "r") local body = f.readAll() f.close() testlib.assertEquals(body, "foo-v2") end) testlib.test("upgrade requires a package cache", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeLock({ packages = { foo = { version = "1.0.0", files = {}, dependencies = {} } } }) local ok, err = ccpm.upgrade({}) testlib.assertTrue(not ok) testlib.assertTrue(string.find(err, "ccpm update", 1, true)) end) testlib.test("uninstall refuses a package with dependents", function() local ccpm = createCcpm({ stateDir = freshDirs() }) ccpm.writeLock({ packages = { ["trapos-core"] = { version = "1", files = {}, dependencies = {} }, ["trapos-net"] = { version = "1", files = {}, dependencies = { "trapos-core" } }, }, }) local ok, err = ccpm.uninstall("trapos-core", {}) testlib.assertTrue(not ok) testlib.assertTrue(string.find(err, "required by", 1, true)) testlib.assertTrue(string.find(err, "trapos-net", 1, true)) end) testlib.test("uninstall removes a leaf package and its files", function() local sd, root = freshDirs() fs.makeDir(root .. "/apis") local f = fs.open(root .. "/apis/net.lua", "w") f.write("x") f.close() local ccpm = createCcpm({ stateDir = sd, installRoot = root }) ccpm.writeLock({ packages = { ["trapos-core"] = { version = "1", files = {}, dependencies = {} }, ["trapos-net"] = { version = "1", files = { "apis/net.lua" }, dependencies = { "trapos-core" } }, }, }) testlib.assertTrue(ccpm.uninstall("trapos-net", {})) testlib.assertTrue(not fs.exists(root .. "/apis/net.lua")) testlib.assertTrue(ccpm.readLock().packages["trapos-net"] == nil) testlib.assertTrue(ccpm.readLock().packages["trapos-core"] ~= nil) end) testlib.run()