test: payment coverage — gift card daily caps, buildSplitRecords, ValidateAmount edge cases, SquareRefundStatusToLocal

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-08-22 00:34:51 +01:00
co-authored by Sisyphus
parent 4d179385e8
commit 509b2d926d
4 changed files with 849 additions and 0 deletions
@@ -0,0 +1,121 @@
//go:build test && dev
package payments
import (
"fmt"
"testing"
)
// TestValidateAmount_ExtraEdgeCases extends the existing ValidateAmount coverage
// with additional boundary and edge cases for the money-validation function.
func TestValidateAmount_ExtraEdgeCases(t *testing.T) {
t.Parallel()
tests := []struct {
amount int64
wantErr bool
desc string
}{
// Zero and negative — must be rejected
{0, true, "zero is not allowed"},
{-1, true, "negative one penny"},
{-100, true, "negative £1"},
{-1000000, true, "negative £10,000 (must be rejected early, not overflow)"},
// Normal positive amounts — must pass
{1, false, "minimum valid: 1 penny"},
{50, false, "50p"},
{100, false, "£1"},
{5000, false, "£50"},
{50000, false, "£500"},
{100000, false, "£1,000"},
{500000, false, "£5,000"},
{999999, false, "£9,999.99 — just under max"},
{1000000, false, "£10,000 exactly — boundary allowed"},
// Above maximum — must be rejected
{1000001, true, "£10,000.01 — one penny over max"},
{1000050, true, "£10,000.50 — 50p over max"},
{2000000, true, "£20,000 — double max"},
{99999999, true, "very large amount"},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s (%d)", tt.desc, tt.amount), func(t *testing.T) {
err := ValidateAmount(tt.amount)
if tt.wantErr {
if err == nil {
t.Errorf("expected error for amount %d (%s), got nil", tt.amount, tt.desc)
}
} else {
if err != nil {
t.Errorf("unexpected error for amount %d (%s): %v", tt.amount, tt.desc, err)
}
}
})
}
}
// TestValidateAmount_ErrorMessage locks the error messages for the two
// rejection paths so callers depending on string matching do not silently
// break.
func TestValidateAmount_ErrorMessage(t *testing.T) {
t.Parallel()
if err := ValidateAmount(0); err == nil || err.Error() != "amount must be greater than 0" {
t.Errorf("zero amount error message mismatch: %v", err)
}
if err := ValidateAmount(1000001); err == nil || err.Error() != "amount exceeds maximum (£10,000)" {
t.Errorf("over-max error message mismatch: %v", err)
}
}
// TestValidatePartialAmount_ErrorMessage locks the error message format.
func TestValidatePartialAmount_ErrorMessage(t *testing.T) {
t.Parallel()
err := ValidatePartialAmount(5000, 2500)
if err == nil {
t.Fatal("expected error for partial exceeding remaining")
}
expected := "partial amount (£50.00) exceeds remaining balance (£25.00)"
if err.Error() != expected {
t.Errorf("error message mismatch:\n got: %s\n want: %s", err.Error(), expected)
}
}
// TestValidatePaymentType_AllValidTypes verifies every entry in the
// validPaymentTypes map is accepted and unknown types are rejected.
func TestValidatePaymentType_AllValidTypes(t *testing.T) {
t.Parallel()
valid := []string{"deposit", "full", "tip", "balance", "partial"}
for _, pt := range valid {
if err := ValidatePaymentType(pt); err != nil {
t.Errorf("expected valid payment type %q to be accepted, got: %v", pt, err)
}
}
invalid := []string{"", "unknown", "deposits", "FULL", "gift_card"}
for _, pt := range invalid {
if err := ValidatePaymentType(pt); err == nil {
t.Errorf("expected invalid payment type %q to be rejected", pt)
}
}
}
// TestValidateRefundReason_Empty verifies an empty refund reason is rejected.
func TestValidateRefundReason_Empty(t *testing.T) {
t.Parallel()
if err := ValidateRefundReason(""); err == nil {
t.Error("expected an error for empty refund reason")
}
if err := ValidateRefundReason("customer changed mind"); err != nil {
t.Errorf("unexpected error for non-empty refund reason: %v", err)
}
if err := ValidateRefundReason(" "); err != nil {
t.Errorf("whitespace-only is arguably a reason — should not error: %v", err)
}
}