package api import ( "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "time" "github.com/kami/hexis/internal/domain" "github.com/kami/hexis/internal/execution" "github.com/kami/hexis/internal/provider" "github.com/kami/hexis/internal/storage" ) func newTestHandler(t *testing.T) (*Handler, *storage.Store) { t.Helper() path := filepath.Join(t.TempDir(), "hexis.db") store, err := storage.Open(path) if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { store.Close() }) engine := execution.New(store, provider.NewRegistry()) return NewHandler(store, engine), store } // TestHandleChanges_SinceFiltersEvents verifies the `since` query param // actually filters to events after that sequence number, rather than being // silently reset to 0 (the bug: `since = 0` unconditionally, ignoring the // parsed value). func TestHandleChanges_SinceFiltersEvents(t *testing.T) { h, store := newTestHandler(t) for i := 0; i < 3; i++ { if err := store.AppendEvent(&domain.Event{ ID: domain.NewEventID(), Type: domain.EventCapabilityRegistered, Timestamp: time.Now().UTC(), }); err != nil { t.Fatalf("append event %d: %v", i, err) } } all, err := store.EventsAfter(0, 100) if err != nil { t.Fatalf("events after 0: %v", err) } if len(all) != 3 { t.Fatalf("expected 3 seed events, got %d", len(all)) } cutoff := all[0].Sequence req := httptest.NewRequest(http.MethodGet, "/api/v1/changes?since="+itoa(cutoff), nil) w := httptest.NewRecorder() h.handleChanges(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var events []*domain.Event if err := json.Unmarshal(w.Body.Bytes(), &events); err != nil { t.Fatalf("decode response: %v", err) } if len(events) != 2 { t.Fatalf("expected 2 events after sequence %d, got %d", cutoff, len(events)) } for _, e := range events { if e.Sequence <= cutoff { t.Errorf("event with sequence %d should have been excluded by since=%d", e.Sequence, cutoff) } } } func itoa(n int64) string { b, _ := json.Marshal(n) return string(b) }