Files
TREK/shared/src/docsync/docsync.schema.spec.ts
T
Maurice b88b92add7 fix(docsync): a paused binding no longer resets its own settings
PATCH links validated its body with a schema derived from the create
contract through .partial(). In Zod 4 a .default() still fires inside an
optional field, so a patch naming one setting arrived with every other
one filled in: pausing a pull-only binding switched it to two-way, reset
both policies and blanked its label. The update contract is now written
out without defaults, and an e2e test drives a patch through the real
pipe, which the controller unit tests never see.

Also, the cosmetic leftovers from the review:
- POST connections/:connectionId/scopes no longer wants the connection
  in the body as well. It was required, never read, and let one request
  name two connections; a client that still sends it has the key
  stripped rather than refused.
- createLink loses the scope argument every caller passed as null.
- The controller's owner check is now assertCanManage, because it lets
  instance admins through just as canManageDocSync does on the client.
  Who may manage a binding is unchanged.
- document_provider_fields drops settings_key and payload_key. Nothing
  read them: the connect form takes its fields from /docsync/providers
  and the admin shelf sends none for document providers.
- The e2e test imports the provider classes under their new names.
2026-09-19 12:07:28 +02:00

63 lines
2.5 KiB
TypeScript

import { docsyncLinkInputSchema, docsyncLinkUpdateSchema, docsyncScopeCreateSchema } from './docsync.schema';
import { describe, it, expect } from 'vitest';
describe('docsyncScopeCreateSchema', () => {
it('takes only a name, because the connection is in the path', () => {
expect(docsyncScopeCreateSchema.parse({ name: ' Norway ' })).toEqual({ name: 'Norway' });
});
it('strips a connection id sent in the body instead of refusing the request', () => {
expect(docsyncScopeCreateSchema.parse({ connectionId: 5, name: 'Norway' })).toEqual({ name: 'Norway' });
});
it('rejects a blank or oversized name', () => {
expect(docsyncScopeCreateSchema.safeParse({ name: ' ' }).success).toBe(false);
expect(docsyncScopeCreateSchema.safeParse({ name: 'x'.repeat(201) }).success).toBe(false);
expect(docsyncScopeCreateSchema.safeParse({}).success).toBe(false);
});
});
describe('docsyncLinkInputSchema', () => {
it('needs the connection in the body, since POST /links has no connection in its path', () => {
expect(docsyncLinkInputSchema.safeParse({ scopeKey: 'tag:1' }).success).toBe(false);
});
it('fills the safe defaults for everything the picker leaves out', () => {
expect(docsyncLinkInputSchema.parse({ connectionId: 3, scopeKey: 'tag:1' })).toEqual({
connectionId: 3,
scopeKey: 'tag:1',
remoteLabel: '',
direction: 'both',
deletePolicy: 'unlink',
conflictPolicy: 'manual',
syncEnabled: true,
});
});
});
describe('docsyncLinkUpdateSchema', () => {
it('passes on only the fields the patch named, with no defaults filled in', () => {
// Defaults here would reach updateLink as real values: pausing a pull-only
// binding used to switch it to two-way and blank its label.
expect(docsyncLinkUpdateSchema.parse({ syncEnabled: false })).toEqual({ syncEnabled: false });
expect(docsyncLinkUpdateSchema.parse({})).toEqual({});
});
it('still checks the values it is given', () => {
expect(docsyncLinkUpdateSchema.safeParse({ direction: 'sideways' }).success).toBe(false);
expect(docsyncLinkUpdateSchema.safeParse({ syncEnabled: 'no' }).success).toBe(false);
});
it('drops the connection and the anchor, which are fixed once a binding exists', () => {
const parsed = docsyncLinkUpdateSchema.parse({
connectionId: 9,
scopeKey: 'tag:2',
remoteRootId: '2',
remoteRootPath: '/elsewhere',
deletePolicy: 'trash',
});
expect(parsed).toEqual({ deletePolicy: 'trash' });
});
});