Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/integration/requisition_integration_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/integration/requisition_integration_test.go |
| Original SHA-256 | fa6330d7ba21af860565b5b6c63aec220bd33dcb7a9fa62c2656055e44fca96c |
| Git blob SHA | 1aae101dbe171f60f4e33593c980dc72810b6980 |
| Reading-copy SHA-256 | fa6330d7ba21af860565b5b6c63aec220bd33dcb7a9fa62c2656055e44fca96c |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/integration/requisition_integration_test.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/integration/requisition_integration_test.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓376 lines · 14.2 KB
//go:build integration
package integration
import (
"context"
"errors"
"testing"
"github.com/google/uuid"
"github.com/missionworkforce/missionmatch/internal/modules/recruiting"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// seedRecruiter creates a real user so ownership and audit actors resolve.
func seedRecruiter(t *testing.T, org uuid.UUID, name string) (uuid.UUID, context.Context) {
t.Helper()
var id uuid.UUID
if err := migrator.QueryRow(context.Background(),
`INSERT INTO users (organization_id, email, full_name, role, status)
VALUES ($1, $2, $3, 'recruiter', 'active') RETURNING id`,
org, name+"-"+uuid.New().String()[:8]+"@example.com", name).Scan(&id); err != nil {
t.Fatalf("seed recruiter: %v", err)
}
ctx := tenant.WithUser(tenant.WithOrg(context.Background(), org.String()),
tenant.Principal{UserID: id.String(), Role: "recruiter"})
return id, ctx
}
func reqSvc() *recruiting.Service { return recruiting.NewService(appDB, audit.New(appDB)) }
// TestRequisitionLifecycleMovesEveryClock walks a job order through the full lifecycle against a real
// database and checks the clocks after each step. The pure unit tests prove the arithmetic; this proves
// the SQL performs the same arithmetic, which is the half a pure test cannot reach.
func TestRequisitionLifecycleMovesEveryClock(t *testing.T) {
org := seedOrg(t)
_, ctx := seedRecruiter(t, org, "Lifecycle Recruiter")
svc := reqSvc()
client, err := svc.CreateClient(ctx, recruiting.CreateClientInput{Name: "Lifecycle Client"})
if err != nil {
t.Fatalf("create client: %v", err)
}
contact, err := svc.CreateContact(ctx, uuid.MustParse(client.ID),
recruiting.CreateContactInput{FullName: "Hiring Mgr", IsHiringManager: true})
if err != nil {
t.Fatalf("create contact: %v", err)
}
r, err := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{
ClientID: client.ID, HiringManagerID: contact.ID,
Title: "Cleared Systems Engineer", Openings: 2, Priority: "high",
BillRateMin: "145.5000", BillRateMax: "165.0000",
})
if err != nil {
t.Fatalf("create requisition: %v", err)
}
if r.Status != "open" {
t.Errorf("new requisition status = %q, want open", r.Status)
}
if r.ClientName != "Lifecycle Client" || r.HiringManagerName != "Hiring Mgr" {
t.Errorf("create did not resolve names: client=%q manager=%q", r.ClientName, r.HiringManagerName)
}
// Money must survive the round trip exactly. A float would not represent 145.5 and 165.0 as the same
// values that went in.
if r.BillRateMin == nil || r.BillRateMin.String() != "145.5" {
t.Errorf("bill_rate_min came back as %v, want 145.5", r.BillRateMin)
}
if r.OwnerName != "Lifecycle Recruiter" {
t.Errorf("the creating recruiter did not take ownership, owner = %q", r.OwnerName)
}
id := uuid.MustParse(r.ID)
// open to on_hold. The hold bracket opens and the order stops accruing workable age.
r, err = svc.ChangeStatus(ctx, id, recruiting.ChangeStatusInput{
ExpectedUpdatedAt: r.UpdatedAt, Status: "on_hold", Note: "client froze the program"})
if err != nil {
t.Fatalf("open to on_hold: %v", err)
}
if r.Status != "on_hold" || r.OnHoldSince == "" {
t.Fatalf("on_hold did not open a hold bracket: status=%q since=%q", r.Status, r.OnHoldSince)
}
if r.ClosedAt != "" {
t.Error("a held order must not carry a closed_at")
}
// on_hold to filled. The hold must be SETTLED even though the destination is terminal, or the bracket
// stays open forever and the accrued hold grows without bound against a dead row.
r, err = svc.ChangeStatus(ctx, id, recruiting.ChangeStatusInput{
ExpectedUpdatedAt: r.UpdatedAt, Status: "filled", Note: "both seats placed"})
if err != nil {
t.Fatalf("on_hold to filled: %v", err)
}
if r.OnHoldSince != "" {
t.Error("closing while on hold left the hold bracket open, so hold time will grow forever")
}
if r.ClosedAt == "" {
t.Error("a filled order must record closed_at")
}
if r.WorkingDays > r.AgeDays {
t.Errorf("working days %v exceeds age %v, hold time was credited twice", r.WorkingDays, r.AgeDays)
}
if r.WorkingDays < 0 || r.AgeDays < 0 || r.HeldDays < 0 {
t.Errorf("a negative duration is never a real measurement: age=%v working=%v held=%v",
r.AgeDays, r.WorkingDays, r.HeldDays)
}
// filled to open. Reopening must clear closed_at and must NOT reset opened_at, or the history of the
// role is lost and time to fill restarts from zero.
openedBefore := r.OpenedAt
r, err = svc.ChangeStatus(ctx, id, recruiting.ChangeStatusInput{
ExpectedUpdatedAt: r.UpdatedAt, Status: "open", Note: "candidate fell off"})
if err != nil {
t.Fatalf("filled to open: %v", err)
}
if r.ClosedAt != "" {
t.Error("reopening did not clear closed_at")
}
if r.OpenedAt != openedBefore {
t.Errorf("reopening moved opened_at from %s to %s, losing the age of the role", openedBefore, r.OpenedAt)
}
// The history must record every one of those transitions, in order, with the first link having no
// predecessor.
d, err := svc.GetRequisition(ctx, id)
if err != nil {
t.Fatalf("get requisition: %v", err)
}
if len(d.Events) != 4 {
t.Fatalf("history has %d events, want 4 (opened, on_hold, filled, open)", len(d.Events))
}
if d.Events[0].FromStatus != "" {
t.Errorf("the first event has a predecessor %q, want none", d.Events[0].FromStatus)
}
want := []string{"open", "on_hold", "filled", "open"}
for i, w := range want {
if d.Events[i].ToStatus != w {
t.Errorf("event %d moved to %q, want %q", i, d.Events[i].ToStatus, w)
}
if d.Events[i].DaysInFrom < 0 {
t.Errorf("event %d spent negative time in its previous state", i)
}
}
if d.Events[1].ActorUserName != "Lifecycle Recruiter" {
t.Errorf("event actor name = %q, want the recruiter's name stored at write time",
d.Events[1].ActorUserName)
}
if d.Events[1].Note != "client froze the program" {
t.Errorf("the note was not recorded: %q", d.Events[1].Note)
}
}
// TestIllegalTransitionIsRefusedByTheService. The graph is proved exhaustively by unit tests; this
// confirms the service consults it rather than writing whatever it was handed.
func TestIllegalTransitionIsRefusedByTheService(t *testing.T) {
org := seedOrg(t)
_, ctx := seedRecruiter(t, org, "Graph Recruiter")
svc := reqSvc()
r, err := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{Title: "Draft Role", Status: "draft"})
if err != nil {
t.Fatalf("create draft: %v", err)
}
id := uuid.MustParse(r.ID)
for _, to := range []string{"on_hold", "filled", "closed"} {
if _, err := svc.ChangeStatus(ctx, id, recruiting.ChangeStatusInput{
ExpectedUpdatedAt: r.UpdatedAt, Status: to}); err == nil {
t.Errorf("a draft was allowed to move to %s", to)
}
}
// And a status that does not exist at all.
if _, err := svc.ChangeStatus(ctx, id, recruiting.ChangeStatusInput{
ExpectedUpdatedAt: r.UpdatedAt, Status: "archived"}); err == nil {
t.Error("an unknown status was accepted")
}
}
// TestRequisitionOwnershipIsTheLock. Two recruiters must not both be editing one job order, and the
// refusal must name who holds it so the answer is a conversation rather than a retry.
func TestRequisitionOwnershipIsTheLock(t *testing.T) {
org := seedOrg(t)
_, alice := seedRecruiter(t, org, "Alice Holder")
_, bob := seedRecruiter(t, org, "Bob Other")
svc := reqSvc()
r, err := svc.CreateRequisition(alice, recruiting.CreateRequisitionInput{Title: "Contested Role"})
if err != nil {
t.Fatalf("create: %v", err)
}
id := uuid.MustParse(r.ID)
title := "Bob's Edit"
_, err = svc.UpdateRequisition(bob, id, recruiting.UpdateRequisitionInput{
ExpectedUpdatedAt: r.UpdatedAt, Title: &title})
var notOwner *recruiting.ErrNotOwner
if !errors.As(err, ¬Owner) {
t.Fatalf("a second recruiter was allowed to edit, got %v", err)
}
if notOwner.OwnerName != "Alice Holder" {
t.Errorf("the refusal names %q, want Alice Holder", notOwner.OwnerName)
}
if notOwner.Entity != "job order" {
t.Errorf("the refusal calls this a %q, want job order", notOwner.Entity)
}
// Released, it becomes claimable and Bob's edit takes it.
if _, err := svc.ReleaseRequisition(alice, id); err != nil {
t.Fatalf("release: %v", err)
}
got, err := svc.ClaimRequisition(bob, id)
if err != nil {
t.Fatalf("claim after release: %v", err)
}
if got.OwnerName != "Bob Other" {
t.Errorf("after claiming, owner = %q, want Bob Other", got.OwnerName)
}
}
// TestCrossClientHiringManagerIsRefused. A submittal addressed to the wrong company is the failure this
// prevents, and the database is the authority because only it can check without a race.
func TestCrossClientHiringManagerIsRefused(t *testing.T) {
org := seedOrg(t)
_, ctx := seedRecruiter(t, org, "Manager Recruiter")
svc := reqSvc()
a, _ := svc.CreateClient(ctx, recruiting.CreateClientInput{Name: "Client A"})
b, _ := svc.CreateClient(ctx, recruiting.CreateClientInput{Name: "Client B"})
mgrB, err := svc.CreateContact(ctx, uuid.MustParse(b.ID), recruiting.CreateContactInput{FullName: "Mgr At B"})
if err != nil {
t.Fatalf("create contact: %v", err)
}
if _, err := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{
ClientID: a.ID, HiringManagerID: mgrB.ID, Title: "Wrong Manager Role"}); err == nil {
t.Fatal("a hiring manager from a different client was accepted")
}
// And a manager with no client at all is refused by the service before the insert.
if _, err := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{
HiringManagerID: mgrB.ID, Title: "No Client Role"}); err == nil {
t.Fatal("a hiring manager with no client was accepted")
}
}
// TestRequisitionTenantIsolation. The whole product rests on this. One tenant must never see another's
// job orders, and the control proves the query was not simply returning nothing.
func TestRequisitionTenantIsolation(t *testing.T) {
orgA, orgB := seedOrg(t), seedOrg(t)
_, ctxA := seedRecruiter(t, orgA, "Tenant A")
_, ctxB := seedRecruiter(t, orgB, "Tenant B")
svc := reqSvc()
made, err := svc.CreateRequisition(ctxA, recruiting.CreateRequisitionInput{Title: "Tenant A Secret Role"})
if err != nil {
t.Fatalf("create in A: %v", err)
}
listB, err := svc.ListRequisitions(ctxB, "", "", "", "", 100, 0)
if err != nil {
t.Fatalf("list in B: %v", err)
}
for _, r := range listB.Requisitions {
if r.ID == made.ID {
t.Fatal("tenant B can see tenant A's job order")
}
}
if _, err := svc.GetRequisition(ctxB, uuid.MustParse(made.ID)); err == nil {
t.Fatal("tenant B fetched tenant A's job order directly by id")
}
// Control: A still sees its own, so the isolation above is not just an empty result.
listA, err := svc.ListRequisitions(ctxA, "", "", "", "", 100, 0)
if err != nil || listA.Total != 1 {
t.Fatalf("tenant A sees %d of its own orders, want 1 (err=%v)", listA.Total, err)
}
}
// TestOptimisticConcurrencyRefusesAStaleWrite. Ownership stops two people editing; the token stops one
// person editing from two stale tabs.
func TestOptimisticConcurrencyRefusesAStaleWrite(t *testing.T) {
org := seedOrg(t)
_, ctx := seedRecruiter(t, org, "Token Recruiter")
svc := reqSvc()
r, err := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{Title: "Token Role"})
if err != nil {
t.Fatalf("create: %v", err)
}
id := uuid.MustParse(r.ID)
stale := r.UpdatedAt
first := "First Edit"
if _, err := svc.UpdateRequisition(ctx, id, recruiting.UpdateRequisitionInput{
ExpectedUpdatedAt: stale, Title: &first}); err != nil {
t.Fatalf("first edit: %v", err)
}
second := "Second Edit"
_, err = svc.UpdateRequisition(ctx, id, recruiting.UpdateRequisitionInput{
ExpectedUpdatedAt: stale, Title: &second})
if err == nil {
t.Fatal("a write against a stale token was accepted, so the first edit was silently lost")
}
}
// TestOwnerFilterIsAppliedInSQL. Filtering after pagination would show a recruiter part of their desk and
// call it all of it.
func TestOwnerFilterIsAppliedInSQL(t *testing.T) {
org := seedOrg(t)
_, alice := seedRecruiter(t, org, "Filter Alice")
_, bob := seedRecruiter(t, org, "Filter Bob")
svc := reqSvc()
for i := 0; i < 3; i++ {
if _, err := svc.CreateRequisition(alice, recruiting.CreateRequisitionInput{
Title: "Alice Role " + string(rune('A'+i))}); err != nil {
t.Fatalf("seed: %v", err)
}
}
r, _ := svc.CreateRequisition(bob, recruiting.CreateRequisitionInput{Title: "Bob Role"})
if _, err := svc.ReleaseRequisition(bob, uuid.MustParse(r.ID)); err != nil {
t.Fatalf("release: %v", err)
}
for _, c := range []struct {
filter string
ctx context.Context
want int
}{
{"", alice, 4},
{"me", alice, 3},
{"me", bob, 0},
{"unassigned", alice, 1},
} {
got, err := svc.ListRequisitions(c.ctx, "", "", c.filter, "", 100, 0)
if err != nil {
t.Fatalf("filter %q: %v", c.filter, err)
}
if got.Total != c.want {
t.Errorf("filter %q returned %d, want %d", c.filter, got.Total, c.want)
}
}
// An unrecognised filter must be refused, not ignored. Silently returning everyone's orders when the
// caller asked for one person's looks like an answer.
if _, err := svc.ListRequisitions(alice, "", "", "not-a-filter", "", 100, 0); err == nil {
t.Error("an invalid owner filter was ignored rather than refused")
}
}
// TestActiveStatusFilterIncludesHeldOrders. A recruiter asking for open roles does not mean to exclude
// the paused ones, and silently doing so understates their desk.
func TestActiveStatusFilterIncludesHeldOrders(t *testing.T) {
org := seedOrg(t)
_, ctx := seedRecruiter(t, org, "Active Recruiter")
svc := reqSvc()
open, _ := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{Title: "Still Open"})
held, _ := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{Title: "On Hold"})
if _, err := svc.ChangeStatus(ctx, uuid.MustParse(held.ID), recruiting.ChangeStatusInput{
ExpectedUpdatedAt: held.UpdatedAt, Status: "on_hold"}); err != nil {
t.Fatalf("hold: %v", err)
}
closed, _ := svc.CreateRequisition(ctx, recruiting.CreateRequisitionInput{Title: "Closed"})
if _, err := svc.ChangeStatus(ctx, uuid.MustParse(closed.ID), recruiting.ChangeStatusInput{
ExpectedUpdatedAt: closed.UpdatedAt, Status: "closed"}); err != nil {
t.Fatalf("close: %v", err)
}
_ = open
got, err := svc.ListRequisitions(ctx, "", "active", "", "", 100, 0)
if err != nil {
t.Fatalf("list active: %v", err)
}
if got.Total != 2 {
t.Errorf("active returned %d orders, want 2 (open and on_hold, not closed)", got.Total)
}
}