package main import ( "bytes" "encoding/json" "net" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/require" ) func TestTerminalImageLineSendsAnAbsolutePathAndCaption(t *testing.T) { client, daemon := net.Pipe() defer func() { require.NoError(t, client.Close()) require.NoError(t, daemon.Close()) }() done := make(chan error, 1) go func() { done <- terminalSendLines(t.Context(), client, strings.NewReader("/image 'image sample.png' Read the label.\n")) }() var request SocketRequest require.NoError(t, json.NewDecoder(daemon).Decode(&request)) require.NoError(t, <-done) expected, err := filepath.Abs("image sample.png") require.NoError(t, err) require.Equal(t, SocketRequest{ Type: "image", Path: expected, Text: "Read the label.", }, request) } func TestTerminalImagePathsAndCaptionsHaveNoShellExpansion(t *testing.T) { testCases := []struct { name string line string path string caption string command bool }{ { name: "accepts paths without captions", line: "/image sample.png", path: "sample.png", command: true, }, { name: "keeps spaces in double quoted paths", line: `/image "a b.png" caption`, path: "a b.png", caption: "caption", command: true, }, { name: "caption stays literal", line: "/image sample.png $(not-a-command) ", path: "sample.png", caption: "$(not-a-command) ", command: true, }, { name: "missing path refused", line: "/image", command: true, }, { name: "unclosed quote refused", line: `/image "sample.png`, command: true, }, { name: "joined caption refused", line: `/image "sample.png"caption`, command: true, }, { name: "other command stays text", line: "/images sample.png", command: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { request, command := terminalImageRequest(tc.line) require.Equal(t, tc.command, command) require.Equal(t, tc.path, request.Path) require.Equal(t, tc.caption, request.Text) }) } } func TestTerminalImageIsStoredBeforeItReachesTheModel(t *testing.T) { server := newModelFake(t) socket := daemonStartTest(t, server) terminal := terminalStartTestClient(t, socket) terminal.WaitFor(t, "[connected to OneChat]") path := filepath.Join(t.TempDir(), "sample.png") data := imageTestPNG(t, 2, 2) require.NoError(t, os.WriteFile(path, data, 0o600)) terminal.Type("/image " + path + " What is this?") terminal.WaitFor(t, "[done]") require.Equal(t, 1, server.Calls()) require.Contains(t, string(server.Call(0).raw), "input_image") require.Contains(t, string(server.Call(0).raw), "What is this?") require.NotContains(t, string(server.Call(0).raw), path) files, err := os.ReadDir(filepath.Join(filepath.Dir(socket), imageDirName)) require.NoError(t, err) require.Len(t, files, 1) stored, err := os.ReadFile(filepath.Join(filepath.Dir(socket), imageDirName, files[0].Name())) require.NoError(t, err) require.Equal(t, data, stored) require.NotContains(t, terminal.Output(), "base64") } func TestTerminalRefusesLargeAndUnsupportedImagesWithoutAModelCall(t *testing.T) { server := newModelFake(t) test := newTestTurnLoop(t, server) path := filepath.Join(t.TempDir(), "sample.png") require.NoError(t, os.WriteFile(path, bytes.Repeat([]byte{0}, imageMaxBytes+1), 0o600)) request := SocketRequest{ Type: "image", Path: path, } require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) { }), "exceeds 20 MB") require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600)) require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) { }), "JPEG, PNG, or WebP") require.Equal(t, 0, test.memory.Len()) require.Equal(t, 0, server.Calls()) _, err := os.Stat(test.agent.Images().dir) require.ErrorIs(t, err, os.ErrNotExist) } func TestImageCommandErrorDoesNotEndTheTerminalClient(t *testing.T) { socket := daemonStartTest(t, newModelFake(t)) terminal := terminalStartTestClient(t, socket) terminal.WaitFor(t, "[connected to OneChat]") terminal.Type("/image") terminal.WaitFor(t, "use /image PATH [caption]") terminal.Type("Continue.") terminal.WaitFor(t, "[done]") require.NoError(t, terminal.Close()) }