paymentsdb: added unit test for computePaymentStatusFromResolutions
What changed, and why it matters
This commit only adds a new unit test file for an existing internal function. It does not change any production code, fix a bug, or alter behavior. There is no security issue here.
No action required. This is a test-only addition and does not affect production security posture.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces payments/db/sql_store_test.go containing table-driven unit tests for computePaymentStatusFromResolutions. The function maps SQL resolution types and a failure reason to a PaymentStatus enum. The test cases cover in-flight, settled, failed, mixed, initiated, and error states. No implementation code is modified.
Changed components
payments/db/sql_store_test.goInspect captured patch +229 / −0
diff --git a/payments/db/sql_store_test.go b/payments/db/sql_store_test.go
new file mode 100644
index 0000000..0f4f310
--- /dev/null
+++ b/payments/db/sql_store_test.go
@@ -0,0 +1,229 @@
+//go:build test_db_sqlite || test_db_postgres
+
+package paymentsdb
+
+import (
+ "database/sql"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+// TestComputePaymentStatus tests the SQL to domain type conversion logic in
+// computePaymentStatusFromResolutions. This is a pure unit test with no
+// database interaction. However the function is only used in the SQL store and
+// used sql data types so we test it in a sql specific file.
+func TestComputePaymentStatus(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ resolutionTypes []sql.NullInt32
+ failReason sql.NullInt32
+ expectedStatus PaymentStatus
+ expectError bool
+ }{
+ {
+ name: "all NULL resolutions means in-flight",
+ resolutionTypes: []sql.NullInt32{
+ {Valid: false}, // NULL = in-flight
+ {Valid: false},
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusInFlight,
+ },
+ {
+ name: "settled resolution without fail reason",
+ resolutionTypes: []sql.NullInt32{{
+ Int32: int32(HTLCAttemptResolutionSettled),
+ Valid: true,
+ }},
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusSucceeded,
+ },
+ {
+ name: "failed resolution without fail reason",
+ resolutionTypes: []sql.NullInt32{{
+ Int32: int32(HTLCAttemptResolutionFailed),
+ Valid: true,
+ }},
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusInFlight,
+ },
+ {
+ name: "failed resolution with fail reason",
+ resolutionTypes: []sql.NullInt32{{
+ Int32: int32(HTLCAttemptResolutionFailed),
+ Valid: true,
+ }},
+ failReason: sql.NullInt32{
+ Int32: int32(FailureReasonNoRoute),
+ Valid: true,
+ },
+ expectedStatus: StatusFailed,
+ },
+ {
+ name: "mixed: in-flight and settled",
+ resolutionTypes: []sql.NullInt32{
+ {Valid: false}, // in-flight
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusInFlight,
+ },
+ {
+ name: "mixed: in-flight and failed",
+ resolutionTypes: []sql.NullInt32{
+ {Valid: false}, // in-flight
+ {
+ Int32: int32(
+ HTLCAttemptResolutionFailed,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusInFlight,
+ },
+ {
+ name: "mixed: settled and failed",
+ resolutionTypes: []sql.NullInt32{
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionFailed,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusSucceeded,
+ },
+ {
+ name: "no resolutions, no fail reason, " +
+ "means initiated",
+ resolutionTypes: []sql.NullInt32{},
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusInitiated,
+ },
+ {
+ name: "no resolutions with fail reason, " +
+ "means failed",
+ resolutionTypes: []sql.NullInt32{},
+ failReason: sql.NullInt32{
+ Int32: int32(FailureReasonNoRoute),
+ Valid: true,
+ },
+ expectedStatus: StatusFailed,
+ },
+ {
+ name: "unknown resolution type returns error",
+ resolutionTypes: []sql.NullInt32{
+ {Int32: 999, Valid: true}, // invalid type
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectError: true,
+ },
+ {
+ name: "all three states: in-flight, settled, failed",
+ resolutionTypes: []sql.NullInt32{
+ {
+ Valid: false, // in-flight
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionFailed,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{
+ Int32: int32(FailureReasonTimeout),
+ Valid: true,
+ },
+ expectedStatus: StatusInFlight,
+ },
+ {
+ name: "multiple settled HTLCs",
+ resolutionTypes: []sql.NullInt32{
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionSettled,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{Valid: false},
+ expectedStatus: StatusSucceeded,
+ },
+ {
+ name: "multiple failed HTLCs with fail reason",
+ resolutionTypes: []sql.NullInt32{
+ {
+ Int32: int32(
+ HTLCAttemptResolutionFailed,
+ ),
+ Valid: true,
+ },
+ {
+ Int32: int32(
+ HTLCAttemptResolutionFailed,
+ ),
+ Valid: true,
+ },
+ },
+ failReason: sql.NullInt32{
+ Int32: int32(FailureReasonNoRoute),
+ Valid: true,
+ },
+ expectedStatus: StatusFailed,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ status, err := computePaymentStatusFromResolutions(
+ tc.resolutionTypes, tc.failReason,
+ )
+
+ if tc.expectError {
+ require.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+ require.Equal(t, tc.expectedStatus, status,
+ "got %s, want %s", status, tc.expectedStatus)
+ })
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.