Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/230 and feature/231 #239

Merged
merged 5 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions controllers/rest/default_controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,20 @@ func DefaultReadController(api Container, commandDispatcher model.CommandDispatc
return NewControllerError("unexpected error getting entity", err, http.StatusBadRequest)
}

if entity == nil && len(templates) == 0 {
return ctxt.JSON(http.StatusNotFound, nil)
}
}
//render html if that is configured

//check header to determine response check the accepts header
acceptHeader := ctxt.Request().Header.Get("Accept")

// if no accept header is found it defaults to application/json
if acceptHeader == "" {
acceptHeader = "application/json"
}

contentType := ResolveResponseType(acceptHeader, operationMap[http.MethodGet].Responses["200"].Value.Content)
switch contentType {
case "application/json":
Expand Down
104 changes: 104 additions & 0 deletions controllers/rest/default_controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,108 @@ func TestDefaultReadController(t *testing.T) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))
}
})

t.Run("test get item that's not found", func(t *testing.T) {
container := &ContainerMock{
GetLogFunc: func(name string) (model.Log, error) {
return &LogMock{}, nil
},
}
repository := &EntityRepositoryMock{
NameFunc: func() string {
return "Blog"
},
GetByKeyFunc: func(ctxt context3.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (*model.ContentEntity, error) {
return nil, nil
},
CreateEntityWithValuesFunc: func(ctx context3.Context, payload []byte) (*model.ContentEntity, error) {
return new(model.ContentEntity).FromSchemaWithValues(ctx, swagger.Components.Schemas["Blog"].Value, []byte(`{}`))
},
}

path := swagger.Paths.Find("/blogs/:id")

controller := rest.DefaultReadController(container, &CommandDispatcherMock{}, repository, map[string]*openapi3.PathItem{
"/blogs/1": path,
}, map[string]*openapi3.Operation{
http.MethodGet: path.Get,
})
e := echo.New()
e.GET("/blogs/:id", controller)
resp := httptest.NewRecorder()
req := httptest.NewRequest(echo.GET, "/blogs/1", nil)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
req.Header.Add(echo.HeaderAccept, echo.MIMEApplicationJSON)
e.ServeHTTP(resp, req)
if resp.Code != http.StatusNotFound {
t.Errorf("expected status code %d, got %d", http.StatusNotFound, resp.Code)
}

if len(repository.GetByKeyCalls()) != 1 {
t.Errorf("expected repository.GetByKey to be called once, got %d", len(repository.GetByKeyCalls()))
}

body := resp.Body.String()

if strings.Contains("null", body) {
t.Errorf("expected body to be empty")
}
})

t.Run("test get item with no accept header", func(t *testing.T) {
container := &ContainerMock{
GetLogFunc: func(name string) (model.Log, error) {
return &LogMock{}, nil
},
}
repository := &EntityRepositoryMock{
NameFunc: func() string {
return "Blog"
},
GetByKeyFunc: func(ctxt context3.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (*model.ContentEntity, error) {
return new(model.ContentEntity).FromSchemaWithValues(ctxt, swagger.Components.Schemas["Blog"].Value, []byte(`{"id":1,"title":"test"}`))
},
CreateEntityWithValuesFunc: func(ctx context3.Context, payload []byte) (*model.ContentEntity, error) {
return new(model.ContentEntity).FromSchemaWithValues(ctx, swagger.Components.Schemas["Blog"].Value, []byte(`{}`))
},
}

path := swagger.Paths.Find("/blogs/:id")

controller := rest.DefaultReadController(container, &CommandDispatcherMock{}, repository, map[string]*openapi3.PathItem{
"/blogs/1": path,
}, map[string]*openapi3.Operation{
http.MethodGet: path.Get,
})
e := echo.New()
e.GET("/blogs/:id", controller)
resp := httptest.NewRecorder()
req := httptest.NewRequest(echo.GET, "/blogs/1", nil)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
e.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Errorf("expected status code %d, got %d", http.StatusOK, resp.Code)
}

if len(repository.GetByKeyCalls()) != 1 {
t.Errorf("expected repository.GetByKey to be called once, got %d", len(repository.GetByKeyCalls()))
}

if len(resp.Body.String()) == 0 {
t.Errorf("expected body to be not empty")
}

var blog map[string]interface{}
if err := json.Unmarshal(resp.Body.Bytes(), &blog); err != nil {
t.Errorf("error unmarshalling body: %s", err)
}
//check that the response body is correct
if blog["id"] != float64(1) {
t.Errorf("expected id to be 1, got %d", blog["id"])
}

if blog["title"] != "test" {
t.Errorf("expected title to be 'test', got '%s'", blog["title"])
}
})
}
222 changes: 222 additions & 0 deletions controllers/rest/fixtures/blog-json-ld.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
openapi: 3.0.3
info:
title: Blog
description: Blog example
version: 1.0.0
servers:
- url: https://prod1.weos.sh/blog/dev
description: WeOS Dev
- url: https://prod1.weos.sh/blog/v1
x-weos-config:
event-source:
- title: default
driver: service
endpoint: https://prod1.weos.sh/events/v1
- title: event
driver: sqlite3
database: test.db
database:
driver: sqlite3
database: test.db
databases:
- title: default
driver: sqlite3
database: test.db
rest:
middleware:
- RequestID
- Recover
- ZapLogger
components:
schemas:
Category:
type: object
properties:
title:
type: string
description:
type: string
required:
- title
x-identifier:
- title
Author:
type: object
properties:
id:
type: string
format: ksuid
firstName:
type: string
lastName:
type: string
email:
type: string
format: email
required:
- firstName
- lastName
x-identifier:
- id
- email
Blog:
type: object
properties:
url:
type: string
format: uri
x-unique: true
title:
type: string
description:
type: string
nullable: true
author:
$ref: "#/components/schemas/Author"
cost:
type: number
format: double
nullable: true
status:
type: string
nullable: true
enum:
- "null"
- unpublished
- published
image:
type: string
format: byte
nullable: true
categories:
type: array
nullable: true
items:
$ref: "#/components/schemas/Category"
posts:
type: array
nullable: true
items:
$ref: "#/components/schemas/Post"
lastUpdated:
type: string
format: date-time
nullable: true
x-update:
- Add Blog
- Update Blog
created:
type: string
format: date-time
nullable: true
x-update:
- Add Blog
required:
- title
- url
Post:
type: object
properties:
title:
type: string
description:
type: string
nullable: true
author:
$ref: "#/components/schemas/Author"
created:
type: string
format: date-time
paths:
/health:
summary: Health Check
get:
x-controller: HealthCheck
x-middleware:
- Recover
- ZapLogger
responses:
200:
description: Health Response
500:
description: API Internal Error
/api:
get:
operationId: Get API Details
x-controller: APIDiscovery
responses:
200:
description: API Details
content:
application/json:
schema:
type: string
/blogs:
get:
operationId: Get Blogs
summary: Get List of Blogs
parameters:
- in: query
name: page
schema:
type: integer
- in: query
name: l
x-alias: limit
schema:
type: integer
- in: query
name: _filters
style: deepObject
explode: true
schema:
type: object
properties:
field:
type: string
operator:
type: string
values:
type: array
items:
type: string

