163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package problem
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestProblemDocument(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
problem *Problem
|
|
statusCode int
|
|
hasType bool
|
|
hasTitle bool
|
|
hasStatus bool
|
|
hasDetail bool
|
|
}{
|
|
{
|
|
name: "BadRequest",
|
|
problem: BadRequest("missing field: model"),
|
|
statusCode: http.StatusBadRequest,
|
|
hasType: true,
|
|
hasTitle: true,
|
|
hasStatus: true,
|
|
hasDetail: true,
|
|
},
|
|
{
|
|
name: "PayloadTooLarge",
|
|
problem: PayloadTooLarge("request body 1001 bytes exceeds max 1000"),
|
|
statusCode: http.StatusRequestEntityTooLarge,
|
|
hasType: true,
|
|
hasTitle: true,
|
|
hasStatus: true,
|
|
hasDetail: true,
|
|
},
|
|
{
|
|
name: "TooManyRequests",
|
|
problem: TooManyRequests("rate limit exceeded", 60),
|
|
statusCode: http.StatusTooManyRequests,
|
|
hasType: true,
|
|
hasTitle: true,
|
|
hasStatus: true,
|
|
hasDetail: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
err := tc.problem.Write(w)
|
|
if err != nil {
|
|
t.Fatalf("Write failed: %v", err)
|
|
}
|
|
|
|
// Check status code
|
|
if w.Code != tc.statusCode {
|
|
t.Errorf("expected status %d, got %d", tc.statusCode, w.Code)
|
|
}
|
|
|
|
// Check Content-Type
|
|
if ct := w.Header().Get("Content-Type"); ct != "application/problem+json" {
|
|
t.Errorf("expected Content-Type: application/problem+json, got %s", ct)
|
|
}
|
|
|
|
// Parse response body
|
|
var p Problem
|
|
err = json.Unmarshal(w.Body.Bytes(), &p)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
// Verify required fields
|
|
if tc.hasType && p.Type == "" {
|
|
t.Errorf("expected 'type' field, got empty")
|
|
}
|
|
if tc.hasTitle && p.Title == "" {
|
|
t.Errorf("expected 'title' field, got empty")
|
|
}
|
|
if tc.hasStatus && p.Status == 0 {
|
|
t.Errorf("expected 'status' field, got 0")
|
|
}
|
|
if tc.hasDetail && p.Detail == "" {
|
|
t.Errorf("expected 'detail' field, got empty")
|
|
}
|
|
|
|
// Verify status matches HTTP response code
|
|
if p.Status != w.Code {
|
|
t.Errorf("status field %d does not match HTTP status %d", p.Status, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProblemRetryAfter(t *testing.T) {
|
|
p := TooManyRequests("rate limit", 120)
|
|
w := httptest.NewRecorder()
|
|
|
|
err := p.Write(w)
|
|
if err != nil {
|
|
t.Fatalf("Write failed: %v", err)
|
|
}
|
|
|
|
// Check Retry-After header is set
|
|
if ra := w.Header().Get("Retry-After"); ra == "" {
|
|
t.Errorf("expected Retry-After header, got empty")
|
|
}
|
|
|
|
var body Problem
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
if body.RetryAfter == nil || *body.RetryAfter != 120 {
|
|
t.Errorf("expected RetryAfter=120, got %v", body.RetryAfter)
|
|
}
|
|
}
|
|
|
|
func TestProblemWithExtra(t *testing.T) {
|
|
p := BadRequest("invalid request")
|
|
p.WithExtra("field", "model")
|
|
p.WithExtra("reason", "unknown_model")
|
|
|
|
w := httptest.NewRecorder()
|
|
err := p.Write(w)
|
|
if err != nil {
|
|
t.Fatalf("Write failed: %v", err)
|
|
}
|
|
|
|
var body Problem
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
if body.Extra["field"] != "model" {
|
|
t.Errorf("expected extra field 'model', got %v", body.Extra["field"])
|
|
}
|
|
if body.Extra["reason"] != "unknown_model" {
|
|
t.Errorf("expected extra reason 'unknown_model', got %v", body.Extra["reason"])
|
|
}
|
|
}
|
|
|
|
func TestNoSecretsInProblem(t *testing.T) {
|
|
// Verify that secrets, tokens, bodies are never leaked
|
|
p := Unauthorized("invalid bearer token").
|
|
WithExtra("attempted_route", "/v1/chat/completions")
|
|
|
|
w := httptest.NewRecorder()
|
|
p.Write(w)
|
|
|
|
body := w.Body.String()
|
|
|
|
// Should not contain any auth-related secrets
|
|
if len(body) > 200 {
|
|
t.Errorf("problem document too large for detail: %d bytes (check for leaked content)", len(body))
|
|
}
|
|
|
|
// Parse and verify no sensitive fields are present
|
|
var doc Problem
|
|
json.Unmarshal(w.Body.Bytes(), &doc)
|
|
|
|
// Detail should describe the problem, not echo the token
|
|
if len(doc.Detail) > 100 {
|
|
t.Errorf("detail too long: %s", doc.Detail)
|
|
}
|
|
}
|