Skip to content

feat(credit): integration tests#4007

Merged
GAlexIHU merged 3 commits into
mainfrom
feat/credit-integration-tests
Mar 27, 2026
Merged

feat(credit): integration tests#4007
GAlexIHU merged 3 commits into
mainfrom
feat/credit-integration-tests

Conversation

@GAlexIHU
Copy link
Copy Markdown
Contributor

@GAlexIHU GAlexIHU commented Mar 25, 2026

Overview

Negative balance will come in a separate PR as its way too large. Related extra tests / corrections will also be included in that one.

I we want we can use this PR to improve on test tooling, but that can also be an increment

Summary by CodeRabbit

Release Notes

  • New Features

    • Added transaction authorization status support for ledger sub-accounts, enabling staged authorization of customer receivables during payment processing
    • Implemented payment settlement transaction processing for flat-fee and credit purchase charge types
  • Improvements

    • Enhanced ledger filtering and balance tracking by authorization status
    • Improved charge handler implementations with full settlement workflow
  • Documentation

    • Updated command execution guidance to prefer direct invocation over shell wrappers

@GAlexIHU GAlexIHU added the release-note/misc Miscellaneous changes label Mar 25, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR introduces transaction authorization status (open and authorized) to ledger routing and settlement. It adds schema support, implements staged payment authorization with split receivable sub-accounts, integrates new routing validation rules, and completes previously-stubbed charge settlement handlers using the new authorization transitions.

Changes

Cohort / File(s) Summary
Documentation Guidance
.agents/skills/charges/SKILL.md, .agents/skills/ledger/SKILL.md, .agents/skills/rebase/SKILL.md, .agents/skills/test/SKILL.md, AGENTS.md
Updated command execution guidance to prefer running commands directly (avoiding sh -lc/bash -lc wrapping) and to use env KEY=value <command> pattern for environment variables.
Schema & Migrations
openmeter/ent/schema/ledger_account.go, tools/migrate/migrations/20260326163949_*.up.sql, tools/migrate/migrations/20260326163949_*.down.sql
Added nullable transaction_authorization_status column to ledger_sub_account_routes table and corresponding Go schema field with immutable/nillable properties.
Core Ledger Types
openmeter/ledger/routing.go, openmeter/ledger/routing_test.go, openmeter/ledger/primitives.go, openmeter/ledger/errors.go
Introduced TransactionAuthorizationStatus type (open, authorized), extended Route and RouteFilter to include authorization status, updated routing key generation, and added validation error constants.
Routing Rules & Validation
openmeter/ledger/routingrules/routingrules.go, openmeter/ledger/routingrules/routingrules_test.go, openmeter/ledger/routingrules/defaults.go, openmeter/ledger/routingrules/view.go
Added RequireAccountAuthorizationStatusRule and RequireReceivableAuthorizationStageRule to enforce authorization transitions; updated default validator rules to include new checks.
Transaction Templates
openmeter/ledger/transactions/customer.go, openmeter/ledger/transactions/customer_test.go, openmeter/ledger/transactions/accrual.go, openmeter/ledger/transactions/testenv_test.go
Added SettleCustomerReceivablePaymentTemplate for payment settlement; updated existing templates (Issue, Fund, Cover, Transfer) to pass explicit authorization statuses; integrated settlement into test environment flow.
Charge Adapters & Handlers
openmeter/ledger/chargeadapter/creditpurchase.go, openmeter/ledger/chargeadapter/creditpurchase_test.go, openmeter/ledger/chargeadapter/flatfee.go, openmeter/ledger/chargeadapter/flatfee_test.go
Implemented OnCreditPurchasePaymentSettled and OnPaymentSettled (previously stubbed); added credit realization generation; updated test expectations for authorization staging flow with split receivable sub-accounts.
Ledger Adapters & Queries
openmeter/ledger/historical/adapter/ledger.go, openmeter/ledger/historical/adapter/sumentries_query.go, openmeter/ledger/testutils/integration.go
Extended transaction history and sub-account routing queries to include authorization status filtering; added test helper ReceivableSubAccountWithStatus for authorization-scoped lookups.
Account Routing
openmeter/ledger/account/adapter/subaccount.go, openmeter/ledger/account/adapter/repo_test.go, openmeter/ledger/accounts.go
Updated route creation/persistence to handle authorization status; extended route filtering predicates; added test cases for authorization-status-filtered sub-account queries; updated CustomerReceivableRouteParams to require authorization status.
Test Infrastructure & Removal
test/credits/mockledger.go, test/credits/sanity_test.go
Deleted mock ledger test double; rewrote CreditsTestSuite to use real ledger-backed environment with new balance query helpers and settlement flow assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ChargeHandler as Charge Handler
    participant Ledger
    participant RoutingRules as Routing & Rules
    participant DB as Database

    rect rgba(100, 150, 200, 0.5)
      Note over Client,DB: Authorization Flow
      Client->>ChargeHandler: OnPaymentAuthorized(charge)
      ChargeHandler->>Ledger: CommitGroup([Issue + Fund + Fund(Authorized)])
      Ledger->>RoutingRules: ValidateEntries()
      RoutingRules->>DB: Check authorization transition
      DB-->>RoutingRules: Valid (open→authorized split)
      RoutingRules-->>Ledger: OK
      Ledger->>DB: Store with split receivables
      Ledger-->>ChargeHandler: TransactionGroupID
    end

    rect rgba(150, 100, 200, 0.5)
      Note over Client,DB: Settlement Flow
      Client->>ChargeHandler: OnPaymentSettled(charge)
      ChargeHandler->>Ledger: CommitGroup([Settle(authorized→open)])
      Ledger->>RoutingRules: ValidateEntries()
      RoutingRules->>DB: Verify authorization state
      DB-->>RoutingRules: Valid (authorized being settled)
      RoutingRules-->>Ledger: OK
      Ledger->>DB: Move from authorized to open receivable
      Ledger-->>ChargeHandler: TransactionGroupID
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #3963: Earlier ledger charge routing changes that this PR extends with authorization status handling in the same chargeadapter/routing code paths.
  • PR #3920: Removes test/credits/mockledger.go that this PR directly replaces with real ledger-backed test infrastructure in sanity_test.go.
  • PR #3896: Implements flat-fee payment settlement (OnPaymentSettled) and ledger handler patterns that this PR parallels for credit purchases.