required: false
description: query string
x-context:
_filters:
- field: status
operator: eq
value: Active
- field: lastUpdated
operator: between
values:
- 2021-12-17 15:46:00
- 2021-12-18 15:46:00
- field: categories
operator: in
values:
- Technology
- Javascript
_sorts:
- field: title
order: asc
page: 1
limit: 10
responses:
200:
description: List of blogs
content:
application/ld+json:
schema:
type: object
properties:
total:
type: integer
page:
type: integer
blogs:
type: array
x-alias: items
items:
$ref: "#/components/schemas/Blog"
5 changes: 4 additions & 1 deletion controllers/rest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,10 @@ func ResolveResponseType(header string, content openapi3.Content) string {
mimeTypes := strings.Split(header, ",")
for _, mimeType := range mimeTypes {
for contentType, _ := range content {
match, _ := regexp.MatchString("^"+mimeType, contentType)
mimeType = strings.ReplaceAll(mimeType, " ", "")
mimeType = strings.ReplaceAll(mimeType, "+", "")

match, _ := regexp.MatchString("^"+mimeType, strings.ReplaceAll(contentType, "+", ""))
if match {
return contentType
}
Expand Down
16 changes: 15 additions & 1 deletion controllers/rest/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,21 @@ func TestResolveResponseType(t *testing.T) {

t.Run("multiple types", func(t *testing.T) {
expectedContentType := "application/json"
contentType := api.ResolveResponseType("text/html, application/xhtml+xml, application/json;q=0.9, */*;q=0.8", path.Get.Responses[strconv.Itoa(http.StatusOK)].Value.Content)
contentType := api.ResolveResponseType("text/html, application/xhtml+xml, application/json, */*;q=0.8", path.Get.Responses[strconv.Itoa(http.StatusOK)].Value.Content)
if contentType != expectedContentType {
t.Errorf("expected %s, got %s", expectedContentType, contentType)
}
})

t.Run("test application/ld+json", func(t *testing.T) {
swagger, err = LoadConfig(t, "./fixtures/blog-json-ld.yaml")
if err != nil {
t.Fatalf("unable to load swagger: %s", err)
}
path = swagger.Paths.Find("/blogs")

expectedContentType := "application/ld+json"
contentType := api.ResolveResponseType("application/ld+json", path.Get.Responses[strconv.Itoa(http.StatusOK)].Value.Content)
if contentType != expectedContentType {
t.Errorf("expected %s, got %s", expectedContentType, contentType)
}
Expand Down