diff --git a/ondeck/src/lib/tasks/__tests__/generate-and-assign-core.test.ts b/ondeck/src/lib/tasks/__tests__/generate-and-assign-core.test.ts new file mode 100644 index 0000000..ed5dccb --- /dev/null +++ b/ondeck/src/lib/tasks/__tests__/generate-and-assign-core.test.ts @@ -0,0 +1,47 @@ +import { parseGenerateAndAssignTarget, GenerateAndAssignError } from '../generate-and-assign-core' + +describe('parseGenerateAndAssignTarget', () => { + it('parses advocate+designation mode', () => { + const { target, dryRun } = parseGenerateAndAssignTarget({ + advocateId: 'adv-1', + designationId: 'des-1', + }) + expect(target).toEqual({ mode: 'advocate-designation', advocateId: 'adv-1', designationId: 'des-1' }) + expect(dryRun).toBe(false) + }) + + it('parses client mode', () => { + const { target, dryRun } = parseGenerateAndAssignTarget({ clientId: 'client-1', dryRun: true }) + expect(target).toEqual({ mode: 'client', clientId: 'client-1' }) + expect(dryRun).toBe(true) + }) + + it('defaults dryRun to false when omitted', () => { + const { dryRun } = parseGenerateAndAssignTarget({ clientId: 'client-1' }) + expect(dryRun).toBe(false) + }) + + it('throws when both modes are provided', () => { + expect(() => + parseGenerateAndAssignTarget({ advocateId: 'a', designationId: 'd', clientId: 'c' }) + ).toThrow(GenerateAndAssignError) + }) + + it('throws when neither mode is provided', () => { + expect(() => parseGenerateAndAssignTarget({})).toThrow(GenerateAndAssignError) + }) + + it('throws when advocateId is given without designationId', () => { + expect(() => parseGenerateAndAssignTarget({ advocateId: 'a' })).toThrow(GenerateAndAssignError) + }) + + it('sets a 400 status on validation errors', () => { + try { + parseGenerateAndAssignTarget({}) + fail('expected throw') + } catch (err) { + expect(err).toBeInstanceOf(GenerateAndAssignError) + expect((err as GenerateAndAssignError).status).toBe(400) + } + }) +}) diff --git a/ondeck/src/lib/tasks/generate-and-assign-core.ts b/ondeck/src/lib/tasks/generate-and-assign-core.ts new file mode 100644 index 0000000..da5d545 --- /dev/null +++ b/ondeck/src/lib/tasks/generate-and-assign-core.ts @@ -0,0 +1,38 @@ +export class GenerateAndAssignError extends Error { + status: number + constructor(message: string, status: number) { + super(message) + this.status = status + } +} + +export type GenerateTarget = + | { mode: 'advocate-designation'; advocateId: string; designationId: string } + | { mode: 'client'; clientId: string } + +/** + * Parses and validates a generate-and-assign request body. Exactly one of + * (advocateId + designationId) or clientId must be present. + */ +export function parseGenerateAndAssignTarget(body: any): { target: GenerateTarget; dryRun: boolean } { + const dryRun = body?.dryRun === true + const hasAdvocateDesignation = !!body?.advocateId && !!body?.designationId + const hasClient = !!body?.clientId + + if (hasAdvocateDesignation && hasClient) { + throw new GenerateAndAssignError( + 'Provide either advocateId+designationId or clientId, not both', + 400 + ) + } + if (hasClient) { + return { target: { mode: 'client', clientId: body.clientId }, dryRun } + } + if (hasAdvocateDesignation) { + return { + target: { mode: 'advocate-designation', advocateId: body.advocateId, designationId: body.designationId }, + dryRun, + } + } + throw new GenerateAndAssignError('advocateId and designationId, or clientId, are required', 400) +}