handle some edge cases
Generate check / check-changes (pull_request) Successful in 41s
Quality / check-changes (pull_request) Successful in 42s
Generate check / verify-generate (pull_request) Successful in 48s
Quality / run-tests (pull_request) Successful in 49s

This commit is contained in:
2026-08-02 16:13:03 +01:00
parent 5fe47746fa
commit 18edd7d171
3 changed files with 75 additions and 5 deletions
+6 -3
View File
@@ -50,6 +50,9 @@ func (fs *FileStore) Load(ctx context.Context) (map[string]*FillerQueue, error)
} }
return nil, fmt.Errorf("reading state file: %w", err) return nil, fmt.Errorf("reading state file: %w", err)
} }
if len(data) == 0 {
return make(map[string]*FillerQueue), nil
}
var s State var s State
if err := json.Unmarshal(data, &s); err != nil { if err := json.Unmarshal(data, &s); err != nil {
@@ -74,7 +77,7 @@ func (fs *FileStore) Load(ctx context.Context) (map[string]*FillerQueue, error)
for symbol, persisted := range s.Queues { for symbol, persisted := range s.Queues {
q := new(FillerQueue) q := new(FillerQueue)
for _, pf := range persisted { for _, pf := range persisted {
rec, err := fs.serializer.UnmarshalRecord(ctx, pf.ReaderData) rec, err := fs.serializer.UnmarshalRecord(ctx, pf.RecordData)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"unmarshalling record for symbol %q: %w", symbol, err, "unmarshalling record for symbol %q: %w", symbol, err,
@@ -98,7 +101,7 @@ func (fs *FileStore) Save(ctx context.Context, queue map[string]*FillerQueue) er
} }
for symbol, q := range queue { for symbol, q := range queue {
if q == nil { if q == nil || q.Len() == 0 {
continue continue
} }
var persisted []persistedFiller var persisted []persistedFiller
@@ -109,7 +112,7 @@ func (fs *FileStore) Save(ctx context.Context, queue map[string]*FillerQueue) er
return fmt.Errorf("marshalling record for symbol %q: %w", symbol, err) return fmt.Errorf("marshalling record for symbol %q: %w", symbol, err)
} }
persisted = append(persisted, persistedFiller{ persisted = append(persisted, persistedFiller{
ReaderData: data, RecordData: data,
Quantity: f.Quantity(), Quantity: f.Quantity(),
Price: f.Price(), Price: f.Price(),
Filled: f.Filled(), Filled: f.Filled(),
+68 -1
View File
@@ -19,7 +19,7 @@ import (
func TestFileStore_RoundTrip(t *testing.T) { func TestFileStore_RoundTrip(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
ser := roundTripSerializer(ctrl) ser := roundTripSerializer(ctrl)
store, _ := newStore(t, "fake", ser) store, path := newStore(t, "fake", ser)
original := map[string]*internal.FillerQueue{ original := map[string]*internal.FillerQueue{
"AAA": newQueue( "AAA": newQueue(
@@ -50,6 +50,73 @@ func TestFileStore_RoundTrip(t *testing.T) {
} }
assertQueueEqual(t, symbol, gotQ, wantQ) assertQueueEqual(t, symbol, gotQ, wantQ)
} }
// Regression: the on-disk JSON key for the per-record blob must be
// "record_data" (renamed from "reader_data") so the field name stays
// honest about what it carries.
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read state file: %v", err)
}
if strings.Contains(string(data), `"reader_data"`) {
t.Errorf("saved state file still contains legacy key \"reader_data\"; want only \"record_data\"")
}
if !strings.Contains(string(data), `"record_data"`) {
t.Errorf("saved state file does not contain expected key \"record_data\"")
}
}
func TestFileStore_SaveSkipsEmptyQueue(t *testing.T) {
ctrl := gomock.NewController(t)
ser := roundTripSerializer(ctrl)
store, path := newStore(t, "fake", ser)
// A non-nil FillerQueue whose underlying list is nil — i.e. a symbol
// that was registered but never received a Push. Save must not panic
// when iterating and must not emit any entry for that symbol.
queues := map[string]*internal.FillerQueue{
"EMPTY": new(internal.FillerQueue),
"REAL": newQueue(newFiller(ctrl, "REAL", 5, 10, 0)),
}
if err := store.Save(t.Context(), queues); err != nil {
t.Fatalf("Save returned unexpected error: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read state file: %v", err)
}
body := string(data)
if strings.Contains(body, `"EMPTY"`) {
t.Errorf("saved state file contains entry for empty queue \"EMPTY\"; want it skipped")
}
if !strings.Contains(body, `"REAL"`) {
t.Errorf("saved state file missing expected entry for \"REAL\"")
}
}
func TestFileStore_LoadEmptyFileReturnsEmpty(t *testing.T) {
ctrl := gomock.NewController(t)
ser := roundTripSerializer(ctrl)
store, path := newStore(t, "fake", ser)
// Pre-create the state file as a zero-byte file. Load must treat this
// the same as a missing file rather than failing JSON unmarshal.
if err := os.WriteFile(path, []byte{}, 0o644); err != nil {
t.Fatalf("write empty state file: %v", err)
}
queues, err := store.Load(t.Context())
if err != nil {
t.Fatalf("Load returned unexpected error for empty file: %v", err)
}
if queues == nil {
t.Fatalf("Load returned nil map; want empty map")
}
if len(queues) != 0 {
t.Fatalf("Load returned %d entries; want 0", len(queues))
}
} }
func TestFileStore_LoadMissingFileReturnsEmpty(t *testing.T) { func TestFileStore_LoadMissingFileReturnsEmpty(t *testing.T) {
+1 -1
View File
@@ -21,7 +21,7 @@ type State struct {
// Record-specific data is kept as opaque bytes so the internal package does // Record-specific data is kept as opaque bytes so the internal package does
// not need to know about any broker package's concrete Record type. // not need to know about any broker package's concrete Record type.
type persistedFiller struct { type persistedFiller struct {
ReaderData []byte `json:"reader_data"` RecordData []byte `json:"record_data"`
Quantity decimal.Decimal `json:"quantity"` Quantity decimal.Decimal `json:"quantity"`
Price decimal.Decimal `json:"price"` Price decimal.Decimal `json:"price"`
Filled decimal.Decimal `json:"filled"` Filled decimal.Decimal `json:"filled"`