Suggested labels

area/billing

Suggested reviewers

  • turip
  • mark-vass-konghq
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feat(credit): integration tests' is vague and overly broad—it doesn't capture the main substance of the changes, which include implementing transaction authorization logic, database migrations, routing rules, and substantial ledger adapter implementations beyond just adding tests. Consider a more specific title that reflects the primary changes, such as 'feat(credit): implement transaction authorization with ledger integration' or 'feat(credit): transaction authorization and payment settlement'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/credit-integration-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@GAlexIHU GAlexIHU marked this pull request as ready for review March 25, 2026 16:53
@GAlexIHU GAlexIHU requested a review from a team as a code owner March 25, 2026 16:53
turip
turip previously approved these changes Mar 26, 2026
Copy link
Copy Markdown
Member

@turip turip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!

return ledgertransaction.GroupReference{}, err
}

externalSettlement, err := charge.Intent.Settlement.AsExternalSettlement()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we also have InvoiceSettlement, we should add support for that too eventually.

type TransactionAuthorizationStatus string

const (
TransactionAuthorizationStatusOpen TransactionAuthorizationStatus = "open"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can consider naming this pending just so that we have parity with the API

// Payment has been initiated and is not yet authorized.
Pending: "pending",
(i would also need to make adjustments to charges eventually on naming front)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
openmeter/ledger/account/adapter/repo_test.go (1)

230-309: Consider adding a uniqueness constraint test for authorization status.

The TestRepo_SubAccountRouteUniquenessConstraints tests cover priority and cost basis uniqueness, but don't exercise the new transaction_authorization_status field. Since this field is part of the routing key (and thus the unique constraint), it'd be worth adding a test case that verifies two routes with the same currency but different authorization statuses can coexist, and that duplicate authorization statuses on the same account are rejected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/account/adapter/repo_test.go` around lines 230 - 309, Add a
subtest to TestRepo_SubAccountRouteUniquenessConstraints exercising the
transaction_authorization_status part of the routing key: extend the createRoute
helper to accept a transactionAuthorizationStatus parameter and set it on the
ent builder (use the SetNillableTransactionAuthorizationStatus / appropriate
setter on env.client.LedgerSubAccountRoute.Create()), then add a t.Run that
creates two routes on the same account with the same currency and routing key
except differing authorization statuses (assert both succeed) and then attempts
to create a duplicate with the same authorization status (assert it fails and
entdb.IsConstraintError is true); keep references to the existing
TestRepo_SubAccountRouteUniquenessConstraints and createRoute helper so the
change is localized.
openmeter/ledger/routingrules/routingrules.go (1)

218-232: Consider documenting the zero-amount entry behavior.

Entries with zero amounts are silently excluded from both negative and positive slices. This is probably intentional (zero doesn't indicate a direction), but a brief comment would clarify for future readers.

📝 Optional: Add a clarifying comment
+// entriesBySign partitions entries into negative and positive amounts.
+// Zero-amount entries are excluded as they don't indicate a flow direction.
 func entriesBySign(entries []EntryView) ([]EntryView, []EntryView) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/routingrules/routingrules.go` around lines 218 - 232, The
function entriesBySign currently drops zero-amount entries by only appending
entries where entry.Amount().IsNegative() or IsPositive(); add a concise comment
inside entriesBySign explaining that zero-amount entries are intentionally
omitted (zero has no sign) so readers know this behavior is deliberate and not a
bug, referencing the function name entriesBySign and the
Amount().IsNegative/IsPositive checks.
openmeter/ledger/chargeadapter/flatfee_test.go (1)

202-240: Consider adding an error-path test for double-settlement.

The happy paths are well covered, but it might be worth adding a test that calls OnPaymentSettled twice on the same charge to verify idempotency or proper error handling. Based on learnings, the customer-scoped lock prevents concurrent execution, but sequential double-calls could still be a concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/chargeadapter/flatfee_test.go` around lines 202 - 240, Add a
test that verifies calling env.handler.OnPaymentSettled twice for the same
charge is handled correctly: create an env (newFlatFeeHandlerTestEnv), accrue
usage (env.newAccrualInput / env.newChargeWithAccruedUsage), call
handler.OnPaymentSettled once and assert success and expected
balances/TransactionGroupID, then call handler.OnPaymentSettled a second time
with the same charge and assert the system either returns a descriptive "already
settled" error from OnPaymentSettled or returns a no-op reference (empty
TransactionGroupID) and leaves all account balances unchanged; reference
handler.OnPaymentSettled, env.newChargeWithAccruedUsage (or
env.newChargeWithCreditRealizationsAndAccruedUsage), and the helper methods
sumBalance/receivableSubAccount/authorizedReceivableSubAccount/washSubAccount to
locate assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/ledger/routing.go`:
- Line 248: The new routing key string now includes
"transaction_authorization_status" which breaks the existing V1 format; revert
introducing this field into the V1 key and instead implement explicit
versioning: add a new constant (e.g., RoutingKeyVersionV2) and produce the new
key format only when version == RoutingKeyVersionV2 while preserving the
original V1 serialization in the code that builds keys (the code that currently
concatenates fields like "currency" and "credit_priority"); update
NewSubAccountRouteFromData (or the constructor/parser that reads routing keys)
to detect and parse both V1 and V2 formats (fall back to V1 parsing for existing
keys and parse the extra transaction_authorization_status for V2), so existing
persisted keys remain compatible without migration.

In
`@tools/migrate/migrations/20260326163949_add_ledger_transaction_authorization_status.up.sql`:
- Line 2: The migration adds
ledger_sub_account_routes.transaction_authorization_status as nullable which
will leave existing rows NULL and create divergent routing keys vs. the new
TransactionAuthorizationStatusOpen values passed by the accrual flow; fix by
either (A) modifying the migration to backfill existing rows to 'open' and set a
DEFAULT 'open' (UPDATE ledger_sub_account_routes SET
transaction_authorization_status='open' WHERE transaction_authorization_status
IS NULL; then ALTER ... SET DEFAULT), or (B) keep it nullable but add
application-side normalization in the route lookup used by accrual (treat NULL
as TransactionAuthorizationStatusOpen) so routing keys collapse; choose one
approach and implement the corresponding change referencing
transaction_authorization_status, TransactionAuthorizationStatusOpen, and the
accrual flow that produces the new status.

---

Nitpick comments:
In `@openmeter/ledger/account/adapter/repo_test.go`:
- Around line 230-309: Add a subtest to
TestRepo_SubAccountRouteUniquenessConstraints exercising the
transaction_authorization_status part of the routing key: extend the createRoute
helper to accept a transactionAuthorizationStatus parameter and set it on the
ent builder (use the SetNillableTransactionAuthorizationStatus / appropriate
setter on env.client.LedgerSubAccountRoute.Create()), then add a t.Run that
creates two routes on the same account with the same currency and routing key
except differing authorization statuses (assert both succeed) and then attempts
to create a duplicate with the same authorization status (assert it fails and
entdb.IsConstraintError is true); keep references to the existing
TestRepo_SubAccountRouteUniquenessConstraints and createRoute helper so the
change is localized.

In `@openmeter/ledger/chargeadapter/flatfee_test.go`:
- Around line 202-240: Add a test that verifies calling
env.handler.OnPaymentSettled twice for the same charge is handled correctly:
create an env (newFlatFeeHandlerTestEnv), accrue usage (env.newAccrualInput /
env.newChargeWithAccruedUsage), call handler.OnPaymentSettled once and assert
success and expected balances/TransactionGroupID, then call
handler.OnPaymentSettled a second time with the same charge and assert the
system either returns a descriptive "already settled" error from
OnPaymentSettled or returns a no-op reference (empty TransactionGroupID) and
leaves all account balances unchanged; reference handler.OnPaymentSettled,
env.newChargeWithAccruedUsage (or
env.newChargeWithCreditRealizationsAndAccruedUsage), and the helper methods
sumBalance/receivableSubAccount/authorizedReceivableSubAccount/washSubAccount to
locate assertions.

In `@openmeter/ledger/routingrules/routingrules.go`:
- Around line 218-232: The function entriesBySign currently drops zero-amount
entries by only appending entries where entry.Amount().IsNegative() or
IsPositive(); add a concise comment inside entriesBySign explaining that
zero-amount entries are intentionally omitted (zero has no sign) so readers know
this behavior is deliberate and not a bug, referencing the function name
entriesBySign and the Amount().IsNegative/IsPositive checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2daf512e-46af-4aed-b6cb-d90c7644fb8b

📥 Commits

Reviewing files that changed from the base of the PR and between ce94176 and 5d9da66.

⛔ Files ignored due to path filters (8)
  • openmeter/ent/db/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (32)
  • .agents/skills/charges/SKILL.md
  • .agents/skills/ledger/SKILL.md
  • .agents/skills/rebase/SKILL.md
  • .agents/skills/test/SKILL.md
  • AGENTS.md
  • openmeter/ent/schema/ledger_account.go
  • openmeter/ledger/account/adapter/repo_test.go
  • openmeter/ledger/account/adapter/subaccount.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase_test.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/errors.go
  • openmeter/ledger/historical/adapter/ledger.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/routing_test.go
  • openmeter/ledger/routingrules/defaults.go
  • openmeter/ledger/routingrules/routingrules.go
  • openmeter/ledger/routingrules/routingrules_test.go
  • openmeter/ledger/routingrules/view.go
  • openmeter/ledger/testutils/integration.go
  • openmeter/ledger/transactions/accrual.go
  • openmeter/ledger/transactions/customer.go
  • openmeter/ledger/transactions/customer_test.go
  • openmeter/ledger/transactions/testenv_test.go
  • test/credits/mockledger.go
  • test/credits/sanity_test.go
  • tools/migrate/migrations/20260326163949_add_ledger_transaction_authorization_status.down.sql
  • tools/migrate/migrations/20260326163949_add_ledger_transaction_authorization_status.up.sql
💤 Files with no reviewable changes (1)
  • test/credits/mockledger.go

Comment thread openmeter/ledger/routing.go
@GAlexIHU GAlexIHU merged commit 39ce034 into main Mar 27, 2026
24 checks passed
@GAlexIHU GAlexIHU deleted the feat/credit-integration-tests branch March 27, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants