Problem: `resolve_credentials` reads from `vim.fn.stdpath('data')`,
the real Neovim data dir. The test passed only because `_wipe()` was
incidentally deleting the user's credential file mid-run.
Solution: stub `oauth.load_json_file` for the duration of the test so
real credential files cannot interfere with the fallback assertion.
233 lines
7.4 KiB
Lua
233 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)
|