(t *testing.T)
| 4019 | } |
| 4020 | |
| 4021 | func TestPersistToolResultWithBinaryData(t *testing.T) { |
| 4022 | t.Parallel() |
| 4023 | |
| 4024 | db, ps := dbtestutil.NewDB(t) |
| 4025 | ctx := testutil.Context(t, testutil.WaitLong) |
| 4026 | |
| 4027 | const binaryOutputBase64 = "SEVBREVSAAAAc29tZSBkYXRhAABtb3JlIGRhdGEARU5E" |
| 4028 | binaryOutput, err := io.ReadAll(base64.NewDecoder( |
| 4029 | base64.StdEncoding, |
| 4030 | strings.NewReader(binaryOutputBase64), |
| 4031 | )) |
| 4032 | require.NoError(t, err) |
| 4033 | |
| 4034 | var streamedCallCount atomic.Int32 |
| 4035 | var streamedCallsMu sync.Mutex |
| 4036 | streamedCalls := make([][]chattest.OpenAIMessage, 0, 2) |
| 4037 | |
| 4038 | openAIURL := chattest.NewOpenAI(t, func(req *chattest.OpenAIRequest) chattest.OpenAIResponse { |
| 4039 | if !req.Stream { |
| 4040 | return chattest.OpenAINonStreamingResponse("Binary tool result test") |
| 4041 | } |
| 4042 | |
| 4043 | streamedCallsMu.Lock() |
| 4044 | streamedCalls = append(streamedCalls, append([]chattest.OpenAIMessage(nil), req.Messages...)) |
| 4045 | streamedCallsMu.Unlock() |
| 4046 | |
| 4047 | if streamedCallCount.Add(1) == 1 { |
| 4048 | return chattest.OpenAIStreamingResponse( |
| 4049 | chattest.OpenAIToolCallChunk( |
| 4050 | "execute", |
| 4051 | `{"command":"cat /home/coder/binary_file.bin"}`, |
| 4052 | ), |
| 4053 | ) |
| 4054 | } |
| 4055 | // Include literal \u0000 in the response text, which is |
| 4056 | // what a real LLM writes when explaining binary output. |
| 4057 | // json.Marshal encodes the backslash as \\, producing |
| 4058 | // \\u0000 in the JSON bytes. The sanitizer must not |
| 4059 | // corrupt this into invalid JSON. |
| 4060 | return chattest.OpenAIStreamingResponse( |
| 4061 | chattest.OpenAITextChunks("The file contains \\u0000 null bytes.")..., |
| 4062 | ) |
| 4063 | }) |
| 4064 | |
| 4065 | // Use "openai-compat" provider so the chatd framework uses the |
| 4066 | // /chat/completions endpoint, where the mock server supports |
| 4067 | // streaming tool calls. The default "openai" provider routes to |
| 4068 | // /responses which only handles text deltas in the mock. |
| 4069 | user, org, model := seedChatDependenciesWithProvider(t, db, "openai-compat", openAIURL) |
| 4070 | ws, dbAgent := seedWorkspaceWithAgent(t, db, user.ID) |
| 4071 | |
| 4072 | ctrl := gomock.NewController(t) |
| 4073 | mockConn := agentconnmock.NewMockAgentConn(ctrl) |
| 4074 | mockConn.EXPECT(). |
| 4075 | SetExtraHeaders(gomock.Any()). |
| 4076 | AnyTimes() |
| 4077 | mockConn.EXPECT(). |
| 4078 | ContextConfig(gomock.Any()). |
nothing calls this directly
no test coverage detected