76ba500417
CI / docker (push) Successful in 2m4s
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
4.9 KiB
Go
180 lines
4.9 KiB
Go
package provider
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestResponsesToChatCompletion_StringInput(t *testing.T) {
|
|
body := []byte(`{"model":"gpt-4o-mini","instructions":"Be brief","input":"Hello"}`)
|
|
out, err := ResponsesToChatCompletion(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var req map[string]any
|
|
if err := json.Unmarshal(out, &req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if req["model"] != "gpt-4o-mini" {
|
|
t.Fatalf("model: %v", req["model"])
|
|
}
|
|
msgs := req["messages"].([]any)
|
|
if len(msgs) != 2 {
|
|
t.Fatalf("messages len: %d", len(msgs))
|
|
}
|
|
sys := msgs[0].(map[string]any)
|
|
user := msgs[1].(map[string]any)
|
|
if sys["role"] != "system" || sys["content"] != "Be brief" {
|
|
t.Fatalf("system: %#v", sys)
|
|
}
|
|
if user["role"] != "user" || user["content"] != "Hello" {
|
|
t.Fatalf("user: %#v", user)
|
|
}
|
|
}
|
|
|
|
func TestChatCompletionToResponses(t *testing.T) {
|
|
body := []byte(`{"id":"chatcmpl-abc123","model":"gpt-4o-mini","created":123,"choices":[{"message":{"role":"assistant","content":"Hi"}}],"usage":{"prompt_tokens":3,"completion_tokens":1,"total_tokens":4}}`)
|
|
out, err := ChatCompletionToResponses(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var resp map[string]any
|
|
if err := json.Unmarshal(out, &resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp["object"] != "response" {
|
|
t.Fatalf("object: %v", resp["object"])
|
|
}
|
|
if resp["id"] != "resp_abc123" {
|
|
t.Fatalf("id: %v", resp["id"])
|
|
}
|
|
if resp["output_text"] != "Hi" {
|
|
t.Fatalf("output_text: %v", resp["output_text"])
|
|
}
|
|
output := resp["output"].([]any)
|
|
msg := output[0].(map[string]any)
|
|
content := msg["content"].([]any)
|
|
text := content[0].(map[string]any)
|
|
if text["type"] != "output_text" || text["text"] != "Hi" {
|
|
t.Fatalf("text: %#v", text)
|
|
}
|
|
if _, ok := text["annotations"]; !ok {
|
|
t.Fatalf("missing annotations: %#v", text)
|
|
}
|
|
usage := resp["usage"].(map[string]any)
|
|
if usage["input_tokens"].(float64) != 3 {
|
|
t.Fatalf("usage: %#v", usage)
|
|
}
|
|
if _, ok := usage["input_tokens_details"]; !ok {
|
|
t.Fatalf("missing input_tokens_details")
|
|
}
|
|
}
|
|
|
|
func TestResponsesToChatCompletion_InputTextParts(t *testing.T) {
|
|
body := []byte(`{
|
|
"model":"gpt-4o-mini",
|
|
"input":[{"role":"user","content":[{"type":"input_text","text":"Hello"}]}]
|
|
}`)
|
|
out, err := ResponsesToChatCompletion(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var req map[string]any
|
|
if err := json.Unmarshal(out, &req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msgs := req["messages"].([]any)
|
|
user := msgs[0].(map[string]any)
|
|
if user["content"] != "Hello" {
|
|
t.Fatalf("content: %#v", user["content"])
|
|
}
|
|
}
|
|
|
|
func TestResponsesToChatCompletion_InputTextAndImage(t *testing.T) {
|
|
body := []byte(`{
|
|
"model":"gpt-4o-mini",
|
|
"input":[{"role":"user","content":[
|
|
{"type":"input_text","text":"describe"},
|
|
{"type":"input_image","image_url":"https://example.com/a.png","detail":"auto"}
|
|
]}]
|
|
}`)
|
|
out, err := ResponsesToChatCompletion(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var req map[string]any
|
|
if err := json.Unmarshal(out, &req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msgs := req["messages"].([]any)
|
|
user := msgs[0].(map[string]any)
|
|
parts := user["content"].([]any)
|
|
if len(parts) != 2 {
|
|
t.Fatalf("parts: %#v", parts)
|
|
}
|
|
textPart := parts[0].(map[string]any)
|
|
if textPart["type"] != "text" || textPart["text"] != "describe" {
|
|
t.Fatalf("text part: %#v", textPart)
|
|
}
|
|
imgPart := parts[1].(map[string]any)
|
|
if imgPart["type"] != "image_url" {
|
|
t.Fatalf("image part: %#v", imgPart)
|
|
}
|
|
}
|
|
|
|
func TestResponsesToChatCompletion_MessageItem(t *testing.T) {
|
|
body := []byte(`{
|
|
"model":"gpt-4o-mini",
|
|
"input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"Hi"}]}]
|
|
}`)
|
|
out, err := ResponsesToChatCompletion(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(out), "input_text") {
|
|
t.Fatalf("should not contain input_text: %s", out)
|
|
}
|
|
var req map[string]any
|
|
if err := json.Unmarshal(out, &req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msgs := req["messages"].([]any)
|
|
user := msgs[0].(map[string]any)
|
|
if user["content"] != "Hi" {
|
|
t.Fatalf("content: %#v", user["content"])
|
|
}
|
|
}
|
|
|
|
func TestAdaptResponsesStream(t *testing.T) {
|
|
upstream := strings.NewReader("data: {\"id\":\"chatcmpl-1\",\"model\":\"gpt-4o-mini\",\"choices\":[{\"delta\":{\"content\":\"Hi\"}}]}\n\ndata: [DONE]\n\n")
|
|
var out strings.Builder
|
|
result, err := AdaptResponsesStream([]byte(`{"model":"gpt-4o-mini","stream":true}`), upstream, &out, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.StatusCode != 200 {
|
|
t.Fatalf("status: %d", result.StatusCode)
|
|
}
|
|
s := out.String()
|
|
for _, event := range []string{
|
|
"response.created",
|
|
"response.in_progress",
|
|
"response.output_item.added",
|
|
"response.output_text.delta",
|
|
"response.output_text.done",
|
|
"response.output_item.done",
|
|
"response.completed",
|
|
} {
|
|
if !strings.Contains(s, event) {
|
|
t.Fatalf("missing event %s: %s", event, s)
|
|
}
|
|
}
|
|
if !strings.Contains(s, `"delta":"Hi"`) {
|
|
t.Fatalf("missing delta text: %s", s)
|
|
}
|
|
if !strings.Contains(s, `"output_text":"Hi"`) {
|
|
t.Fatalf("missing output_text in completed: %s", s)
|
|
}
|
|
}
|