package miro import ( "bytes" "errors" "os" "path/filepath" "testing" ) func TestRecordCreatesRelativePath(t *testing.T) { root := t.TempDir() testDir := filepath.Join(root, "e2e") mustMkdirAll(t, testDir) addFakeRecordDependencies(t, "script") got := withWorkingDir(t, root, func() string { withStdin(t, "y\n", func() {}) path, err := Record(filepath.Join("a", "b", "c") + string(os.PathSeparator)) if err != nil { t.Fatalf("Record() error = %v", err) } return path }) want := filepath.Join(testDir, "a", "b", "c") if got != want { t.Fatalf("Record() = %q, want %q", got, want) } for _, name := range []string{"in", "out"} { if _, err := os.Stat(filepath.Join(want, name)); err != nil { t.Fatalf("Stat(%q) error = %v", filepath.Join(want, name), err) } } if got := readFile(t, filepath.Join(want, "in")); got != "fake recorded input\n" { t.Fatalf("saved in = %q, want %q", got, "fake recorded input\n") } if got := readFile(t, filepath.Join(want, "out")); got != "fake recorded output\n" { t.Fatalf("saved out = %q, want %q", got, "fake recorded output\n") } } func TestRecordStripsExplicitTestDirPrefix(t *testing.T) { root := t.TempDir() testDir := filepath.Join(root, "e2e") mustMkdirAll(t, testDir) addFakeRecordDependencies(t, "script") got := withWorkingDir(t, root, func() string { withStdin(t, "y\n", func() {}) path, err := Record(filepath.Join("e2e", "a", "b", "c")) if err != nil { t.Fatalf("Record() error = %v", err) } return path }) want := filepath.Join(testDir, "a", "b", "c") if got != want { t.Fatalf("Record() = %q, want %q", got, want) } for _, name := range []string{"in", "out"} { if _, err := os.Stat(filepath.Join(want, name)); err != nil { t.Fatalf("Stat(%q) error = %v", filepath.Join(want, name), err) } } if got := readFile(t, filepath.Join(want, "out")); got != "fake recorded output\n" { t.Fatalf("saved out = %q, want %q", got, "fake recorded output\n") } } func TestRecordRejectsAbsolutePathOutsideTestDir(t *testing.T) { root := t.TempDir() mustMkdirAll(t, filepath.Join(root, "e2e")) outside := filepath.Join(root, "outside", "a", "b", "c") err := withWorkingDir(t, root, func() error { _, err := Record(outside) return err }) if err == nil { t.Fatal("Record() error = nil, want error") } if _, statErr := os.Stat(outside); !os.IsNotExist(statErr) { t.Fatalf("Stat(%q) error = %v, want not exists", outside, statErr) } } func TestRecordReturnsDiscardedErrorWhenSaveDeclined(t *testing.T) { root := t.TempDir() testDir := filepath.Join(root, "e2e") mustMkdirAll(t, testDir) addFakeRecordDependencies(t, "script") err := withWorkingDir(t, root, func() error { target := filepath.Join(testDir, "a", "b", "c") mustMkdirAll(t, target) return withRecordStreams(t, "n\n", func(rio recordIO) error { return recordScenario(target, rio) }) }) if !errors.Is(err, ErrRecordingDiscarded) { t.Fatalf("Record() error = %v, want ErrRecordingDiscarded", err) } target := filepath.Join(testDir, "a", "b", "c") for _, name := range []string{"in", "out"} { if _, err := os.Stat(filepath.Join(target, name)); !os.IsNotExist(err) { t.Fatalf("Stat(%q) error = %v, want not exists", filepath.Join(target, name), err) } } } func TestRecordReturnsDiscardedErrorWhenOverwriteDeclined(t *testing.T) { root := t.TempDir() testDir := filepath.Join(root, "e2e") target := filepath.Join(testDir, "a", "b", "c") mustMkdirAll(t, target) addFakeRecordDependencies(t, "script") writeFile(t, filepath.Join(target, "in"), "existing in\n") writeFile(t, filepath.Join(target, "out"), "existing out\n") err := withWorkingDir(t, root, func() error { return withRecordStreams(t, "n\n", func(rio recordIO) error { return recordScenario(target, rio) }) }) if !errors.Is(err, ErrRecordingDiscarded) { t.Fatalf("recordScenario() error = %v, want ErrRecordingDiscarded", err) } for _, tc := range []struct { name string want string }{ {name: "in", want: "existing in\n"}, {name: "out", want: "existing out\n"}, } { got, readErr := os.ReadFile(filepath.Join(target, tc.name)) if readErr != nil { t.Fatalf("ReadFile(%q) error = %v", filepath.Join(target, tc.name), readErr) } if string(got) != tc.want { t.Fatalf("%s = %q, want %q", tc.name, string(got), tc.want) } } } func withRecordStreams[T any](t *testing.T, input string, fn func(recordIO) T) T { t.Helper() path := filepath.Join(t.TempDir(), "stdin.txt") if err := os.WriteFile(path, []byte(input), 0o644); err != nil { t.Fatalf("WriteFile(%q) error = %v", path, err) } file, err := os.Open(path) if err != nil { t.Fatalf("Open(%q) error = %v", path, err) } t.Cleanup(func() { if err := file.Close(); err != nil { t.Fatalf("close record input: %v", err) } }) return fn(recordIO{ in: file, out: ioDiscard{}, err: &bytes.Buffer{}, }) } func withStdin(t *testing.T, input string, fn func()) { t.Helper() path := filepath.Join(t.TempDir(), "stdin.txt") if err := os.WriteFile(path, []byte(input), 0o644); err != nil { t.Fatalf("WriteFile(%q) error = %v", path, err) } file, err := os.Open(path) if err != nil { t.Fatalf("Open(%q) error = %v", path, err) } oldStdin := os.Stdin os.Stdin = file t.Cleanup(func() { os.Stdin = oldStdin if err := file.Close(); err != nil { t.Fatalf("close stdin file: %v", err) } }) fn() } func readFile(t *testing.T, path string) string { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile(%q) error = %v", path, err) } return string(data) } type ioDiscard struct{} func (ioDiscard) Write(p []byte) (int, error) { return len(p), nil } func addFakeRecordDependencies(t *testing.T, names ...string) { t.Helper() binDir := t.TempDir() for _, name := range names { path := filepath.Join(binDir, name) body := "#!/bin/sh\nexit 0\n" if name == "script" { body = "#!/bin/sh\nin=''\nout=''\nwhile [ \"$#\" -gt 0 ]; do\n case \"$1\" in\n -I)\n in=\"$2\"\n shift 2\n ;;\n -O)\n out=\"$2\"\n shift 2\n ;;\n *)\n shift\n ;;\n esac\ndone\nif [ -n \"${FAKE_SCRIPT_LOG_IN+x}\" ]; then\n printf '%s' \"$FAKE_SCRIPT_LOG_IN\" > \"$in\"\nelse\n cat <<'EOF' > \"$in\"\nfake recorded input\nEOF\nfi\nif [ -n \"${FAKE_SCRIPT_LOG_OUT+x}\" ]; then\n printf '%s' \"$FAKE_SCRIPT_LOG_OUT\" > \"$out\"\nelse\n cat <<'EOF' > \"$out\"\nScript started on 2026-03-18 11:13:38+00:00 [TERM=\"xterm-256color\"]\nfake recorded output\nScript done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE=\"0\"]\nEOF\nfi\nexit 0\n" } if err := os.WriteFile(path, []byte(body), 0o755); err != nil { t.Fatalf("WriteFile(%q) error = %v", path, err) } } t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) }