Skip to content

Commit cc01ffe

Browse files
authored
test: add multi-asset test fixtures and demo seed for energy accounts (#1258)
- Add TestNewAmountFromInstrument_COMPUTE (GPU_HOUR/COMPUTE dimension) and TestNewAmountFromInstrument_AllDimensions table test covering all four dimensions (CURRENCY, ENERGY, CARBON, COMPUTE) in quantity_test.go - Add TestNewLien_EnergyAccount verifying NewLien works with KWH amounts, plus TestNewLien_KWHAmountHelper and TestNewLien_CarbonCreditAmountHelper exercising the new multi-asset test helper functions in lien_test.go - Add helper functions createKWHTestAccount, createKWHAmount, and createCarbonCreditAmount for reuse across domain tests - Update seed-demo to create a KWH consumption tracking account per customer alongside the existing GBP billing account, and seed 30 days of meter reading credits into each KWH account at daily resolution Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 2499c7d commit cc01ffe

3 files changed

Lines changed: 133 additions & 4 deletions

File tree

cmd/seed-demo/main.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
// - An energy company tenant ("volterra-energy")
55
// - The energy manifest (instruments: GBP, KWH, CARBON_CREDIT)
66
// - A DNO organization (UK Power Networks) with 4 Grid Supply Points
7-
// - 10 residential customers with GBP + KWH current accounts
8-
// - Initial deposits: GBP balances and KWH consumption credits
7+
// - 10 residential customers each with:
8+
// - A GBP billing account (charges in pounds sterling)
9+
// - A KWH consumption tracking account (meter reading credits)
10+
// - Initial deposits: GBP billing charges and KWH consumption credits (30 days simulated)
911
// - A wholesale energy price dataset with 30 days of historical prices
1012
//
1113
// All operations are idempotent — safe to run multiple times.
@@ -148,7 +150,7 @@ func run() error {
148150
fmt.Printf("Tenant: %s (slug: %s)\n", tenantID, tenantSlug)
149151
fmt.Printf("DNO: %s\n", dnoPartyID)
150152
fmt.Printf("GSPs: %d grid supply points\n", len(gspPartyIDs))
151-
fmt.Printf("Customers: %d customers with GBP billing accounts\n", len(customerPartyIDs))
153+
fmt.Printf("Customers: %d customers with GBP billing + KWH consumption accounts\n", len(customerPartyIDs))
152154
fmt.Printf("Market: 30 days of wholesale energy prices\n")
153155
return nil
154156
}
@@ -328,6 +330,7 @@ type customerAccountPair struct {
328330
customerName string
329331
partyID string
330332
gbpAccountID string
333+
kwhAccountID string
331334
}
332335

333336
func createAccounts(ctx context.Context, conn *grpc.ClientConn, dnoPartyID string, customerPartyIDs []string) ([]customerAccountPair, error) {
@@ -339,13 +342,21 @@ func createAccounts(ctx context.Context, conn *grpc.ClientConn, dnoPartyID strin
339342
accounts[i].customerName = cust.legalName
340343
accounts[i].partyID = partyID
341344

342-
// GBP billing account (energy consumption tracked via market data + position-keeping)
345+
// GBP billing account — charges in pounds sterling
343346
gbpID, err := createAccountIdempotent(ctx, client, partyID, fmt.Sprintf("VE-GBP-%03d", i+1), "GBP", dnoPartyID)
344347
if err != nil {
345348
return nil, fmt.Errorf("create GBP account for %s: %w", cust.legalName, err)
346349
}
347350
accounts[i].gbpAccountID = gbpID
348351
fmt.Printf(" GBP: %s (%s)\n", gbpID, cust.legalName)
352+
353+
// KWH consumption tracking account — meter reading credits (ENERGY dimension)
354+
kwhID, err := createAccountIdempotent(ctx, client, partyID, fmt.Sprintf("VE-KWH-%03d", i+1), "KWH", dnoPartyID)
355+
if err != nil {
356+
return nil, fmt.Errorf("create KWH account for %s: %w", cust.legalName, err)
357+
}
358+
accounts[i].kwhAccountID = kwhID
359+
fmt.Printf(" KWH: %s (%s)\n", kwhID, cust.legalName)
349360
}
350361

351362
return accounts, nil
@@ -418,12 +429,23 @@ func seedCustomerBalances(ctx context.Context, client currentaccountv1.CurrentAc
418429
totalKWH += dailyKWH
419430
totalGBP += dailyGBP
420431

432+
// GBP billing deposit — charge for energy consumed at fixed retail tariff
421433
if err := depositIdempotent(ctx, client, acct.gbpAccountID, dailyGBP, "GBP",
422434
fmt.Sprintf("Energy billing %s: %.2f kWh @ %.1fp/kWh", date.Format("2006-01-02"), dailyKWH, fixedRate*100),
423435
fmt.Sprintf("BILL-%s-%s", acct.partyID, date.Format("20060102")),
424436
); err != nil {
425437
return fmt.Errorf("deposit GBP for %s day %d: %w", acct.customerName, day, err)
426438
}
439+
440+
// KWH consumption deposit — meter reading credit for energy consumed
441+
if acct.kwhAccountID != "" {
442+
if err := depositIdempotent(ctx, client, acct.kwhAccountID, dailyKWH, "KWH",
443+
fmt.Sprintf("Meter reading %s: %.3f kWh consumed", date.Format("2006-01-02"), dailyKWH),
444+
fmt.Sprintf("METER-%s-%s", acct.partyID, date.Format("20060102")),
445+
); err != nil {
446+
return fmt.Errorf("deposit KWH for %s day %d: %w", acct.customerName, day, err)
447+
}
448+
}
427449
}
428450

429451
fmt.Printf(" %s: %.1f kWh consumed, £%.2f billed (30 days)\n", acct.customerName, totalKWH, totalGBP)

services/current-account/domain/lien_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,24 @@ func TestLien_CanTerminate(t *testing.T) {
245245
}
246246
}
247247

248+
func TestNewLien_EnergyAccount(t *testing.T) {
249+
accountID := uuid.New()
250+
amount, err := NewAmountFromInstrument("KWH", "ENERGY", 0, 5000) // 5000 KWH
251+
require.NoError(t, err)
252+
253+
lien, err := NewLien(accountID, amount, "bucket-energy", "PO-ENERGY-001", nil)
254+
255+
require.NoError(t, err)
256+
assert.NotEqual(t, uuid.Nil, lien.ID)
257+
assert.Equal(t, accountID, lien.AccountID)
258+
assert.Equal(t, int64(5000), toMinorUnits(lien.Amount))
259+
assert.Equal(t, "KWH", lien.Amount.InstrumentCode())
260+
assert.Equal(t, "ENERGY", lien.Amount.Dimension())
261+
assert.Equal(t, "bucket-energy", lien.BucketID)
262+
assert.Equal(t, LienStatusActive, lien.Status)
263+
assert.Equal(t, "PO-ENERGY-001", lien.PaymentOrderReference)
264+
}
265+
248266
// createTestLien is a helper to create a lien with a specific status for testing
249267
func createTestLien(t *testing.T, status LienStatus) *Lien {
250268
t.Helper()
@@ -263,3 +281,59 @@ func createTestLien(t *testing.T, status LienStatus) *Lien {
263281
UpdatedAt: now,
264282
}
265283
}
284+
285+
func TestNewLien_KWHAmountHelper(t *testing.T) {
286+
account := createKWHTestAccount(t, "ACC-KWH-LIEN-001", 10000)
287+
lienAmount := createKWHAmount(t, 3000)
288+
289+
lien, err := NewLien(account.ID(), lienAmount, "bucket-kwh", "PO-KWH-001", nil)
290+
291+
require.NoError(t, err)
292+
assert.Equal(t, int64(3000), toMinorUnits(lien.Amount))
293+
assert.Equal(t, "KWH", lien.Amount.InstrumentCode())
294+
assert.Equal(t, "ENERGY", lien.Amount.Dimension())
295+
assert.Equal(t, account.ID(), lien.AccountID)
296+
}
297+
298+
func TestNewLien_CarbonCreditAmountHelper(t *testing.T) {
299+
creditAmount := createCarbonCreditAmount(t, 50)
300+
301+
lien, err := NewLien(uuid.New(), creditAmount, "bucket-carbon", "PO-CC-001", nil)
302+
303+
require.NoError(t, err)
304+
assert.Equal(t, int64(50), toMinorUnits(lien.Amount))
305+
assert.Equal(t, "CARBON_CREDIT", lien.Amount.InstrumentCode())
306+
assert.Equal(t, "CARBON", lien.Amount.Dimension())
307+
}
308+
309+
// createKWHTestAccount creates a KWH energy account with an optional initial balance for testing.
310+
func createKWHTestAccount(t *testing.T, accountID string, initialKWH int64) CurrentAccount {
311+
t.Helper()
312+
account, err := NewCurrentAccountWithDimension(accountID, "KWH-"+accountID, "PARTY-TEST", "KWH", "ENERGY", 0)
313+
require.NoError(t, err)
314+
315+
if initialKWH > 0 {
316+
deposit, err := NewAmountFromInstrument("KWH", "ENERGY", 0, initialKWH)
317+
require.NoError(t, err)
318+
account, err = account.Deposit(deposit)
319+
require.NoError(t, err)
320+
}
321+
322+
return account
323+
}
324+
325+
// createKWHAmount creates a KWH energy Amount for test assertions.
326+
func createKWHAmount(t *testing.T, minorUnits int64) Amount {
327+
t.Helper()
328+
a, err := NewAmountFromInstrument("KWH", "ENERGY", 0, minorUnits)
329+
require.NoError(t, err)
330+
return a
331+
}
332+
333+
// createCarbonCreditAmount creates a CARBON_CREDIT Amount for test assertions.
334+
func createCarbonCreditAmount(t *testing.T, minorUnits int64) Amount {
335+
t.Helper()
336+
a, err := NewAmountFromInstrument("CARBON_CREDIT", "CARBON", 0, minorUnits)
337+
require.NoError(t, err)
338+
return a
339+
}

services/current-account/domain/quantity_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,39 @@ func TestNewAmountFromInstrument_CARBON(t *testing.T) {
254254
assert.Equal(t, int64(100), toMinorUnits(a))
255255
}
256256

257+
func TestNewAmountFromInstrument_COMPUTE(t *testing.T) {
258+
a, err := NewAmountFromInstrument("GPU_HOUR", "COMPUTE", 6, 2000000)
259+
260+
assert.NoError(t, err)
261+
assert.Equal(t, "GPU_HOUR", a.InstrumentCode())
262+
assert.Equal(t, "COMPUTE", a.Dimension())
263+
assert.Equal(t, int64(2000000), toMinorUnits(a))
264+
}
265+
266+
func TestNewAmountFromInstrument_AllDimensions(t *testing.T) {
267+
tests := []struct {
268+
instrument string
269+
dimension string
270+
precision int
271+
minorUnits int64
272+
}{
273+
{"GBP", "CURRENCY", 2, 10000},
274+
{"KWH", "ENERGY", 3, 1500},
275+
{"CARBON_CREDIT", "CARBON", 0, 100},
276+
{"GPU_HOUR", "COMPUTE", 6, 2000000},
277+
}
278+
279+
for _, tc := range tests {
280+
t.Run(tc.instrument, func(t *testing.T) {
281+
a, err := NewAmountFromInstrument(tc.instrument, tc.dimension, tc.precision, tc.minorUnits)
282+
assert.NoError(t, err)
283+
assert.Equal(t, tc.instrument, a.InstrumentCode())
284+
assert.Equal(t, tc.dimension, a.Dimension())
285+
assert.Equal(t, tc.minorUnits, toMinorUnits(a))
286+
})
287+
}
288+
}
289+
257290
func TestNewAmountFromInstrument_InvalidDimension_ReturnsError(t *testing.T) {
258291
_, err := NewAmountFromInstrument("XYZ", "INVALID_DIM", 0, 100)
259292

0 commit comments

Comments
 (0)