82 lines
2.8 KiB
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)
|
|
}
|
|
}
|
|
}
|