Files
vimpack/tests/registry_spec.lua
T
unkin-agent 35b9df9eba
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
Add vimpack: archive-based Neovim plugin manager
## Why
Cloning plugins from git ties the editor to GitHub uptime and moving branches; archives at a pinned version from our own registry are reproducible and reachable offline.

## How
- Add a registry resolver interface `(spec, registry) -> {url, headers, filename, sha256?}` with builtin `template` and `artifactapi` kinds, a per-plugin registry override and a `url` escape hatch.
- Take auth tokens only from an env var or a `token_cmd`; reject a literal token at setup.
- Download with curl, verify an optional sha256, extract to a staging dir and rename into `pack/vimpack/{start,opt}/<name>`, restoring the previous tree if the swap fails.
- Track installed name/version/sha256 in `stdpath('data')/vimpack/manifest.json` and skip plugins already at the wanted version.
- Add `:VimpackInstall`, `:VimpackUpdate`, `:VimpackClean` and `:VimpackStatus`, running downloads through `vim.system` under a concurrency cap.
- Hash with a pure-Lua SHA-256; `vim.fn.sha256()` rejects binary archives with E976.
- Add a dependency-free `tests/minimal.lua` harness and 57 specs covering resolution, auth, manifest, planning, checksum mismatch, orphan detection and end-to-end install.
- Add `.woodpecker/{test,lint,pre-commit}.yaml`, luacheck/stylua config and pre-commit hooks.
2026-09-05 13:28:53 +10:00

137 lines
4.8 KiB
Lua

local registry = require('vimpack.registry')
local util = require('vimpack.util')
describe('interpolate', function()
it('substitutes every placeholder', function()
assert_eq(
'a/1.0/a.tar.gz',
(util.interpolate('${name}/${version}/${name}.${ext}', { name = 'a', version = '1.0', ext = 'tar.gz' }))
)
end)
it('reports an unknown placeholder', function()
local out, err = util.interpolate('${name}-${nope}', { name = 'a' })
assert_nil(out)
assert_match('${nope}', err)
end)
end)
describe('template resolver', function()
local reg = {
kind = 'template',
url_template = 'https://artifacts.example.com/nvim/${name}/${version}/${name}-${version}.${ext}',
}
it('renders name and version into the url', function()
local resolved = assert(registry.resolve({ name = 'telescope.nvim', version = '0.1.8' }, reg))
assert_eq('https://artifacts.example.com/nvim/telescope.nvim/0.1.8/telescope.nvim-0.1.8.tar.gz', resolved.url)
assert_eq('telescope.nvim-0.1.8.tar.gz', resolved.filename)
end)
it('honours a per-plugin ext override', function()
local resolved = assert(registry.resolve({ name = 'plenary.nvim', version = '1', ext = 'zip' }, reg))
assert_match('plenary.nvim-1.zip', resolved.url)
end)
it('fails loudly without a url_template', function()
local resolved, err = registry.resolve({ name = 'x', version = '1' }, { kind = 'template' })
assert_nil(resolved)
assert_match('url_template', err)
end)
it('names the unknown kind', function()
local resolved, err = registry.resolve({ name = 'x', version = '1' }, { kind = 'nosuch' })
assert_nil(resolved)
assert_match('nosuch', err)
end)
end)
describe('artifactapi resolver', function()
local reg = {
kind = 'artifactapi',
base_url = 'https://artifactapi.k8s.syd1.au.unkin.net/',
repo = 'nvim-plugins',
}
it('builds the generic-repo path', function()
local resolved = assert(registry.resolve({ name = 'oil.nvim', version = '2.11.0' }, reg))
assert_eq(
'https://artifactapi.k8s.syd1.au.unkin.net/api/v2/remotes/nvim-plugins/files/oil.nvim/oil.nvim-2.11.0.tar.gz',
resolved.url
)
end)
it('accepts a path_template override', function()
local reg2 = vim.tbl_extend('force', reg, { path_template = '${version}/${slug}.${ext}' })
local resolved = assert(registry.resolve({ name = 'oil.nvim', version = '2.11.0' }, reg2))
assert_match('/files/2.11.0/oil.nvim.tar.gz', resolved.url)
end)
end)
describe('url escape hatch', function()
it('bypasses the registry entirely', function()
local resolved = assert(registry.resolve({ name = 'x', version = '1', url = 'https://h/x.zip' }, nil))
assert_eq('https://h/x.zip', resolved.url)
assert_eq('x.zip', resolved.filename)
end)
end)
describe('auth headers', function()
it('reads a token from the environment', function()
vim.env.VIMPACK_TEST_TOKEN = 'sekret'
local headers = assert(registry.build_headers({ auth = { env = 'VIMPACK_TEST_TOKEN' } }))
assert_eq({ Authorization = 'Bearer sekret' }, headers)
vim.env.VIMPACK_TEST_TOKEN = nil
end)
it('reports an unset env var', function()
vim.env.VIMPACK_TEST_TOKEN = nil
local headers, err = registry.build_headers({ auth = { env = 'VIMPACK_TEST_TOKEN' } })
assert_nil(headers)
assert_match('VIMPACK_TEST_TOKEN', err)
end)
it('runs token_cmd and uses a custom header', function()
local headers = assert(registry.build_headers({
auth = { token_cmd = { 'printf', '%s', 'abc123' }, header = 'X-JFrog-Art-Api', prefix = '' },
}))
assert_eq({ ['X-JFrog-Art-Api'] = 'abc123' }, headers)
end)
it('rejects a literal token', function()
local headers, err = registry.build_headers({ auth = { token = 'oops' } })
assert_nil(headers)
assert_match('literal token', err)
end)
it('attaches auth to a resolved url', function()
vim.env.VIMPACK_TEST_TOKEN = 'sekret'
local resolved = assert(registry.resolve({ name = 'a', version = '1' }, {
kind = 'template',
url_template = 'https://h/${name}-${version}.tar.gz',
auth = { env = 'VIMPACK_TEST_TOKEN' },
}))
assert_eq('Bearer sekret', resolved.headers.Authorization)
vim.env.VIMPACK_TEST_TOKEN = nil
end)
end)
describe('custom resolvers', function()
it('are usable once registered', function()
registry.register('spec_custom', function(spec)
return { url = 'https://custom/' .. spec.name, headers = {}, filename = 'a.tar.gz' }
end)
local resolved = assert(registry.resolve({ name = 'z', version = '1' }, { kind = 'spec_custom' }))
assert_eq('https://custom/z', resolved.url)
end)
end)
describe('archive_kind', function()
it('classifies suffixes', function()
assert_eq('tar', util.archive_kind('a.tar.gz'))
assert_eq('tar', util.archive_kind('a.tgz'))
assert_eq('zip', util.archive_kind('A.ZIP'))
assert_nil(util.archive_kind('a.rpm'))
end)
end)