diff --git a/internal/json_store.go b/internal/json_store.go index ad43c91..f0268ce 100644 --- a/internal/json_store.go +++ b/internal/json_store.go @@ -96,11 +96,9 @@ func (fs *JSONStore) Load(ctx context.Context) (map[string]*FillerQueue, error) // the same directory followed by an atomic rename so a crash mid-write cannot // leave a half-written state file. func (fs *JSONStore) Save(ctx context.Context, queue map[string]*FillerQueue) error { - state := State{ - Version: StateVersion, - Platform: fs.platform, - Queues: make(map[string][]persistedFiller), - } + queues := make(map[string][]persistedFiller) + latest := time.Time{} + earliest := time.Time{} for symbol, q := range queue { if q == nil || q.Len() == 0 { @@ -119,8 +117,23 @@ func (fs *JSONStore) Save(ctx context.Context, queue map[string]*FillerQueue) er Price: f.Price(), Filled: f.Filled(), }) + + if f.Timestamp().After(latest) { + latest = f.Timestamp() + } + if f.Timestamp().Before(earliest) || earliest.IsZero() { + earliest = f.Timestamp() + } } - state.Queues[symbol] = persisted + queues[symbol] = persisted + } + + state := State{ + Version: StateVersion, + Platform: fs.platform, + Queues: queues, + Latest: latest, + Earliest: earliest, } buf := &bytes.Buffer{} diff --git a/internal/json_store_test.go b/internal/json_store_test.go index 6ec0539..93856fe 100644 --- a/internal/json_store_test.go +++ b/internal/json_store_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/nmoniz/any2anexoj/internal" "github.com/nmoniz/any2anexoj/internal/mocks" @@ -84,13 +85,25 @@ func TestJSONStore_RoundTrip(t *testing.T) { // confirming the inline form is parseable guards against accidentally // double-encoding it again in the future). var parsed struct { - Queues map[string][]struct { + Latest time.Time `json:"latest"` + Earliest time.Time `json:"earliest"` + Queues map[string][]struct { RecordData json.RawMessage `json:"record_data"` } `json:"queues"` } if err := json.Unmarshal(data, &parsed); err != nil { t.Fatalf("saved state file is not valid JSON: %v", err) } + + // The latest/earliest bounds must be persisted alongside the queues. + // Every record in this test returns testRecordTime, so both bounds must + // equal it exactly. + if !parsed.Latest.Equal(testRecordTime) { + t.Errorf("state latest = %v; want %v", parsed.Latest, testRecordTime) + } + if !parsed.Earliest.Equal(testRecordTime) { + t.Errorf("state earliest = %v; want %v", parsed.Earliest, testRecordTime) + } for symbol, lots := range parsed.Queues { for i, lot := range lots { if len(lot.RecordData) == 0 { @@ -341,13 +354,20 @@ func TestNewJSONStore_ValidatesArguments(t *testing.T) { } } -// newRecord builds a MockRecord whose Symbol() returns the given symbol. -// The JSONStore only reads Symbol() off the loaded Record during tests -// (quantity/price/filled come from the persisted struct fields), so all -// other Record methods can be left as default-mocked values. +// testRecordTime is the deterministic timestamp returned by every record +// built in this file. Using a fixed value keeps the latest/earliest bounds +// written by JSONStore.Save predictable and assertable. +var testRecordTime = time.Date(2024, 6, 15, 12, 0, 0, 0, time.UTC) + +// newRecord builds a MockRecord whose Symbol() returns the given symbol and +// whose Timestamp() returns testRecordTime. The JSONStore only reads Symbol() +// and Timestamp() off the Record during tests (quantity/price/filled come +// from the persisted struct fields), so all other Record methods can be left +// as default-mocked values. func newRecord(ctrl *gomock.Controller, symbol string) *mocks.MockRecord { r := mocks.NewMockRecord(ctrl) r.EXPECT().Symbol().Return(symbol).AnyTimes() + r.EXPECT().Timestamp().Return(testRecordTime).AnyTimes() return r } diff --git a/internal/state.go b/internal/state.go index 10cb8c2..53ae36b 100644 --- a/internal/state.go +++ b/internal/state.go @@ -3,6 +3,7 @@ package internal import ( "context" "encoding/json" + "time" "github.com/shopspring/decimal" ) @@ -15,6 +16,8 @@ const StateVersion = "2" type State struct { Version string `json:"version"` Platform string `json:"platform"` + Latest time.Time `json:"latest"` + Earliest time.Time `json:"earliest"` Queues map[string][]persistedFiller `json:"queues"` }