diff --git a/web/src/lib/projectSettingsDuties.test.ts b/web/src/lib/projectSettingsDuties.test.ts index 7e6287c7..219851a3 100644 --- a/web/src/lib/projectSettingsDuties.test.ts +++ b/web/src/lib/projectSettingsDuties.test.ts @@ -7,6 +7,8 @@ import { clientDutyIssues, locateDutyIssue, needsThreeMembersForDuties, + resolveClientDocTaskDuty, + toggleAuthorOnly, toggleHumanApproval, } from './projectSettingsDuties'; @@ -81,4 +83,91 @@ describe('projectSettingsDuties', () => { ]); assert.equal(needsThreeMembersForDuties(draft), true); }); + + it('resolves author_only to A/A/A without three-member requirement', () => { + const task = { + task_key: 'intake', + enabled: true, + approval_configurable: true, + duty_policy: 'author_only' as const, + duties: { author_user_id: 7 }, + }; + const resolved = resolveClientDocTaskDuty(task); + assert.equal(resolved.mode, 'full'); + assert.equal(resolved.isAuthorOnly, true); + assert.deepEqual(resolved.effective, { + author_user_id: 7, + reviewer_user_id: 7, + approver_user_id: 7, + }); + const draft = plan([task]); + assert.equal(needsThreeMembersForDuties(draft), false); + assert.deepEqual(clientDutyIssues(draft, [{ userId: 7 }]), []); + }); + + it('rejects author_only without author or with conflicting reviewers', () => { + const missing = plan([ + { + task_key: 'intake', + task_title: '需求', + enabled: true, + approval_configurable: true, + duty_policy: 'author_only', + duties: {}, + }, + ]); + assert.equal( + locateDutyIssue(clientDutyIssues(missing, [{ userId: 1 }]), 'intake')?.code, + 'AUTHOR_ONLY_AUTHOR_REQUIRED', + ); + + const conflict = plan([ + { + task_key: 'intake', + task_title: '需求', + enabled: true, + approval_configurable: true, + duty_policy: 'author_only', + duties: { author_user_id: 1, reviewer_user_id: 2 }, + }, + ]); + assert.equal( + locateDutyIssue(clientDutyIssues(conflict, [{ userId: 1 }, { userId: 2 }]), 'intake')?.code, + 'AUTHOR_ONLY_DUTY_CONFLICT', + ); + }); + + it('mixed full + author_only still requires three members for the full doc', () => { + const draft = plan([ + { + task_key: 'intake', + enabled: true, + approval_configurable: true, + duties: { author_user_id: 1, reviewer_user_id: 2, approver_user_id: 3 }, + }, + { + task_key: 'research', + enabled: true, + approval_configurable: true, + duty_policy: 'author_only', + duties: { author_user_id: 1 }, + }, + ]); + assert.equal(needsThreeMembersForDuties(draft), true); + }); + + it('toggleAuthorOnly clears review/approve slots and restores on disable', () => { + const base = { + task_key: 'intake', + enabled: true, + approval_configurable: true, + duties: { author_user_id: 7, reviewer_user_id: 2, approver_user_id: 3 }, + }; + const on = toggleAuthorOnly(base, true); + assert.equal(on.duty_policy, 'author_only'); + assert.deepEqual(on.duties, { author_user_id: 7 }); + const off = toggleAuthorOnly(on, false); + assert.equal(off.duty_policy, ''); + assert.deepEqual(off.duties, { author_user_id: 7 }); + }); });