Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

Commit

Permalink
feat: api schema tests
Browse files Browse the repository at this point in the history
  • Loading branch information
elijaharita committed Oct 18, 2023
1 parent 2d51fc1 commit 406d61a
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 2 deletions.
2 changes: 2 additions & 0 deletions api/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func (m *HttpServer) handleBlobGetByID(w http.ResponseWriter, r *http.Request, i
respondWithJson(w, errResponseBlobNotFound, http.StatusNotFound)
return
default:
logger.Warnf("m.store.Describe() failed")
respondWithJson(w, errResponseInternalError(err), http.StatusInternalServerError)
return
}
Expand All @@ -117,6 +118,7 @@ func (m *HttpServer) handleBlobGetByID(w http.ResponseWriter, r *http.Request, i
respondWithJson(w, errResponseBlobNotFound, http.StatusNotFound)
return
default:
logger.Warn("m.store.Get() failed")
respondWithJson(w, errResponseInternalError(err), http.StatusInternalServerError)
return
}
Expand Down
7 changes: 7 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMu
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
Expand Down Expand Up @@ -749,6 +750,7 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
Expand Down Expand Up @@ -1216,6 +1218,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM=
github.com/oracle/oci-go-sdk/v65 v65.32.0 h1:6ASjGPE+k42xHgeAavNGbWtTZ4Z4KhlEhvJ4SVFMZrI=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
Expand Down Expand Up @@ -1295,6 +1298,7 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
Expand All @@ -1311,6 +1315,7 @@ github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
Expand Down Expand Up @@ -1340,6 +1345,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542/go.mod h1:7T39/ZMvaSEZlBPoYfVFmsBLmUl3uz9IuzWj/U6FtvQ=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk=
go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
Expand Down Expand Up @@ -1714,3 +1720,4 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
storj.io/common v0.0.0-20221123115229-fed3e6651b63 h1:OuleF/3FvZe3Nnu6NdwVr+FvCXjfD4iNNdgfI2kcs3k=
7 changes: 5 additions & 2 deletions integration/singularity/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package singularity

import (
"context"
"errors"
"fmt"
"io"
"math/big"
Expand Down Expand Up @@ -416,10 +417,12 @@ func (s *SingularityStore) Get(ctx context.Context, id blob.ID) (io.ReadSeekClos
}

func (s *SingularityStore) Describe(ctx context.Context, id blob.ID) (*blob.Descriptor, error) {
// this is largely artificial -- we're verifying the singularity item, but just reading from
// the local store
idStream, err := os.Open(path.Join(s.local.Dir(), id.String()+".id"))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, blob.ErrBlobNotFound
}

return nil, err
}
fileIDString, err := io.ReadAll(idStream)
Expand Down
228 changes: 228 additions & 0 deletions integration/test/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package test

import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

type testCase struct {
name string
onMethod string
onPath string
onBody string
onContentType string
expectStatus int
expectBody string // may be regex. optional (empty = not tested)

// Silences error for this schema case
skip bool
}

type schemaCase struct {
method string
path string
status string
covered bool
}

func (s schemaCase) String() string {
return fmt.Sprintf("%s %s -> %s (covered: %v)\n", s.method, s.path, s.status, s.covered)
}

func TestApi(t *testing.T) {
env := NewEnvironment(t)

// ---- Add test cases here ----
tests := []testCase{
{
name: "POST /v0/blob is 201",
onMethod: http.MethodPost,
onPath: "/v0/blob",
onBody: "fish",
onContentType: "application/octet-stream",
expectBody: "{\"id\":\".*\"}",
expectStatus: 201,
},
{
// not reliably testable
onMethod: http.MethodPost,
onPath: "/v0/blob",
expectStatus: 500,
skip: true,
},
{
// not reliably testable
onMethod: http.MethodPost,
onPath: "/v0/blob",
expectStatus: 503,
skip: true,
},
{
// tested in integration
name: "GET /v0/blob/{id} is 200",
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000",
expectStatus: 200,
skip: true,
},
{
name: "GET /v0/blob/{id} for unknown ID is 404",
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000",
expectStatus: 404,
},
{
// not reliably testable
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000",
expectStatus: 500,
skip: true,
},
{
// not reliably testable
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000",
expectStatus: 503,
skip: true,
},
{
// tested in integration
name: "GET /v0/blob/{id}/status is 200",
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000/status",
expectStatus: 200,
skip: true,
},
{
// tested in integration
name: "GET /v0/blob/{id}/status for unknown ID is 404",
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000/status",
expectStatus: 404,
},
{
// not reliably testable
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000/status",
expectStatus: 500,
skip: true,
},
{
// not reliably testable
onMethod: http.MethodGet,
onPath: "/v0/blob/00000000-0000-0000-0000-000000000000/status",
expectStatus: 503,
skip: true,
},
}

// Read and parse openapi.yaml for ensuring all paths, methods, and status
// codes are covered

schemaString, err := os.ReadFile("../../openapi.yaml")
require.NoError(t, err, "could not find openapi.yaml")

schemaMap := make(map[string]interface{})
err = yaml.Unmarshal(schemaString, schemaMap)
require.NoError(t, err)

var schemaCases []schemaCase

type kvmap = map[string]interface{}
for pathName, path := range schemaMap["paths"].(kvmap) {
for methodName, method := range path.(kvmap) {
for statusCode := range method.(kvmap)["responses"].(kvmap) {
schemaCases = append(schemaCases, schemaCase{
method: methodName,
path: pathName,
status: statusCode,
covered: false,
})
}
}
}

// Run all tests
for _, test := range tests {
if !test.skip {
req, err := http.NewRequest(
test.onMethod,
requireJoinUrlPath(t, env.MotionAPIEndpoint, test.onPath),
bytes.NewReader([]byte(test.expectBody)),
)
req.Header.Set("Content-Type", test.onContentType)
require.NoError(t, err)

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)

// Body must be as expected
var body string
if test.expectBody != "" {
bodyBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)

body = string(bodyBytes)
require.Regexp(t, test.expectBody, string(body))
}

// Status code must be as expected
require.Equal(t, test.expectStatus, resp.StatusCode, "Incorrect status code for test %#v (resp body: %v)", test, body)
}

// Find matching schema case and mark as covered
for i := range schemaCases {
methodsMatch := strings.EqualFold(schemaCases[i].method, test.onMethod)
pathsMatch := schemaPathFitsTest(schemaCases[i].path, test.onPath)
statusesMatch := schemaCases[i].status == strconv.Itoa(test.expectStatus)

if methodsMatch && pathsMatch && statusesMatch {
schemaCases[i].covered = true
break
}
}
}

// Make sure all schema cases are covered
var notCovered []schemaCase
for _, schemaCase := range schemaCases {
if !schemaCase.covered {
notCovered = append(notCovered, schemaCase)
}
}

require.Empty(t, notCovered, "all schema cases must be covered")
}

// Checks whether a test's path fits into a schema path listed in openapi.yaml,
// where the schema path may have variable parts (example, schema /foo/bar/{x}
// == test /foo/bar/5).
func schemaPathFitsTest(schemaPath string, testPath string) bool {
schemaParts := strings.Split(schemaPath, "/")
testParts := strings.Split(testPath, "/")

if len(schemaParts) != len(testParts) {
return false
}

for i := range schemaParts {
if schemaParts[i] == testParts[i] {
continue
} else if schemaParts[i][0] == '{' {
continue
} else {
return false
}
}

return true
}

0 comments on commit 406d61a

Please sign in to comment.