-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclassify_property_test.go
More file actions
103 lines (82 loc) · 3.18 KB
/
Copy pathclassify_property_test.go
File metadata and controls
103 lines (82 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package auditlog_test
import (
"fmt"
"math/rand/v2"
"strings"
"testing"
errorfamily "github.com/larsartmann/go-error-family"
auditlog "github.com/larsartmann/go-workflow-auditlog"
)
// =============================================================================
// P2-15: Property — wrapping preserves Family through arbitrary depth
// =============================================================================
func TestClassifyProperty_WrappingPreservesFamily(t *testing.T) {
t.Parallel()
rng := rand.New(rand.NewPCG(42, 42))
classifications := auditlog.ErrorClassifications()
for range 200 {
sentinel := allPublicSentinels()[rng.IntN(len(allPublicSentinels()))]
wantFamily := classifications[sentinel]
depth := 1 + rng.IntN(10) // 1–10 layers of wrapping
err := sentinel
for range depth {
err = fmt.Errorf("layer %d: %w", depth, err)
}
gotFamily := errorfamily.Classify(err)
if gotFamily != wantFamily {
t.Errorf("Classify(%d-deep wrapped %v) = %v, want %v", depth, sentinel, gotFamily, wantFamily)
}
}
}
// =============================================================================
// P2-17: Property — Classify identity: every registered sentinel matches its map entry
// Verifies the registration mechanism is consistent with the declared mapping.
// =============================================================================
func TestClassifyProperty_IdentityMatchesErrorClassificationsMap(t *testing.T) {
t.Parallel()
classifications := auditlog.ErrorClassifications()
for sentinel, wantFamily := range classifications {
gotFamily := errorfamily.Classify(sentinel)
if gotFamily != wantFamily {
t.Errorf("Classify(%v) = %v, want %v (from ErrorClassifications map)", sentinel, gotFamily, wantFamily)
}
}
}
// =============================================================================
// P2-16: Fuzz — Classify on adversarial wrapped error chains
// Ensures Classify never panics and always returns a valid Family for any input.
// =============================================================================
func FuzzClassifyAdversarialChains(f *testing.F) {
// Seed with known sentinel-wrapping patterns
seeds := []string{
"wrapped error",
"nil",
"",
"very long error message " + strings.Repeat("x", 200),
"special: %s %d %v %w",
"nested: outer: inner: core",
}
for _, s := range seeds {
f.Add(s)
}
f.Fuzz(func(t *testing.T, payload string) {
t.Parallel()
// Direct unregistered error — should classify as Transient (fail-open default)
bareErr := fmt.Errorf("fuzz: %s", payload)
family := errorfamily.Classify(bareErr)
if family != errorfamily.Transient {
t.Errorf("Classify(unregistered) = %v, want Transient (fail-open default)", family)
}
// Wrapped sentinel → family should match sentinel's classification
sentinels := allPublicSentinels()
expected := auditlog.ErrorClassifications()
for _, sentinel := range sentinels {
wrapped := fmt.Errorf("%s: %w: %s", payload, sentinel, payload)
gotFamily := errorfamily.Classify(wrapped)
if gotFamily != expected[sentinel] {
t.Errorf("Classify(wrapped %v payload=%q) = %v, want %v",
sentinel, payload, gotFamily, expected[sentinel])
}
}
})
}