78 lines
2.6 KiB
Go
78 lines
2.6 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"textmachine/backend/internal/obs"
|
|
)
|
|
|
|
// runner_readonly_test.go pins the operator-visibility contract (пакет №4): the
|
|
// read-only runner (tmctl status/report) must work WHILE a write runner owns the
|
|
// project — «оператор слеп ровно когда висит 300-я глава» was the top smoke
|
|
// finding, caused by status/report sharing the writer's exclusive flock.
|
|
|
|
// TestReadOnlyRunnerWorksDuringLiveRun: a NewReadOnlyRunner must open and project
|
|
// Status while a NewRunner (write path, flock held) is alive on the same book.
|
|
func TestReadOnlyRunnerWorksDuringLiveRun(t *testing.T) {
|
|
rec := &reqRec{}
|
|
srv := newJSONProvider(rec, draftEdit)
|
|
defer srv.Close()
|
|
bookPath := setupProject(t, srv.URL)
|
|
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
|
|
|
|
// The write runner owns the project (flock held for its whole lifetime) and
|
|
// has completed a run, so there are rows to project.
|
|
w := newRunner(t, bookPath)
|
|
defer w.Close()
|
|
if _, err := w.TranslateBook(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Simulates `tmctl status` from a second shell mid-run: the write runner is
|
|
// still open. The old store.Open path failed here with "project database is
|
|
// in use by another tmctl process".
|
|
ro, err := NewReadOnlyRunner(bookPath, obs.NewLogger())
|
|
if err != nil {
|
|
t.Fatalf("read-only runner must open during a live run: %v", err)
|
|
}
|
|
defer ro.Close()
|
|
|
|
rep, err := ro.Status(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rep.TotalChunks != 1 || rep.Done != 1 || rep.Flagged != 0 {
|
|
t.Fatalf("status projection over a live-locked book = %+v", rep)
|
|
}
|
|
if rep.CommittedUSD == 0 {
|
|
t.Fatal("status must see the writer's committed spend")
|
|
}
|
|
}
|
|
|
|
// TestReadOnlyRunnerFirstTouchCreates: before the first run there is no DB file —
|
|
// the read-only runner falls back to the creating Open (мигрирует пустую базу), so
|
|
// «status до первого прогона» keeps showing 0/N pending instead of erroring.
|
|
func TestReadOnlyRunnerFirstTouchCreates(t *testing.T) {
|
|
rec := &reqRec{}
|
|
srv := newJSONProvider(rec, draftEdit)
|
|
defer srv.Close()
|
|
bookPath := setupProject(t, srv.URL)
|
|
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
|
|
|
|
ro, err := NewReadOnlyRunner(bookPath, obs.NewLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ro.Close()
|
|
rep, err := ro.Status(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rep.TotalChunks != 1 || rep.Done != 0 || rep.Pending != 1 {
|
|
t.Fatalf("pre-run status = %+v", rep)
|
|
}
|
|
if rec.count() != 0 {
|
|
t.Fatalf("read-only status must make zero provider calls, got %d", rec.count())
|
|
}
|
|
}
|