* refactor(oauth): async coroutine support, pure-Lua PKCE, server hardening Problem: OAuth module shelled out to openssl for PKCE, used blocking `vim.system():wait()`, had a weak `os.time()` PRNG seed, and the TCP callback server leaked on read errors with no timeout. Solution: Add `M.system()` coroutine wrapper and `M.async()` helper, replace openssl with `vim.fn.sha256` + `vim.base64.encode`, seed from `vim.uv.hrtime()`, add `close_server()` guard with 120s timeout, and close the server on read errors. * fix(gtasks): async operations, error notifications, buffer refresh Problem: Sync operations blocked the editor, `push_pass` silently dropped delete/update/create API errors, and the buffer was not re-rendered after push/pull/sync. Solution: Wrap `push`, `pull`, `sync` in `oauth.async()`, add `vim.notify` for all `push_pass` failure paths, and re-render the pending buffer after each operation. * fix(init): edit recompute, filter predicates, sync action listing Problem: `M.edit()` skipped `_recompute_counts()` after saving, `compute_hidden_ids` lacked `done`/`pending` predicates, and `run_sync` defaulted to `sync` instead of listing available actions. Solution: Replace `s:save()` with `_save_and_notify()` in `M.edit()`, add `done` and `pending` filter predicates, and list backend actions when no action is specified. * refactor(gcal): per-category calendars, async push, error notifications Problem: gcal used a single hardcoded calendar name, ran synchronously blocking the editor, and silently dropped some API errors. Solution: Fetch all calendars and map categories to calendars (creating on demand), wrap push in `oauth.async()`, notify on individual API failures, track `_gcal_calendar_id` in `_extra`, and remove the `$` anchor from `next_day` pattern. * refactor: formatting fixes, config cleanup, health simplification Problem: Formatter disagreements in `init.lua` and `gtasks.lua`, stale `calendar` field in gcal config, and redundant health checks for data directory existence. Solution: Apply stylua formatting, remove `calendar` field from `pending.GcalConfig`, drop data-dir and no-file health messages, add `done`/`pending` to filter tab-completion candidates. * docs: update vimdoc for sync refactor, remove demo scripts Problem: Docs still referenced openssl dependency, defaulting to `sync` action, and the `calendar` config field. Demo scripts used the old singleton `store` API. Solution: Update vimdoc and README to reflect explicit actions, per- category calendars, and pure-Lua PKCE. Remove stale demo scripts and update sync specs to match new behavior. * fix(types): correct LuaLS annotations in oauth and gcal
120 lines
3.1 KiB
Lua
120 lines
3.1 KiB
Lua
require('spec.helpers')
|
|
|
|
local config = require('pending.config')
|
|
|
|
describe('sync', function()
|
|
local tmpdir
|
|
local pending
|
|
|
|
before_each(function()
|
|
tmpdir = vim.fn.tempname()
|
|
vim.fn.mkdir(tmpdir, 'p')
|
|
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
|
|
config.reset()
|
|
package.loaded['pending'] = nil
|
|
pending = require('pending')
|
|
end)
|
|
|
|
after_each(function()
|
|
vim.fn.delete(tmpdir, 'rf')
|
|
vim.g.pending = nil
|
|
config.reset()
|
|
package.loaded['pending'] = nil
|
|
end)
|
|
|
|
describe('dispatch', function()
|
|
it('errors on unknown subcommand', function()
|
|
local msg
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.ERROR then
|
|
msg = m
|
|
end
|
|
end
|
|
pending.command('notreal')
|
|
vim.notify = orig
|
|
assert.are.equal('Unknown Pending subcommand: notreal', msg)
|
|
end)
|
|
|
|
it('errors on unknown action for valid backend', function()
|
|
local msg
|
|
local orig = vim.notify
|
|
vim.notify = function(m, level)
|
|
if level == vim.log.levels.ERROR then
|
|
msg = m
|
|
end
|
|
end
|
|
pending.command('gcal notreal')
|
|
vim.notify = orig
|
|
assert.are.equal("gcal backend has no 'notreal' action", msg)
|
|
end)
|
|
|
|
it('lists actions when action is omitted', function()
|
|
local msg = nil
|
|
local orig = vim.notify
|
|
vim.notify = function(m)
|
|
msg = m
|
|
end
|
|
pending.command('gcal')
|
|
vim.notify = orig
|
|
assert.is_not_nil(msg)
|
|
assert.is_truthy(msg:find('push'))
|
|
end)
|
|
|
|
it('routes explicit push action', function()
|
|
local called = false
|
|
local gcal = require('pending.sync.gcal')
|
|
local orig_push = gcal.push
|
|
gcal.push = function()
|
|
called = true
|
|
end
|
|
pending.command('gcal push')
|
|
gcal.push = orig_push
|
|
assert.is_true(called)
|
|
end)
|
|
|
|
it('routes auth action', function()
|
|
local called = false
|
|
local gcal = require('pending.sync.gcal')
|
|
local orig_auth = gcal.auth
|
|
gcal.auth = function()
|
|
called = true
|
|
end
|
|
pending.command('gcal auth')
|
|
gcal.auth = orig_auth
|
|
assert.is_true(called)
|
|
end)
|
|
end)
|
|
|
|
it('works with sync.gcal config', function()
|
|
config.reset()
|
|
vim.g.pending = {
|
|
data_path = tmpdir .. '/tasks.json',
|
|
sync = { gcal = { client_id = 'test-id' } },
|
|
}
|
|
local cfg = config.get()
|
|
assert.are.equal('test-id', cfg.sync.gcal.client_id)
|
|
end)
|
|
|
|
describe('gcal module', function()
|
|
it('has name field', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('gcal', gcal.name)
|
|
end)
|
|
|
|
it('has auth function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.auth))
|
|
end)
|
|
|
|
it('has push function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.push))
|
|
end)
|
|
|
|
it('has health function', function()
|
|
local gcal = require('pending.sync.gcal')
|
|
assert.are.equal('function', type(gcal.health))
|
|
end)
|
|
end)
|
|
end)
|