textmachine/platform/internal/money/money_test.go

82 lines
2.8 KiB
Go

package money
import "testing"
// The whole point of the type is that these answers are exact. Mutation caught: implementing
// ParseUSD with strconv.ParseFloat and a multiplication.
func TestParseUSDIsExactAndRoundsUp(t *testing.T) {
cases := map[string]MicroUSD{
"0": 0,
"5": 5 * PerUSD,
"2.50": 2_500_000,
"0.1": 100_000, // 0.1 has no float64 representation; 0.1*1e6 is 100000.00000000001
"29.7": 29_700_000,
"0.000001": 1,
"0.0000001": 1, // a tenth of a micro-dollar still costs one
"1e-6": 1,
"-2.5": -2_500_000,
// Up means toward +infinity on BOTH sides of zero, which is what "never undercharge"
// means when the amount is a debit: a fraction of a micro-dollar owed is not owed.
"-0.0000001": 0,
"-1.9999999": -1_999_999,
" 1.25 ": 1_250_000,
"123456.7891": 123_456_789_100,
}
for in, want := range cases {
got, err := ParseUSD(in)
if err != nil {
t.Fatalf("ParseUSD(%q): %v", in, err)
}
if got != want {
t.Fatalf("ParseUSD(%q) = %d, want %d", in, got, want)
}
}
}
func TestParseUSDRefusesNonsense(t *testing.T) {
for _, in := range []string{"", "free", "5 dollars", "1e30", "0x10"} {
if got, err := ParseUSD(in); err == nil {
t.Fatalf("ParseUSD(%q) = %d, want an error", in, got)
}
}
}
// PD-79. Only the JSON literal null is absence. Everything that merely LOOKS like it — the quoted
// word, the empty string — is a sender who had no figure, and reading any of them as zero is how a
// missing cost becomes "the attempt was free" on the settlement path.
//
// The literal is judged before the quotes come off, and the value is left untouched rather than set:
// what "absent" means is the caller's business (the seam binds this to a POINTER), not a number this
// package invents. Mutation caught: trimming the quotes first, or treating the trimmed word as null.
func TestUnmarshalTellsTheNullLiteralFromTheWordNull(t *testing.T) {
for _, raw := range []string{`"null"`, `""`, `" "`, `"NULL"`} {
m := MicroUSD(-7) // a value nothing should overwrite
if err := m.UnmarshalJSON([]byte(raw)); err == nil {
t.Fatalf("UnmarshalJSON(%s) = %d, want an error", raw, int64(m))
}
if m != -7 {
t.Fatalf("UnmarshalJSON(%s) refused and still wrote %d", raw, int64(m))
}
}
m := MicroUSD(-7)
if err := m.UnmarshalJSON([]byte("null")); err != nil {
t.Fatalf("the JSON literal null: %v", err)
}
if m != -7 {
t.Fatalf("the literal null overwrote the value with %d", int64(m))
}
}
func TestUSDRendersForOperatorsOnly(t *testing.T) {
cases := map[MicroUSD]string{
0: "0.000000",
5 * PerUSD: "5.000000",
1: "0.000001",
-2_500_000: "-2.500000",
}
for in, want := range cases {
if got := in.USD(); got != want {
t.Fatalf("%d.USD() = %q, want %q", int64(in), got, want)
}
}
}