Skip to content

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_by
  • decided_at

Example scenarios

  • creating a second pending approval for the same timesheet raises IntegrityError
  • calling full_clean() on an approved approval without decision metadata raises ValidationError

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 kind returns 400
  • 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]


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_by
  • declined_at
  • decline_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]

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]

8. Delegation model tests

These tests validate the delegation model invariants.

Covered behavior

  • self-delegation is invalid
  • ends_at must be after starts_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/me returns:

  • 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.kind is 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.py
  • test_assignees.py
  • test_inbox.py
  • test_delegations.py
  • test_forwarding.py
  • test_decisions.py
  • test_notifications.py
  • test_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_approval
  • forward_approval
  • unforward_approval
  • create_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