Skip to content

Commit 02ce60a

Browse files
committed
test(plugin-approvals): pin the registered picker filter literally; add the changeset
The registered-body pin compared the registered field with the source field, so dropping the filter left both undefined and the pin green. It now compares with the literal filter. Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y Co-authored-by: Claude <noreply@anthropic.com>
1 parent 49c6d95 commit 02ce60a

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
fix(plugin-approvals): the out-of-office delegation form's Delegator picker offers only the signed-in user, the one delegator the server accepts
6+
7+
Clause-②: no
8+
9+
No accept set moves. The field gains a picker filter, which nothing on the write path reads, and the delegation write guard and `required` are unchanged.
10+
11+
- **Before.** `sys_approval_delegation.delegator_id` was a free `sys_user` lookup, so Approvals → Delegations → New offered every user. The delegation write guard accepts one delegator on a non-system write, the acting user, so picking anyone else was refused with 403 `FORBIDDEN`.
12+
- **Now.** The field declares `lookupFilters: [{ field: 'id', operator: 'eq', value: '{current_user_id}' }]`. The engine resolves the placeholder against the caller on every read, so the picker's candidate query offers the signed-in user and nobody else.
13+
- **Unchanged.** What a write may store. A create or an update that names another delegator is still refused, and there is no admin exemption. An admin covering for an absent approver reassigns, recalls or rejects that approver's pending approvals through the approval override instead.

‎packages/plugins/plugin-approvals/src/sys-approval-delegation-delegator-picker.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ const sysUser = {
5656

5757
type LookupFilter = { field: string; operator: string; value: unknown };
5858

59+
/** The one candidate the guard admits: the acting user, by id. */
60+
const ACTING_USER_ONLY: LookupFilter[] = [{ field: 'id', operator: 'eq', value: '{current_user_id}' }];
61+
5962
const delegatorField = (): Record<string, any> =>
6063
(SysApprovalDelegation.fields as Record<string, any>).delegator_id;
6164

@@ -100,12 +103,12 @@ describe('sys_approval_delegation — the Delegator picker offers only what the
100103
expect(field.type).toBe('lookup');
101104
expect(field.reference).toBe('sys_user');
102105
expect(field.required).toBe(true);
103-
expect(field.lookupFilters).toEqual([{ field: 'id', operator: 'eq', value: '{current_user_id}' }]);
106+
expect(field.lookupFilters).toEqual(ACTING_USER_ONLY);
104107
});
105108

106109
it('the registered body keeps the declaration', () => {
107110
const registered = (engine.registry.getObject(OBJECT) as any).fields.delegator_id;
108-
expect(registered.lookupFilters).toEqual(delegatorField().lookupFilters);
111+
expect(registered.lookupFilters).toEqual(ACTING_USER_ONLY);
109112
});
110113

111114
it.each(USERS.map((u) => [u.id]))('the picker offers %s themselves and nobody else', async (caller) => {

0 commit comments

Comments
 (0)