Approvals — Tests¶
Overview¶
The approvals test suite verifies the core workflow behavior of the approvals app.
It covers:
- approval creation constraints
- assignee computation and refresh behavior
- inbox visibility
- approval decision permissions and side effects
- delegation lifecycle
- forwarding and unforwarding
- serializer validation rules
- notification emission for approval events
The overall goal of the suite is to ensure that the approvals system remains:
- workflow-correct
- permission-safe
- delegation-aware
- idempotent
- integration-safe with timesheets and notifications
Main test areas¶
1. Approval model and constraint tests¶
These tests validate the core invariants of the approval domain model.
Covered behavior¶
- only one active pending approval may exist per:
- org
- target object
-
approval kind
-
decided approvals require:
decided_bydecided_at
Example scenarios¶
- creating a second pending approval for the same timesheet raises
IntegrityError - calling
full_clean()on an approved approval without decision metadata raisesValidationError
Why this matters¶
These tests protect the most important workflow guarantees at the database and model level.
2. Assignee service tests¶
These tests verify the behavior of refresh_assignees(...) and related assignee computation logic.
Covered behavior¶
- assignees are created correctly for timesheet approvals
- assignee refresh is idempotent
- stale assignee rows are removed
- manager/delegate logic is respected
- the requestor or engineer is not accidentally assigned
Example scenarios¶
- manager becomes assignee for submitted timesheet approval
- running
refresh_assignees()twice produces no duplicates - stale “bogus” assignee rows are removed
Why this matters¶
Approval inboxes and permission checks depend on accurate assignee rows.
3. Approval creation tests¶
These tests validate serializer and API behavior when creating approvals.
Covered behavior¶
- invalid approval kind values are rejected
- pending approval creation is idempotent when using helper services
- duplicate active pending approvals are prevented
Example scenarios¶
- posting invalid
kindreturns400 get_or_create_pending_timesheet_approval(...)returns the same approval on repeated calls
Why this matters¶
Approval creation must be safe under retries and explicit about allowed kinds.
4. Inbox visibility tests¶
These tests validate approval inbox behavior for:
- engineers
- managers
- delegated users
- unrelated org users
Covered behavior¶
- engineers do not see approvals they should not act on
- managers see assigned approvals
- delegated users see delegated approvals
- cross-org users do not gain access
- forwarded approvals appear correctly
- inactive or expired delegations do not grant inbox visibility
Example scenarios¶
- direct manager sees a timesheet approval in inbox
- submitter does not see their own approval in inbox
- delegate sees approval when delegation is active
- unrelated org member sees nothing
- non-member request with org headers returns
403
Mermaid flow¶
flowchart TD
A[Pending approval exists] --> B{Who is the user?}
B -->|direct assignee| C[Visible in inbox]
B -->|active delegate| C
B -->|forwarded assignee| C
B -->|unrelated org member| D[Not visible]
B -->|non-member| E[403]
¶
flowchart TD
A[Pending approval exists] --> B{Who is the user?}
B -->|direct assignee| C[Visible in inbox]
B -->|active delegate| C
B -->|forwarded assignee| C
B -->|unrelated org member| D[Not visible]
B -->|non-member| E[403]
5. Decision permission tests¶
These tests verify that only allowed users may decide an approval.
Covered behavior¶
- direct assignee may approve
- delegated assignee may approve when delegation matches
- non-assignee may not approve
- expired delegation does not grant decision rights
- kind mismatch does not grant delegated decision rights
- second decision attempt returns
409
Example scenarios¶
- delegated user approves on behalf of manager
- unrelated org member receives
403 - expired delegation causes
403 - second decision attempt after approval returns
409
Why this matters¶
Decision endpoints are the most sensitive approval action and must be strongly protected.
6. Decision side-effect tests¶
These tests validate that deciding an approval updates the underlying target object correctly.
Covered behavior¶
For timesheet approvals:
- approval → timesheet approved
- rejection → timesheet rejected
- approval clears previous decline fields
- rejection clears previous approval fields
Example scenarios¶
- approving a timesheet approval sets:
- timesheet status to approved
approved_by-
approved_at -
rejecting a timesheet approval sets:
- timesheet status to rejected
declined_bydeclined_atdecline_reason
flowchart TD
A[Approval decision] --> B{Decision}
B -->|approve| C[Approval becomes APPROVED]
B -->|reject| D[Approval becomes REJECTED]
C --> E[WeeklyTimesheet becomes APPROVED]
D --> F[WeeklyTimesheet becomes REJECTED]
¶
flowchart TD
A[Approval decision] --> B{Decision}
B -->|approve| C[Approval becomes APPROVED]
B -->|reject| D[Approval becomes REJECTED]
C --> E[WeeklyTimesheet becomes APPROVED]
D --> F[WeeklyTimesheet becomes REJECTED]
7. Forward and unforward tests¶
These tests validate forwarding behavior for active pending approvals.
Covered behavior¶
- direct assignee or admin may forward
- non-assignee non-admin may not forward
- forwarded assignee row is created
- forwarded assignee may then decide
- unforward removes forwarded assignee
- forwarding replaces the previous forwarded assignee
- forwarding to same user again is handled consistently
Example scenarios¶
- manager forwards approval to another active member
- forwarded delegate can then approve
- second forward replaces prior forwarded assignee row
- unforward removes
reason="forwarded"assignee row
flowchart TD
A[Manager assigned to approval] --> B[Forward to delegate]
B --> C[ApprovalAssignee reason=forwarded created]
C --> D[Delegate may decide]
B --> E[Forward again to another user]
E --> F[Old forwarded assignee removed]
F --> G[New forwarded assignee remains]
¶
flowchart TD
A[Manager assigned to approval] --> B[Forward to delegate]
B --> C[ApprovalAssignee reason=forwarded created]
C --> D[Delegate may decide]
B --> E[Forward again to another user]
E --> F[Old forwarded assignee removed]
F --> G[New forwarded assignee remains]8. Delegation model tests¶
These tests validate the delegation model invariants.
Covered behavior¶
- self-delegation is invalid
ends_atmust be afterstarts_at- only one active delegation may exist for same:
- org
- from_user
- kind
Example scenarios¶
- delegating to yourself raises
ValidationError - overlapping same-kind active delegation raises
IntegrityError
Why this matters¶
Delegation is a core access path and must remain structurally correct.
9. Delegation API tests¶
These tests verify the approval delegation API surface.
Covered behavior¶
- serializer accepts:
- empty string kind for global delegation
-
valid approval kind values
-
serializer rejects invalid kind
-
/delegations/mereturns: - outgoing
-
incoming
-
non-admin delegation list is scoped to relevant rows
- admin sees all delegations
- disable endpoint deactivates delegation
- disabled delegations disappear from
/me - creating a second delegation for same kind replaces the previous active one
Example scenarios¶
- user creates global delegation
- user creates kind-specific delegation
- user disables delegation and it disappears from active view
- second same-kind POST replaces first delegation
Mermaid flow¶
flowchart TD
A[Create delegation request] --> B{Valid kind?}
B -->|no| C[400 validation error]
B -->|yes| D[Deactivate existing same-scope delegation]
D --> E[Create new active delegation]
E --> F[/delegations/me shows delegation]
F --> G[Disable delegation]
G --> H[Set is_active=false]
H --> I[Delegation hidden from /me]
E --> J[Create another delegation same kind]
J --> K[Previous becomes inactive]
K --> L[Only one active remains]
10. Serializer validation tests¶
These tests focus on request validation rules in serializers.
Covered behavior¶
- invalid
Approval.kindis rejected - invalid delegation kind is rejected
- rejection without reason returns
400
Example scenarios¶
- invalid approval kind returns field error on
kind - invalid delegation kind returns field error on
kind - reject decision without reason returns
400
Why this matters¶
These tests ensure API contracts remain stable and explicit.
11. Decision audit payload tests¶
These tests verify that delegated decisions record compact audit metadata inside approval payload.
Covered behavior¶
When a delegate decides on behalf of another assignee:
approval.payload["decision_audit"]is written- includes:
- acting user id and label
- delegated-from user id and label
- delegation id
- approval kind
Example scenario¶
- manager is assignee
- manager delegates to another user
- delegate approves
- approval payload contains decision audit block
Why this matters¶
This provides traceability for delegated approval actions.
12. Notification integration tests¶
These tests verify that approval workflows emit the expected notification events.
Covered behavior¶
- creating or submitting timesheet approval sends:
-
timesheets.approval.assigned -
approving sends:
-
timesheet.approved -
rejecting sends:
-
timesheet.declined -
declined notification includes rejection reason
Example scenarios¶
- timesheet submission creates pending approval and emits assigned notification to manager
- manager approves and engineer receives approved notification
- manager rejects and engineer receives declined notification with reason
Mermaid flow¶
flowchart TD
A[Timesheet submitted] --> B[Approval created]
B --> C[Assign approvers]
C --> D[Notify: timesheets.approval.assigned]
E[Approval decision] --> F{Decision}
F -->|approve| G[Notify: timesheet.approved]
F -->|reject| H[Notify: timesheet.declined with reason]
```mermaid
flowchart TD
A[Timesheet submitted] --> B[Pending Approval created]
B --> C[Assignee rows created]
C --> D[Notification: timesheets.approval.assigned]
E[Approval decided] --> F{Decision}
F -->|approve| G[Notification: timesheet.approved]
F -->|reject| H[Notification: timesheet.declined]
Test structure¶
The current tests naturally fall into these logical groups:
test_models.pytest_assignees.pytest_inbox.pytest_delegations.pytest_forwarding.pytest_decisions.pytest_notifications.pytest_serializers.py
Even if the current files are still mixed, this is the best target structure over time.
Test helpers and fixtures¶
The suite relies heavily on helpers and shared fixtures such as:
client_for(...)client_for_non_member(...)make_pending_timesheet_approval(...)make_week_submittable(...)ensure_org_membership(...)bind_client_to_org(...)
These helpers keep tests concise and make org-scoped API behavior easier to verify.
Important fixture behavior¶
Many tests explicitly ensure org membership before use.
This is important because:
- permissions are org-scoped
- inbox visibility depends on active membership
- delegation validity depends on active membership
- approval actions depend on active membership and assignment
What the tests are protecting¶
The approvals tests protect several critical system guarantees:
Approval correctness¶
- no duplicate active pending approvals
- decision state is valid
- side effects update target object correctly
Permission correctness¶
- only allowed users may view or decide approvals
- delegations are enforced correctly
- expired or mismatched delegations do not leak access
Workflow correctness¶
- forwarding replaces previous forwarded user
- unforward removes forwarded assignee
- decision only happens once
Integration correctness¶
- notifications fire with the right event keys
- timesheets stay consistent with approval state
- serializer rules reject invalid API input
Design principles of the test suite¶
Domain-first testing¶
Tests are organized around business behavior, not just endpoints.
Permission-heavy coverage¶
Because approvals are security-sensitive, permission and delegation behavior gets strong emphasis.
Idempotency checks¶
Repeated workflow calls should not create duplicate state.
Integration-aware coverage¶
The suite deliberately checks interactions with:
- timesheets
- org memberships
- notifications
Realistic API behavior¶
Many tests use authenticated API clients with org headers, which makes them closer to real application flows.
Best practices¶
- keep tests focused on behavior, not internal implementation details
- prefer explicit assignee setup when a test should not depend on manager resolution internals
- test both direct and delegated access paths
- test expired/inactive delegation explicitly
- test notification keys when approval workflows emit events
- test idempotency for create and refresh flows
- isolate serializer validation tests from service workflow tests where possible
Future test extensions¶
Possible future additions include:
- separate tests for
approvals.permissions - service-level tests for:
decide_approvalforward_approvalunforward_approvalcreate_delegation-
disable_delegation -
tests for admin override behavior
- tests for retrieve permission vs decide permission if they diverge
- tests for multiple approval kinds beyond timesheets
- tests for approval history if added later
- tests for bulk approval or escalation logic if introduced
Summary¶
The approvals test suite currently verifies the most important behavior of the app:
- approval constraints
- assignee refresh logic
- inbox visibility
- decision permissions
- side effects on timesheets
- delegation lifecycle
- forwarding and unforwarding
- serializer validation
- notification emission
This gives the approvals app strong protection around its most important concerns:
- correctness
- authorization
- workflow consistency
- integration safety