model_not_found_error_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package service
  2. import (
  3. "net/http"
  4. "testing"
  5. )
  6. func TestIsUpstreamModelNotFoundError(t *testing.T) {
  7. tests := []struct {
  8. name string
  9. statusCode int
  10. body []byte
  11. want bool
  12. }{
  13. {
  14. name: "404 model not found message",
  15. statusCode: http.StatusNotFound,
  16. body: []byte(`{"error":{"message":"model not found"}}`),
  17. want: true,
  18. },
  19. {
  20. name: "404 model_not_found code",
  21. statusCode: http.StatusNotFound,
  22. body: []byte(`{"error":{"code":"model_not_found","message":"The requested model was not found"}}`),
  23. want: true,
  24. },
  25. {
  26. name: "404 unknown model message",
  27. statusCode: http.StatusNotFound,
  28. body: []byte(`{"error":{"message":"unknown model gpt-5.4"}}`),
  29. want: true,
  30. },
  31. {
  32. name: "404 endpoint not found is not model specific",
  33. statusCode: http.StatusNotFound,
  34. body: []byte(`{"error":{"message":"endpoint not found"}}`),
  35. want: false,
  36. },
  37. {
  38. name: "404 arbitrary body is not model specific",
  39. statusCode: http.StatusNotFound,
  40. body: []byte(`404 page not found`),
  41. want: false,
  42. },
  43. {
  44. name: "non 404 does not match",
  45. statusCode: http.StatusBadRequest,
  46. body: []byte(`{"error":{"message":"model not found"}}`),
  47. want: false,
  48. },
  49. }
  50. for _, tt := range tests {
  51. t.Run(tt.name, func(t *testing.T) {
  52. if got := isUpstreamModelNotFoundError(tt.statusCode, tt.body); got != tt.want {
  53. t.Fatalf("isUpstreamModelNotFoundError() = %v, want %v", got, tt.want)
  54. }
  55. })
  56. }
  57. }
  58. func TestAntigravityModelNotFoundKeepsBare404Fallback(t *testing.T) {
  59. if !isModelNotFoundError(http.StatusNotFound, []byte(`endpoint not found`)) {
  60. t.Fatal("antigravity model-not-found helper should keep bare 404 fallback")
  61. }
  62. }
  63. func TestIsOpenAICodexPlanGatedModelError(t *testing.T) {
  64. tests := []struct {
  65. name string
  66. statusCode int
  67. body []byte
  68. want bool
  69. }{
  70. {
  71. name: "400 codex plan gated detail payload",
  72. statusCode: http.StatusBadRequest,
  73. body: []byte(`{"detail":"The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account."}`),
  74. want: true,
  75. },
  76. {
  77. name: "400 codex plan gated error message payload",
  78. statusCode: http.StatusBadRequest,
  79. body: []byte(`{"error":{"message":"The 'gpt-5.4' model is not supported when using Codex with a ChatGPT account."}}`),
  80. want: true,
  81. },
  82. {
  83. name: "400 unrelated invalid request does not match",
  84. statusCode: http.StatusBadRequest,
  85. body: []byte(`{"error":{"message":"Invalid schema for response_format 'agentic_plan'"}}`),
  86. want: false,
  87. },
  88. {
  89. name: "404 with plan gated message does not match",
  90. statusCode: http.StatusNotFound,
  91. body: []byte(`{"detail":"The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account."}`),
  92. want: false,
  93. },
  94. {
  95. name: "400 empty body does not match",
  96. statusCode: http.StatusBadRequest,
  97. body: nil,
  98. want: false,
  99. },
  100. }
  101. for _, tt := range tests {
  102. t.Run(tt.name, func(t *testing.T) {
  103. if got := isOpenAICodexPlanGatedModelError(tt.statusCode, tt.body); got != tt.want {
  104. t.Fatalf("isOpenAICodexPlanGatedModelError() = %v, want %v", got, tt.want)
  105. }
  106. })
  107. }
  108. }
  109. func TestIsOpenAICompatibleModelNotFound400(t *testing.T) {
  110. tests := []struct {
  111. name string
  112. body string
  113. want bool
  114. }{
  115. {name: "structured code", body: `{"error":{"code":"model_not_found","message":"No such deployment"}}`, want: true},
  116. {name: "unknown provider", body: `{"error":{"message":"Unknown provider for model claude-x"}}`, want: true},
  117. {name: "model not found", body: `{"error":{"message":"Model not found: claude-x"}}`, want: true},
  118. {name: "model unsupported", body: `{"error":{"message":"The requested model is not supported"}}`, want: true},
  119. {name: "plain text compatible gateway", body: `unknown provider for model claude-x`, want: true},
  120. {name: "invalid parameter remains terminal", body: `{"error":{"code":"invalid_request_error","message":"Invalid value for temperature"}}`, want: false},
  121. {name: "structured non-matching code overrides model not found message", body: `{"error":{"code":"invalid_request_error","message":"Model not found: claude-x"}}`, want: false},
  122. {name: "structured non-matching error code overrides unsupported message", body: `{"error":{"code":"upstream_error","message":"The requested model is not supported"}}`, want: false},
  123. {name: "echoed phrase is ignored", body: `{"error":{"message":"Invalid request"},"echo":"model not found"}`, want: false},
  124. }
  125. for _, tt := range tests {
  126. t.Run(tt.name, func(t *testing.T) {
  127. if got := isOpenAICompatibleModelNotFound400([]byte(tt.body)); got != tt.want {
  128. t.Fatalf("isOpenAICompatibleModelNotFound400() = %v, want %v", got, tt.want)
  129. }
  130. })
  131. }
  132. }