Skip to content

Commit

Permalink
feat: support to get headers from binary request (#565)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
LinuxSuRen and LinuxSuRen authored Nov 29, 2024
1 parent 0f02984 commit 072cd42
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 42 deletions.
10 changes: 4 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ COPY --from=ui /workspace/dist/assets/*.css cmd/data/index.css
RUN CGO_ENABLED=0 go build -v -a -ldflags "-w -s -X github.com/linuxsuren/api-testing/pkg/version.version=${VERSION}\
-X github.com/linuxsuren/api-testing/pkg/version.date=$(date +%Y-%m-%d)" -o atest .

FROM docker.io/library/ubuntu:23.10
FROM docker.io/library/alpine:3.20.3

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

RUN apt update -y && \
# required for atest-store-git
apt install -y --no-install-recommends ssh-client ca-certificates curl && \
rm -rf /var/lib/apt/lists/*

# required for atest-store-git
RUN apk add curl openssh-client bash openssl

EXPOSE 8080
CMD ["atest", "server", "--local-storage=/var/data/api-testing/*.yaml"]
4 changes: 2 additions & 2 deletions console/atest-ui/src/views/TestCase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1335,10 +1335,10 @@ const renameTestCase = (name: string) => {
<Codemirror v-if="!isResponseFile" v-model="testResult.bodyText"/>
<div v-if="isResponseFile" style="padding-top: 10px;">
<el-row>
<el-col :span="8">
<el-col :span="10">
<div>Response body is too large, please download to view.</div>
</el-col>
<el-col :span="4">
<el-col :span="2">
<el-button type="primary" @click="downloadResponseFile">Download</el-button>
</el-col>
</el-row>
Expand Down
1 change: 1 addition & 0 deletions e2e/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM ghcr.io/linuxsuren/api-testing:master
WORKDIR /workspace
COPY e2e/* .
COPY helm/api-testing api-testing
RUN apk add curl openssh-client bash openssl
RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
RUN chmod 700 get_helm.sh
RUN ./get_helm.sh
Expand Down
26 changes: 26 additions & 0 deletions e2e/test-suite-common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,32 @@ items:
}
}
}
- name: createTestCaseForBinary
request:
api: /suites/{{.param.suiteName}}/cases
method: POST
header:
X-Store-Name: "{{.param.store}}"
body: |
{
"suiteName": "{{.param.suiteName}}",
"data": {
"name": "binary",
"request": {
"api": "{{.param.server}}/get",
"method": "GET"
}
}
}
- name: runBinaryCase
request:
api: /suites/{{.param.suiteName}}/cases/binary/run
method: POST
header:
X-Store-Name: "{{.param.store}}"
expect:
verify:
- any(data.header, {.value == "application/octet-stream"})
- name: getTestCase
request:
api: /suites/{{.param.suiteName}}/cases/{{.param.caseName}}
Expand Down
55 changes: 49 additions & 6 deletions pkg/runner/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -206,10 +208,11 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte

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

r.withSimpleResponseRecord(resp)
if isNonBinaryContent(respType) {
var responseBodyData []byte
var rErr error
if responseBodyData, rErr = r.withResponseRecord(resp); rErr != nil {
if responseBodyData, rErr = r.withResponseBodyRecord(resp); rErr != nil {
err = errors.Join(err, rErr)
return
}
Expand All @@ -224,13 +227,49 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte

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

r.cookies = append(r.cookies, resp.Cookies()...)
return
}

func HandleLargeResponseBody(resp SimpleResponse, suite string, caseName string) (SimpleResponse, error) {
const maxSize = 5120
prefix := "isFilePath-" + strings.Join([]string{suite, caseName}, "-")
if len(resp.Body) > 0 {
resp.RawBody = []byte(resp.Body)
}

if len(resp.RawBody) > maxSize {
fmt.Println("response body is too large, will be saved to file", "size", len(resp.RawBody))
tmpFile, err := os.CreateTemp("", prefix+"-")
defer tmpFile.Close()
if err != nil {
return resp, fmt.Errorf("failed to create file: %w", err)
}

if _, err = tmpFile.Write(resp.RawBody); err != nil {
return resp, fmt.Errorf("failed to write response body to file: %w", err)
}
absFilePath, err := filepath.Abs(tmpFile.Name())
if err != nil {
return resp, fmt.Errorf("failed to get absolute file path: %w", err)
}
resp.Body = filepath.Base(absFilePath)
return resp, nil
}
return resp, nil
}

func ammendHeaders(headers http.Header, body []byte) {
// add content-length if it's missing
if val := headers.Get(util.ContentLength); val == "" {
Expand Down Expand Up @@ -300,19 +339,23 @@ func generateRandomValue(param spec.Parameter) interface{} {
}
}

func (r *simpleTestCaseRunner) withResponseRecord(resp *http.Response) (responseBodyData []byte, err error) {
responseBodyData, err = io.ReadAll(resp.Body)
func (r *simpleTestCaseRunner) withSimpleResponseRecord(resp *http.Response) {
r.simpleResponse = SimpleResponse{
StatusCode: resp.StatusCode,
Header: make(map[string]string),
Body: string(responseBodyData),
}

// add some headers for convienience
ammendHeaders(resp.Header, responseBodyData)
for key := range resp.Header {
r.simpleResponse.Header[key] = resp.Header.Get(key)
}
}

func (r *simpleTestCaseRunner) withResponseBodyRecord(resp *http.Response) (responseBodyData []byte, err error) {
responseBodyData, err = io.ReadAll(resp.Body)
r.simpleResponse.Body = string(responseBodyData)

// add some headers for convenience
ammendHeaders(resp.Header, responseBodyData)
return
}

Expand Down
1 change: 1 addition & 0 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type ResponseRecord interface {
type SimpleResponse struct {
Header map[string]string
Body string
RawBody []byte
StatusCode int
}

Expand Down
28 changes: 1 addition & 27 deletions pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, er
output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx)
if getter, ok := suiteRunner.(runner.ResponseRecord); ok {
resp := getter.GetResponseRecord()
resp, err = handleLargeResponseBody(resp, suite.Name, testCase.Name)
resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name)
reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{
StatusCode: int32(resp.StatusCode),
Body: resp.Body,
Expand Down Expand Up @@ -343,32 +343,6 @@ func (s *server) BatchRun(srv Runner_BatchRunServer) (err error) {
}
}
}
return
}

func handleLargeResponseBody(resp runner.SimpleResponse, suite string, caseName string) (reply runner.SimpleResponse, err error) {
const maxSize = 5120
prefix := "isFilePath-" + strings.Join([]string{suite, caseName}, "-")

if len(resp.Body) > maxSize {
remoteServerLogger.Logger.Info("response body is too large, will be saved to file", "size", len(resp.Body))
tmpFile, err := os.CreateTemp("", prefix+"-")
defer tmpFile.Close()
if err != nil {
return resp, fmt.Errorf("failed to create file: %w", err)
}

if _, err = tmpFile.Write([]byte(resp.Body)); err != nil {
return resp, fmt.Errorf("failed to write response body to file: %w", err)
}
absFilePath, err := filepath.Abs(tmpFile.Name())
if err != nil {
return resp, fmt.Errorf("failed to get absolute file path: %w", err)
}
resp.Body = filepath.Base(absFilePath)
return resp, nil
}
return resp, nil
}

func (s *server) DownloadResponseFile(ctx context.Context, in *TestCase) (reply *FileData, err error) {
Expand Down
1 change: 1 addition & 0 deletions pkg/util/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const (
YAML = "application/yaml"
ZIP = "application/zip"
OctetStream = "application/octet-stream"
Image = "image/jpeg"
Plain = "text/plain"
Authorization = "Authorization"
)
1 change: 0 additions & 1 deletion tools/make/run.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ run-server: ## Run the API Testing server
run-server: build-ui run-backend
run-backend:
go run . server --local-storage 'bin/*.yaml' --console-path ${ATEST_UI}/dist \
--mock-config bin/mock.yaml \
--extension-registry ghcr.io --download-timeout 90s

.PHONY: run-console
Expand Down

0 comments on commit 072cd42

Please sign in to comment.