// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package errscontract import ( "strings" "testing" ) // 4 source-level rules: // (B) typed Error must embed Problem → REJECT // (C) no service-side mergeCodeMeta / registrar → REJECT // (D) Subtype: "ad_hoc_*" literal → LABEL (governance signal) // (E) Subtype value not in declared allowlist → REJECT / LABEL / WARNING func TestCheckProblemEmbed_RejectsMissingProblemEmbed(t *testing.T) { src := `package errs type FrobnicateError struct { Code int Msg string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "FrobnicateError") { t.Errorf("message should name the violating type: %s", v[0].Message) } } func TestCheckProblemEmbed_AcceptsPackageLocalEmbed(t *testing.T) { src := `package errs type Problem struct{} type GoodError struct { Problem Extra string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 0 { t.Errorf("compliant struct should pass, got: %+v", v) } } func TestCheckProblemEmbed_AcceptsImportedEmbed(t *testing.T) { // `errs.Problem` selector form: used by re-export packages. src := `package alias import "github.com/larksuite/cli/errs" type GoodError struct { errs.Problem Extra string } ` v := CheckProblemEmbed("internal/alias/x.go", src) if len(v) != 0 { t.Errorf("imported-embed should pass, got: %+v", v) } } func TestCheckProblemEmbed_RejectsSecurityPolicyErrorWithoutProblem(t *testing.T) { // Production SecurityPolicyError embeds Problem (see errs/types.go); the // previous CheckProblemEmbed whitelist for this type was dead code that would also // mask a future regression where the embed gets dropped. src := `package errs type SecurityPolicyError struct { ChallengeURL string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "SecurityPolicyError") { t.Errorf("message should name the violating type: %s", v[0].Message) } } func TestCheckProblemEmbed_AcceptsSecurityPolicyErrorWithProblem(t *testing.T) { // Mirrors the real errs/types.go declaration — must pass with no violation. src := `package errs type Problem struct{} type SecurityPolicyError struct { Problem ChallengeURL string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 0 { t.Errorf("compliant SecurityPolicyError must pass, got: %+v", v) } } func TestCheckNoRegistrar_RejectsMergeCodeMetaInShortcuts(t *testing.T) { src := `package task func init() { mergeCodeMeta(taskMap, "task") } var taskMap = map[int]any{} ` v := CheckNoRegistrar("shortcuts/task/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "mergeCodeMeta") { t.Errorf("message must name the offending call: %s", v[0].Message) } if !strings.Contains(v[0].Suggestion, "internal/errclass/codemeta_") { t.Errorf("suggestion must point to the right location: %s", v[0].Suggestion) } } func TestCheckNoRegistrar_RejectsRegisterServiceMapInInternal(t *testing.T) { src := `package auth import "github.com/larksuite/cli/internal/output" func init() { output.RegisterServiceMap("auth", nil) } ` v := CheckNoRegistrar("internal/auth/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "RegisterServiceMap") { t.Errorf("message must name the offending call: %s", v[0].Message) } } func TestCheckNoRegistrar_AllowsInternalErrclass(t *testing.T) { // internal/errclass legitimately owns mergeCodeMeta; rule must not fire here. src := `package errclass func init() { mergeCodeMeta(taskCodeMeta, "task") } var taskCodeMeta = map[int]any{} ` v := CheckNoRegistrar("internal/errclass/codemeta_task.go", src) if len(v) != 0 { t.Errorf("internal/errclass must be exempt, got: %+v", v) } } func TestCheckNoRegistrar_IgnoresTestFiles(t *testing.T) { src := `package task_test func TestFoo(t *testing.T) { mergeCodeMeta(nil, "fixture") } ` v := CheckNoRegistrar("shortcuts/task/init_test.go", src) if len(v) != 0 { t.Errorf("test fixtures must be exempt, got: %+v", v) } } func TestCheckNoRegistrar_IgnoresCmdAndRoot(t *testing.T) { src := `package main func init() { mergeCodeMeta(nil, "x") } ` v := CheckNoRegistrar("cmd/foo/main.go", src) if len(v) != 0 { t.Errorf("cmd/ paths are out of CheckNoRegistrar scope, got: %+v", v) } } func TestCheckAdHocSubtype_EmitsLabel(t *testing.T) { src := `package task func makeErr() any { return struct{ Subtype string }{Subtype: "ad_hoc_task_quota_breach"} } ` v := CheckAdHocSubtype("shortcuts/task/quota.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionLabel { t.Errorf("action = %q, want LABEL (ad_hoc_* is soft governance signal)", v[0].Action) } if !strings.Contains(v[0].Message, "needs-taxonomy-decision") { t.Errorf("message should carry the label prefix so CI can grep it: %s", v[0].Message) } if !strings.Contains(v[0].Suggestion, "1 week") { t.Errorf("suggestion should state the ad_hoc_* promotion window: %s", v[0].Suggestion) } } func TestCheckAdHocSubtype_DetectsCastForm(t *testing.T) { // Subtype field assigned via errs.Subtype("ad_hoc_xxx") cast. src := `package task type problem struct{ Subtype any } var _ = problem{Subtype: Subtype("ad_hoc_new_feature")} func Subtype(s string) string { return s } ` v := CheckAdHocSubtype("shortcuts/task/x.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionLabel { t.Errorf("cast form must also LABEL, got %q", v[0].Action) } } func TestCheckDeclaredSubtype(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, "rate_limit": {}, "invalid_parameters": {}, } cases := []struct { name string src string wantAction Action wantInMsg string }{ { name: "named_const_selector_accepted", src: `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeMissingScope} `, wantAction: "", }, { name: "literal_in_allowlist_accepted", src: `package x var _ = struct{ Subtype string }{Subtype: "missing_scope"} `, wantAction: "", }, { name: "undeclared_literal_rejected", src: `package x var _ = struct{ Subtype string }{Subtype: "my_custom_thing"} `, wantAction: ActionReject, wantInMsg: "my_custom_thing", }, { name: "undeclared_via_cast_rejected", src: `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.Subtype("custom_value")} `, wantAction: ActionReject, wantInMsg: "custom_value", }, { name: "ad_hoc_does_not_fire_in_rule_e", src: `package x var _ = struct{ Subtype string }{Subtype: "ad_hoc_thing"} `, // CheckDeclaredSubtype hands ad_hoc_* off to CheckAdHocSubtype — returns no E-class violation. wantAction: "", }, { name: "dynamic_local_var_warns", src: `package x var loc = "x" var _ = struct{ Subtype string }{Subtype: loc} `, wantAction: ActionWarning, wantInMsg: "manual review", }, { name: "dynamic_cast_warns", src: `package x import "github.com/larksuite/cli/errs" func f(raw string) { _ = struct{ Subtype errs.Subtype }{Subtype: errs.Subtype(raw)} } `, wantAction: ActionWarning, wantInMsg: "non-literal", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { v := CheckDeclaredSubtype("x.go", tc.src, allowlist) if tc.wantAction == "" { if len(v) != 0 { t.Fatalf("expected pass, got %d violations: %+v", len(v), v) } return } if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != tc.wantAction { t.Errorf("action = %q, want %q", v[0].Action, tc.wantAction) } if tc.wantInMsg != "" && !strings.Contains(v[0].Message, tc.wantInMsg) { t.Errorf("message %q lacks expected substring %q", v[0].Message, tc.wantInMsg) } }) } } // TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteral pins that codemeta_task.go and // codemeta.go use positional `{cat, subtype, retryable}` literals inside a // `map[int]CodeMeta{...}` — element [1] is the Subtype slot. The AST walker // must recognise the positional form; otherwise an undeclared subtype cast // here would bypass CheckDeclaredSubtype. func TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteral(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var m = map[int]CodeMeta{ 1: {errs.CategoryAPI, errs.Subtype("totally_bogus_undeclared"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "totally_bogus_undeclared") { t.Errorf("message should name the violating subtype: %s", v[0].Message) } } // TestCheckDeclaredSubtype_AcceptsPositionalCodeMetaLiteral: same positional form but the // Subtype literal is in the allowlist — no violation should fire. func TestCheckDeclaredSubtype_AcceptsPositionalCodeMetaLiteral(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var m = map[int]CodeMeta{ 1: {errs.CategoryAuthorization, errs.SubtypeMissingScope, false}, 2: {errs.CategoryAuthorization, errs.Subtype("missing_scope"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 0 { t.Errorf("allowlisted subtypes in positional form must pass, got: %+v", v) } } // TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteralInSlice: covers the slice form // `[]CodeMeta{{cat, subtype, retryable}}` so other call-site shapes are also // guarded. func TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteralInSlice(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var s = []CodeMeta{ {errs.CategoryAPI, errs.Subtype("undeclared_via_slice"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "undeclared_via_slice") { t.Errorf("message should name the violating subtype: %s", v[0].Message) } } // TestCheckDeclaredSubtype_WithNames_RejectsTypoedSelector pins the strengthened CheckDeclaredSubtype: // when a nameset is supplied, selectors like `errs.SubtypeBogus` that satisfy // the "Subtype*" prefix but reference no declared constant must REJECT. The // nil-nameset path preserves the legacy prefix-only acceptance. func TestCheckDeclaredSubtype_WithNames_RejectsTypoedSelector(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} // Typo'd selector — REJECT under strengthened rule. src := `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeBogus} ` v := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nameset) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "SubtypeBogus") { t.Errorf("message should name the offending selector: %s", v[0].Message) } // Same source, nil nameset → legacy prefix-only path, no violation. v2 := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nil) if len(v2) != 0 { t.Errorf("nil nameset must preserve legacy prefix acceptance, got: %+v", v2) } } // TestCheckDeclaredSubtype_WithNames_AcceptsDeclaredSelector: declared selector with nameset // supplied must still pass. func TestCheckDeclaredSubtype_WithNames_AcceptsDeclaredSelector(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} src := `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeMissingScope} ` v := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nameset) if len(v) != 0 { t.Errorf("declared selector must pass, got: %+v", v) } } // TestCheckDeclaredSubtype_WithNames_RejectsTypoedIdent: in-package identifier form (no errs. // prefix) must also be checked against the nameset. func TestCheckDeclaredSubtype_WithNames_RejectsTypoedIdent(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} src := `package errs type Subtype string type myErr struct{ Subtype Subtype } var _ = myErr{Subtype: SubtypeNotDeclared} ` v := CheckDeclaredSubtypeWithNames("internal/x.go", src, allowlist, nameset) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "SubtypeNotDeclared") { t.Errorf("message should name the offending identifier: %s", v[0].Message) } } func TestCheckDeclaredSubtype_NilAllowlist_IsNoOp(t *testing.T) { // Caller can disable CheckDeclaredSubtype by passing nil; that should not panic and must // not emit any E-class violation, even on undeclared subtypes. src := `package x var _ = struct{ Subtype string }{Subtype: "anything"} ` v := CheckDeclaredSubtype("x.go", src, nil) if len(v) != 0 { t.Errorf("nil allowlist must disable CheckDeclaredSubtype, got: %+v", v) } } // TestRunAll_OneFileFourViolations exercises the combined entry point: a // synthetic file under shortcuts/ that violates B, C, D, and E together. func TestRunAll_OneFileFourViolations(t *testing.T) { // Path is shortcuts/* so CheckNoRegistrar fires; file declared in errs-like package // header is irrelevant for B (we test B in errs/ files only via path). src := `package task type LooseError struct{} func init() { mergeCodeMeta(nil, "task") } var _ = struct{ Subtype string }{Subtype: "ad_hoc_thing"} var _ = struct{ Subtype string }{Subtype: "bogus"} ` allowlist := map[string]struct{}{ "missing_scope": {}, } v := RunAll("shortcuts/task/all_bad.go", src, allowlist) byRule := map[string]int{} byAction := map[Action]int{} for _, vv := range v { byRule[vv.Rule]++ byAction[vv.Action]++ } // CheckProblemEmbed is path-scoped to errs/, so it does NOT fire on shortcuts/. if byRule["problem_embed"] != 0 { t.Errorf("CheckProblemEmbed should not fire outside errs/, got %d", byRule["problem_embed"]) } if byRule["no_registrar"] != 1 { t.Errorf("CheckNoRegistrar count = %d, want 1", byRule["no_registrar"]) } if byRule["adhoc_subtype"] != 1 { t.Errorf("CheckAdHocSubtype count = %d, want 1", byRule["adhoc_subtype"]) } if byRule["declared_subtype"] != 1 { t.Errorf("CheckDeclaredSubtype count = %d, want 1", byRule["declared_subtype"]) } if byAction[ActionReject] != 2 { t.Errorf("REJECT count = %d, want 2 (Rules C+E)", byAction[ActionReject]) } if byAction[ActionLabel] != 1 { t.Errorf("LABEL count = %d, want 1 (CheckAdHocSubtype)", byAction[ActionLabel]) } } func TestRunAll_ErrsPathRunsRuleB(t *testing.T) { src := `package errs type NoEmbedError struct { Code int } ` v := RunAll("errs/types.go", src, nil) if len(v) != 1 || v[0].Rule != "problem_embed" { t.Fatalf("expected one CheckProblemEmbed violation, got %+v", v) } } // TestCheckProblemEmbed_SkipsUnexportedErrorType pins that CheckProblemEmbed only // enforces the Problem embed on EXPORTED *Error types — unexported helper // types that happen to end in "Error" are not part of the public taxonomy // and would create false-positive REJECT violations. func TestCheckProblemEmbed_SkipsUnexportedErrorType(t *testing.T) { src := `package internal type myInternalError struct { Code int Msg string } ` v := CheckProblemEmbed("internal/foo/internal.go", src) if len(v) != 0 { t.Errorf("expected 0 violations for unexported helper, got %d: %+v", len(v), v) } } // TestCheckNoRegistrar_CatchesMiddleAffix pins that the registrar matcher // catches RegisterServiceMap even when it has affixes on both sides — the // older prefix-or-suffix-only check would have missed FooRegisterServiceMapBar. func TestCheckNoRegistrar_CatchesMiddleAffix(t *testing.T) { src := `package auth func init() { FooRegisterServiceMapBar("auth", nil) } func FooRegisterServiceMapBar(name string, _ interface{}) {} ` v := CheckNoRegistrar("internal/auth/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for middle-affix registrar, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "FooRegisterServiceMapBar") { t.Errorf("message must name the offending call: %s", v[0].Message) } }