Skip to content

Commit 072cd42

Browse files
authored
feat: support to get headers from binary request (#565)
* feat: support to get headers from binary request * change base image from ubuntu to alpine * install bash in alpine * install openssl in alpine * fix e2e * support to download bianry files on ui --------- Co-authored-by: rick <[email protected]>
1 parent 0f02984 commit 072cd42

File tree

9 files changed

+85
-42
lines changed

9 files changed

+85
-42
lines changed

Dockerfile

+4-6
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ COPY --from=ui /workspace/dist/assets/*.css cmd/data/index.css
3636
RUN CGO_ENABLED=0 go build -v -a -ldflags "-w -s -X github.com/linuxsuren/api-testing/pkg/version.version=${VERSION}\
3737
-X github.com/linuxsuren/api-testing/pkg/version.date=$(date +%Y-%m-%d)" -o atest .
3838

39-
FROM docker.io/library/ubuntu:23.10
39+
FROM docker.io/library/alpine:3.20.3
4040

4141
LABEL "com.github.actions.name"="API testing"
4242
LABEL "com.github.actions.description"="API testing"
@@ -54,10 +54,8 @@ COPY --from=builder /workspace/atest /usr/local/bin/atest
5454
COPY --from=builder /workspace/LICENSE /LICENSE
5555
COPY --from=builder /workspace/README.md /README.md
5656

57-
RUN apt update -y && \
58-
# required for atest-store-git
59-
apt install -y --no-install-recommends ssh-client ca-certificates curl && \
60-
rm -rf /var/lib/apt/lists/*
61-
57+
# required for atest-store-git
58+
RUN apk add curl openssh-client bash openssl
59+
6260
EXPOSE 8080
6361
CMD ["atest", "server", "--local-storage=/var/data/api-testing/*.yaml"]

console/atest-ui/src/views/TestCase.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1335,10 +1335,10 @@ const renameTestCase = (name: string) => {
13351335
<Codemirror v-if="!isResponseFile" v-model="testResult.bodyText"/>
13361336
<div v-if="isResponseFile" style="padding-top: 10px;">
13371337
<el-row>
1338-
<el-col :span="8">
1338+
<el-col :span="10">
13391339
<div>Response body is too large, please download to view.</div>
13401340
</el-col>
1341-
<el-col :span="4">
1341+
<el-col :span="2">
13421342
<el-button type="primary" @click="downloadResponseFile">Download</el-button>
13431343
</el-col>
13441344
</el-row>

e2e/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ FROM ghcr.io/linuxsuren/api-testing:master
33
WORKDIR /workspace
44
COPY e2e/* .
55
COPY helm/api-testing api-testing
6+
RUN apk add curl openssh-client bash openssl
67
RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
78
RUN chmod 700 get_helm.sh
89
RUN ./get_helm.sh

e2e/test-suite-common.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,32 @@ items:
163163
}
164164
}
165165
}
166+
- name: createTestCaseForBinary
167+
request:
168+
api: /suites/{{.param.suiteName}}/cases
169+
method: POST
170+
header:
171+
X-Store-Name: "{{.param.store}}"
172+
body: |
173+
{
174+
"suiteName": "{{.param.suiteName}}",
175+
"data": {
176+
"name": "binary",
177+
"request": {
178+
"api": "{{.param.server}}/get",
179+
"method": "GET"
180+
}
181+
}
182+
}
183+
- name: runBinaryCase
184+
request:
185+
api: /suites/{{.param.suiteName}}/cases/binary/run
186+
method: POST
187+
header:
188+
X-Store-Name: "{{.param.store}}"
189+
expect:
190+
verify:
191+
- any(data.header, {.value == "application/octet-stream"})
166192
- name: getTestCase
167193
request:
168194
api: /suites/{{.param.suiteName}}/cases/{{.param.caseName}}

pkg/runner/http.go

+49-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"fmt"
2323
"io"
2424
"net/http"
25+
"os"
26+
"path/filepath"
2527
"strconv"
2628
"strings"
2729
"time"
@@ -206,10 +208,11 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
206208

207209
respType := util.GetFirstHeaderValue(resp.Header, util.ContentType)
208210

211+
r.withSimpleResponseRecord(resp)
209212
if isNonBinaryContent(respType) {
210213
var responseBodyData []byte
211214
var rErr error
212-
if responseBodyData, rErr = r.withResponseRecord(resp); rErr != nil {
215+
if responseBodyData, rErr = r.withResponseBodyRecord(resp); rErr != nil {
213216
err = errors.Join(err, rErr)
214217
return
215218
}
@@ -224,13 +227,49 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
224227

225228
err = errors.Join(err, jsonSchemaValidation(testcase.Expect.Schema, responseBodyData))
226229
} else {
230+
switch respType {
231+
case util.OctetStream, util.Image:
232+
var data []byte
233+
if data, err = io.ReadAll(resp.Body); err == nil {
234+
r.simpleResponse.RawBody = data
235+
r.simpleResponse, err = HandleLargeResponseBody(r.simpleResponse, testcase.Group, testcase.Name)
236+
}
237+
}
227238
r.log.Debug("skip to read the body due to it is not struct content: %q\n", respType)
228239
}
229240

230241
r.cookies = append(r.cookies, resp.Cookies()...)
231242
return
232243
}
233244

245+
func HandleLargeResponseBody(resp SimpleResponse, suite string, caseName string) (SimpleResponse, error) {
246+
const maxSize = 5120
247+
prefix := "isFilePath-" + strings.Join([]string{suite, caseName}, "-")
248+
if len(resp.Body) > 0 {
249+
resp.RawBody = []byte(resp.Body)
250+
}
251+
252+
if len(resp.RawBody) > maxSize {
253+
fmt.Println("response body is too large, will be saved to file", "size", len(resp.RawBody))
254+
tmpFile, err := os.CreateTemp("", prefix+"-")
255+
defer tmpFile.Close()
256+
if err != nil {
257+
return resp, fmt.Errorf("failed to create file: %w", err)
258+
}
259+
260+
if _, err = tmpFile.Write(resp.RawBody); err != nil {
261+
return resp, fmt.Errorf("failed to write response body to file: %w", err)
262+
}
263+
absFilePath, err := filepath.Abs(tmpFile.Name())
264+
if err != nil {
265+
return resp, fmt.Errorf("failed to get absolute file path: %w", err)
266+
}
267+
resp.Body = filepath.Base(absFilePath)
268+
return resp, nil
269+
}
270+
return resp, nil
271+
}
272+
234273
func ammendHeaders(headers http.Header, body []byte) {
235274
// add content-length if it's missing
236275
if val := headers.Get(util.ContentLength); val == "" {
@@ -300,19 +339,23 @@ func generateRandomValue(param spec.Parameter) interface{} {
300339
}
301340
}
302341

303-
func (r *simpleTestCaseRunner) withResponseRecord(resp *http.Response) (responseBodyData []byte, err error) {
304-
responseBodyData, err = io.ReadAll(resp.Body)
342+
func (r *simpleTestCaseRunner) withSimpleResponseRecord(resp *http.Response) {
305343
r.simpleResponse = SimpleResponse{
306344
StatusCode: resp.StatusCode,
307345
Header: make(map[string]string),
308-
Body: string(responseBodyData),
309346
}
310347

311-
// add some headers for convienience
312-
ammendHeaders(resp.Header, responseBodyData)
313348
for key := range resp.Header {
314349
r.simpleResponse.Header[key] = resp.Header.Get(key)
315350
}
351+
}
352+
353+
func (r *simpleTestCaseRunner) withResponseBodyRecord(resp *http.Response) (responseBodyData []byte, err error) {
354+
responseBodyData, err = io.ReadAll(resp.Body)
355+
r.simpleResponse.Body = string(responseBodyData)
356+
357+
// add some headers for convenience
358+
ammendHeaders(resp.Header, responseBodyData)
316359
return
317360
}
318361

pkg/runner/runner.go

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ResponseRecord interface {
4747
type SimpleResponse struct {
4848
Header map[string]string
4949
Body string
50+
RawBody []byte
5051
StatusCode int
5152
}
5253

pkg/server/remote_server.go

+1-27
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, er
262262
output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx)
263263
if getter, ok := suiteRunner.(runner.ResponseRecord); ok {
264264
resp := getter.GetResponseRecord()
265-
resp, err = handleLargeResponseBody(resp, suite.Name, testCase.Name)
265+
resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name)
266266
reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{
267267
StatusCode: int32(resp.StatusCode),
268268
Body: resp.Body,
@@ -343,32 +343,6 @@ func (s *server) BatchRun(srv Runner_BatchRunServer) (err error) {
343343
}
344344
}
345345
}
346-
return
347-
}
348-
349-
func handleLargeResponseBody(resp runner.SimpleResponse, suite string, caseName string) (reply runner.SimpleResponse, err error) {
350-
const maxSize = 5120
351-
prefix := "isFilePath-" + strings.Join([]string{suite, caseName}, "-")
352-
353-
if len(resp.Body) > maxSize {
354-
remoteServerLogger.Logger.Info("response body is too large, will be saved to file", "size", len(resp.Body))
355-
tmpFile, err := os.CreateTemp("", prefix+"-")
356-
defer tmpFile.Close()
357-
if err != nil {
358-
return resp, fmt.Errorf("failed to create file: %w", err)
359-
}
360-
361-
if _, err = tmpFile.Write([]byte(resp.Body)); err != nil {
362-
return resp, fmt.Errorf("failed to write response body to file: %w", err)
363-
}
364-
absFilePath, err := filepath.Abs(tmpFile.Name())
365-
if err != nil {
366-
return resp, fmt.Errorf("failed to get absolute file path: %w", err)
367-
}
368-
resp.Body = filepath.Base(absFilePath)
369-
return resp, nil
370-
}
371-
return resp, nil
372346
}
373347

374348
func (s *server) DownloadResponseFile(ctx context.Context, in *TestCase) (reply *FileData, err error) {

pkg/util/default.go

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const (
7878
YAML = "application/yaml"
7979
ZIP = "application/zip"
8080
OctetStream = "application/octet-stream"
81+
Image = "image/jpeg"
8182
Plain = "text/plain"
8283
Authorization = "Authorization"
8384
)

tools/make/run.mk

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ run-server: ## Run the API Testing server
1313
run-server: build-ui run-backend
1414
run-backend:
1515
go run . server --local-storage 'bin/*.yaml' --console-path ${ATEST_UI}/dist \
16-
--mock-config bin/mock.yaml \
1716
--extension-registry ghcr.io --download-timeout 90s
1817

1918
.PHONY: run-console

0 commit comments

Comments
 (0)