test: cover openai responses params

Michael Suchacz created

Change summary

providers/openai/responses_params_test.go | 275 +++++++++++++++++++++++++
1 file changed, 275 insertions(+)

Detailed changes

providers/openai/responses_params_test.go 🔗

@@ -0,0 +1,275 @@
+package openai
+
+import (
+	"encoding/json"
+	"testing"
+
+	"charm.land/fantasy"
+	"github.com/stretchr/testify/require"
+)
+
+func TestPrepareParams_Store(t *testing.T) {
+	t.Parallel()
+
+	lm := testResponsesLM()
+	prompt := fantasy.Prompt{testTextMessage(fantasy.MessageRoleUser, "hello")}
+
+	tests := []struct {
+		name      string
+		opts      *ResponsesProviderOptions
+		wantStore bool
+	}{
+		{
+			name:      "store true",
+			opts:      &ResponsesProviderOptions{Store: fantasy.Opt(true)},
+			wantStore: true,
+		},
+		{
+			name:      "store false",
+			opts:      &ResponsesProviderOptions{Store: fantasy.Opt(false)},
+			wantStore: false,
+		},
+		{
+			name:      "store default",
+			opts:      &ResponsesProviderOptions{},
+			wantStore: false,
+		},
+		{
+			name:      "no provider options",
+			opts:      nil,
+			wantStore: false,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			params, warnings, err := lm.prepareParams(testCall(prompt, tt.opts))
+			require.NoError(t, err)
+			require.Empty(t, warnings)
+			require.True(t, params.Store.Valid())
+			require.Equal(t, tt.wantStore, params.Store.Value)
+		})
+	}
+}
+
+func TestPrepareParams_PreviousResponseID(t *testing.T) {
+	t.Parallel()
+
+	lm := testResponsesLM()
+	prompt := fantasy.Prompt{testTextMessage(fantasy.MessageRoleUser, "hello")}
+
+	t.Run("forwarded", func(t *testing.T) {
+		t.Parallel()
+
+		params, warnings, err := lm.prepareParams(testCall(prompt, &ResponsesProviderOptions{
+			PreviousResponseID: fantasy.Opt("resp_abc123"),
+		}))
+		require.NoError(t, err)
+		require.Empty(t, warnings)
+		require.True(t, params.PreviousResponseID.Valid())
+		require.Equal(t, "resp_abc123", params.PreviousResponseID.Value)
+	})
+
+	t.Run("not set", func(t *testing.T) {
+		t.Parallel()
+
+		params, warnings, err := lm.prepareParams(testCall(prompt, &ResponsesProviderOptions{}))
+		require.NoError(t, err)
+		require.Empty(t, warnings)
+		require.False(t, params.PreviousResponseID.Valid())
+	})
+}
+
+func TestPrepareParams_PreviousResponseID_Validation(t *testing.T) {
+	t.Parallel()
+
+	lm := testResponsesLM()
+	opts := &ResponsesProviderOptions{PreviousResponseID: fantasy.Opt("resp_abc123")}
+
+	t.Run("rejects with assistant messages", func(t *testing.T) {
+		t.Parallel()
+
+		_, _, err := lm.prepareParams(testCall(fantasy.Prompt{
+			testTextMessage(fantasy.MessageRoleUser, "hello"),
+			testTextMessage(fantasy.MessageRoleAssistant, "hi there"),
+		}, opts))
+		require.EqualError(t, err, previousResponseIDHistoryError)
+	})
+
+	t.Run("allows user-only prompt", func(t *testing.T) {
+		t.Parallel()
+
+		_, warnings, err := lm.prepareParams(testCall(fantasy.Prompt{
+			testTextMessage(fantasy.MessageRoleUser, "hello"),
+			testTextMessage(fantasy.MessageRoleUser, "follow up"),
+		}, opts))
+		require.NoError(t, err)
+		require.Empty(t, warnings)
+	})
+
+	t.Run("allows system + user prompt", func(t *testing.T) {
+		t.Parallel()
+
+		_, warnings, err := lm.prepareParams(testCall(fantasy.Prompt{
+			testTextMessage(fantasy.MessageRoleSystem, "be concise"),
+			testTextMessage(fantasy.MessageRoleUser, "hello"),
+		}, opts))
+		require.NoError(t, err)
+		require.Empty(t, warnings)
+	})
+
+	t.Run("allows tool messages without assistant history", func(t *testing.T) {
+		t.Parallel()
+
+		_, warnings, err := lm.prepareParams(testCall(fantasy.Prompt{
+			testToolResultMessage("done"),
+			testTextMessage(fantasy.MessageRoleUser, "hello"),
+		}, opts))
+		require.NoError(t, err)
+		require.Empty(t, warnings)
+	})
+}
+
+func TestValidatePreviousResponseIDPrompt(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		prompt  fantasy.Prompt
+		wantErr bool
+	}{
+		{
+			name:   "empty prompt",
+			prompt: nil,
+		},
+		{
+			name: "user-only messages",
+			prompt: fantasy.Prompt{
+				testTextMessage(fantasy.MessageRoleUser, "hello"),
+				testTextMessage(fantasy.MessageRoleUser, "follow up"),
+			},
+		},
+		{
+			name: "system + user messages",
+			prompt: fantasy.Prompt{
+				testTextMessage(fantasy.MessageRoleSystem, "be concise"),
+				testTextMessage(fantasy.MessageRoleUser, "hello"),
+			},
+		},
+		{
+			name: "contains assistant message",
+			prompt: fantasy.Prompt{
+				testTextMessage(fantasy.MessageRoleAssistant, "hi there"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "assistant in the middle",
+			prompt: fantasy.Prompt{
+				testTextMessage(fantasy.MessageRoleUser, "hello"),
+				testTextMessage(fantasy.MessageRoleAssistant, "hi there"),
+				testTextMessage(fantasy.MessageRoleUser, "follow up"),
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			err := validatePreviousResponseIDPrompt(tt.prompt)
+			if tt.wantErr {
+				require.EqualError(t, err, previousResponseIDHistoryError)
+				return
+			}
+
+			require.NoError(t, err)
+		})
+	}
+}
+
+func TestResponsesProviderMetadata_Helper(t *testing.T) {
+	t.Parallel()
+
+	t.Run("non-empty id", func(t *testing.T) {
+		t.Parallel()
+
+		metadata := responsesProviderMetadata("resp_123")
+		require.Len(t, metadata, 1)
+
+		providerMetadata, ok := metadata[Name].(*ResponsesProviderMetadata)
+		require.True(t, ok)
+		require.Equal(t, "resp_123", providerMetadata.ResponseID)
+	})
+
+	t.Run("empty id", func(t *testing.T) {
+		t.Parallel()
+
+		metadata := responsesProviderMetadata("")
+		require.Empty(t, metadata)
+	})
+}
+
+func TestResponsesProviderMetadata_JSON(t *testing.T) {
+	t.Parallel()
+
+	encoded, err := json.Marshal(ResponsesProviderMetadata{ResponseID: "resp_123"})
+	require.NoError(t, err)
+	require.Contains(t, string(encoded), `"response_id":"resp_123"`)
+
+	decoded, err := fantasy.UnmarshalProviderMetadata(map[string]json.RawMessage{
+		Name: encoded,
+	})
+	require.NoError(t, err)
+
+	providerMetadata, ok := decoded[Name].(*ResponsesProviderMetadata)
+	require.True(t, ok)
+	require.Equal(t, "resp_123", providerMetadata.ResponseID)
+}
+
+func testCall(prompt fantasy.Prompt, opts *ResponsesProviderOptions) fantasy.Call {
+	call := fantasy.Call{
+		Prompt: prompt,
+	}
+	if opts != nil {
+		call.ProviderOptions = fantasy.ProviderOptions{
+			Name: opts,
+		}
+	}
+	return call
+}
+
+func testResponsesLM() responsesLanguageModel {
+	return responsesLanguageModel{
+		provider: Name,
+		modelID:  "gpt-4o",
+	}
+}
+
+func testTextMessage(role fantasy.MessageRole, text string) fantasy.Message {
+	return fantasy.Message{
+		Role: role,
+		Content: []fantasy.MessagePart{
+			fantasy.TextPart{Text: text},
+		},
+	}
+}
+
+func testToolResultMessage(text string) fantasy.Message {
+	return fantasy.Message{
+		Role: fantasy.MessageRoleTool,
+		Content: []fantasy.MessagePart{
+			fantasy.ToolResultPart{
+				ToolCallID: "call_123",
+				Output: fantasy.ToolResultOutputContentText{
+					Text: text,
+				},
+			},
+		},
+	}
+}