//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) } }