package main import ( "os" "path/filepath" "strings" "testing" "textmachine/platform/internal/runner" ) // exit-marker runs INSIDE a run's transient unit, as ExecStopPost, where there is no database and no // credential to open one with. Reaching for the DSN there would fail every marker in production and // lose the only record of how the run ended — so the dispatch has to answer this command before it // reads a secret. Pinned here because the dispatch order is invisible at the call site. func TestTheExitMarkerCommandNeedsNoDatabase(t *testing.T) { t.Setenv("TM_PLATFORM_DSN", "") t.Setenv("TM_PLATFORM_DSN_FILE", "") t.Setenv("SERVICE_RESULT", "oom-kill") t.Setenv("EXIT_CODE", "killed") t.Setenv("EXIT_STATUS", "TERM") path := filepath.Join(t.TempDir(), "runs", "run_1-1.exit") if err := run([]string{"exit-marker", path, "tm-run-run_1-1"}, os.Stdout); err != nil { t.Fatalf("exit-marker with no DSN: %v", err) } m, err := runner.ReadMarker(path) if err != nil { t.Fatal(err) } if m.Result != "oom-kill" || m.Code != "killed" || m.Status != "TERM" || m.Unit != "tm-run-run_1-1" { t.Errorf("marker: %+v", m) } // Every other command still needs one, or it would silently do nothing. if err := run([]string{"balance", "--user", "u1"}, os.Stdout); err == nil { t.Error("a database command ran without a DSN") } } func TestTheExitMarkerCommandNeedsBothArguments(t *testing.T) { t.Setenv("TM_PLATFORM_DSN", "") for _, args := range [][]string{{"exit-marker"}, {"exit-marker", "only-a-path"}} { if err := run(append([]string{}, args...), os.Stdout); err == nil { t.Errorf("%v was accepted", args) } } } // The development intake is what puts a book in the read model until the contract's upload exists. // It refuses a directory that is not an engine project, because the alternative is a run that dies // at argument parsing inside a transient unit, where the only trace is "exit-code 1". func TestAddingABookRefusesADirectoryTheEngineCannotUse(t *testing.T) { dir := t.TempDir() args := []string{"--user", "u1", "--workdir", dir, "--title", "b", "--source-lang", "zh", "--target-lang", "ru", "--chapters", "10", "--characters", "23000000"} _, err := parseBookIntake(args) if err == nil || !strings.Contains(err.Error(), "engine project directory") { t.Fatalf("a directory without book.yaml gave %v", err) } if err := os.WriteFile(filepath.Join(dir, runner.ConfigFile), []byte("book_id: x\n"), 0o600); err != nil { t.Fatal(err) } got, err := parseBookIntake(args) if err != nil { t.Fatalf("a valid project directory was refused: %v", err) } if got.ChapterCount != 10 || got.CharacterCount != 23_000_000 || got.OwnerID != "u1" { t.Errorf("intake: %+v", got) } if !filepath.IsAbs(got.Workdir) { t.Errorf("the workdir was not made absolute: %q — the unit runs with its own working directory", got.Workdir) } } // Chapters are what bound the run-ceiling scale, so a book registered without them would offer a // scale of zero and no run could ever start on it. func TestAddingABookRequiresTheFactsTheScaleIsBuiltFrom(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, runner.ConfigFile), []byte("book_id: x\n"), 0o600); err != nil { t.Fatal(err) } full := []string{"--user", "u1", "--workdir", dir, "--title", "b", "--source-lang", "zh", "--target-lang", "ru", "--chapters", "10"} drop := func(flag string) []string { out := []string{} for i := 0; i < len(full); i += 2 { if full[i] != flag { out = append(out, full[i], full[i+1]) } } return out } for _, flag := range []string{"--user", "--workdir", "--title", "--source-lang", "--target-lang", "--chapters"} { if _, err := parseBookIntake(drop(flag)); err == nil { t.Errorf("a book without %s was accepted", flag) } } }