112 lines
3.5 KiB
Go
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)
|
|
}
|