Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/integration/scope_review_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/integration/scope_review_test.go |
| Original SHA-256 | 22b5f4d6b8c1225b4730948cad845a760bc7be60fa95269070762b0da604cb2a |
| Git blob SHA | e3947aeefd00e4935d5efd839e8572fb139ab317 |
| Reading-copy SHA-256 | 22b5f4d6b8c1225b4730948cad845a760bc7be60fa95269070762b0da604cb2a |
Recovered source transcript
Download complete source text ↓311 lines · 13.0 KB
//go:build integration
package integration
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/missionworkforce/missionmatch/internal/modules/opportunities"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
)
// SPLIT, MERGE AND DRAFT GENERATION AGAINST A REAL DATABASE.
//
// THESE EXIST BECAUSE OF FINDING F3, and repeating F3 would be inexcusable. The document upload path was
// committed, reviewed, and reported as built while having never once written a row, because every test it had
// was pure and none of them touched Postgres. The column list was therefore never executed, and a timestamptz
// scanned into a Go string only failed when a real database refused it.
//
// Split, merge and generation are new code with pure validation tests. Pure tests cannot see a column that
// does not exist, a constraint that refuses the write, or a citation that fails to carry. So they run here too.
func oppSvc() *opportunities.Service { return opportunities.NewService(appDB, audit.New(appDB)) }
// seedScopeFixture builds one opportunity with one cited extracted scope item, through the migrator, and
// returns a recruiter context that can review it.
func seedScopeFixture(t *testing.T, scopeType, title string) (org, opp, item uuid.UUID, ctx context.Context) {
t.Helper()
bg := context.Background()
org = seedOrg(t)
opp, item = uuid.New(), uuid.New()
notice := "SR-" + opp.String()[:8]
if _, err := migrator.Exec(bg, `
INSERT INTO sam_opportunities (notice_id, title, department, naics_code, raw, source)
VALUES ($1, 'Enterprise Operations Support', 'DEPT OF THE AIR FORCE', '541512', '{}'::jsonb, 'test')
ON CONFLICT (notice_id) DO NOTHING`, notice); err != nil {
t.Fatalf("seed cache: %v", err)
}
if _, err := migrator.Exec(bg, `
INSERT INTO tracked_opportunities (organization_id, id, sam_notice_id, source_type, title, status)
VALUES ($1,$2,$3,'sam_gov','Enterprise Operations Support','qualifying')`, org, opp, notice); err != nil {
t.Fatalf("seed tracked: %v", err)
}
// A CITED extracted item. source_quote_hash is what ck_scope_items_cited accepts for a machine item, and
// it is the value every split child must inherit.
if _, err := migrator.Exec(bg, `
INSERT INTO opportunity_scope_items (organization_id, id, opportunity_id, scope_type, title,
description, source_section_label, source_quote_hash,
extraction_method, extraction_confidence, review_status)
VALUES ($1,$2,$3,$4,$5,'The contractor shall provide four Cyber Systems Operations specialists holding TS/SCI with a CI polygraph, minimum of 5 years experience.','C.3.1','quote-hash-abc','numbered_clause',1.0,'extracted')`,
org, item, opp, scopeType, title); err != nil {
t.Fatalf("seed scope item: %v", err)
}
_, ctx = seedRoleUser(t, org, "Rita Reviewer", "recruiter")
t.Cleanup(func() {
c := context.Background()
_, _ = migrator.Exec(c, `DELETE FROM requirement_drafts WHERE organization_id=$1`, org)
_, _ = migrator.Exec(c, `DELETE FROM opportunity_scope_items WHERE organization_id=$1`, org)
_, _ = migrator.Exec(c, `DELETE FROM tracked_opportunities WHERE organization_id=$1`, org)
_, _ = migrator.Exec(c, `DELETE FROM sam_opportunities WHERE notice_id=$1`, notice)
})
return org, opp, item, ctx
}
func TestSplitWritesChildrenThatInheritTheCitation(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
_, _, item, ctx := seedScopeFixture(t, "task", "Monitoring and reporting")
svc := oppSvc()
children, err := svc.SplitScopeItem(ctx, item.String(), []opportunities.SplitPart{
{Title: "Continuous monitoring of the enclave"},
{Title: "Incident response reporting", ScopeType: "reporting"},
})
if err != nil {
t.Fatalf("split: %v", err)
}
if len(children) != 2 {
t.Fatalf("children = %d, want 2", len(children))
}
for _, c := range children {
// THE CITATION IS THE WHOLE POINT. A child with no source is a machine item with no provenance, and
// ck_scope_items_cited would have refused it, so this asserts the design rather than the constraint.
if c.SourceQuoteHash != "quote-hash-abc" {
t.Errorf("child %q lost the citation, quote hash = %q", c.Title, c.SourceQuoteHash)
}
if c.SourceSectionLabel != "C.3.1" {
t.Errorf("child %q lost the section label, got %q", c.Title, c.SourceSectionLabel)
}
if c.ReviewStatus != "edited" {
t.Errorf("child %q status = %q, want edited", c.Title, c.ReviewStatus)
}
}
// Scope type inheritance: the first child inherits, the second overrides.
if children[0].ScopeType != "task" {
t.Errorf("first child scope_type = %q, want the inherited task", children[0].ScopeType)
}
if children[1].ScopeType != "reporting" {
t.Errorf("second child scope_type = %q, want the override reporting", children[1].ScopeType)
}
// THE PARENT IS RETIRED, NOT DELETED. Migration 0084 added 'split' for exactly this.
var status string
if err := migrator.QueryRow(context.Background(),
`SELECT review_status FROM opportunity_scope_items WHERE id=$1`, item).Scan(&status); err != nil {
t.Fatalf("read parent: %v", err)
}
if status != "split" {
t.Errorf("parent status = %q, want split", status)
}
t.Logf("split wrote %d children, each carrying the parent's citation, parent retired to %q", len(children), status)
}
func TestSplitRefusesAnAlreadyRetiredItem(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
_, _, item, ctx := seedScopeFixture(t, "task", "Already gone")
svc := oppSvc()
if _, err := svc.SplitScopeItem(ctx, item.String(), []opportunities.SplitPart{{Title: "a"}, {Title: "b"}}); err != nil {
t.Fatalf("first split: %v", err)
}
// A second split would orphan the first set of children from the queue.
if _, err := svc.SplitScopeItem(ctx, item.String(), []opportunities.SplitPart{{Title: "c"}, {Title: "d"}}); err == nil {
t.Fatal("splitting an already split item succeeded, which would orphan the first children")
}
}
func TestMergeRetiresTheAbsorbedAndKeepsTheSurvivorsCitation(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
org, opp, primary, ctx := seedScopeFixture(t, "task", "Primary task")
svc := oppSvc()
// A second cited item to absorb.
other := uuid.New()
if _, err := migrator.Exec(context.Background(), `
INSERT INTO opportunity_scope_items (organization_id, id, opportunity_id, scope_type, title,
source_section_label, source_quote_hash, extraction_method,
extraction_confidence, review_status)
VALUES ($1,$2,$3,'task','Secondary task','C.3.2','quote-hash-def','numbered_clause',1.0,'extracted')`,
org, other, opp); err != nil {
t.Fatalf("seed second item: %v", err)
}
survivor, err := svc.MergeScopeItems(ctx, primary.String(), []string{other.String()},
"Monitoring and reporting combined", "", "")
if err != nil {
t.Fatalf("merge: %v", err)
}
if survivor.Title != "Monitoring and reporting combined" {
t.Errorf("survivor title = %q", survivor.Title)
}
if survivor.SourceQuoteHash != "quote-hash-abc" {
t.Errorf("survivor lost its own citation, quote hash = %q", survivor.SourceQuoteHash)
}
if survivor.ReviewStatus != "edited" {
t.Errorf("survivor status = %q, want edited", survivor.ReviewStatus)
}
var absorbedStatus, parentRef string
if err := migrator.QueryRow(context.Background(),
`SELECT review_status, COALESCE(parent_scope_item_id::text,'') FROM opportunity_scope_items WHERE id=$1`,
other).Scan(&absorbedStatus, &parentRef); err != nil {
t.Fatalf("read absorbed: %v", err)
}
if absorbedStatus != "merged" {
t.Errorf("absorbed status = %q, want merged", absorbedStatus)
}
if parentRef != primary.String() {
t.Errorf("absorbed parent ref = %q, want the survivor %q", parentRef, primary)
}
t.Logf("merge kept the survivor's citation and retired the absorbed item to %q pointing at the survivor", absorbedStatus)
}
func TestMergeRefusesAnItemThatDoesNotExist(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
_, _, primary, ctx := seedScopeFixture(t, "task", "Primary")
svc := oppSvc()
// A random uuid is not in this tenant, so RLS hides it and the count check must catch the shortfall rather
// than silently merging nothing.
if _, err := svc.MergeScopeItems(ctx, primary.String(), []string{uuid.New().String()}, "Combined", "", ""); err == nil {
t.Fatal("merging a nonexistent item succeeded, so a caller could retire the survivor for nothing")
}
}
func TestGenerateDraftsDerivesFromAcceptedScopeAndIsIdempotent(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
_, opp, item, ctx := seedScopeFixture(t, "labor_category", "Cyber Systems Operations Specialist")
svc := oppSvc()
// EXTRACTED SCOPE MUST YIELD NOTHING. Generating from unreviewed output would move the machine's own text
// one step closer to the engine with no human in between, which is the boundary this phase is built on.
created, _, err := svc.GenerateDrafts(ctx, opp.String())
if err != nil {
t.Fatalf("generate on extracted scope: %v", err)
}
if created != 0 {
t.Fatalf("generated %d draft(s) from UNREVIEWED scope, which bypasses the human gate", created)
}
// Accept it, then generate.
if _, err := svc.ReviewScopeItem(ctx, item.String(), opportunities.ScopeReview{ReviewStatus: "accepted"}); err != nil {
t.Fatalf("accept: %v", err)
}
created, skipped, err := svc.GenerateDrafts(ctx, opp.String())
if err != nil {
t.Fatalf("generate: %v", err)
}
if created != 1 || skipped != 0 {
t.Fatalf("created=%d skipped=%d, want 1 and 0", created, skipped)
}
// THE DERIVED VALUES came from the item's own text, deterministically.
var laborCat, clearance, poly, method, srcItem string
var qty, years int
if err := migrator.QueryRow(context.Background(), `
SELECT labor_category, clearance_level, polygraph, derivation_method,
COALESCE(source_scope_item_id::text,''), quantity, minimum_years_experience
FROM requirement_drafts WHERE opportunity_id=$1`, opp).
Scan(&laborCat, &clearance, &poly, &method, &srcItem, &qty, &years); err != nil {
t.Fatalf("read draft: %v", err)
}
if laborCat != "Cyber Systems Operations Specialist" {
t.Errorf("labor category = %q", laborCat)
}
if clearance != "ts_sci" {
t.Errorf("clearance = %q, want ts_sci read from the scope text", clearance)
}
if poly != "ci" {
t.Errorf("polygraph = %q, want ci", poly)
}
if qty != 4 {
t.Errorf("quantity = %d, want 4", qty)
}
if years != 5 {
t.Errorf("minimum years = %d, want 5", years)
}
// LINEAGE. This column is what the amendment view follows to say which approved requirement is affected.
if srcItem != item.String() {
t.Errorf("lineage lost: source_scope_item_id = %q, want %q", srcItem, item)
}
if method != "deterministic_scope" {
t.Errorf("derivation method = %q", method)
}
// IDEMPOTENCE. A reviewer presses generate after every batch, so a second run must add nothing.
created2, skipped2, err := svc.GenerateDrafts(ctx, opp.String())
if err != nil {
t.Fatalf("second generate: %v", err)
}
if created2 != 0 || skipped2 != 1 {
t.Fatalf("second run created=%d skipped=%d, want 0 and 1", created2, skipped2)
}
t.Logf("generated one draft with clearance %s, %d needed, %d years, lineage intact, second run idempotent",
clearance, qty, years)
}
func TestPatchDraftClosesTheGapItReports(t *testing.T) {
if appDB == nil {
t.Skip("integration pools not configured")
}
_, opp, item, ctx := seedScopeFixture(t, "labor_category", "Program Analyst")
svc := oppSvc()
// Overwrite the description so nothing states a clearance, which is the gap case screen 5 exists for.
if _, err := migrator.Exec(context.Background(),
`UPDATE opportunity_scope_items SET description='Analytical support to the program office.' WHERE id=$1`,
item); err != nil {
t.Fatalf("blank the description: %v", err)
}
if _, err := svc.ReviewScopeItem(ctx, item.String(), opportunities.ScopeReview{ReviewStatus: "accepted"}); err != nil {
t.Fatalf("accept: %v", err)
}
if _, _, err := svc.GenerateDrafts(ctx, opp.String()); err != nil {
t.Fatalf("generate: %v", err)
}
var draftID, clearance string
if err := migrator.QueryRow(context.Background(),
`SELECT id::text, clearance_level FROM requirement_drafts WHERE opportunity_id=$1`, opp).
Scan(&draftID, &clearance); err != nil {
t.Fatalf("read draft: %v", err)
}
// The document said nothing, so the draft must carry unknown rather than a guess.
if clearance != "unknown" {
t.Fatalf("clearance = %q, want unknown when the text never states one", clearance)
}
secret := "secret"
out, err := svc.PatchDraft(ctx, draftID, opportunities.DraftPatch{ClearanceLevel: &secret})
if err != nil {
t.Fatalf("patch: %v", err)
}
if got, _ := out["clearance_level"].(string); got != "secret" {
t.Errorf("after the patch clearance = %q, want secret", got)
}
missing, _ := out["missing_required_fields"].([]string)
if len(missing) != 0 {
t.Errorf("after supplying the clearance, still missing %v", missing)
}
t.Logf("the gap was reported as unknown, a human closed it, and the draft now reports %v missing", missing)
}