invoices/sql_store: add tests for targeted invoice query variants

Add testFetchPendingInvoicesAccepted to explicitly verify the state
filtering behaviour of FetchPendingInvoices across all four contract
states:

  ContractOpen     (state 0) – must be returned
  ContractAccepted (state 3) – must be returned
  ContractSettled  (state 1) – must be excluded
  ContractCanceled (state 2) – must be excluded

This directly exercises the `state IN (0, 3)` predicate introduced in
the FetchPendingInvoices SQL query and addresses the review request for
explicit test coverage of the new targeted query variants. The test
runs against the KV, SQLite, and Postgres backends.
This commit is contained in:
ziggie committed 2026-02-21 17:05:44 +01:00
1 parent 9becdfa8a3
commit d26bf84843
1 file changed
+113
+113
View File
@@ -166,6 +166,10 @@ func TestInvoices(t *testing.T) {
name: "FetchPendingInvoices",
test: testFetchPendingInvoices,
},
{
name: "FetchPendingInvoicesAccepted",
test: testFetchPendingInvoicesAccepted,
},
{
name: "DuplicateSettleInvoice",
test: testDuplicateSettleInvoice,
@@ -1288,6 +1292,115 @@ func testFetchPendingInvoices(t *testing.T,
require.Equal(t, pendingInvoices, pending)
}
// testFetchPendingInvoicesAccepted verifies that FetchPendingInvoices returns
// invoices in both ContractOpen (state 0) and ContractAccepted (state 3)
// states, and that ContractSettled (state 1) and ContractCanceled (state 2)
// invoices are excluded. This specifically exercises the `state IN (0, 3)`
// predicate in the underlying SQL query.
func testFetchPendingInvoicesAccepted(t *testing.T,
makeDB func(t *testing.T) invpkg.InvoiceDB) {
t.Parallel()
db := makeDB(t)
ctxb := t.Context()
amt := lnwire.MilliSatoshi(1000)
// Add an invoice that stays in ContractOpen state.
openInvoice, err := randInvoice(amt)
require.NoError(t, err)
openHash := openInvoice.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(ctxb, openInvoice, openHash)
require.NoError(t, err)
// Add a second invoice and transition it to ContractAccepted by
// adding an HTLC while setting the new invoice state in a single
// UpdateInvoice call (addHTLCs processes the HTLC list before
// validating the state transition, so the empty-set check passes).
acceptedInvoice, err := randInvoice(amt)
require.NoError(t, err)
acceptedHash := acceptedInvoice.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(ctxb, acceptedInvoice, acceptedHash)
require.NoError(t, err)
acceptKey := models.CircuitKey{HtlcID: 1}
acceptRef := invpkg.InvoiceRefByHash(acceptedHash)
addHtlcs := map[models.CircuitKey]*invpkg.HtlcAcceptDesc{
acceptKey: {
Amt: amt,
CustomRecords: make(
record.CustomSet,
),
},
}
dbAccepted, err := db.UpdateInvoice(
ctxb, acceptRef, nil,
func(inv *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.AddHTLCsUpdate,
State: &invpkg.InvoiceStateUpdateDesc{
NewState: invpkg.ContractAccepted,
},
AddHtlcs: addHtlcs,
}, nil
},
)
require.NoError(t, err)
require.Equal(t, invpkg.ContractAccepted, dbAccepted.State)
// Add a settled invoice – it must NOT appear in the pending result.
settledInvoice, err := randInvoice(amt)
require.NoError(t, err)
settledHash := settledInvoice.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(ctxb, settledInvoice, settledHash)
require.NoError(t, err)
_, err = db.UpdateInvoice(
ctxb, invpkg.InvoiceRefByHash(settledHash), nil,
getUpdateInvoice(2, amt),
)
require.NoError(t, err)
// Add a canceled invoice – it must also NOT appear in the pending
// result, verifying that state 2 (ContractCanceled) is excluded by
// the `state IN (0, 3)` SQL predicate.
canceledInvoice, err := randInvoice(amt)
require.NoError(t, err)
canceledHash := canceledInvoice.Terms.PaymentPreimage.Hash()
_, err = db.AddInvoice(ctxb, canceledInvoice, canceledHash)
require.NoError(t, err)
_, err = db.UpdateInvoice(
ctxb, invpkg.InvoiceRefByHash(canceledHash), nil,
func(inv *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.CancelInvoiceUpdate,
State: &invpkg.InvoiceStateUpdateDesc{
NewState: invpkg.ContractCanceled,
},
}, nil
},
)
require.NoError(t, err)
// FetchPendingInvoices must return exactly the two pending invoices.
pending, err := db.FetchPendingInvoices(ctxb)
require.NoError(t, err)
require.Len(t, pending, 2)
_, hasOpen := pending[openHash]
require.True(t, hasOpen, "ContractOpen invoice missing from results")
_, hasAccepted := pending[acceptedHash]
require.True(t, hasAccepted, "ContractAccepted invoice missing")
require.NotContains(t, pending, settledHash,
"ContractSettled invoice should not appear in pending results")
require.NotContains(t, pending, canceledHash,
"ContractCanceled invoice should not appear in pending results")
require.Equal(t, invpkg.ContractOpen, pending[openHash].State)
require.Equal(t, invpkg.ContractAccepted, pending[acceptedHash].State)
}
// testDuplicateSettleInvoice tests that if we add a new invoice and settle it
// twice, then the second time we also receive the invoice that we settled as a
// return argument.