package core import ( "errors" "strings" "testing" ) func TestParseDocID(t *testing.T) { tests := []struct { name string in string wantPrefix string wantSeq string ok bool }{ {"spec", "SPEC-0007", "SPEC", "0007", true}, {"rfc", "RFC-1", "RFC", "1", true}, {"single letter prefix", "X-42", "X", "42", true}, {"digits in prefix", "C4-0001", "C4", "0001", true}, {"all zeros", "SPEC-0000", "SPEC", "0000", true}, {"max prefix", strings.Repeat("A", MaxDocIDPrefixLen) + "-1", strings.Repeat("A", MaxDocIDPrefixLen), "1", true}, {"max seq", "SPEC-" + strings.Repeat("9", MaxDocIDSeqLen), "SPEC", strings.Repeat("9", MaxDocIDSeqLen), true}, {"empty", "", "", "", false}, {"no separator", "SPEC0007", "", "", false}, {"two separators", "HOME-OPS-0001", "", "", false}, {"double dash", "SPEC--1", "", "", false}, {"empty prefix", "-0007", "", "", false}, {"empty seq", "SPEC-", "", "", false}, {"lowercase prefix", "spec-0007", "", "", false}, {"mixed case prefix", "Spec-0007", "", "", false}, {"digit-leading prefix", "0SPEC-1", "", "", false}, {"underscore in prefix", "SPEC_A-1", "", "", false}, {"dot in prefix", "SPEC.A-1", "", "", false}, {"non-digit seq", "SPEC-007a", "", "", false}, {"signed seq", "SPEC-+1", "", "", false}, {"hex seq", "SPEC-0x7", "", "", false}, {"prefix too long", strings.Repeat("A", MaxDocIDPrefixLen+1) + "-1", "", "", false}, {"seq too long", "SPEC-" + strings.Repeat("9", MaxDocIDSeqLen+1), "", "", false}, {"leading space", " SPEC-0007", "", "", false}, {"trailing space", "SPEC-0007 ", "", "", false}, {"inner space", "SPEC - 0007", "", "", false}, {"newline", "SPEC-0007\n", "", "", false}, // Homograph: Cyrillic С/Р/Е/С look identical to Latin in most fonts. // Accepting them would let two visually identical IDs both register. {"cyrillic homograph", "СПЕС-0007", "", "", false}, {"fullwidth digits", "SPEC-0007", "", "", false}, {"arabic-indic digits", "SPEC-٠٠٠٧", "", "", false}, {"path injection", "SPEC-0007/../x", "", "", false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { id, err := ParseDocID(tc.in) if (err == nil) != tc.ok { t.Fatalf("ParseDocID(%q) = %+v, %v, want ok=%v", tc.in, id, err, tc.ok) } if !tc.ok { if !errors.Is(err, ErrInvalidDocID) { t.Fatalf("error %v is not ErrInvalidDocID", err) } return } if id.Prefix != tc.wantPrefix || id.Seq != tc.wantSeq { t.Fatalf("ParseDocID(%q) = %+v, want {%q %q}", tc.in, id, tc.wantPrefix, tc.wantSeq) } if got := id.String(); got != tc.in { t.Fatalf("String() = %q, want round-trip %q", got, tc.in) } }) } } // Leading zeros are significant: the registry is global and "SPEC-7" is a // different key from "SPEC-0007". Normalizing them together would silently // merge two documents. func TestDocIDLeadingZerosAreSignificant(t *testing.T) { a, err := ParseDocID("SPEC-7") if err != nil { t.Fatal(err) } b, err := ParseDocID("SPEC-0007") if err != nil { t.Fatal(err) } if a == b { t.Fatalf("SPEC-7 and SPEC-0007 must not compare equal, got %+v", a) } if a.String() == b.String() { t.Fatal("SPEC-7 and SPEC-0007 must not render identically") } } func TestValidateDocID(t *testing.T) { if err := ValidateDocID("SPEC-0007"); err != nil { t.Fatalf("ValidateDocID(SPEC-0007) = %v, want nil", err) } if err := ValidateDocID("spec-0007"); !errors.Is(err, ErrInvalidDocID) { t.Fatalf("ValidateDocID(spec-0007) = %v, want ErrInvalidDocID", err) } }