Skip to content

Status — read endpoint with full state machine #488

@adityapat24

Description

@adityapat24

Goal

GET /api/v1/status returns the correct dashboard branch for any user state. The state machine is fully implemented and exhaustively tested.

Tasks

Define the state machine inputs and outputs

  • Confirm the type definitions in src/lib/status/types.ts: DashboardBranch is the union 'pre-registration' | 'in-progress' | 'submitted' | 'admitted' | 'waitlisted' | 'declined'. ApplicantStatus is 'not-started' | 'in-progress' | 'submitted'. DecisionStatus is 'undecided' | 'admitted' | 'waitlisted' | 'declined'.
    • What this accomplishes: Locks the type vocabulary so the machine and its callers agree on every state name.
  • Define the input type to the machine: { user: { applicationStatus, decisionStatus, rsvpStatus }, dates: { registrationOpen, registrationClosed, confirmBy }, showDecision: boolean, now: Date }.
    • What this accomplishes: Makes the machine a pure function — given these inputs it always returns the same output. Trivially testable.

Implement src/lib/status/machine.ts

  • Write decideBranch(input): returns the correct DashboardBranch.
    • What this accomplishes: The core logic of the dashboard lives in one pure function, not scattered across the page.
  • Branch logic in priority order: if now < registrationOpen, return 'pre-registration'. Else if applicationStatus !== 'submitted', return 'in-progress'. Else if showDecision === false OR decisionStatus === 'undecided', return 'submitted'. Else return decisionStatus ('admitted' | 'waitlisted' | 'declined').
    • What this accomplishes: Each branch covers exactly the case it should. Pre-registration takes precedence over everything else; submission state takes precedence over decision state; admins can hide decisions even after they're made.
  • Handle edge cases explicitly: what if applicationStatus === 'not-started' and now > registrationOpen? Return 'in-progress' (the dashboard shows "continue your application" which works fine for not-yet-started users too). What if decisionStatus === 'admitted' but rsvpStatus === 'declined'? Still return 'admitted' (the dashboard view itself handles the RSVP state).
    • What this accomplishes: Removes ambiguity for the unusual but possible combinations.

Implement getApplicantStatus(userId) in src/lib/status/service.ts

  • Fetch the user document from applicant_data using the Mongo client.
    • What this accomplishes: Pulls everything the machine needs about the user.
  • Read the three date singletons from the existing src/lib/status/mock-singletons.ts mock file. Add a comment // TODO: replace with getSingleton() calls once Ticket 5 lands real singleton service.
    • What this accomplishes: Lets you ship today without depending on the singleton service ticket. The swap is mechanical.
  • Read showDecision the same way.
  • Construct the machine input from the user + singletons, call decideBranch, return { branch, user, dates, showDecision }.
    • What this accomplishes: The response contains everything the dashboard page needs to render any view, not just the branch name. Avoids a second fetch.

Wire the API endpoint

  • Update src/app/api/v1/status/route.ts. Implement GET: read userId from query string (placeholder until auth ships), call getApplicantStatus, return JSON.
    • What this accomplishes: The endpoint the dashboard page already fetches now returns real branching logic.
  • Add a comment // TODO: gate with requireUser() once Ticket 1 ships its helpers.

Exhaustive unit tests

  • Write a test file src/lib/status/machine.test.ts.
  • Test pre-registration: now < registrationOpen always returns 'pre-registration', regardless of other fields.
  • Test in-progress: applicationStatus === 'in-progress' and now >= registrationOpen returns 'in-progress'.
  • Test not-started after open: applicationStatus === 'not-started' and now >= registrationOpen returns 'in-progress'.
  • Test submitted with decisions hidden: applicationStatus === 'submitted', decisionStatus === 'admitted', showDecision === false returns 'submitted'.
  • Test submitted with decision undecided: applicationStatus === 'submitted', decisionStatus === 'undecided', showDecision === true returns 'submitted'.
  • Test admitted: applicationStatus === 'submitted', decisionStatus === 'admitted', showDecision === true returns 'admitted'.
  • Test waitlisted: same with 'waitlisted'.
  • Test declined: same with 'declined'.
    • What this accomplishes: Every meaningful combination has a test. Future changes to the machine can't silently break a branch.

Integration test

  • Seed a Mongo test DB with three users: one not-started, one submitted+undecided, one submitted+admitted. Hit the endpoint for each and verify the returned branch.
    • What this accomplishes: Proves the database → service → API path actually works against real Mongo.

Definition of done

  • GET /api/v1/status?userId=X returns the right branch for X based on Mongo state.
  • All eight branch-logic unit tests pass.
  • Integration test against seeded data passes.
  • The dashboard page rendering against this endpoint shows different views as you change the user's database state.

Metadata

Metadata

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions