package testutils import ( "fmt" "testing" "time" "github.com/alpacahq/alpacadecimal" "github.com/oklog/ulid/v2" "github.com/stretchr/testify/require" "github.com/openmeterio/openmeter/openmeter/customer" entdb "github.com/openmeterio/openmeter/openmeter/ent/db" "github.com/openmeterio/openmeter/openmeter/ledger" omtestutils "github.com/openmeterio/openmeter/openmeter/testutils" "github.com/openmeterio/openmeter/pkg/clock" "github.com/openmeterio/openmeter/pkg/currencyx" ) type IntegrationEnv struct { Namespace string CustomerID customer.CustomerID Currency currencyx.Code DB *entdb.Client CustomerAccounts ledger.CustomerAccounts BusinessAccounts ledger.BusinessAccounts Deps Deps } func NewIntegrationEnv(t *testing.T, namespacePrefix string) *IntegrationEnv { t.Helper() now := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) clock.FreezeTime(now) t.Cleanup(clock.UnFreeze) testDB := omtestutils.InitPostgresDB(t, omtestutils.PostgresDBStateAtlasMigrated) t.Cleanup(func() { require.NoError(t, testDB.EntDriver.Close()) require.NoError(t, testDB.PGDriver.Close()) }) namespace := fmt.Sprintf("%s-%d", namespacePrefix, clock.Now().UnixNano()) db := testDB.EntDriver.Client() deps, err := InitDeps(db, omtestutils.NewDiscardLogger(t)) require.NoError(t, err) customerID := customer.CustomerID{ Namespace: namespace, ID: ulid.Make().String(), } _, err = db.Customer.Create(). SetNamespace(namespace). SetID(customerID.ID). SetName("Test Customer"). Save(t.Context()) require.NoError(t, err) customerAccounts, err := deps.ResolversService.CreateCustomerAccounts(t.Context(), customerID) require.NoError(t, err) businessAccounts, err := deps.ResolversService.EnsureBusinessAccounts(t.Context(), namespace) require.NoError(t, err) return &IntegrationEnv{ Namespace: namespace, CustomerID: customerID, Currency: currencyx.Code("USD"), DB: db, CustomerAccounts: customerAccounts, BusinessAccounts: businessAccounts, Deps: deps, } } func (e *IntegrationEnv) Now() time.Time { return clock.Now().UTC() } func (e *IntegrationEnv) FBOSubAccount(t *testing.T, priority int) ledger.SubAccount { t.Helper() subAccount, err := e.CustomerAccounts.FBOAccount.GetSubAccountForRoute(t.Context(), ledger.CustomerFBORouteParams{ Currency: e.Currency, CreditPriority: priority, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) ReceivableSubAccount(t *testing.T) ledger.SubAccount { t.Helper() return e.ReceivableSubAccountWithCostBasisAndStatus(t, nil, ledger.TransactionAuthorizationStatusOpen) } func (e *IntegrationEnv) ReceivableSubAccountWithStatus(t *testing.T, status ledger.TransactionAuthorizationStatus) ledger.SubAccount { t.Helper() return e.ReceivableSubAccountWithCostBasisAndStatus(t, nil, status) } func (e *IntegrationEnv) ReceivableSubAccountWithCostBasis(t *testing.T, costBasis *alpacadecimal.Decimal) ledger.SubAccount { t.Helper() return e.ReceivableSubAccountWithCostBasisAndStatus(t, costBasis, ledger.TransactionAuthorizationStatusOpen) } func (e *IntegrationEnv) ReceivableSubAccountWithCostBasisAndStatus(t *testing.T, costBasis *alpacadecimal.Decimal, status ledger.TransactionAuthorizationStatus) ledger.SubAccount { t.Helper() subAccount, err := e.CustomerAccounts.ReceivableAccount.GetSubAccountForRoute(t.Context(), ledger.CustomerReceivableRouteParams{ Currency: e.Currency, CostBasis: costBasis, TransactionAuthorizationStatus: status, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) AccruedSubAccount(t *testing.T) ledger.SubAccount { t.Helper() return e.AccruedSubAccountWithCostBasis(t, nil) } func (e *IntegrationEnv) AccruedSubAccountWithCostBasis(t *testing.T, costBasis *alpacadecimal.Decimal) ledger.SubAccount { t.Helper() return e.AccruedSubAccountWithCostBasisAndTaxCode(t, costBasis, nil) } func (e *IntegrationEnv) AccruedSubAccountWithCostBasisAndTaxCode(t *testing.T, costBasis *alpacadecimal.Decimal, taxCode *string) ledger.SubAccount { t.Helper() subAccount, err := e.CustomerAccounts.AccruedAccount.GetSubAccountForRoute(t.Context(), ledger.CustomerAccruedRouteParams{ Currency: e.Currency, TaxCode: taxCode, CostBasis: costBasis, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) WashSubAccount(t *testing.T) ledger.SubAccount { t.Helper() return e.WashSubAccountWithCostBasis(t, nil) } func (e *IntegrationEnv) WashSubAccountWithCostBasis(t *testing.T, costBasis *alpacadecimal.Decimal) ledger.SubAccount { t.Helper() subAccount, err := e.BusinessAccounts.WashAccount.GetSubAccountForRoute(t.Context(), ledger.BusinessRouteParams{ Currency: e.Currency, CostBasis: costBasis, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) EarningsSubAccount(t *testing.T) ledger.SubAccount { t.Helper() return e.EarningsSubAccountWithCostBasis(t, nil) } func (e *IntegrationEnv) EarningsSubAccountWithCostBasis(t *testing.T, costBasis *alpacadecimal.Decimal) ledger.SubAccount { t.Helper() return e.EarningsSubAccountWithCostBasisAndTaxCode(t, costBasis, nil) } func (e *IntegrationEnv) EarningsSubAccountWithCostBasisAndTaxCode(t *testing.T, costBasis *alpacadecimal.Decimal, taxCode *string) ledger.SubAccount { t.Helper() subAccount, err := e.BusinessAccounts.EarningsAccount.GetSubAccountForRoute(t.Context(), ledger.BusinessRouteParams{ Currency: e.Currency, CostBasis: costBasis, TaxCode: taxCode, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) BrokerageSubAccount(t *testing.T) ledger.SubAccount { t.Helper() subAccount, err := e.BusinessAccounts.BrokerageAccount.GetSubAccountForRoute(t.Context(), ledger.BusinessRouteParams{ Currency: e.Currency, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) BreakageSubAccountWithCostBasis(t *testing.T, costBasis *alpacadecimal.Decimal) ledger.SubAccount { t.Helper() subAccount, err := e.BusinessAccounts.BreakageAccount.GetSubAccountForRoute(t.Context(), ledger.BusinessRouteParams{ Currency: e.Currency, CostBasis: costBasis, }) require.NoError(t, err) return subAccount } func (e *IntegrationEnv) SumBalance(t *testing.T, subAccount ledger.SubAccount) alpacadecimal.Decimal { t.Helper() sum, err := e.Deps.HistoricalLedger.GetSubAccountBalance(t.Context(), subAccount, ledger.BalanceQuery{}) require.NoError(t, err) return sum }