Files
vimpack/tests/manifest_spec.lua
T
unkin-agent ca55c9dd9f
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Validate archive entries and stage inside the pack root
- Reject archive members that are absolute, escape via .., are special files, or link outside the destination, before anything is written

- Stage under the pack root so the swap is a same-filesystem rename, with an EXDEV copy fallback; propagate registry auth errors and validate dir_name
2026-09-05 16:32:47 +10:00

163 lines
5.6 KiB
Lua

local checksum = require('vimpack.checksum')
local config = require('vimpack.config')
local manifest = require('vimpack.manifest')
describe('manifest', function()
it('returns an empty manifest for a missing file', function()
local data = manifest.read(tmpdir() .. '/nope.json')
assert_eq(1, data.version)
assert_eq({}, data.plugins)
end)
it('round-trips through disk', function()
local path = tmpdir() .. '/manifest.json'
local data = manifest.empty()
manifest.set(data, 'oil.nvim', { version = '2.11.0', sha256 = 'abc', opt = false })
assert_true(manifest.write(path, data))
local read_back = manifest.read(path)
assert_eq('2.11.0', manifest.get(read_back, 'oil.nvim').version)
assert_eq('abc', read_back.plugins['oil.nvim'].sha256)
end)
it('survives a corrupt file', function()
local path = tmpdir() .. '/manifest.json'
vim.fn.writefile({ 'not json {' }, path)
local data, err = manifest.read(path)
assert_eq({}, data.plugins)
assert_match('not valid JSON', err)
end)
it('removes entries', function()
local data = manifest.empty()
manifest.set(data, 'a', { version = '1' })
manifest.remove(data, 'a')
assert_nil(manifest.get(data, 'a'))
end)
end)
describe('sha256', function()
local sha256 = require('vimpack.sha256')
it('matches the NIST vectors', function()
assert_eq('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256.hash(''))
assert_eq('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', sha256.hash('abc'))
assert_eq(
'248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1',
sha256.hash('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')
)
end)
it('handles binary data and multi-block input', function()
assert_eq(
'79eebf0b2aae05b2490234d0b884581935b2e28127f06a74a5b48e758f86bd25',
sha256.hash(string.char(0, 1, 2, 255, 0))
)
assert_eq('541b3e9daa09b20bf85fa273e5cbd3e80185aa4ec298e765db87742b70138a53', sha256.hash(string.rep('\0', 1000)))
assert_eq('cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0', sha256.hash(string.rep('a', 1000000)))
end)
end)
describe('checksum', function()
local function write(contents)
local path = tmpdir() .. '/blob.bin'
vim.fn.writefile({ contents }, path, 'b')
return path
end
it('passes when the digest matches', function()
local path = write('hello vimpack')
local digest = assert(checksum.sha256_file(path))
assert_true(checksum.verify(path, digest))
assert_true(checksum.verify(path, digest:upper()))
end)
it('skips verification when no digest is configured', function()
assert_true(checksum.verify(write('x'), nil))
end)
it('names expected and got on mismatch', function()
local path = write('hello vimpack')
local ok, err = checksum.verify(path, string.rep('0', 64))
assert_true(not ok)
assert_match('checksum mismatch', err)
assert_match(string.rep('0', 64), err)
assert_match(assert(checksum.sha256_file(path)), err)
end)
it('reports an unreadable file', function()
local ok, err = checksum.verify(tmpdir() .. '/missing', string.rep('0', 64))
assert_true(not ok)
assert_match('checksum', err)
end)
end)
describe('config', function()
local reg = { kind = 'template', url_template = 'https://h/${name}-${version}.tar.gz' }
it('normalizes strings and defaults', function()
local cfg = assert(config.normalize({
registry = reg,
plugins = { { name = 'oil.nvim', version = '2.11.0' } },
}))
assert_eq(false, cfg.plugins[1].opt)
assert_eq('oil.nvim', cfg.plugins[1].dir_name)
assert_eq(reg, cfg.plugins[1].registry)
end)
it('lets a plugin override the registry', function()
local other = { kind = 'template', url_template = 'https://other/${name}' }
local cfg = assert(config.normalize({
registry = reg,
plugins = { { name = 'a', version = '1', registry = other } },
}))
assert_eq('https://other/${name}', cfg.plugins[1].registry.url_template)
end)
it('requires a version unless a url is given', function()
local cfg, err = config.normalize({ registry = reg, plugins = { { name = 'a' } } })
assert_nil(cfg)
assert_match('version is required', err)
assert_true(config.normalize({ plugins = { { name = 'a', url = 'https://h/a.tar.gz' } } }) ~= nil)
end)
it('rejects duplicates and literal tokens', function()
local _, dup = config.normalize({
registry = reg,
plugins = { { name = 'a', version = '1' }, { name = 'a', version = '2' } },
})
assert_match('duplicate', dup)
local _, tok = config.normalize({
registry = vim.tbl_extend('force', reg, { auth = { token = 'literal' } }),
plugins = {},
})
assert_match('literal token', tok)
end)
it('rejects a plugin with no registry and no url', function()
local cfg, err = config.normalize({ plugins = { { name = 'a', version = '1' } } })
assert_nil(cfg)
assert_match('no registry', err)
end)
it('keeps a well-formed dir_name override', function()
local cfg = assert(config.normalize({
registry = reg,
plugins = { { name = 'oil.nvim', version = '2.11.0', dir_name = 'oil' } },
}))
assert_eq('oil', cfg.plugins[1].dir_name)
end)
it('rejects a dir_name that is not a single path component', function()
for _, bad in ipairs({ '../evil', '/abs', 'a/b', '..', '.', '' }) do
local cfg, err = config.normalize({
registry = reg,
plugins = { { name = 'a', version = '1', dir_name = bad } },
})
assert_nil(cfg, 'dir_name ' .. vim.inspect(bad) .. ' should be rejected')
assert_match('dir_name', err)
end
end)
end)