gateway_oauth_metadata_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package service
  2. import (
  3. "regexp"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. )
  7. func TestBuildOAuthMetadataUserID_FallbackWithoutAccountUUID(t *testing.T) {
  8. svc := &GatewayService{}
  9. parsed := &ParsedRequest{
  10. Model: "claude-sonnet-4-5",
  11. Stream: true,
  12. MetadataUserID: "",
  13. }
  14. account := &Account{
  15. ID: 123,
  16. Type: AccountTypeOAuth,
  17. Extra: map[string]any{}, // intentionally missing account_uuid / claude_user_id
  18. }
  19. fp := &Fingerprint{ClientID: "deadbeef"} // should be used as user id in legacy format
  20. got := svc.buildOAuthMetadataUserID(parsed, account, fp)
  21. require.NotEmpty(t, got)
  22. // Legacy format: user_{client}_account__session_{uuid}
  23. re := regexp.MustCompile(`^user_[a-zA-Z0-9]+_account__session_[a-f0-9-]{36}$`)
  24. require.True(t, re.MatchString(got), "unexpected user_id format: %s", got)
  25. }
  26. func TestBuildOAuthMetadataUserID_UsesAccountUUIDWhenPresent(t *testing.T) {
  27. svc := &GatewayService{}
  28. parsed := &ParsedRequest{
  29. Model: "claude-sonnet-4-5",
  30. Stream: true,
  31. MetadataUserID: "",
  32. }
  33. account := &Account{
  34. ID: 123,
  35. Type: AccountTypeOAuth,
  36. Extra: map[string]any{
  37. "account_uuid": "acc-uuid",
  38. "claude_user_id": "clientid123",
  39. "anthropic_user_id": "",
  40. },
  41. }
  42. got := svc.buildOAuthMetadataUserID(parsed, account, nil)
  43. require.NotEmpty(t, got)
  44. // New format: user_{client}_account_{account_uuid}_session_{uuid}
  45. re := regexp.MustCompile(`^user_clientid123_account_acc-uuid_session_[a-f0-9-]{36}$`)
  46. require.True(t, re.MatchString(got), "unexpected user_id format: %s", got)
  47. }
  48. // TestBuildOAuthMetadataUserID_SessionIDStableAcrossTurns 验证伪装路径合成的
  49. // metadata.user_id 在同一会话多轮请求间保持不变(session_id 稳定),贴近真实 Claude Code
  50. // 进程级稳定的 session。账号 / 指纹 / UA 版本均相同,唯一可能变化的就是 session_id,
  51. // 因此直接比较完整 user_id 字符串即可判定 session_id 是否稳定。
  52. func TestBuildOAuthMetadataUserID_SessionIDStableAcrossTurns(t *testing.T) {
  53. svc := &GatewayService{}
  54. account := &Account{ID: 777, Type: AccountTypeOAuth, Extra: map[string]any{"account_uuid": "acc-uuid"}}
  55. fp := &Fingerprint{ClientID: "clientid777", UserAgent: "claude-cli/2.1.161 (external, cli)"}
  56. mustParse := func(body string) *ParsedRequest {
  57. parsed, err := ParseGatewayRequest(NewRequestBodyRef([]byte(body)), PlatformAnthropic)
  58. require.NoError(t, err)
  59. return parsed
  60. }
  61. round1 := mustParse(`{"model":"claude-sonnet-4-5","system":"sys","messages":[` +
  62. `{"role":"user","content":"first question"}]}`)
  63. round2 := mustParse(`{"model":"claude-sonnet-4-5","system":"sys","messages":[` +
  64. `{"role":"user","content":"first question"},` +
  65. `{"role":"assistant","content":"answer 1"},` +
  66. `{"role":"user","content":"second question"}]}`)
  67. round3 := mustParse(`{"model":"claude-sonnet-4-5","system":"sys","messages":[` +
  68. `{"role":"user","content":"first question"},` +
  69. `{"role":"assistant","content":"answer 1"},` +
  70. `{"role":"user","content":"second question"},` +
  71. `{"role":"assistant","content":"answer 2"},` +
  72. `{"role":"user","content":"third question"}]}`)
  73. id1 := svc.buildOAuthMetadataUserID(round1, account, fp)
  74. id2 := svc.buildOAuthMetadataUserID(round2, account, fp)
  75. id3 := svc.buildOAuthMetadataUserID(round3, account, fp)
  76. require.NotEmpty(t, id1)
  77. require.Equal(t, id1, id2, "session_id 应随对话增长保持不变")
  78. require.Equal(t, id2, id3, "session_id 应跨所有轮次保持不变")
  79. // 不同的首条 user 消息应派生出不同的 session_id(不同会话)。
  80. other := mustParse(`{"model":"claude-sonnet-4-5","system":"sys","messages":[` +
  81. `{"role":"user","content":"a completely different opener"}]}`)
  82. idOther := svc.buildOAuthMetadataUserID(other, account, fp)
  83. require.NotEqual(t, id1, idOther, "不同首条消息应派生不同 session_id")
  84. }