veylant/internal/apierror/errors_test.go
2026-02-23 13:35:04 +01:00

112 lines
3.5 KiB
Go

package apierror_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/veylant/ia-gateway/internal/apierror"
)
func TestNewAuthError(t *testing.T) {
e := apierror.NewAuthError("bad token")
assert.Equal(t, http.StatusUnauthorized, e.HTTPStatus)
assert.Equal(t, "authentication_error", e.Type)
assert.Equal(t, "bad token", e.Message)
assert.NotEmpty(t, e.Code)
}
func TestNewForbiddenError(t *testing.T) {
e := apierror.NewForbiddenError("no access")
assert.Equal(t, http.StatusForbidden, e.HTTPStatus)
assert.Equal(t, "permission_error", e.Type)
}
func TestNewBadRequestError(t *testing.T) {
e := apierror.NewBadRequestError("missing model")
assert.Equal(t, http.StatusBadRequest, e.HTTPStatus)
assert.Equal(t, "invalid_request_error", e.Type)
}
func TestNewUpstreamError(t *testing.T) {
e := apierror.NewUpstreamError("OpenAI down")
assert.Equal(t, http.StatusBadGateway, e.HTTPStatus)
assert.Equal(t, "api_error", e.Type)
}
func TestNewRateLimitError(t *testing.T) {
e := apierror.NewRateLimitError("too many requests")
assert.Equal(t, http.StatusTooManyRequests, e.HTTPStatus)
assert.Equal(t, "rate_limit_error", e.Type)
assert.Equal(t, 1, e.RetryAfterSec, "NewRateLimitError must set RetryAfterSec=1 (RFC 6585)")
}
func TestWriteError_RetryAfter_SetWhenPresent(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteError(rec, apierror.NewRateLimitError("slow down"))
assert.Equal(t, "1", rec.Header().Get("Retry-After"))
}
func TestWriteError_NoRetryAfter_WhenZero(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteError(rec, apierror.NewAuthError("denied"))
assert.Empty(t, rec.Header().Get("Retry-After"))
}
func TestWriteErrorWithRequestID_SetsHeader(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteErrorWithRequestID(rec, apierror.NewAuthError("denied"), "req-abc-123")
assert.Equal(t, "req-abc-123", rec.Header().Get("X-Request-Id"))
assert.Equal(t, http.StatusUnauthorized, rec.Code)
}
func TestWriteErrorWithRequestID_EmptyID_NoHeader(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteErrorWithRequestID(rec, apierror.NewAuthError("denied"), "")
assert.Empty(t, rec.Header().Get("X-Request-Id"))
}
func TestNewTimeoutError(t *testing.T) {
e := apierror.NewTimeoutError("upstream timed out")
assert.Equal(t, http.StatusGatewayTimeout, e.HTTPStatus)
}
func TestNewInternalError(t *testing.T) {
e := apierror.NewInternalError("unexpected panic")
assert.Equal(t, http.StatusInternalServerError, e.HTTPStatus)
}
func TestAPIError_Error(t *testing.T) {
e := apierror.NewAuthError("some message")
assert.Equal(t, "some message", e.Error())
}
func TestWriteError_SetsStatusAndContentType(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteError(rec, apierror.NewAuthError("denied"))
assert.Equal(t, http.StatusUnauthorized, rec.Code)
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
}
func TestWriteError_BodyIsOpenAIEnvelope(t *testing.T) {
rec := httptest.NewRecorder()
apierror.WriteError(rec, apierror.NewRateLimitError("slow down"))
var body struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
Code string `json:"code"`
} `json:"error"`
}
require.NoError(t, json.NewDecoder(rec.Body).Decode(&body))
assert.Equal(t, "rate_limit_error", body.Error.Type)
assert.Equal(t, "slow down", body.Error.Message)
assert.NotEmpty(t, body.Error.Code)
}