package runner import ( "bytes" "errors" "go/parser" "go/printer" "go/token" "os" "os/exec" "path/filepath" "strings" "testing" ) // The intake channel is the one engine command the platform runs before a book has ever been // translated. These tests run a REAL process — a shell script standing in for tmctl — because what // the caller decides on is the difference between "the engine answered no" and "the engine could not // be run", and that difference is a type from os/exec rather than a value the platform chooses. // fakeEngine writes an executable that behaves as told and returns its path. func fakeEngine(t *testing.T, script string) string { t.Helper() path := filepath.Join(t.TempDir(), "tmctl") if err := os.WriteFile(path, []byte("#!/bin/sh\n"+script+"\n"), 0o755); err != nil { t.Fatal(err) } return path } func TestTheManifestCommandIsTheZeroCostOneAndCarriesTheBooksConfig(t *testing.T) { got := ManifestArgs("/srv/books/bk_1") want := []string{"manifest", "--config", "/srv/books/bk_1/book.yaml", "--json"} if strings.Join(got, " ") != strings.Join(want, " ") { t.Fatalf("argv %v, want %v", got, want) } } func TestAManifestIsReadForTheFourThingsIntakeDecidesWith(t *testing.T) { // A document with far more in it than the platform takes: the per-chapter and per-unit arrays are // the engine's and have no reader on this side. bin := fakeEngine(t, `cat <<'JSON' {"manifest_version":"tm-manifest-v2","book_id":"gu","key":"abcdef", "chunker_version":"chunk-2026.07","source_sha256":"`+strings.Repeat("ab", 32)+`", "source_bytes":24117248,"source_lang":"zh","target_lang":"ru","encoding":"utf-8", "chapters_total":2283,"units_total":4402,"chunks_total":9130, "chapters":[{"id":"0011223344556677","number":1,"heading":"Глава 1","units_total":2, "chunks_total":4,"units":[{"id":"0011223344556677:aabbccdd:0","first_chunk_idx":0, "chunk_count":2,"edit_unit_id":1}]}]} JSON`) r := New(nil) m, err := r.Manifest(t.Context(), bin, t.TempDir()) if err != nil { t.Fatal(err) } if m.ChaptersTotal != 2283 || m.UnitsTotal != 4402 { t.Errorf("counts %d/%d", m.ChaptersTotal, m.UnitsTotal) } if m.Version != "tm-manifest-v2" || m.ChunkerVersion != "chunk-2026.07" { t.Errorf("identity %q/%q", m.Version, m.ChunkerVersion) } if len(m.SourceSHA256Bytes()) != 32 { t.Errorf("the source digest did not decode: %q", m.SourceSHA256) } } // The classifier the intake keys on: a process that RAN and refused carries *exec.ExitError, and one // that could not be run does not. Getting this backwards either retries a book forever or rejects a // user's file because a host was misconfigured. func TestARefusalAndAnAbsenceAreDifferentErrors(t *testing.T) { r := New(nil) refused := fakeEngine(t, `echo "chunk: ingest txt: not a book" >&2; exit 1`) _, err := r.Manifest(t.Context(), refused, t.TempDir()) var exit *exec.ExitError if !errors.As(err, &exit) { t.Fatalf("a refusal came back as %T: %v", err, err) } _, err = r.Manifest(t.Context(), filepath.Join(t.TempDir(), "no-such-binary"), t.TempDir()) if errors.As(err, &exit) { t.Fatalf("a missing binary came back as an exit status: %v", err) } // The engine's own words stay out of the platform's error, which is what a product phrase is // eventually written from (contract §Problem). if _, err := r.Manifest(t.Context(), refused, t.TempDir()); !strings.Contains(err.Error(), "tmctl manifest") { t.Errorf("the error does not name what failed: %v", err) } } func TestAManifestThatIsNotJSONIsAnError(t *testing.T) { r := New(nil) bin := fakeEngine(t, `echo "=== MANIFEST: gu ==="`) if _, err := r.Manifest(t.Context(), bin, t.TempDir()); err == nil { t.Fatal("a human summary was accepted as a manifest document") } } // FP5-7 (acceptance): the gate on these tests must ask whether this host CAN run them, not whether // systemd phrased its refusal the way it did in 2026. It skipped on "Failed to connect to bus" and // systemd 259 answers "Failed to connect to user scope bus", so on a host with no user manager the // tests failed instead of skipping — a gate that stopped gating when somebody else edited a string. func TestTheSystemdGateAsksAboutTheCapabilityAndNotAMessage(t *testing.T) { src, err := os.ReadFile("systemd_test.go") if err != nil { t.Fatal(err) } // The CODE only: the comments there explain the history of this very defect and quote the message // the gate used to match, which a plain text scan would read as the defect itself. file, err := parser.ParseFile(token.NewFileSet(), "systemd_test.go", src, 0) if err != nil { t.Fatal(err) } var code bytes.Buffer if err := printer.Fprint(&code, token.NewFileSet(), file); err != nil { t.Fatal(err) } // ⚠ The assertion is on the WORDS systemd could change, not on one spelling of the old check: // pinning two exact expressions is a gate that a third spelling walks straight past (found by // cross-family review of the acceptance dofix). Any occurrence of systemd's refusal text in this // file means the gate is reading messages again. if bytes.Contains(code.Bytes(), []byte("Failed to connect")) || bytes.Contains(code.Bytes(), []byte("No medium found")) { t.Fatal("the systemd gate matches a message again: it must skip on the failure to reach the manager, whatever systemd calls it") } if !bytes.Contains(code.Bytes(), []byte("systemctl")) { t.Fatal("the systemd gate no longer asks the manager anything: a gate that probes nothing cannot know whether these tests can run") } }