handle some edge cases
This commit is contained in:
@@ -50,6 +50,9 @@ func (fs *FileStore) Load(ctx context.Context) (map[string]*FillerQueue, error)
|
||||
}
|
||||
return nil, fmt.Errorf("reading state file: %w", err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return make(map[string]*FillerQueue), nil
|
||||
}
|
||||
|
||||
var s State
|
||||
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 {
|
||||
q := new(FillerQueue)
|
||||
for _, pf := range persisted {
|
||||
rec, err := fs.serializer.UnmarshalRecord(ctx, pf.ReaderData)
|
||||
rec, err := fs.serializer.UnmarshalRecord(ctx, pf.RecordData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"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 {
|
||||
if q == nil {
|
||||
if q == nil || q.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
}
|
||||
persisted = append(persisted, persistedFiller{
|
||||
ReaderData: data,
|
||||
RecordData: data,
|
||||
Quantity: f.Quantity(),
|
||||
Price: f.Price(),
|
||||
Filled: f.Filled(),
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func TestFileStore_RoundTrip(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
ser := roundTripSerializer(ctrl)
|
||||
store, _ := newStore(t, "fake", ser)
|
||||
store, path := newStore(t, "fake", ser)
|
||||
|
||||
original := map[string]*internal.FillerQueue{
|
||||
"AAA": newQueue(
|
||||
@@ -50,6 +50,73 @@ func TestFileStore_RoundTrip(t *testing.T) {
|
||||
}
|
||||
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) {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ type State struct {
|
||||
// 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.
|
||||
type persistedFiller struct {
|
||||
ReaderData []byte `json:"reader_data"`
|
||||
RecordData []byte `json:"record_data"`
|
||||
Quantity decimal.Decimal `json:"quantity"`
|
||||
Price decimal.Decimal `json:"price"`
|
||||
Filled decimal.Decimal `json:"filled"`
|
||||
|
||||
Reference in New Issue
Block a user