v1.2.0: skip unchanged writes, transactional schema/root counters, snapshot replay for meta:events 0-0 (drops boot republish), collect-once search driver
Build and Push Docker Image / build (push) Successful in 7m38s
Build and Push Docker Image / build (push) Successful in 7m38s
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// syncRecorder is a ResponseRecorder safe to read while the handler writes.
|
||||
type syncRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (r *syncRecorder) Write(b []byte) (int, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.ResponseRecorder.Write(b)
|
||||
}
|
||||
|
||||
func (r *syncRecorder) body() string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.Body.String()
|
||||
}
|
||||
|
||||
// Last-Event-ID 0-0 on meta:events returns the whole current state — one set
|
||||
// per record key, only the last carrying an id — then the live tail.
|
||||
func TestMetaEventsSnapshotThenTail(t *testing.T) {
|
||||
stor := newTestStorage(t)
|
||||
s := &Server{storage: stor}
|
||||
for i, id := range []string{"R1", "R2"} {
|
||||
if err := stor.SetMetadataFlat(id, map[string]string{"title": "T", "n": string(rune('a' + i))}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/events/meta", nil).WithContext(ctx)
|
||||
req.Header.Set("Last-Event-ID", "0-0")
|
||||
rec := &syncRecorder{ResponseRecorder: httptest.NewRecorder()}
|
||||
done := make(chan struct{})
|
||||
go func() { s.handleEventsMeta(rec, req); close(done) }()
|
||||
|
||||
waitFor := func(substr string) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for !strings.Contains(rec.body(), substr) {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("timed out waiting for %q in:\n%s", substr, rec.body())
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
waitFor(`"key":"file:R2/title"`)
|
||||
if err := stor.SetProperty("R1", "title", "changed"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitFor(`event: set`)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
cancel()
|
||||
<-done
|
||||
|
||||
body := rec.body()
|
||||
for _, k := range []string{"file:R1/title", "file:R1/n", "file:R2/title", "file:R2/n"} {
|
||||
if !strings.Contains(body, `"key":"`+k+`"`) {
|
||||
t.Errorf("snapshot is missing %s", k)
|
||||
}
|
||||
}
|
||||
events := strings.Split(strings.TrimSpace(body), "\n\n")
|
||||
if len(events) != 5 {
|
||||
t.Fatalf("want 4 snapshot events + 1 live event, got %d:\n%s", len(events), body)
|
||||
}
|
||||
for i, e := range events[:3] {
|
||||
if strings.Contains(e, "id: ") {
|
||||
t.Errorf("snapshot event %d carries an id: %q", i, e)
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(events[3], "id: ") || !strings.HasPrefix(events[4], "id: ") {
|
||||
t.Fatalf("the last snapshot event and the live event must carry ids:\n%s", body)
|
||||
}
|
||||
if !strings.Contains(events[4], `"key":"file:R1/title"`) {
|
||||
t.Fatalf("live tail did not follow the snapshot: %q", events[4])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user