Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/opportunities/scope_splitmerge_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/opportunities/scope_splitmerge_test.go |
| Original SHA-256 | c367ba23703227294b1505347d40b60ded6a4a165c40a7ef5cac1b73cb9746cf |
| Git blob SHA | 6b8199056ae1b31d0cc0a9ab9ca559b5dba3bcc3 |
| Reading-copy SHA-256 | c367ba23703227294b1505347d40b60ded6a4a165c40a7ef5cac1b73cb9746cf |
Recovered source transcript
Download complete source text ↓140 lines · 5.7 KB
package opportunities
import (
"errors"
"strings"
"testing"
)
// PHASE 2 ITEM 4, the two operations that were missing. Screen 4 requires accept, edit and accept, reject,
// SPLIT, MERGE, and add manually. Four existed. These tests cover the pure validation, which is where the
// rules that protect the citation live; the database behaviour is proven live against staging.
//
// THE RULE THAT MATTERS MOST: neither operation may produce an uncited machine item. A split inherits the
// parent's citation for every child, and a merge keeps the primary's. If either invented a fresh item with no
// source, ck_scope_items_cited would refuse it, and that refusal is the schema catching a bug rather than the
// design working. So validation refuses it first, and the constraint stays as the backstop.
func TestValidateSplitRequiresAtLeastTwoParts(t *testing.T) {
cases := map[string][]SplitPart{
"no parts": {},
"one part": {{Title: "Only one"}},
}
for name, parts := range cases {
if err := validateSplit(parts); err == nil {
t.Errorf("%s: split accepted %d part(s). Splitting into one is an edit, not a split, and it "+
"would retire the parent while changing nothing", name, len(parts))
} else if !errors.Is(err, ErrInvalidInput) {
t.Errorf("%s: error = %v, want ErrInvalidInput", name, err)
}
}
}
func TestValidateSplitAcceptsTwoOrMore(t *testing.T) {
parts := []SplitPart{{Title: "Monitoring of the enclave"}, {Title: "Incident response reporting"}}
if err := validateSplit(parts); err != nil {
t.Fatalf("a legitimate two way split was refused: %v", err)
}
}
func TestValidateSplitRefusesAnEmptyTitle(t *testing.T) {
parts := []SplitPart{{Title: "Real half"}, {Title: " "}}
if err := validateSplit(parts); err == nil {
t.Fatal("a part with a blank title was accepted, which would create an unreadable scope row")
}
}
func TestValidateSplitRefusesAnUnknownScopeType(t *testing.T) {
parts := []SplitPart{{Title: "A", ScopeType: "objective"}, {Title: "B", ScopeType: "not_a_type"}}
if err := validateSplit(parts); err == nil {
t.Fatal("an out of vocabulary scope_type was accepted; the column CHECK would then refuse the write")
}
}
func TestValidateSplitAllowsAnEmptyScopeTypeMeaningInherit(t *testing.T) {
// An empty scope type is not missing data, it means keep the parent's classification. Requiring the
// reviewer to restate it on every child would invite them to get it wrong.
parts := []SplitPart{{Title: "A"}, {Title: "B"}}
if err := validateSplit(parts); err != nil {
t.Fatalf("empty scope_type should inherit from the parent, got %v", err)
}
}
func TestValidateSplitRefusesTooManyParts(t *testing.T) {
parts := make([]SplitPart, 51)
for i := range parts {
parts[i] = SplitPart{Title: "part"}
}
if err := validateSplit(parts); err == nil {
t.Fatal("51 parts accepted; an unbounded split is a way to flood the review queue from one item")
}
}
func TestValidateMergeRequiresSomethingToAbsorb(t *testing.T) {
if err := validateMerge("11111111-1111-1111-1111-111111111111", nil, "Combined"); err == nil {
t.Fatal("a merge with nothing to absorb was accepted, which is an edit wearing a merge's name")
}
}
func TestValidateMergeRefusesMergingAnItemIntoItself(t *testing.T) {
id := "11111111-1111-1111-1111-111111111111"
if err := validateMerge(id, []string{id}, "Combined"); err == nil {
t.Fatal("an item was allowed to absorb itself, which would retire the survivor and lose the item")
}
}
func TestValidateMergeRefusesADuplicateInTheAbsorbList(t *testing.T) {
a := "11111111-1111-1111-1111-111111111111"
b := "22222222-2222-2222-2222-222222222222"
if err := validateMerge(a, []string{b, b}, "Combined"); err == nil {
t.Fatal("a duplicated id was accepted; the second write would find the row already retired")
}
}
func TestValidateMergeRefusesANonUUID(t *testing.T) {
a := "11111111-1111-1111-1111-111111111111"
if err := validateMerge(a, []string{"not-a-uuid"}, "Combined"); err == nil {
t.Fatal("a non uuid was accepted into the absorb list")
}
}
func TestValidateMergeRequiresATitle(t *testing.T) {
a := "11111111-1111-1111-1111-111111111111"
b := "22222222-2222-2222-2222-222222222222"
if err := validateMerge(a, []string{b}, " "); err == nil {
t.Fatal("a merge with a blank title was accepted; the combined item must say what it now covers")
}
}
func TestValidateMergeAcceptsALegitimateMerge(t *testing.T) {
a := "11111111-1111-1111-1111-111111111111"
b := "22222222-2222-2222-2222-222222222222"
c := "33333333-3333-3333-3333-333333333333"
if err := validateMerge(a, []string{b, c}, "Continuous monitoring and reporting"); err != nil {
t.Fatalf("a legitimate three way merge was refused: %v", err)
}
}
// The status vocabulary must carry split, and migration 0084 adds it. A test on the map is cheap insurance
// against someone reverting the Go side and leaving the column permitting a value the code rejects.
func TestSplitIsAValidReviewStatus(t *testing.T) {
if !validScopeReviewStatuses["split"] {
t.Fatal("split is not an accepted review status in Go, so the API would refuse what the column permits")
}
for _, want := range []string{"extracted", "accepted", "edited", "rejected", "merged", "manual"} {
if !validScopeReviewStatuses[want] {
t.Errorf("status %q was dropped from the vocabulary; extend, never remove", want)
}
}
}
func TestSplitTitlesAreTrimmedNotRejectedForWhitespace(t *testing.T) {
parts := []SplitPart{{Title: " Leading and trailing "}, {Title: "Second"}}
if err := validateSplit(parts); err != nil {
t.Fatalf("surrounding whitespace should be trimmed, not refused: %v", err)
}
if strings.TrimSpace(parts[0].Title) != "Leading and trailing" {
t.Errorf("unexpected trim result %q", strings.TrimSpace(parts[0].Title))
}
}