intelligent_test_protection_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/url"
  9. "testing"
  10. "time"
  11. "github.com/Wei-Shaw/sub2api/internal/config"
  12. "github.com/gin-gonic/gin"
  13. "github.com/stretchr/testify/require"
  14. )
  15. func TestIntelligentProtectedResponsesUsesStableDeviceAndIndependentSessions(t *testing.T) {
  16. gin.SetMode(gin.TestMode)
  17. var headers []http.Header
  18. var bodies []map[string]any
  19. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  20. var body map[string]any
  21. require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
  22. headers = append(headers, r.Header.Clone())
  23. bodies = append(bodies, body)
  24. w.Header().Set("Content-Type", "text/event-stream")
  25. fmt.Fprint(w, "data: {\"type\":\"response.output_text.delta\",\"delta\":\"ANSWER: 12\"}\n\ndata: {\"type\":\"response.completed\"}\n\n")
  26. }))
  27. defer server.Close()
  28. target, err := url.Parse(server.URL)
  29. require.NoError(t, err)
  30. transport := &intelligentRedirectUpstream{target: target, client: server.Client()}
  31. account := &Account{ID: 810, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 50,
  32. Credentials: map[string]any{"access_token": "test-oauth-access-token", "expires_at": time.Now().Add(time.Hour).Format(time.RFC3339), "chatgpt_account_id": "test-account"}}
  33. admin := &adminServiceImpl{accountRepo: &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{account.ID: account}}}
  34. account, err = NewAntiDegradeService(admin).ApplyAntiDegradeMode(context.Background(), account.ID, AntiDegradeMode1)
  35. require.NoError(t, err)
  36. before, err := json.Marshal(account.Extra)
  37. require.NoError(t, err)
  38. repo := &intelligentRunnerRepo{account: account}
  39. svc := NewAccountTestService(repo, nil, nil, nil, nil, transport, &config.Config{}, nil)
  40. for range 2 {
  41. record := &IntelligentTestRecord{AccountID: account.ID, Input: "Please solve this candy question. Do not change it.", Model: "gpt-5.2", ConfigSnapshot: &IntelligentTestConfig{Model: "gpt-5.2"}}
  42. require.NoError(t, svc.RunIntelligentTest(context.Background(), record))
  43. require.True(t, record.AntiDegradation)
  44. require.NotNil(t, record.ConfigSnapshot.Execution)
  45. require.Equal(t, "mode1", record.ConfigSnapshot.Execution.Strategy)
  46. require.Equal(t, "standard", record.ConfigSnapshot.Execution.EffectiveTLS)
  47. require.Equal(t, "gpt-5.2", record.ConfigSnapshot.Execution.Model)
  48. require.Len(t, record.ConfigSnapshot.Execution.PromptDigest, 64)
  49. require.Contains(t, record.Result, "ANSWER: 12")
  50. }
  51. require.Len(t, headers, 2)
  52. expectedDevice := resolveCodexFingerprintIDs(account, "", codexFingerprintDevice).installationID
  53. for i, header := range headers {
  54. require.Equal(t, expectedDevice, header.Get("x-codex-installation-id"))
  55. require.NotEmpty(t, header.Get("session-id"))
  56. require.Equal(t, header.Get("session-id"), header.Get("session_id"))
  57. require.Equal(t, header.Get("session-id"), header.Get("conversation_id"))
  58. require.NotEmpty(t, header.Get("thread-id"))
  59. metadata := bodies[i]["client_metadata"].(map[string]any)
  60. require.Equal(t, expectedDevice, metadata["x-codex-installation-id"])
  61. require.Equal(t, header.Get("session-id"), metadata["session_id"])
  62. require.Equal(t, header.Get("thread-id"), metadata["thread_id"])
  63. encoded, err := json.Marshal(bodies[i])
  64. require.NoError(t, err)
  65. require.Contains(t, string(encoded), "Please solve this candy question. Do not change it.")
  66. }
  67. require.NotEqual(t, headers[0].Get("session-id"), headers[1].Get("session-id"))
  68. require.NotEqual(t, headers[0].Get("thread-id"), headers[1].Get("thread-id"))
  69. after, err := json.Marshal(account.Extra)
  70. require.NoError(t, err)
  71. require.JSONEq(t, string(before), string(after))
  72. require.Zero(t, repo.writes)
  73. // Ordinary connectivity probes must use the same protected identity too.
  74. c, _ := gin.CreateTestContext(httptest.NewRecorder())
  75. c.Request = httptest.NewRequest(http.MethodPost, "/test", nil)
  76. require.NoError(t, svc.TestAccountConnection(c, account.ID, "gpt-5.2", "", AccountTestModeDefault))
  77. require.Len(t, headers, 3)
  78. require.Equal(t, expectedDevice, headers[2].Get("x-codex-installation-id"))
  79. require.Equal(t, expectedDevice, bodies[2]["client_metadata"].(map[string]any)["x-codex-installation-id"])
  80. }
  81. func TestIntelligentProtectionRejectsLostPrompt(t *testing.T) {
  82. c, _ := gin.CreateTestContext(httptest.NewRecorder())
  83. c.Request = httptest.NewRequest(http.MethodPost, "/test", nil).WithContext(context.WithValue(context.Background(), intelligentRunKey{}, &intelligentRunContext{prompt: "original"}))
  84. account := &Account{ID: 811, Platform: PlatformOpenAI, Type: AccountTypeOAuth}
  85. admin := &adminServiceImpl{accountRepo: &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{account.ID: account}}}
  86. account, err := NewAntiDegradeService(admin).ApplyAntiDegradeMode(context.Background(), account.ID, AntiDegradeMode1)
  87. require.NoError(t, err)
  88. payload := createOpenAITestPayload("gpt-5.2", true)
  89. applyIntelligentPayloadPrompt(c.Request.Context(), payload)
  90. require.NoError(t, prepareIntelligentTestProtection(c, account, payload))
  91. payload["input"] = "changed by an incompatible transformation"
  92. body, err := json.Marshal(payload)
  93. require.NoError(t, err)
  94. require.ErrorContains(t, applyIntelligentTestProtection(c, account, http.Header{}, body), "input")
  95. }