Skip to content

Commit a693fdd

Browse files
authored
test: close reference-data coverage gap to 80% (#1847)
* test: close reference-data coverage gap to 80% * fix: replace nil context with context.TODO() to satisfy staticcheck --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent dbb73de commit a693fdd

4 files changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package registry_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/meridianhub/meridian/services/reference-data/registry"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestInstrumentStatus_IsValid(t *testing.T) {
12+
assert.True(t, registry.StatusDraft.IsValid())
13+
assert.True(t, registry.StatusActive.IsValid())
14+
assert.True(t, registry.StatusDeprecated.IsValid())
15+
assert.False(t, registry.Status("UNKNOWN").IsValid())
16+
assert.False(t, registry.Status("").IsValid())
17+
}
18+
19+
func TestInstrumentStatus_CanTransitionTo(t *testing.T) {
20+
tests := []struct {
21+
name string
22+
from registry.Status
23+
to registry.Status
24+
expect bool
25+
}{
26+
{"DRAFT to ACTIVE", registry.StatusDraft, registry.StatusActive, true},
27+
{"DRAFT to DEPRECATED", registry.StatusDraft, registry.StatusDeprecated, false},
28+
{"DRAFT to DRAFT", registry.StatusDraft, registry.StatusDraft, false},
29+
{"ACTIVE to DEPRECATED", registry.StatusActive, registry.StatusDeprecated, true},
30+
{"ACTIVE to DRAFT", registry.StatusActive, registry.StatusDraft, false},
31+
{"ACTIVE to ACTIVE", registry.StatusActive, registry.StatusActive, false},
32+
{"DEPRECATED to DRAFT", registry.StatusDeprecated, registry.StatusDraft, false},
33+
{"DEPRECATED to ACTIVE", registry.StatusDeprecated, registry.StatusActive, false},
34+
{"DEPRECATED to DEPRECATED", registry.StatusDeprecated, registry.StatusDeprecated, false},
35+
{"unknown to ACTIVE", registry.Status("UNKNOWN"), registry.StatusActive, false},
36+
}
37+
38+
for _, tt := range tests {
39+
t.Run(tt.name, func(t *testing.T) {
40+
assert.Equal(t, tt.expect, tt.from.CanTransitionTo(tt.to))
41+
})
42+
}
43+
}
44+
45+
func TestValidateStatusTransition(t *testing.T) {
46+
t.Run("valid DRAFT to ACTIVE", func(t *testing.T) {
47+
err := registry.ValidateStatusTransition(registry.StatusDraft, registry.StatusActive)
48+
require.NoError(t, err)
49+
})
50+
51+
t.Run("valid ACTIVE to DEPRECATED", func(t *testing.T) {
52+
err := registry.ValidateStatusTransition(registry.StatusActive, registry.StatusDeprecated)
53+
require.NoError(t, err)
54+
})
55+
56+
t.Run("same status returns error", func(t *testing.T) {
57+
err := registry.ValidateStatusTransition(registry.StatusDraft, registry.StatusDraft)
58+
require.ErrorIs(t, err, registry.ErrInvalidStateTransition)
59+
assert.Contains(t, err.Error(), "same")
60+
})
61+
62+
t.Run("invalid transition returns error", func(t *testing.T) {
63+
err := registry.ValidateStatusTransition(registry.StatusDraft, registry.StatusDeprecated)
64+
require.ErrorIs(t, err, registry.ErrInvalidStateTransition)
65+
assert.Contains(t, err.Error(), "cannot transition")
66+
})
67+
68+
t.Run("DEPRECATED is terminal", func(t *testing.T) {
69+
err := registry.ValidateStatusTransition(registry.StatusDeprecated, registry.StatusActive)
70+
require.ErrorIs(t, err, registry.ErrInvalidStateTransition)
71+
})
72+
}
73+
74+
func TestInstrumentStatus_String(t *testing.T) {
75+
assert.Equal(t, "DRAFT", registry.StatusDraft.String())
76+
assert.Equal(t, "ACTIVE", registry.StatusActive.String())
77+
assert.Equal(t, "DEPRECATED", registry.StatusDeprecated.String())
78+
assert.Equal(t, "UNKNOWN", registry.Status("UNKNOWN").String())
79+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package saga
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestTenantMigrationSummary_Counts(t *testing.T) {
13+
t.Run("empty results", func(t *testing.T) {
14+
s := &TenantMigrationSummary{}
15+
m, sk, wm := s.Counts()
16+
assert.Equal(t, 0, m)
17+
assert.Equal(t, 0, sk)
18+
assert.Equal(t, 0, wm)
19+
})
20+
21+
t.Run("mixed actions", func(t *testing.T) {
22+
s := &TenantMigrationSummary{
23+
Results: []MigrationResult{
24+
{Action: MigrationActionMigrated},
25+
{Action: MigrationActionMigrated},
26+
{Action: MigrationActionSkipped},
27+
{Action: MigrationActionWouldMigrate},
28+
{Action: MigrationActionWouldMigrate},
29+
{Action: MigrationActionWouldMigrate},
30+
{Action: "unknown_action"},
31+
},
32+
}
33+
m, sk, wm := s.Counts()
34+
assert.Equal(t, 2, m)
35+
assert.Equal(t, 1, sk)
36+
assert.Equal(t, 3, wm)
37+
})
38+
}
39+
40+
func TestFormatReport(t *testing.T) {
41+
refID := uuid.New()
42+
43+
t.Run("dry run report", func(t *testing.T) {
44+
summaries := []TenantMigrationSummary{
45+
{
46+
TenantID: "tenant-1",
47+
Results: []MigrationResult{
48+
{Action: MigrationActionWouldMigrate, SagaName: "payment.initiate", Reason: "95% similar", PlatformRefID: &refID},
49+
{Action: MigrationActionSkipped, SagaName: "custom.workflow", Reason: "no match"},
50+
},
51+
},
52+
}
53+
report := FormatReport(summaries, true)
54+
assert.Contains(t, report, "DRY RUN")
55+
assert.Contains(t, report, "tenant-1")
56+
assert.Contains(t, report, "payment.initiate")
57+
assert.Contains(t, report, "Would migrate: 1")
58+
assert.Contains(t, report, "Skipped: 1")
59+
assert.Contains(t, report, "platform_ref=")
60+
})
61+
62+
t.Run("apply report with error", func(t *testing.T) {
63+
summaries := []TenantMigrationSummary{
64+
{
65+
TenantID: "tenant-ok",
66+
Results: []MigrationResult{
67+
{Action: MigrationActionMigrated, SagaName: "payment.settle", Reason: "matched"},
68+
},
69+
},
70+
{
71+
TenantID: "tenant-fail",
72+
Error: errors.New("db connection lost"),
73+
},
74+
}
75+
report := FormatReport(summaries, false)
76+
assert.NotContains(t, report, "DRY RUN")
77+
assert.Contains(t, report, "Migrated: 1")
78+
assert.Contains(t, report, "Errors: 1")
79+
assert.Contains(t, report, "db connection lost")
80+
})
81+
82+
t.Run("empty summaries", func(t *testing.T) {
83+
report := FormatReport(nil, false)
84+
assert.Contains(t, report, "Tenants processed: 0")
85+
})
86+
}
87+
88+
func TestErrPartialMigrationFailure(t *testing.T) {
89+
err := ErrPartialMigrationFailure
90+
require.Error(t, err)
91+
assert.Equal(t, "some tenant migrations failed", err.Error())
92+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package saga
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
pkgsaga "github.com/meridianhub/meridian/shared/pkg/saga"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestSimilarity_IdenticalStrings(t *testing.T) {
12+
score := similarity("hello", "hello")
13+
assert.Equal(t, 10, score) // len("hello") * 2
14+
}
15+
16+
func TestSimilarity_EmptyStrings(t *testing.T) {
17+
score := similarity("", "")
18+
assert.Equal(t, 0, score) // len("") * 2 = 0
19+
}
20+
21+
func TestSimilarity_CommonPrefix(t *testing.T) {
22+
score := similarity("hello_world", "hello_earth")
23+
assert.Greater(t, score, 0, "common prefix should contribute to score")
24+
}
25+
26+
func TestSimilarity_CommonSuffix(t *testing.T) {
27+
score := similarity("foo_handler", "bar_handler")
28+
assert.Greater(t, score, 0, "common suffix should contribute to score")
29+
}
30+
31+
func TestSimilarity_SubstringMatch(t *testing.T) {
32+
score := similarity("handler", "step_handler_v2")
33+
assert.Greater(t, score, 0, "substring match should increase score")
34+
}
35+
36+
func TestSimilarity_CompletelyDifferent(t *testing.T) {
37+
score := similarity("abc", "xyz")
38+
assert.Equal(t, 0, score, "no common prefix/suffix/substring should yield 0")
39+
}
40+
41+
func TestFindSimilar_EmptyCandidates(t *testing.T) {
42+
result := findSimilar("hello", nil)
43+
assert.Equal(t, "", result)
44+
45+
result = findSimilar("hello", []string{})
46+
assert.Equal(t, "", result)
47+
}
48+
49+
func TestFindSimilar_ExactMatch(t *testing.T) {
50+
result := findSimilar("hello", []string{"world", "hello", "hi"})
51+
assert.Equal(t, "hello", result)
52+
}
53+
54+
func TestFindSimilar_CaseInsensitive(t *testing.T) {
55+
result := findSimilar("Hello", []string{"world", "hello", "hi"})
56+
assert.Equal(t, "hello", result)
57+
}
58+
59+
func TestFindSimilar_NoGoodMatch(t *testing.T) {
60+
result := findSimilar("xyz", []string{"abcdefgh", "ijklmnop"})
61+
assert.Equal(t, "", result, "no sufficiently similar candidate should return empty")
62+
}
63+
64+
func TestFindSimilar_BestMatchSelected(t *testing.T) {
65+
result := findSimilar("position_keeping", []string{
66+
"position_taking",
67+
"account_keeping",
68+
"totally_different",
69+
})
70+
// "position_taking" shares more prefix with "position_keeping"
71+
assert.Equal(t, "position_taking", result)
72+
}
73+
74+
func TestSuggestAttribute_EmptySchema(t *testing.T) {
75+
v := &ReferenceValidator{}
76+
result := v.suggestAttribute(map[string]interface{}{}, "key")
77+
assert.Equal(t, "", result)
78+
}
79+
80+
func TestSuggestAttribute_HasMatch(t *testing.T) {
81+
v := &ReferenceValidator{}
82+
schema := map[string]interface{}{
83+
"status": "string",
84+
"balance": "decimal",
85+
"created_at": "timestamp",
86+
}
87+
result := v.suggestAttribute(schema, "statu") // close to "status"
88+
assert.Contains(t, result, "status")
89+
}
90+
91+
func TestSuggestHandler_EmptyRegistry(t *testing.T) {
92+
reg := pkgsaga.NewHandlerRegistry()
93+
v := &ReferenceValidator{handlerRegistry: reg}
94+
result := v.suggestHandler("nonexistent_handler")
95+
assert.Equal(t, "", result)
96+
}
97+
98+
func TestSuggestInstrument_NilChecker(t *testing.T) {
99+
v := &ReferenceValidator{instrumentChecker: nil}
100+
result := v.suggestInstrument(context.TODO(), "GBX")
101+
assert.Equal(t, "", result)
102+
}
103+
104+
func TestSuggestSaga_NilChecker(t *testing.T) {
105+
v := &ReferenceValidator{definitionChecker: nil}
106+
result := v.suggestSaga(context.TODO(), "payment.initiate")
107+
assert.Equal(t, "", result)
108+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package valuation
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestEstimateCELCost(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
expr string
14+
expected int
15+
}{
16+
{"empty expression returns 1", "", 1},
17+
{"short expression", "a > 0", 1},
18+
{"medium expression", "amount > 0 && amount <= 1000000 && currency == 'GBP'", 5},
19+
{"very long expression clamps at 9999", string(make([]byte, 200000)), 9999},
20+
}
21+
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
assert.Equal(t, tt.expected, estimateCELCost(tt.expr))
25+
})
26+
}
27+
}
28+
29+
func TestComputeHash(t *testing.T) {
30+
t.Run("deterministic", func(t *testing.T) {
31+
h1 := computeHash("hello")
32+
h2 := computeHash("hello")
33+
assert.Equal(t, h1, h2)
34+
})
35+
36+
t.Run("different inputs produce different hashes", func(t *testing.T) {
37+
h1 := computeHash("hello")
38+
h2 := computeHash("world")
39+
assert.NotEqual(t, h1, h2)
40+
})
41+
42+
t.Run("returns 64 char hex string", func(t *testing.T) {
43+
h := computeHash("test")
44+
assert.Len(t, h, 64) // SHA-256 = 32 bytes = 64 hex chars
45+
})
46+
}
47+
48+
func TestNullString(t *testing.T) {
49+
t.Run("empty string returns invalid", func(t *testing.T) {
50+
ns := nullString("")
51+
assert.Equal(t, sql.NullString{Valid: false}, ns)
52+
})
53+
54+
t.Run("non-empty string returns valid", func(t *testing.T) {
55+
ns := nullString("hello")
56+
assert.Equal(t, sql.NullString{String: "hello", Valid: true}, ns)
57+
})
58+
}
59+
60+
func TestBuildDryRunInput(t *testing.T) {
61+
t.Run("nil inputs", func(t *testing.T) {
62+
result := buildDryRunInput(nil)
63+
assert.NotNil(t, result["attributes"])
64+
assert.Equal(t, "", result["amount"])
65+
assert.Equal(t, "", result["source"])
66+
})
67+
68+
t.Run("with amount", func(t *testing.T) {
69+
result := buildDryRunInput(map[string]string{"amount": "100.50"})
70+
assert.Equal(t, "100.50", result["amount"])
71+
})
72+
73+
t.Run("without amount", func(t *testing.T) {
74+
result := buildDryRunInput(map[string]string{"currency": "GBP"})
75+
assert.Equal(t, "", result["amount"])
76+
})
77+
}
78+
79+
func TestFailedDryRun(t *testing.T) {
80+
result := failedDryRun(42, "compilation error")
81+
assert.False(t, result.Success)
82+
assert.Equal(t, 42, result.EstimatedCost)
83+
assert.Equal(t, []string{"compilation error"}, result.Errors)
84+
}

0 commit comments

Comments
 (0)