Skip to content

Commit

Permalink
improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac committed Mar 21, 2024
1 parent 3c8f9bf commit 3853870
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 16 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ files:
spec: openapi2
- path: openapi.yaml
spec: openapi3
trimPrefix: /v1
envPrefix: PET_STORE
methodAlias:
post: create
put: update
- path: schema.json
spec: ndc
```
`trimPrefix`, `envPrefix` and `methodAlias` options are used to convert OpenAPI by [ndc-rest-schema](https://github.com/hasura/ndc-rest-schema#openapi).

**Supported specs**

- `openapi3`: OpenAPI 3.0/3.1.
Expand All @@ -45,3 +52,13 @@ ndc-rest convert -f ./rest/testdata/jsonplaceholder/swagger.json -o ./rest/testd
```

> The `convert` command is imported from the [NDC REST Schema](https://github.com/hasura/ndc-rest-schema#quick-start) CLI tool.

### Environment variable template

The connector can replaces `{{xxx}}` templates with environment variables. The converter automatically renders variables for API keys and tokens when converting OpenAPI documents. However, you can add your custom variables as well.

### Authentication

The current version supports API key and Auth token authentication schemes. The configuration is inspired from `securitySchemes` [with env variables](https://github.com/hasura/ndc-rest-schema#authentication)

See [this example](rest/testdata/auth/schema.yaml) for more context.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
toolchain go1.22.0

require (
github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855
github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217
github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3
github.com/lmittmann/tint v1.0.4
gopkg.in/yaml.v3 v3.0.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0Q
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855 h1:d3bq7jjsK/aPcFih6B+h1uM3CkNY24F8Eo5JXx8QBo8=
github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg=
github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217 h1:HhfRPEHzEpBO2j55oHV7r+uBQcg2KMgWlmEtp3TvWO4=
github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg=
github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3 h1:A1N3ilX1EIxjTA2qaHPXFnIECpYjKhIFmtva8qJrpHk=
github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3/go.mod h1:EeM3hKbhCfBjDDva8mP4D2KeptTqAaxNqNw8rFQAnMs=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
Expand Down
4 changes: 3 additions & 1 deletion rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ func evalHTTPResponse(resp *http.Response, selection schema.NestedField) (any, e

return utils.EvalNestedColumnFields(selection, result)
default:
return nil, fmt.Errorf("unsupported content type %s", contentType)
return nil, schema.NewConnectorError(http.StatusInternalServerError, "failed to evaluate response", map[string]any{
"cause": fmt.Sprintf("unsupported content type %s", contentType),
})
}
}

Expand Down
136 changes: 136 additions & 0 deletions rest/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"path"
"reflect"
Expand Down Expand Up @@ -86,7 +88,141 @@ func TestRESTConnector_configurationFailure(t *testing.T) {
}

func TestRESTConnector_authentication(t *testing.T) {
apiKey := "random_api_key"
bearerToken := "random_bearer_token"
slog.SetLogLoggerLevel(slog.LevelDebug)
server := createMockServer(t, apiKey, bearerToken)
defer server.Close()

t.Setenv("PET_STORE_URL", server.URL)
t.Setenv("PET_STORE_API_KEY", apiKey)
t.Setenv("PET_STORE_BEARER_TOKEN", bearerToken)
connServer, err := connector.NewServer(NewRESTConnector(), &connector.ServerOptions{
Configuration: "testdata/auth",
}, connector.WithoutRecovery())
assertNoError(t, err)
testServer := connServer.BuildTestServer()
defer testServer.Close()

t.Run("auth_default", func(t *testing.T) {
reqBody := []byte(`{
"collection": "findPets",
"query": {
"fields": {
"__value": {
"type": "column",
"column": "__value"
}
}
},
"arguments": {},
"collection_relationships": {}
}`)

res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody))
assertNoError(t, err)
assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{
{
Rows: []map[string]any{
{"__value": map[string]any{}},
},
},
})
})

t.Run("auth_api_key", func(t *testing.T) {
reqBody := []byte(`{
"operation": "addPet",
"query": {
"fields": {
"__value": {
"type": "column",
"column": "__value"
}
}
},
"arguments": {},
"collection_relationships": {}
}`)

res, err := http.Post(fmt.Sprintf("%s/mutation", testServer.URL), "application/json", bytes.NewBuffer(reqBody))
assertNoError(t, err)
assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{
{
Rows: []map[string]any{
{"__value": map[string]any{}},
},
},
})
})

