mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(docsync): refuse an orphaned binding over MCP as the REST route does
A binding whose credential owner left the trip is orphaned: the scheduler, the webhook and the REST "Sync now" route all refuse it, because a run would use the credential of somebody who is no longer on the trip. The MCP sync_trip_documents tool only checked the admin switch, so any member with MCP could still run such a binding on the ex-member's account. The tool now skips an orphaned binding before anything is un-shelved, reports it with the orphaned code, and answers with an error when no binding of the trip could run at all.
This commit is contained in:
@@ -27,8 +27,8 @@ import type { McpContext, McpTextResult, ToolOptions } from '../../../../src/nes
|
||||
|
||||
const ctx = { userId: 7, scopes: null, isStaticToken: false } as McpContext;
|
||||
|
||||
const link = (id: number, providerId = 'paperless'): LinkRow =>
|
||||
({ id, provider_id: providerId, trip_id: 3 } as LinkRow);
|
||||
const link = (id: number, providerId = 'paperless', lastSyncState: string | null = null): LinkRow =>
|
||||
({ id, provider_id: providerId, trip_id: 3, last_sync_state: lastSyncState } as LinkRow);
|
||||
|
||||
const RUN = { state: 'ok', pulled: 2, pushed: 1, conflicts: 0, missing: 0 };
|
||||
|
||||
@@ -256,6 +256,29 @@ describe('sync_trip_documents', () => {
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('refuses an orphaned binding as the REST route does, without touching it', async () => {
|
||||
const { mcp, sync } = makeMcp({ links: [link(1, 'paperless', 'orphaned')] });
|
||||
const text = refusal(await mcp.syncNow({ tripId: 3 }, ctx));
|
||||
expect(text.startsWith('orphaned:')).toBe(true);
|
||||
expect(text).toContain('connect the store again');
|
||||
// The credential belongs to somebody who left the trip: no run, and the
|
||||
// shelved rows stay shelved.
|
||||
expect(sync.retryShelvedItems).not.toHaveBeenCalled();
|
||||
expect(sync.syncLink).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs the bindings that still have an owner and reports the orphaned one by its code', async () => {
|
||||
const links = [link(1, 'paperless', 'orphaned'), link(2, 'nextcloud')];
|
||||
const { mcp, sync } = makeMcp({ links });
|
||||
const body = payload(await mcp.syncNow({ tripId: 3 }, ctx));
|
||||
expect(body.runs).toEqual([
|
||||
{ linkId: 1, provider: 'paperless', state: 'orphaned', errorCode: 'orphaned' },
|
||||
{ linkId: 2, provider: 'nextcloud', ...RUN },
|
||||
]);
|
||||
expect(sync.retryShelvedItems.mock.calls).toEqual([[2]]);
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('asks for the cheap incremental run unless the caller says otherwise', async () => {
|
||||
const { mcp, sync } = makeMcp({ links: [link(1)] });
|
||||
await mcp.syncNow({ tripId: 3 }, ctx);
|
||||
|
||||
Reference in New Issue
Block a user