forked from QuantumNous/new-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopenai_image_request_test.go
More file actions
160 lines (141 loc) 路 5.12 KB
/
Copy pathopenai_image_request_test.go
File metadata and controls
160 lines (141 loc) 路 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package helper
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/QuantumNous/new-api/common"
relayconstant "github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
// TestGetAndValidOpenAIImageRequestMultipartStream verifies multipart image
// edit parsing: the stream field is parsed and validated, and the request body
// stays replayable for the upstream request.
func TestGetAndValidOpenAIImageRequestMultipartStream(t *testing.T) {
gin.SetMode(gin.TestMode)
newContext := func(t *testing.T, streamValue string, withImage bool) (*gin.Context, string) {
var body bytes.Buffer
writer := multipart.NewWriter(&body)
require.NoError(t, writer.WriteField("model", "gpt-image-1"))
require.NoError(t, writer.WriteField("prompt", "edit this image"))
require.NoError(t, writer.WriteField("stream", streamValue))
if withImage {
part, err := writer.CreateFormFile("image", "input.png")
require.NoError(t, err)
_, err = part.Write([]byte("fake image"))
require.NoError(t, err)
}
require.NoError(t, writer.Close())
originalBody := body.String()
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", &body)
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
return c, originalBody
}
t.Run("valid stream value keeps body replayable", func(t *testing.T) {
c, originalBody := newContext(t, "true", true)
req, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits)
require.NoError(t, err)
require.NotNil(t, req.Stream)
require.True(t, *req.Stream)
require.True(t, req.IsStream(c.Request))
bodyAfterValidation, err := io.ReadAll(c.Request.Body)
require.NoError(t, err)
require.Equal(t, originalBody, string(bodyAfterValidation))
form, err := common.ParseMultipartFormReusable(c)
require.NoError(t, err)
require.Equal(t, "true", url.Values(form.Value).Get("stream"))
require.Len(t, form.File["image"], 1)
})
t.Run("invalid stream value is rejected", func(t *testing.T) {
c, _ := newContext(t, "notabool", false)
_, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid stream value")
})
}
// TestGetAndValidOpenAIImageRequestNBounds guards the billing invariant that
// the image generation count can never reach quota calculation with a value
// large enough to overflow int64 into a negative charge.
func TestGetAndValidOpenAIImageRequestNBounds(t *testing.T) {
gin.SetMode(gin.TestMode)
newJSONContext := func(t *testing.T, body string) *gin.Context {
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/generations", bytes.NewBufferString(body))
c.Request.Header.Set("Content-Type", "application/json")
return c
}
boundErr := fmt.Sprintf("n must be an integer between 1 and %d", dto.MaxImageN)
tests := []struct {
name string
body string
wantErr string
wantN uint
}{
{
name: "overflowed uint64 n is rejected",
body: `{"model":"gpt-image-1","prompt":"a cat","n":18446744073686646784}`,
wantErr: boundErr,
},
{
name: "n above max is rejected",
body: fmt.Sprintf(`{"model":"gpt-image-1","prompt":"a cat","n":%d}`, dto.MaxImageN+1),
wantErr: boundErr,
},
{
name: "n at max is accepted",
body: fmt.Sprintf(`{"model":"gpt-image-1","prompt":"a cat","n":%d}`, dto.MaxImageN),
wantN: dto.MaxImageN,
},
{
name: "explicit n is accepted",
body: `{"model":"gpt-image-1","prompt":"a cat","n":3}`,
wantN: 3,
},
{
name: "zero n defaults to 1",
body: `{"model":"gpt-image-1","prompt":"a cat","n":0}`,
wantN: 1,
},
{
name: "absent n defaults to 1",
body: `{"model":"gpt-image-1","prompt":"a cat"}`,
wantN: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := newJSONContext(t, tt.body)
req, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesGenerations)
if tt.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
require.NotNil(t, req.N)
require.Equal(t, tt.wantN, *req.N)
require.Equal(t, float64(tt.wantN), req.GetTokenCountMeta().BillingRatios["n"])
})
}
t.Run("negative multipart n is rejected", func(t *testing.T) {
var body bytes.Buffer
writer := multipart.NewWriter(&body)
require.NoError(t, writer.WriteField("model", "gpt-image-1"))
require.NoError(t, writer.WriteField("prompt", "edit this image"))
require.NoError(t, writer.WriteField("n", "-22904832"))
require.NoError(t, writer.Close())
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", &body)
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
_, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits)
require.Error(t, err)
require.Contains(t, err.Error(), boundErr)
})
}