Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
202 lines
6.6 KiB
Go
202 lines
6.6 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_ContentNull(t *testing.T) {
|
|
reqBody := map[string]any{
|
|
"model": "grok-4.5",
|
|
"input": []any{
|
|
map[string]any{"type": "message", "role": "user", "content": "hi"},
|
|
map[string]any{
|
|
"type": "reasoning",
|
|
"summary": []any{map[string]any{"type": "summary_text", "text": "thinking..."}},
|
|
"content": nil,
|
|
"encrypted_content": nil,
|
|
},
|
|
map[string]any{"type": "message", "role": "assistant", "content": "Hello!"},
|
|
},
|
|
}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
require.True(t, changed)
|
|
|
|
input, ok := reqBody["input"].([]any)
|
|
require.True(t, ok)
|
|
require.Len(t, input, 3)
|
|
|
|
reasoning, ok := input[1].(map[string]any)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "reasoning", reasoning["type"])
|
|
assert.NotNil(t, reasoning["summary"])
|
|
_, hasContent := reasoning["content"]
|
|
assert.False(t, hasContent, "content: null should be stripped")
|
|
_, hasEncrypted := reasoning["encrypted_content"]
|
|
assert.False(t, hasEncrypted, "encrypted_content should be stripped")
|
|
}
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_ContentNullOnly(t *testing.T) {
|
|
reqBody := map[string]any{
|
|
"model": "grok-4.5",
|
|
"input": []any{
|
|
map[string]any{
|
|
"type": "reasoning",
|
|
"summary": []any{map[string]any{"type": "summary_text", "text": "ok"}},
|
|
"content": nil,
|
|
},
|
|
},
|
|
}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
require.True(t, changed)
|
|
|
|
input, ok := reqBody["input"].([]any)
|
|
require.True(t, ok)
|
|
require.Len(t, input, 1)
|
|
|
|
reasoning, ok := input[0].(map[string]any)
|
|
require.True(t, ok)
|
|
_, hasContent := reasoning["content"]
|
|
assert.False(t, hasContent, "content: null should be stripped even without encrypted_content")
|
|
}
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_ContentNonNull(t *testing.T) {
|
|
reqBody := map[string]any{
|
|
"model": "grok-4.5",
|
|
"input": []any{
|
|
map[string]any{
|
|
"type": "reasoning",
|
|
"summary": []any{map[string]any{"type": "summary_text", "text": "ok"}},
|
|
"content": "some actual content",
|
|
},
|
|
},
|
|
}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
assert.False(t, changed, "non-null content should not be stripped")
|
|
|
|
input, ok := reqBody["input"].([]any)
|
|
require.True(t, ok)
|
|
reasoning, ok := input[0].(map[string]any)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "some actual content", reasoning["content"])
|
|
}
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_NoReasoningItems(t *testing.T) {
|
|
reqBody := map[string]any{
|
|
"model": "grok-4.5",
|
|
"input": []any{
|
|
map[string]any{"type": "message", "role": "user", "content": "hi"},
|
|
},
|
|
}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
assert.False(t, changed)
|
|
}
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_Compaction(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
itemType string
|
|
encrypted bool
|
|
changed bool
|
|
}{
|
|
{name: "compaction", itemType: "compaction", encrypted: true, changed: true},
|
|
{name: "compaction summary", itemType: "compaction_summary", encrypted: true, changed: true},
|
|
{name: "unencrypted compaction", itemType: "compaction", encrypted: false, changed: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
item := map[string]any{"type": tt.itemType, "id": "cmp_stale"}
|
|
if tt.encrypted {
|
|
item["encrypted_content"] = "gAAA"
|
|
}
|
|
reqBody := map[string]any{"input": []any{
|
|
item,
|
|
map[string]any{"type": "message", "content": "hi"},
|
|
}}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
assert.Equal(t, tt.changed, changed)
|
|
input, ok := reqBody["input"].([]any)
|
|
require.True(t, ok)
|
|
require.NotEmpty(t, input)
|
|
first, ok := input[0].(map[string]any)
|
|
require.True(t, ok)
|
|
if tt.changed {
|
|
require.Len(t, input, 1)
|
|
assert.Equal(t, "message", first["type"])
|
|
return
|
|
}
|
|
require.Len(t, input, 2)
|
|
assert.Equal(t, tt.itemType, first["type"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSanitizeOpenAICrossModeFailoverReasoning_DropsWholeEncryptedItem(t *testing.T) {
|
|
body := []byte(`{"model":"gpt-5.1","input":[` +
|
|
`{"type":"message","role":"user","content":"hi"},` +
|
|
`{"type":"reasoning","id":"rs_kiro_1","encrypted_content":"ENC","summary":[{"type":"summary_text","text":"t"}]},` +
|
|
`{"type":"message","role":"assistant","content":"yo"}` +
|
|
`]}`)
|
|
|
|
sanitized, changed, err := SanitizeOpenAICrossModeFailoverReasoning(body)
|
|
require.NoError(t, err)
|
|
require.True(t, changed)
|
|
// The whole reasoning item is gone — id and summary go with encrypted_content,
|
|
// unlike trimOpenAIEncryptedReasoningItems which keeps the skeleton.
|
|
require.NotContains(t, string(sanitized), "reasoning")
|
|
require.NotContains(t, string(sanitized), "rs_kiro_1")
|
|
require.NotContains(t, string(sanitized), "summary_text")
|
|
require.Equal(t, int64(2), gjson.GetBytes(sanitized, "input.#").Int())
|
|
}
|
|
|
|
func TestSanitizeOpenAICrossModeFailoverReasoning_NoEncryptedIsNoop(t *testing.T) {
|
|
body := []byte(`{"model":"gpt-5.1","input":[{"type":"reasoning","summary":[{"type":"summary_text","text":"t"}]}]}`)
|
|
sanitized, changed, err := SanitizeOpenAICrossModeFailoverReasoning(body)
|
|
require.NoError(t, err)
|
|
require.False(t, changed, "reasoning without encrypted_content must be preserved")
|
|
require.Equal(t, string(body), string(sanitized))
|
|
}
|
|
|
|
func TestSanitizeOpenAICrossModeFailoverReasoning_NoInputIsNoop(t *testing.T) {
|
|
body := []byte(`{"model":"gpt-5.1"}`)
|
|
sanitized, changed, err := SanitizeOpenAICrossModeFailoverReasoning(body)
|
|
require.NoError(t, err)
|
|
require.False(t, changed)
|
|
require.Equal(t, string(body), string(sanitized))
|
|
}
|
|
|
|
func TestSanitizeOpenAICrossModeFailoverReasoning_PreservesLargeIntegers(t *testing.T) {
|
|
body := []byte(`{"model":"gpt-5.1","input":[` +
|
|
`{"type":"reasoning","id":"rs_kiro_1","encrypted_content":"ENC"},` +
|
|
`{"type":"message","role":"user","content":"hi"}` +
|
|
`],"tools":[{"type":"function","function":{"name":"lookup","parameters":{"type":"object","properties":{"id":{"const":9007199254740993}}}}}]}`)
|
|
|
|
sanitized, changed, err := SanitizeOpenAICrossModeFailoverReasoning(body)
|
|
require.NoError(t, err)
|
|
require.True(t, changed)
|
|
require.Contains(t, string(sanitized), `"const":9007199254740993`,
|
|
"sanitization must not round JSON integers through float64")
|
|
}
|
|
|
|
func TestTrimOpenAIEncryptedReasoningItems_ContentNullDropsBareSkeleton(t *testing.T) {
|
|
reqBody := map[string]any{
|
|
"input": []any{
|
|
map[string]any{"type": "reasoning", "content": nil},
|
|
},
|
|
}
|
|
|
|
changed := trimOpenAIEncryptedReasoningItems(reqBody)
|
|
require.True(t, changed)
|
|
_, hasInput := reqBody["input"]
|
|
assert.False(t, hasInput, "bare reasoning skeleton should be dropped, emptying input")
|
|
}
|