mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
fix(docsync): stop a push from stealing another file's pairing
Papra answers a push of bytes it already holds with the document that has them. Two trip files with the same content therefore pushed to one document, and the unique index on (link_id, remote_id) aborted that run and every run after it with a constraint error. The pairing now stays with the file that owns it and the second file is recorded as a conflict. Also in this pass: - Paperless no longer adopts a same-bytes document that lacks the scope's tag when a consume outlives the poll budget; that document belongs to someone else's filing and delete-through would have trashed it. - The pull path writes the file row, the retirement of the copy it replaces and the pairing in one transaction, and announces them only once committed. - PATCH links stops accepting remoteRootId/remoteRootPath, which it read and silently dropped. - The MCP sync tool clears shelved items before running, as the REST route does.
This commit is contained in:
@@ -371,3 +371,40 @@ describe('the admin switches', () => {
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A nudge that lands on a run already in flight.
|
||||
*
|
||||
* `syncLink` answers `busy` and returns, so the nudge was thrown away — and the
|
||||
* changes it was about may well have landed after the running pass read the
|
||||
* folder, which means waiting out a whole poll interval for them. Asked again
|
||||
* once, and only once, so two clients cannot keep each other going.
|
||||
*/
|
||||
describe('a nudge that arrives mid-run', () => {
|
||||
it('asks again once when the run was busy', async () => {
|
||||
sync.syncLink.mockResolvedValueOnce({ state: 'busy', pulled: 0, pushed: 0, conflicts: 0, missing: 0 });
|
||||
controller.nudge('tok-live', makeReq({ 'x-trek-docsync-secret': SECRET }));
|
||||
settle();
|
||||
await Promise.resolve();
|
||||
settle();
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('gives up after that one retry rather than chasing itself', async () => {
|
||||
sync.syncLink.mockResolvedValue({ state: 'busy', pulled: 0, pushed: 0, conflicts: 0, missing: 0 });
|
||||
controller.nudge('tok-live', makeReq({ 'x-trek-docsync-secret': SECRET }));
|
||||
for (let i = 0; i < 5; i += 1) { settle(); await Promise.resolve(); }
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not retry a run that worked', async () => {
|
||||
// Set explicitly: clearAllMocks drops the calls but keeps the
|
||||
// implementation the previous case installed.
|
||||
sync.syncLink.mockResolvedValue({ state: 'ok', pulled: 0, pushed: 0, conflicts: 0, missing: 0 });
|
||||
controller.nudge('tok-live', makeReq({ 'x-trek-docsync-secret': SECRET }));
|
||||
settle();
|
||||
await Promise.resolve();
|
||||
settle();
|
||||
expect(sync.syncLink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user