t.Run("auth_bearer", func(t *testing.T) {
reqBody := []byte(`{
"collection": "findPetsByStatus",
"query": {
"fields": {
"__value": {
"type": "column",
"column": "__value"
}
}
},
"arguments": {},
"collection_relationships": {}
}`)

res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody))
assertNoError(t, err)
assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{
{
Rows: []map[string]any{
{"__value": map[string]any{}},
},
},
})
})
}

func createMockServer(t *testing.T, apiKey string, bearerToken string) *httptest.Server {
mux := http.NewServeMux()

writeResponse := func(w http.ResponseWriter) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{}`))

Check failure on line 192 in rest/connector_test.go

View workflow job for this annotation

GitHub Actions / Run Go lint and unit tests

Error return value of `w.Write` is not checked (errcheck)

}
mux.HandleFunc("/pet", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet, http.MethodPost:
if r.Header.Get("api_key") != apiKey {
t.Errorf("invalid api key, expected %s, got %s", apiKey, r.Header.Get("api_key"))
t.FailNow()
return
}
writeResponse(w)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
})

mux.HandleFunc("/pet/findByStatus", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", bearerToken) {
t.Errorf("invalid bearer token, expected %s, got %s", bearerToken, r.Header.Get("Authorization"))
t.FailNow()
return
}
writeResponse(w)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
})

return httptest.NewServer(mux)
}

func assertNdcOperations(t *testing.T, dir string, targetURL string) {
Expand Down
9 changes: 6 additions & 3 deletions rest/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rest

import (
"fmt"
"log"
"net/url"
"strings"

Expand Down Expand Up @@ -99,6 +100,7 @@ func (rm RESTMetadata) buildURL(endpoint string) string {
}

func (rm RESTMetadata) applySecurity(req *rest.Request) (*rest.Request, error) {
log.Println("security", req.URL, req.Security)
if req.Security.IsEmpty() {
req.Security = rm.settings.Security
}
Expand All @@ -115,15 +117,16 @@ func (rm RESTMetadata) applySecurity(req *rest.Request) (*rest.Request, error) {
if req.Headers == nil {
req.Headers = make(map[string]string)
}

switch securityScheme.Type {
case rest.HTTPAuthScheme:
headerName := securityScheme.Header
if headerName == "" {
headerName = "Authorization"
}
scheme := securityScheme.Name
if securityScheme.Name == "bearer" || securityScheme.Name == "basic" {
scheme = utils.ToPascalCase(securityScheme.Name)
scheme := securityScheme.Scheme
if scheme == "bearer" || scheme == "basic" {
scheme = utils.ToPascalCase(securityScheme.Scheme)
}
req.Headers[headerName] = fmt.Sprintf("%s %s", scheme, securityScheme.Value)
case rest.APIKeyScheme:
Expand Down
2 changes: 1 addition & 1 deletion rest/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]stri
switch conf.Spec {
case rest.NDCSpec:
var result rest.NDCRestSchema
fileFormat, err := rest.ParseSchemaFileFormat(conf.Path)
fileFormat, err := rest.ParseSchemaFileFormat(strings.Trim(path.Ext(conf.Path), "."))
if err != nil {
return nil, err
}
Expand Down
15 changes: 5 additions & 10 deletions rest/testdata/auth/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,15 @@ settings:
read:pets: read your pets
write:pets: modify pets in your account
security:
- api_key
- api_key: []
version: 1.0.18
collections: []
functions:
- request:
url: "/pet"
method: get
parameters: {}
security:
- petstore_auth:
- write:pets
- read:pets
parameters: []
security: []
arguments: {}
description: Finds Pets
name: findPets
Expand All @@ -55,7 +52,7 @@ functions:
- pending
- sold
security:
- bearer
- bearer: []
arguments:
status:
description: Status values that need to be considered for filter
Expand All @@ -78,9 +75,7 @@ procedures:
headers:
Content-Type: application/json
security:
- petstore_auth:
- write:pets
- read:pets
- api_key: []
arguments:
body:
description: Request body of /pet
Expand Down

0 comments on commit 3853870

Please sign in to comment.