Internal state persistence #27

Merged
natercio merged 15 commits from internal-state-persistence into main 2026-08-03 00:33:07 +01:00
3 changed files with 93 additions and 50 deletions
Showing only changes of commit 8f6cefcd68 - Show all commits
+11 -5
View File
@@ -7,11 +7,12 @@ const (
KindBuy KindBuy
KindSell KindSell
KindSplit KindSplit
sentinelKind
) )
// String returns a human readable value // String returns a unique string value for Kind k
func (d Kind) String() string { func (k Kind) String() string {
switch d { switch k {
case KindBuy: case KindBuy:
return "buy" return "buy"
case KindSell: 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 { func (k Kind) Is(o any) bool {
other, ok := o.(Kind) other, ok := o.(Kind)
return ok && k == other return ok && k.Valid() && k == other
} }
+80 -45
View File
@@ -1,60 +1,95 @@
package internal package internal
import "testing" import (
"fmt"
"testing"
)
func TestSide_String(t *testing.T) { func TestSide_String(t *testing.T) {
tests := []struct { const unknown = "unknown"
name string
side Kind seen := make(map[string]Kind, sentinelKind)
want string for k := Kind(1); k < sentinelKind; k++ {
}{ t.Run(fmt.Sprintf("Kind %d", k), func(t *testing.T) {
{"buy", KindBuy, "buy"}, str := k.String()
{"sell", KindSell, "sell"},
{"unknown", KindUnknown, "unknown"}, if other, ok := seen[str]; ok {
} t.Errorf("want Kind(%d).String to be unique but was a duplicate of Kind(%d)", k, other)
for _, tt := range tests { } else {
t.Run(tt.name, func(t *testing.T) { seen[str] = k
if got := tt.side.String(); got != tt.want { }
t.Errorf("want Side.String() to be %v but got %v", tt.want, got)
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) { func TestSide_Valid(t *testing.T) {
tests := []struct { for k := Kind(1); k < sentinelKind; k++ {
name string if !k.Valid() {
side Kind t.Errorf("want %s(%d) to be valid", k, k)
want bool }
}{
{"buy", KindBuy, true},
{"sell", KindSell, false},
{"unknown", KindUnknown, false},
} }
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { if KindUnknown.Valid() {
if got := tt.side.Is(KindBuy); got != tt.want { t.Errorf("want Kind(0) to be invalid")
t.Errorf("want Side.IsBuy() to be %v but got %v", tt.want, got) }
}
}) if Kind(sentinelKind).Valid() {
t.Errorf("want Kind(%d) to be invalid", sentinelKind)
} }
} }
func TestSide_IsSell(t *testing.T) { func TestSide_Is(t *testing.T) {
tests := []struct { t.Run("valid is self", func(t *testing.T) {
name string for k := Kind(1); k < sentinelKind; k++ {
side Kind if !k.Is(k) {
want bool t.Errorf("want Kind(%d).Is(%d) to be true", k, k)
}{
{"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)
} }
}) }
} })
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)
}
})
} }
+2
View File
@@ -132,6 +132,8 @@ func BuildReport(ctx context.Context, reader RecordReader, writer ReportWriter,
buysCount++ buysCount++
} else if rec.Kind().Is(KindSell) { } else if rec.Kind().Is(KindSell) {
sellsCount++ sellsCount++
} else if !rec.Kind().Valid() {
return fmt.Errorf("cannot process Kind(%d)", rec.Kind())
} }
lastTimestamp = rec.Timestamp() lastTimestamp = rec.Timestamp()