| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- package service
- import (
- "encoding/json"
- "strings"
- "testing"
- "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
- "github.com/stretchr/testify/require"
- )
- func mustRawJSON(t *testing.T, s string) json.RawMessage {
- t.Helper()
- return json.RawMessage(s)
- }
- func TestShouldAutoInjectPromptCacheKeyForCompat(t *testing.T) {
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.5"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.5-pro"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.4"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.4-mini"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.2"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.3"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.3-codex"))
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-5.3-codex-spark"))
- require.False(t, shouldAutoInjectPromptCacheKeyForCompat("gpt-4o"))
- }
- func TestShouldAutoInjectPromptCacheKeyForCompat_GPT6AstraForms(t *testing.T) {
- for _, model := range []string{
- "gpt-6",
- "gpt-6-astra",
- "openai/gpt-6",
- "openai/gpt-6-astra",
- "OPENAI/GPT-6_ASTRA",
- "provider/gpt-6-astra",
- } {
- require.True(t, shouldAutoInjectPromptCacheKeyForCompat(model), model)
- }
- for _, model := range []string{
- "gpt-6-terra",
- "gpt-6.1",
- "gpt-6-astra-preview",
- "gpt-6-astra-unrelated-name",
- "gpt-6-astra-2026-09-01",
- "gpt-6-astra-2026-09-01-extra",
- "gpt-6-astra-v",
- "gpt-6-astra-v1.beta",
- "claude-sonnet-4-5",
- "gpt-4o",
- } {
- require.False(t, shouldAutoInjectPromptCacheKeyForCompat(model), model)
- }
- }
- func TestDeriveCompatPromptCacheKey_StableAcrossLaterTurns(t *testing.T) {
- base := &apicompat.ChatCompletionsRequest{
- Model: "gpt-5.4",
- Messages: []apicompat.ChatMessage{
- {Role: "system", Content: mustRawJSON(t, `"You are helpful."`)},
- {Role: "user", Content: mustRawJSON(t, `"Hello"`)},
- },
- }
- extended := &apicompat.ChatCompletionsRequest{
- Model: "gpt-5.4",
- Messages: []apicompat.ChatMessage{
- {Role: "system", Content: mustRawJSON(t, `"You are helpful."`)},
- {Role: "user", Content: mustRawJSON(t, `"Hello"`)},
- {Role: "assistant", Content: mustRawJSON(t, `"Hi there!"`)},
- {Role: "user", Content: mustRawJSON(t, `"How are you?"`)},
- },
- }
- k1 := deriveCompatPromptCacheKey(base, "gpt-5.4")
- k2 := deriveCompatPromptCacheKey(extended, "gpt-5.4")
- require.Equal(t, k1, k2, "cache key should be stable across later turns")
- require.NotEmpty(t, k1)
- }
- func TestDeriveCompatPromptCacheKey_DiffersAcrossSessions(t *testing.T) {
- req1 := &apicompat.ChatCompletionsRequest{
- Model: "gpt-5.4",
- Messages: []apicompat.ChatMessage{
- {Role: "user", Content: mustRawJSON(t, `"Question A"`)},
- },
- }
- req2 := &apicompat.ChatCompletionsRequest{
- Model: "gpt-5.4",
- Messages: []apicompat.ChatMessage{
- {Role: "user", Content: mustRawJSON(t, `"Question B"`)},
- },
- }
- k1 := deriveCompatPromptCacheKey(req1, "gpt-5.4")
- k2 := deriveCompatPromptCacheKey(req2, "gpt-5.4")
- require.NotEqual(t, k1, k2, "different first user messages should yield different keys")
- }
- func TestDeriveCompatPromptCacheKey_UsesResolvedSparkFamily(t *testing.T) {
- req := &apicompat.ChatCompletionsRequest{
- Model: "gpt-5.3-codex-spark",
- Messages: []apicompat.ChatMessage{
- {Role: "user", Content: mustRawJSON(t, `"Question A"`)},
- },
- }
- k1 := deriveCompatPromptCacheKey(req, "gpt-5.3-codex-spark")
- k2 := deriveCompatPromptCacheKey(req, " openai/gpt-5.3-codex-spark ")
- require.NotEmpty(t, k1)
- require.Equal(t, k1, k2, "resolved spark family should derive a stable compat cache key")
- }
- func TestDeriveAnthropicCompatPromptCacheKey_StableAcrossLaterTurns(t *testing.T) {
- base := &apicompat.AnthropicRequest{
- Model: "claude-sonnet-4-5",
- System: mustRawJSON(t, `"You are helpful."`),
- Messages: []apicompat.AnthropicMessage{
- {Role: "user", Content: mustRawJSON(t, `"Open repo"`)},
- },
- }
- extended := &apicompat.AnthropicRequest{
- Model: "claude-sonnet-4-5",
- System: mustRawJSON(t, `"You are helpful."`),
- Messages: []apicompat.AnthropicMessage{
- {Role: "user", Content: mustRawJSON(t, `"Open repo"`)},
- {Role: "assistant", Content: mustRawJSON(t, `"Opened."`)},
- {Role: "user", Content: mustRawJSON(t, `"Run tests"`)},
- },
- }
- k1 := deriveAnthropicCompatPromptCacheKey(base, "gpt-5.3-codex")
- k2 := deriveAnthropicCompatPromptCacheKey(extended, "gpt-5.3-codex")
- require.NotEmpty(t, k1)
- require.Equal(t, k1, k2, "cache key should stay stable as later Claude Code turns append history")
- }
- func TestDeriveAnthropicCompatPromptCacheKey_UsesCacheControlAnchors(t *testing.T) {
- base := &apicompat.AnthropicRequest{
- Model: "claude-sonnet-4-5",
- System: mustRawJSON(t, `[
- {"type":"text","text":"project instructions","cache_control":{"type":"ephemeral"}}
- ]`),
- Messages: []apicompat.AnthropicMessage{
- {Role: "user", Content: mustRawJSON(t, `[
- {"type":"text","text":"repo anchor","cache_control":{"type":"ephemeral"}}
- ]`)},
- },
- }
- extended := &apicompat.AnthropicRequest{
- Model: base.Model,
- System: base.System,
- Messages: []apicompat.AnthropicMessage{
- base.Messages[0],
- {Role: "assistant", Content: mustRawJSON(t, `[{"type":"text","text":"Opened."}]`)},
- {Role: "user", Content: mustRawJSON(t, `[{"type":"text","text":"Run tests"}]`)},
- },
- }
- k1 := deriveAnthropicCompatPromptCacheKey(base, "gpt-5.4")
- k2 := deriveAnthropicCompatPromptCacheKey(extended, "gpt-5.4")
- require.NotEmpty(t, k1)
- require.Equal(t, k1, k2)
- require.True(t, strings.HasPrefix(k1, "anthropic-cache-"))
- require.False(t, strings.HasPrefix(k1, compatPromptCacheKeyPrefix))
- }
|