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