Skip to content

Commit e916f9b

Browse files
authored
refactor: extract shared test context helpers and per-service test helpers (#1791)
* refactor: extract shared test context helpers and per-service test helpers Task 22: Add testdb.ContextWithTenant and testdb.ContextWithDefaultTenant helpers to shared/platform/testdb/context.go, reducing repetitive tenant.WithTenant(context.Background(), ...) boilerplate across 382+ test occurrences. Migrate 8 high-traffic test files to use the new helpers. Task 23: Create testhelpers/ packages for 5 services (current-account, internal-account, position-keeping, reconciliation, reference-data) with entity factory functions that consolidate duplicated test setup patterns. Migrate reconciliation service tests to use the shared factories. * fix: correct gofmt formatting in migrated test files * fix: add missing newline at end of audit_tables.go --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent b937db9 commit e916f9b

16 files changed

Lines changed: 395 additions & 75 deletions

File tree

services/current-account/service/fungibility_validator_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
"github.com/meridianhub/meridian/services/reference-data/cache"
1313
"github.com/meridianhub/meridian/services/reference-data/registry"
14-
"github.com/meridianhub/meridian/shared/platform/tenant"
14+
"github.com/meridianhub/meridian/shared/platform/testdb"
1515
)
1616

1717
// Test sentinel errors for wrapping.
@@ -61,7 +61,7 @@ func (m *mockFungibilityKeyProgram) Eval(activation interface{}) (interface{}, e
6161

6262
func TestFungibilityValidator_ValidateDoubleEntry_FullyFungible(t *testing.T) {
6363
// Instrument with empty fungibility_key_expression is fully fungible
64-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
64+
ctx := testdb.ContextWithTenant(t, "test-tenant")
6565

6666
mock := &mockInstrumentGetter{
6767
instruments: map[string]*cache.CachedInstrument{
@@ -84,7 +84,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_FullyFungible(t *testing.T) {
8484

8585
func TestFungibilityValidator_ValidateDoubleEntry_MatchingKeys(t *testing.T) {
8686
// Instrument with fungibility_key_expression where attributes produce matching keys
87-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
87+
ctx := testdb.ContextWithTenant(t, "test-tenant")
8888

8989
mock := &mockInstrumentGetter{
9090
instruments: map[string]*cache.CachedInstrument{
@@ -118,7 +118,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_MatchingKeys(t *testing.T) {
118118

119119
func TestFungibilityValidator_ValidateDoubleEntry_MismatchedKeys(t *testing.T) {
120120
// Instrument with fungibility_key_expression where attributes produce different keys
121-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
121+
ctx := testdb.ContextWithTenant(t, "test-tenant")
122122

123123
mock := &mockInstrumentGetter{
124124
instruments: map[string]*cache.CachedInstrument{
@@ -150,7 +150,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_MismatchedKeys(t *testing.T) {
150150
}
151151

152152
func TestFungibilityValidator_ValidateDoubleEntry_InstrumentNotFound(t *testing.T) {
153-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
153+
ctx := testdb.ContextWithTenant(t, "test-tenant")
154154

155155
mock := &mockInstrumentGetter{
156156
instruments: map[string]*cache.CachedInstrument{}, // Empty - no instruments
@@ -164,7 +164,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_InstrumentNotFound(t *testing.
164164
}
165165

166166
func TestFungibilityValidator_ValidateDoubleEntry_InstrumentLookupError(t *testing.T) {
167-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
167+
ctx := testdb.ContextWithTenant(t, "test-tenant")
168168

169169
mock := &mockInstrumentGetter{
170170
err: fmt.Errorf("lookup failed: %w", errFungibilityTestConnRefused),
@@ -179,7 +179,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_InstrumentLookupError(t *testi
179179

180180
func TestFungibilityValidator_ValidateDoubleEntry_NilAttributes(t *testing.T) {
181181
// Test that nil attributes are handled gracefully
182-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
182+
ctx := testdb.ContextWithTenant(t, "test-tenant")
183183

184184
mock := &mockInstrumentGetter{
185185
instruments: map[string]*cache.CachedInstrument{
@@ -208,7 +208,7 @@ func TestFungibilityValidator_ValidateDoubleEntry_NilAttributes(t *testing.T) {
208208
}
209209

210210
func TestFungibilityValidator_ValidateDoubleEntry_CELEvaluationError(t *testing.T) {
211-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
211+
ctx := testdb.ContextWithTenant(t, "test-tenant")
212212

213213
mock := &mockInstrumentGetter{
214214
instruments: map[string]*cache.CachedInstrument{
@@ -247,7 +247,7 @@ func TestNewFungibilityValidator(t *testing.T) {
247247

248248
func TestFungibilityValidator_UseCELProgramFromInstrument(t *testing.T) {
249249
// Test that when no custom evaluator is set, the validator uses the instrument's BucketKeyProgram
250-
ctx := tenant.WithTenant(context.Background(), "test-tenant")
250+
ctx := testdb.ContextWithTenant(t, "test-tenant")
251251

252252
// This test verifies the production path works when BucketKeyProgram is nil (fully fungible)
253253
mock := &mockInstrumentGetter{
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Package testhelpers provides shared test factories and utilities for current-account tests.
2+
package testhelpers
3+
4+
import (
5+
"testing"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/meridianhub/meridian/services/current-account/domain"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// NewCurrentAccount creates a test current account with sensible defaults.
14+
// The account is created with GBP currency and "CURRENCY" dimension.
15+
func NewCurrentAccount(t *testing.T, accountID string) domain.CurrentAccount {
16+
t.Helper()
17+
account, err := domain.NewCurrentAccount(
18+
accountID,
19+
"GB"+accountID, // external identifier
20+
uuid.New().String(),
21+
"GBP",
22+
)
23+
require.NoError(t, err)
24+
return account
25+
}
26+
27+
// NewCurrentAccountWithInstrument creates a test current account with a specific instrument code.
28+
func NewCurrentAccountWithInstrument(t *testing.T, accountID, instrumentCode string) domain.CurrentAccount {
29+
t.Helper()
30+
account, err := domain.NewCurrentAccount(
31+
accountID,
32+
"GB"+accountID,
33+
uuid.New().String(),
34+
instrumentCode,
35+
)
36+
require.NoError(t, err)
37+
return account
38+
}
39+
40+
// NewLien creates a test lien with the given status.
41+
func NewLien(t *testing.T, status domain.LienStatus) *domain.Lien {
42+
t.Helper()
43+
amount, err := domain.NewMoney("GBP", 10000)
44+
require.NoError(t, err)
45+
46+
now := time.Now()
47+
return &domain.Lien{
48+
ID: uuid.New(),
49+
AccountID: uuid.New(),
50+
Amount: amount,
51+
Status: status,
52+
PaymentOrderReference: "PO-TEST-001",
53+
Version: 1,
54+
CreatedAt: now,
55+
UpdatedAt: now,
56+
}
57+
}

services/internal-account/adapters/persistence/repository_integration_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/meridianhub/meridian/shared/platform/auth"
2222
"github.com/meridianhub/meridian/shared/platform/await"
2323
"github.com/meridianhub/meridian/shared/platform/tenant"
24+
"github.com/meridianhub/meridian/shared/platform/testdb"
2425
"github.com/stretchr/testify/assert"
2526
"github.com/stretchr/testify/require"
2627
"github.com/testcontainers/testcontainers-go"
@@ -1671,8 +1672,7 @@ func TestIntegration_LoadTest_ConcurrentCreation(t *testing.T) {
16711672
tc := setupIntegrationTestContainer(t)
16721673
defer tc.cleanup(t)
16731674

1674-
tid := defaultTestTenantID
1675-
ctx := tenant.WithTenant(context.Background(), tid)
1675+
ctx := testdb.ContextWithTenant(t, string(defaultTestTenantID))
16761676
ctx = context.WithValue(ctx, auth.UserIDContextKey, "load-test-user")
16771677

16781678
const numAccounts = 1000
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Package testhelpers provides shared test factories and utilities for internal-account tests.
2+
package testhelpers
3+
4+
import (
5+
"testing"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/meridianhub/meridian/services/internal-account/domain"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// NewInternalAccount creates a test internal account of the given type.
14+
// CLEARING accounts are automatically given ClearingPurposeGeneral.
15+
func NewInternalAccount(t *testing.T, accountType domain.AccountType) domain.InternalAccount {
16+
t.Helper()
17+
18+
accountID := "IBA-TEST"
19+
accountCode := "TEST_" + string(accountType)
20+
name := "Test " + string(accountType) + " Account"
21+
22+
clearingPurpose := domain.ClearingPurposeUnspecified
23+
if accountType == domain.AccountTypeClearing {
24+
clearingPurpose = domain.ClearingPurposeGeneral
25+
}
26+
27+
account, err := domain.NewInternalAccount(
28+
accountID, accountCode, name,
29+
accountType, clearingPurpose,
30+
"GBP", "CURRENCY",
31+
)
32+
require.NoError(t, err)
33+
return account
34+
}
35+
36+
// NewInternalAccountWithID creates a test internal account with a specific ID and code.
37+
func NewInternalAccountWithID(t *testing.T, accountID, accountCode, name string, accountType domain.AccountType) domain.InternalAccount {
38+
t.Helper()
39+
40+
clearingPurpose := domain.ClearingPurposeUnspecified
41+
if accountType == domain.AccountTypeClearing {
42+
clearingPurpose = domain.ClearingPurposeGeneral
43+
}
44+
45+
account, err := domain.NewInternalAccount(
46+
accountID, accountCode, name,
47+
accountType, clearingPurpose,
48+
"GBP", "CURRENCY",
49+
)
50+
require.NoError(t, err)
51+
return account
52+
}
53+
54+
// NewLien creates a test lien with the given status. All fields are exported
55+
// on the Lien struct so this can be used from any package.
56+
func NewLien(t *testing.T, status domain.LienStatus) *domain.Lien {
57+
t.Helper()
58+
now := time.Now()
59+
return &domain.Lien{
60+
ID: uuid.New(),
61+
AccountID: uuid.New(),
62+
AmountCents: 10000,
63+
InstrumentCode: "GBP",
64+
Status: status,
65+
PaymentOrderReference: "PO-TEST-001",
66+
Version: 1,
67+
CreatedAt: now,
68+
UpdatedAt: now,
69+
}
70+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Package testhelpers provides shared test utilities for position-keeping tests.
2+
//
3+
// For domain entity factories (FinancialPositionLog, Money, TransactionCapturedEvent),
4+
// use the domain/testfixtures package which provides a comprehensive builder-pattern API.
5+
//
6+
// This package provides service-level test utilities that complement the domain fixtures.
7+
package testhelpers
8+
9+
import (
10+
"context"
11+
"testing"
12+
13+
"github.com/meridianhub/meridian/shared/platform/testdb"
14+
)
15+
16+
// ContextWithTenant returns a context with the given tenant ID for position-keeping tests.
17+
// This is a convenience wrapper around testdb.ContextWithTenant.
18+
func ContextWithTenant(t *testing.T, tenantID string) context.Context {
19+
t.Helper()
20+
return testdb.ContextWithTenant(t, tenantID)
21+
}
22+
23+
// DefaultTestTenantID is the standard tenant ID used in position-keeping tests.
24+
const DefaultTestTenantID = "test_pk_tenant"

services/reconciliation/service/snapshot_capturer_test.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"errors"
66
"sync"
77
"testing"
8-
"time"
98

109
"github.com/google/uuid"
1110
"github.com/meridianhub/meridian/services/reconciliation/domain"
11+
"github.com/meridianhub/meridian/services/reconciliation/testhelpers"
1212
"github.com/shopspring/decimal"
1313
"github.com/stretchr/testify/assert"
1414
"github.com/stretchr/testify/require"
@@ -168,16 +168,7 @@ func (m *mockSnapshotRepo) snapshotCount() int {
168168

169169
func newTestRun(t *testing.T) *domain.SettlementRun {
170170
t.Helper()
171-
run, err := domain.NewSettlementRun(
172-
"ACC-001",
173-
domain.ReconciliationScopeAccount,
174-
domain.SettlementTypeDaily,
175-
time.Now().Add(-24*time.Hour),
176-
time.Now(),
177-
"test-user",
178-
)
179-
require.NoError(t, err)
180-
return run
171+
return testhelpers.NewSettlementRun(t)
181172
}
182173

183174
// --- Tests ---

services/reconciliation/service/variance_detector_test.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/google/uuid"
1111
"github.com/meridianhub/meridian/services/reconciliation/domain"
12+
"github.com/meridianhub/meridian/services/reconciliation/testhelpers"
1213
"github.com/shopspring/decimal"
1314
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/require"
@@ -110,17 +111,7 @@ func (m *mockVarianceRepoFull) varianceCount() int {
110111

111112
func newRunningTestRun(t *testing.T) *domain.SettlementRun {
112113
t.Helper()
113-
run, err := domain.NewSettlementRun(
114-
"ACC-001",
115-
domain.ReconciliationScopeAccount,
116-
domain.SettlementTypeDaily,
117-
time.Now().Add(-24*time.Hour),
118-
time.Now(),
119-
"test-user",
120-
)
121-
require.NoError(t, err)
122-
require.NoError(t, run.Start())
123-
return run
114+
return testhelpers.NewRunningSettlementRun(t)
124115
}
125116

126117
// --- Tests ---
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Package testhelpers provides shared test factories and utilities for reconciliation tests.
2+
package testhelpers
3+
4+
import (
5+
"testing"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/meridianhub/meridian/services/reconciliation/domain"
10+
"github.com/shopspring/decimal"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
// NewSettlementRun creates a standard settlement run for testing.
15+
// The run is in PENDING status with daily scope for account ACC-001.
16+
func NewSettlementRun(t *testing.T) *domain.SettlementRun {
17+
t.Helper()
18+
now := time.Now().UTC()
19+
run, err := domain.NewSettlementRun(
20+
"ACC-001",
21+
domain.ReconciliationScopeAccount,
22+
domain.SettlementTypeDaily,
23+
now.Add(-24*time.Hour),
24+
now,
25+
"test-user",
26+
)
27+
require.NoError(t, err)
28+
return run
29+
}
30+
31+
// NewRunningSettlementRun creates a settlement run that has been started.
32+
func NewRunningSettlementRun(t *testing.T) *domain.SettlementRun {
33+
t.Helper()
34+
run := NewSettlementRun(t)
35+
err := run.Start()
36+
require.NoError(t, err)
37+
return run
38+
}
39+
40+
// NewBalanceAssertion creates a standard balance assertion for testing.
41+
func NewBalanceAssertion(t *testing.T) *domain.BalanceAssertion {
42+
t.Helper()
43+
runID := uuid.New()
44+
a, err := domain.NewBalanceAssertion(
45+
&runID, "ACC-001", "GBP",
46+
"balance == 10000", decimal.NewFromFloat(10000.00),
47+
)
48+
require.NoError(t, err)
49+
return a
50+
}
51+
52+
// NewDispute creates a standard dispute for testing.
53+
func NewDispute(t *testing.T) *domain.Dispute {
54+
t.Helper()
55+
d, err := domain.NewDispute(
56+
uuid.New(), uuid.New(), "ACC-001",
57+
"Amount discrepancy noted", "user-1",
58+
)
59+
require.NoError(t, err)
60+
return d
61+
}
62+
63+
// NewVariance creates a standard variance for testing with the given run and snapshot IDs.
64+
func NewVariance(t *testing.T, runSurrogateID, snapshotSurrogateID uuid.UUID) *domain.Variance {
65+
t.Helper()
66+
v, err := domain.NewVariance(
67+
runSurrogateID,
68+
snapshotSurrogateID,
69+
"ACC-001", "GBP",
70+
decimal.NewFromFloat(1000.00), decimal.NewFromFloat(995.50),
71+
domain.VarianceReasonAmountMismatch,
72+
)
73+
require.NoError(t, err)
74+
return v
75+
}
76+
77+
// NewDefaultVariance creates a variance with generated IDs for simple test cases.
78+
func NewDefaultVariance(t *testing.T) *domain.Variance {
79+
t.Helper()
80+
return NewVariance(t, uuid.New(), uuid.New())
81+
}

0 commit comments

Comments
 (0)