pending.nvim/spec/oauth_spec.lua
2026-03-06 15:47:24 -05:00

235 lines
7.4 KiB
Lua

require('spec.helpers')
local config = require('pending.config')
local oauth = require('pending.sync.oauth')
describe('oauth', function()
local tmpdir
before_each(function()
tmpdir = vim.fn.tempname()
vim.fn.mkdir(tmpdir, 'p')
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
config.reset()
end)
after_each(function()
vim.fn.delete(tmpdir, 'rf')
vim.g.pending = nil
config.reset()
end)
describe('url_encode', function()
it('leaves alphanumerics unchanged', function()
assert.equals('hello123', oauth.url_encode('hello123'))
end)
it('encodes spaces', function()
assert.equals('hello%20world', oauth.url_encode('hello world'))
end)
it('encodes special characters', function()
assert.equals('a%3Db%26c', oauth.url_encode('a=b&c'))
end)
it('preserves hyphens, dots, underscores, tildes', function()
assert.equals('a-b.c_d~e', oauth.url_encode('a-b.c_d~e'))
end)
end)
describe('load_json_file', function()
it('returns nil for missing file', function()
assert.is_nil(oauth.load_json_file(tmpdir .. '/nonexistent.json'))
end)
it('returns nil for empty file', function()
local path = tmpdir .. '/empty.json'
local f = io.open(path, 'w')
f:write('')
f:close()
assert.is_nil(oauth.load_json_file(path))
end)
it('returns nil for invalid JSON', function()
local path = tmpdir .. '/bad.json'
local f = io.open(path, 'w')
f:write('not json')
f:close()
assert.is_nil(oauth.load_json_file(path))
end)
it('parses valid JSON', function()
local path = tmpdir .. '/good.json'
local f = io.open(path, 'w')
f:write('{"key":"value"}')
f:close()
local data = oauth.load_json_file(path)
assert.equals('value', data.key)
end)
end)
describe('save_json_file', function()
it('creates parent directories', function()
local path = tmpdir .. '/sub/dir/file.json'
local ok = oauth.save_json_file(path, { test = true })
assert.is_true(ok)
local data = oauth.load_json_file(path)
assert.is_true(data.test)
end)
it('sets restrictive permissions', function()
local path = tmpdir .. '/secret.json'
oauth.save_json_file(path, { x = 1 })
local perms = vim.fn.getfperm(path)
assert.equals('rw-------', perms)
end)
end)
describe('resolve_credentials', function()
it('uses config fields when set', function()
config.reset()
vim.g.pending = {
data_path = tmpdir .. '/tasks.json',
sync = {
gtasks = {
client_id = 'config-id',
client_secret = 'config-secret',
},
},
}
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
local creds = c:resolve_credentials()
assert.equals('config-id', creds.client_id)
assert.equals('config-secret', creds.client_secret)
end)
it('uses credentials file when config fields absent', function()
local cred_path = tmpdir .. '/creds.json'
oauth.save_json_file(cred_path, {
client_id = 'file-id',
client_secret = 'file-secret',
})
config.reset()
vim.g.pending = {
data_path = tmpdir .. '/tasks.json',
sync = { gtasks = { credentials_path = cred_path } },
}
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
local creds = c:resolve_credentials()
assert.equals('file-id', creds.client_id)
assert.equals('file-secret', creds.client_secret)
end)
it('unwraps installed wrapper format', function()
local cred_path = tmpdir .. '/wrapped.json'
oauth.save_json_file(cred_path, {
installed = {
client_id = 'wrapped-id',
client_secret = 'wrapped-secret',
},
})
config.reset()
vim.g.pending = {
data_path = tmpdir .. '/tasks.json',
sync = { gcal = { credentials_path = cred_path } },
}
local c = oauth.new({ name = 'gcal', scope = 'x', port = 0, config_key = 'gcal' })
local creds = c:resolve_credentials()
assert.equals('wrapped-id', creds.client_id)
assert.equals('wrapped-secret', creds.client_secret)
end)
it('falls back to bundled credentials', function()
config.reset()
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
local orig_load = oauth.load_json_file
oauth.load_json_file = function()
return nil
end
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
local creds = c:resolve_credentials()
oauth.load_json_file = orig_load
assert.equals(oauth._BUNDLED_CLIENT_ID, creds.client_id)
assert.equals(oauth._BUNDLED_CLIENT_SECRET, creds.client_secret)
end)
it('prefers config fields over credentials file', function()
local cred_path = tmpdir .. '/creds2.json'
oauth.save_json_file(cred_path, {
client_id = 'file-id',
client_secret = 'file-secret',
})
config.reset()
vim.g.pending = {
data_path = tmpdir .. '/tasks.json',
sync = {
gtasks = {
credentials_path = cred_path,
client_id = 'config-id',
client_secret = 'config-secret',
},
},
}
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
local creds = c:resolve_credentials()
assert.equals('config-id', creds.client_id)
assert.equals('config-secret', creds.client_secret)
end)
end)
describe('token_path', function()
it('includes backend name', function()
local c = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
assert.truthy(c:token_path():match('gtasks_tokens%.json$'))
end)
it('differs between backends', function()
local g = oauth.new({ name = 'gcal', scope = 'x', port = 0, config_key = 'gcal' })
local t = oauth.new({ name = 'gtasks', scope = 'x', port = 0, config_key = 'gtasks' })
assert.not_equals(g:token_path(), t:token_path())
end)
end)
describe('load_tokens / save_tokens', function()
it('round-trips tokens', function()
local c = oauth.new({ name = 'test', scope = 'x', port = 0, config_key = 'gtasks' })
local path = c:token_path()
local dir = vim.fn.fnamemodify(path, ':h')
vim.fn.mkdir(dir, 'p')
local tokens = {
access_token = 'at',
refresh_token = 'rt',
expires_in = 3600,
obtained_at = 1000,
}
c:save_tokens(tokens)
local loaded = c:load_tokens()
assert.equals('at', loaded.access_token)
assert.equals('rt', loaded.refresh_token)
vim.fn.delete(dir, 'rf')
end)
end)
describe('auth_headers', function()
it('includes bearer token', function()
local headers = oauth.auth_headers('mytoken')
assert.equals('Authorization: Bearer mytoken', headers[1])
assert.equals('Content-Type: application/json', headers[2])
end)
end)
describe('new', function()
it('creates client with correct fields', function()
local c = oauth.new({
name = 'test',
scope = 'https://example.com',
port = 12345,
config_key = 'test',
})
assert.equals('test', c.name)
assert.equals('https://example.com', c.scope)
assert.equals(12345, c.port)
assert.equals('test', c.config_key)
end)
end)
end)