168 lines
5.3 KiB
Go
168 lines
5.3 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hjbdev/patterm/internal/mcp"
|
|
"github.com/hjbdev/patterm/internal/preset"
|
|
)
|
|
|
|
func TestCanonicalizeTerminalText(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
}{
|
|
{
|
|
name: "ansi osc and controls",
|
|
in: "\x1b]0;title\x07\x1b[31mred\x1b[0m\x00\nok",
|
|
want: "red\nok",
|
|
},
|
|
{
|
|
name: "noisy harness stream",
|
|
in: "\x1b]0;noise\x07\x1b[31mStatus: running 12s\x1b[0m\nStatus: running 13s\n╭────╮\n│ │\nDownloading 10%\rDownloading 100%\nFINAL: deploy ready\n",
|
|
want: "Status: running [time]\nDownloading [count]\nFINAL: deploy ready",
|
|
},
|
|
{
|
|
name: "repeated blank collapse",
|
|
in: "one\n\n\n two\n \n\t\nthree",
|
|
want: "one\n\n two\n\nthree",
|
|
},
|
|
{
|
|
name: "border only box drawing removal",
|
|
in: "╭────────╮\n│ │\nimportant\n╰────────╯",
|
|
want: "important",
|
|
},
|
|
{
|
|
name: "carriage return progress coalesces final frame",
|
|
in: "Downloading 10%\rDownloading 20%\rDownloading 100%\nDone",
|
|
want: "Downloading [count]\nDone",
|
|
},
|
|
{
|
|
name: "volatile timer duplicate collapse",
|
|
in: "Status: running 12s\nStatus: running 13s\nStatus: running 01:23",
|
|
want: "Status: running [time]",
|
|
},
|
|
{
|
|
name: "duplicate status row collapse",
|
|
in: "⠋ Building 1/4\n⠙ Building 2/4\n⠹ Building 3/4\nready",
|
|
want: "Building [count]\nready",
|
|
},
|
|
{
|
|
name: "preserve meaningful indented code and tables",
|
|
in: " if elapsed == 12s {\n return value\n }\n| name | value |\n| a | 1 |",
|
|
want: " if elapsed == 12s {\n return value\n }\n| name | value |\n| a | 1 |",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, truncated, _ := canonicalizeTerminalText(tc.in, 120)
|
|
if truncated {
|
|
t.Fatalf("unexpected truncation")
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("got %q want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCanonicalizeTerminalTextMaxLines(t *testing.T) {
|
|
got, truncated, dropped := canonicalizeTerminalText("one\ntwo\nthree", 2)
|
|
if !truncated {
|
|
t.Fatalf("expected truncation")
|
|
}
|
|
if dropped == 0 {
|
|
t.Fatalf("expected dropped bytes")
|
|
}
|
|
if got != "two\nthree" {
|
|
t.Fatalf("got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestGetProcessOutputStreamCanonicalByDefault(t *testing.T) {
|
|
sess := NewSession(t.TempDir(), "test")
|
|
c := newChildEntry("p1", "proc", KindCommand, nil, nil, "", "", "")
|
|
addChild(sess, c)
|
|
c.recordWrite([]byte("\x1b[31mStatus: running 12s\x1b[0m\nStatus: running 13s\nresult\n"))
|
|
host := newToolHost(sess, nil, nil, preset.Set{}, nil, 80, 24)
|
|
|
|
out, err := host.GetProcessOutput("", mcp.ProcessOutputArgs{ProcessID: c.ID, Mode: "stream"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !out.Canonicalized {
|
|
t.Fatalf("expected canonicalized output")
|
|
}
|
|
if out.Content != "Status: running [time]\nresult" {
|
|
t.Fatalf("content = %q", out.Content)
|
|
}
|
|
if out.Cursor != nil || out.Rows != 0 || out.Cols != 0 || out.ScreenVersion != 0 || out.IdleMS != 0 {
|
|
t.Fatalf("default output should be metadata-light: %#v", out)
|
|
}
|
|
}
|
|
|
|
func TestGetProcessOutputRawReturnsStreamBytes(t *testing.T) {
|
|
sess := NewSession(t.TempDir(), "test")
|
|
c := newChildEntry("p1", "proc", KindCommand, nil, nil, "", "", "")
|
|
addChild(sess, c)
|
|
c.recordWrite([]byte("\x1b[31mred\x1b[0m"))
|
|
host := newToolHost(sess, nil, nil, preset.Set{}, nil, 80, 24)
|
|
|
|
out, err := host.GetProcessOutput("", mcp.ProcessOutputArgs{ProcessID: c.ID, Mode: "grid", Raw: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Mode != "stream" {
|
|
t.Fatalf("raw grid mode should report stream semantics, got %q", out.Mode)
|
|
}
|
|
if out.Canonicalized {
|
|
t.Fatalf("raw output should not be canonicalized")
|
|
}
|
|
if out.Content != "\x1b[31mred\x1b[0m" {
|
|
t.Fatalf("content = %q", out.Content)
|
|
}
|
|
if out.NewOffset != int64(len(out.Content)) {
|
|
t.Fatalf("new_offset=%d want %d", out.NewOffset, len(out.Content))
|
|
}
|
|
}
|
|
|
|
func TestGetProcessOutputCanonicalAfterRawRead(t *testing.T) {
|
|
sess := NewSession(t.TempDir(), "test")
|
|
c := newChildEntry("p1", "proc", KindCommand, nil, nil, "", "", "")
|
|
addChild(sess, c)
|
|
c.recordWrite([]byte("\x1b[31mStatus: running 12s\x1b[0m\nStatus: running 13s\nDownloading 10%\rDownloading 100%\nFINAL: deploy ready\n"))
|
|
host := newToolHost(sess, nil, nil, preset.Set{}, nil, 80, 24)
|
|
|
|
if _, err := host.GetProcessOutput("", mcp.ProcessOutputArgs{ProcessID: c.ID, Mode: "stream", Raw: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := host.GetProcessOutput("", mcp.ProcessOutputArgs{ProcessID: c.ID, Mode: "stream", MaxLines: 20})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Content != "Status: running [time]\nDownloading [count]\nFINAL: deploy ready" {
|
|
t.Fatalf("content = %q", out.Content)
|
|
}
|
|
}
|
|
|
|
func TestGetProcessOutputIncludeMetaRestoresFields(t *testing.T) {
|
|
sess := NewSession(t.TempDir(), "test")
|
|
c := newChildEntry("p1", "proc", KindCommand, nil, nil, "", "", "")
|
|
addChild(sess, c)
|
|
c.recordWrite([]byte("ok"))
|
|
host := newToolHost(sess, nil, nil, preset.Set{}, nil, 80, 24)
|
|
|
|
out, err := host.GetProcessOutput("", mcp.ProcessOutputArgs{ProcessID: c.ID, Mode: "stream", IncludeMeta: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.ScreenVersion == 0 {
|
|
t.Fatalf("screen_version missing with include_meta: %#v", out)
|
|
}
|
|
if !strings.Contains(out.Content, "ok") {
|
|
t.Fatalf("content = %q", out.Content)
|
|
}
|
|
}
|