From 8f6cefcd68540f984ae916a456d19c37f464635d Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Sat, 1 Aug 2026 16:31:30 +0100 Subject: [PATCH] stronger kind tests and validation --- internal/kind.go | 16 ++++-- internal/kind_test.go | 125 +++++++++++++++++++++++++++--------------- internal/report.go | 2 + 3 files changed, 93 insertions(+), 50 deletions(-) diff --git a/internal/kind.go b/internal/kind.go index 50c8af0..9eee9a4 100644 --- a/internal/kind.go +++ b/internal/kind.go @@ -7,11 +7,12 @@ const ( KindBuy KindSell KindSplit + sentinelKind ) -// String returns a human readable value -func (d Kind) String() string { - switch d { +// String returns a unique string value for Kind k +func (k Kind) String() string { + switch k { case KindBuy: return "buy" case KindSell: @@ -23,8 +24,13 @@ func (d Kind) String() string { } } -// Is returns true when k equals o +// Valid returns true if k is an accepted value for the Kind type +func (k Kind) Valid() bool { + return k > 0 && k < sentinelKind +} + +// Is returns true when k and o are valid and equal. func (k Kind) Is(o any) bool { other, ok := o.(Kind) - return ok && k == other + return ok && k.Valid() && k == other } diff --git a/internal/kind_test.go b/internal/kind_test.go index a52d7e6..c5af02b 100644 --- a/internal/kind_test.go +++ b/internal/kind_test.go @@ -1,60 +1,95 @@ package internal -import "testing" +import ( + "fmt" + "testing" +) func TestSide_String(t *testing.T) { - tests := []struct { - name string - side Kind - want string - }{ - {"buy", KindBuy, "buy"}, - {"sell", KindSell, "sell"}, - {"unknown", KindUnknown, "unknown"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.side.String(); got != tt.want { - t.Errorf("want Side.String() to be %v but got %v", tt.want, got) + const unknown = "unknown" + + seen := make(map[string]Kind, sentinelKind) + for k := Kind(1); k < sentinelKind; k++ { + t.Run(fmt.Sprintf("Kind %d", k), func(t *testing.T) { + str := k.String() + + if other, ok := seen[str]; ok { + t.Errorf("want Kind(%d).String to be unique but was a duplicate of Kind(%d)", k, other) + } else { + seen[str] = k + } + + if len(str) == 0 { + t.Errorf("want Kind(%d).String to be non-empty", k) + } + + if str == unknown { + t.Errorf("want Kind(%d).String to be a known value", k) } }) } + + if KindUnknown.String() != unknown { + t.Errorf("want Kind(0) to be unknown") + } + + if Kind(sentinelKind).String() != unknown { + t.Errorf("want Kind(%d) to be unknown", sentinelKind) + } } -func TestSide_IsBuy(t *testing.T) { - tests := []struct { - name string - side Kind - want bool - }{ - {"buy", KindBuy, true}, - {"sell", KindSell, false}, - {"unknown", KindUnknown, false}, +func TestSide_Valid(t *testing.T) { + for k := Kind(1); k < sentinelKind; k++ { + if !k.Valid() { + t.Errorf("want %s(%d) to be valid", k, k) + } } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.side.Is(KindBuy); got != tt.want { - t.Errorf("want Side.IsBuy() to be %v but got %v", tt.want, got) - } - }) + + if KindUnknown.Valid() { + t.Errorf("want Kind(0) to be invalid") + } + + if Kind(sentinelKind).Valid() { + t.Errorf("want Kind(%d) to be invalid", sentinelKind) } } -func TestSide_IsSell(t *testing.T) { - tests := []struct { - name string - side Kind - want bool - }{ - {"buy", KindBuy, false}, - {"sell", KindSell, true}, - {"unknown", KindUnknown, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.side.Is(KindSell); got != tt.want { - t.Errorf("want Side.IsSell() to be %v but got %v", tt.want, got) +func TestSide_Is(t *testing.T) { + t.Run("valid is self", func(t *testing.T) { + for k := Kind(1); k < sentinelKind; k++ { + if !k.Is(k) { + t.Errorf("want Kind(%d).Is(%d) to be true", k, k) } - }) - } + } + }) + + t.Run("valid is unknown", func(t *testing.T) { + for k := Kind(1); k < sentinelKind; k++ { + if k.Is(KindUnknown) { + t.Errorf("want Kind(%d).Is(0) to be false", k) + } + + if k.Is(sentinelKind) { + t.Errorf("want Kind(%d).Is(%d) to be false", k, sentinelKind) + } + + if k.Is(struct{}{}) { + t.Errorf("want Kind(%d).Is(other type) to be false", k) + } + } + }) + + t.Run("unknown is unknown", func(t *testing.T) { + if KindUnknown.Is(KindUnknown) { + t.Errorf("want Kind(0).Is(0) to be false") + } + + if KindUnknown.Is(sentinelKind) { + t.Errorf("want Kind(0).Is(%d) to be false", sentinelKind) + } + + if sentinelKind.Is(sentinelKind) { + t.Errorf("want Kind(%d).Is(%d) to be false", sentinelKind, sentinelKind) + } + }) } diff --git a/internal/report.go b/internal/report.go index eb0e6b0..4368653 100644 --- a/internal/report.go +++ b/internal/report.go @@ -132,6 +132,8 @@ func BuildReport(ctx context.Context, reader RecordReader, writer ReportWriter, buysCount++ } else if rec.Kind().Is(KindSell) { sellsCount++ + } else if !rec.Kind().Valid() { + return fmt.Errorf("cannot process Kind(%d)", rec.Kind()) } lastTimestamp = rec.Timestamp()