-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
286 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"components":{"schemas":{"Dates":{"properties":{"end_at":{"type":"string"},"start_at":{"type":"string"}},"required":["start_at","end_at"]},"Milestone":{"properties":{"title":{"type":"string"},"urls":{"items":{"type":"string"},"type":"array"}},"required":["title"]},"Problem":{"properties":{"detail":{"description":"A human readable explanation specific to this occurrence of the\nproblem.\n","example":"Connection to database timed out","type":"string"},"instance":{"description":"An absolute URI that identifies the specific occurrence of the problem.\nIt may or may not yield further information if dereferenced.\n","format":"uri","type":"string"},"status":{"description":"The HTTP status code generated by the origin server for this occurrence\nof the problem.\n","example":503,"exclusiveMaximum":true,"format":"int32","maximum":600,"minimum":100,"type":"integer"},"title":{"description":"A short, summary of the problem type. Written in English and readable\nfor engineers (usually not suited for non technical stakeholders and\nnot localized); example: Service Unavailable\n","type":"string"},"type":{"default":"about:blank","description":"An absolute URI that identifies the problem type. When dereferenced,\nit SHOULD provide human-readable documentation for the problem type\n(e.g., using HTML).\n","example":"https://rdmp.app/problem/constraint-violation","format":"uri","type":"string"}},"required":["type","title"]},"Project":{"properties":{"dates":{"$ref":"#/components/schemas/Dates"},"indentation":{"minimum":0,"type":"integer"},"milestone":{"minimum":1,"type":"integer"},"percentage":{"maximum":100,"minimum":0,"type":"integer"},"title":{"type":"string"},"urls":{"items":{"type":"string"},"type":"array"}},"required":["title"]},"Roadmap":{"properties":{"base_url":{"type":"string"},"date_format":{"type":"string"},"milestones":{"items":{"$ref":"#/components/schemas/Milestone"},"type":"array"},"prev_id":{"type":"string"},"projects":{"items":{"$ref":"#/components/schemas/Project"},"type":"array"},"title":{"type":"string"}},"required":["title","date_format"]},"RoadmapRequest":{"allOf":[{"$ref":"#/components/schemas/Roadmap"}]},"RoadmapResponse":{"allOf":[{"$ref":"#/components/schemas/Roadmap"}],"properties":{"id":{"type":"string"}},"required":["id"]}}},"info":{"contact":{"email":"[email protected]","name":"Roadmapper Team","url":"https://rdmp.app"},"description":"API for Roadmapper","license":{"name":"ISC","url":"https://opensource.org/licenses/ISC"},"termsOfService":"https://docs.rdmp.app/terms/","title":"Roadmapper API","version":"0.0.1"},"openapi":"3.0.3","paths":{"/":{"post":{"description":"Creates a new roadmap","operationId":"createRoadmap","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapRequest"}}},"description":"Roadmap to create","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapResponse"}}},"description":"Roadmap create success response"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Unexpected error"}}}},"/{roadmap_id}":{"get":{"description":"Retrieves a roadmap","operationId":"getRoadmap","parameters":[{"description":"ID of roadmap to update","in":"path","name":"roadmap_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoadmapResponse"}}},"description":"Roadmap response"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Unexpected error"}}}}},"servers":[{"url":"https://rdmp.app/api"},{"url":"http://localhost:1323/api"}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
// +build api | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
gofakeit "github.com/brianvoe/gofakeit/v5" | ||
"github.com/getkin/kin-openapi/openapi3filter" | ||
"github.com/ghodss/yaml" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/peteraba/roadmapper/pkg/roadmap" | ||
) | ||
|
||
const ( | ||
baseUrl = "http://localhost:1323/api" | ||
yamlFilePath = "../../api.yml" | ||
jsonFilePath = "../../api.json" | ||
) | ||
|
||
var ( | ||
router *openapi3filter.Router | ||
httpClient *http.Client | ||
|
||
minDate = time.Date(2020, 1, 10, 0, 0, 0, 0, time.UTC) | ||
maxDate = time.Date(2020, 4, 25, 0, 0, 0, 0, time.UTC) | ||
) | ||
|
||
// init will: | ||
// - provide a seed for all used random generators | ||
// - create a new router from the api.json file | ||
// but first it will check if api.yml is newer than api.json and re-converts it if needed | ||
func init() { | ||
gofakeit.Seed(0) | ||
|
||
if httpClient == nil { | ||
httpClient = &http.Client{} | ||
} | ||
|
||
if router != nil { | ||
return | ||
} | ||
|
||
yamlFile, err := os.Stat(yamlFilePath) | ||
if err != nil { | ||
panic("could not find '" + yamlFilePath + "'" + err.Error()) | ||
} | ||
|
||
jsonFile, err := os.Stat(jsonFilePath) | ||
if err != nil || yamlFile.ModTime().After(jsonFile.ModTime()) { | ||
content, err := ioutil.ReadFile(yamlFilePath) | ||
if err != nil { | ||
panic("could not read '" + yamlFilePath + "': " + err.Error()) | ||
} | ||
|
||
jsonContent, err := yaml.YAMLToJSON(content) | ||
if err != nil { | ||
panic("could not parse '" + yamlFilePath + "': " + err.Error()) | ||
} | ||
|
||
err = ioutil.WriteFile(jsonFilePath, jsonContent, os.ModePerm) | ||
if err != nil { | ||
panic("could not write '" + yamlFilePath + "': " + err.Error()) | ||
} | ||
|
||
time.Sleep(10 * time.Second) | ||
} | ||
|
||
router = openapi3filter.NewRouter().WithSwaggerFromFile(jsonFilePath) | ||
} | ||
|
||
// doHttpWithBody sends an HTTP request and returns an HTTP response and the body content | ||
func doHttpWithBody(t *testing.T, req *http.Request, expectedStatusCode int) (*http.Response, []byte) { | ||
resp, err := httpClient.Do(req) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedStatusCode, resp.StatusCode) | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
|
||
require.NoError(t, err) | ||
resp.Body.Close() | ||
resp.Body = ioutil.NopCloser(bytes.NewBuffer(body)) | ||
|
||
return resp, body | ||
} | ||
|
||
func newRoadmapPayload() roadmap.RoadmapExchange { | ||
p := gofakeit.Number(0, 20) | ||
m := gofakeit.Number(0, p) | ||
|
||
var ( | ||
milestones []roadmap.Milestone | ||
projects []roadmap.Project | ||
project roadmap.Project | ||
ind = 0 | ||
) | ||
|
||
for i := 0; i < m; i++ { | ||
milestones = append(milestones, newMilestone()) | ||
} | ||
|
||
for i := 0; i < p; i++ { | ||
project = newProject(m, ind) | ||
projects = append(projects, project) | ||
ind = nextIndentation(ind) | ||
} | ||
|
||
return roadmap.RoadmapExchange{ | ||
Title: newWords(), | ||
DateFormat: "2006-01-02", | ||
BaseURL: gofakeit.URL(), | ||
Projects: projects, | ||
Milestones: milestones, | ||
} | ||
} | ||
|
||
func newProject(milestoneCount, ind int) roadmap.Project { | ||
m := gofakeit.Number(0, milestoneCount) | ||
d := newDates() | ||
p := gofakeit.Number(0, 100) | ||
|
||
project := roadmap.Project{ | ||
Indentation: uint8(ind), | ||
Title: newWords(), | ||
Milestone: uint8(m), | ||
Dates: d, | ||
Percentage: uint8(p), | ||
} | ||
|
||
return project | ||
} | ||
|
||
func newWords() string { | ||
var w []string | ||
|
||
for i := 0; i < gofakeit.Number(1, 5); i++ { | ||
w = append(w, gofakeit.HipsterWord()) | ||
} | ||
|
||
return strings.Join(w, " ") | ||
} | ||
|
||
func nextIndentation(indentation int) int { | ||
return indentation - gofakeit.Number(-1, indentation) | ||
} | ||
|
||
func newDates() *roadmap.Dates { | ||
if gofakeit.Bool() { | ||
return nil | ||
} | ||
|
||
var ( | ||
d0 = gofakeit.DateRange(minDate, maxDate) | ||
d1 = gofakeit.DateRange(minDate, maxDate) | ||
) | ||
|
||
if d0.Before(d1) { | ||
return &roadmap.Dates{ | ||
StartAt: d0, | ||
EndAt: d1, | ||
} | ||
} | ||
|
||
return &roadmap.Dates{ | ||
StartAt: d1, | ||
EndAt: d0, | ||
} | ||
} | ||
|
||
func getURLs() []string { | ||
var ( | ||
urls []string | ||
) | ||
|
||
for i := 0; i < gofakeit.Number(0, 2); i++ { | ||
urls = append(urls, gofakeit.URL()) | ||
} | ||
|
||
for i := 0; i < gofakeit.Number(0, 2); i++ { | ||
urls = append(urls, gofakeit.Word()) | ||
} | ||
|
||
return urls | ||
} | ||
|
||
func newMilestone() roadmap.Milestone { | ||
return roadmap.Milestone{ | ||
Title: newWords(), | ||
DeadlineAt: newDateOptional(), | ||
URLs: getURLs(), | ||
} | ||
} | ||
|
||
func newDateOptional() *time.Time { | ||
var ( | ||
optionalDate *time.Time | ||
) | ||
|
||
if gofakeit.Bool() { | ||
date := gofakeit.DateRange(minDate, maxDate) | ||
optionalDate = &date | ||
} | ||
|
||
return optionalDate | ||
} | ||
|
||
func newCreateRoadmapRequest(t *testing.T, re roadmap.RoadmapExchange) *http.Request { | ||
marshaled, err := json.Marshal(re) | ||
require.NoError(t, err) | ||
|
||
url := fmt.Sprintf("%s/", baseUrl) | ||
req, err := http.NewRequest("POST", url, bytes.NewReader(marshaled)) | ||
require.NoError(t, err) | ||
|
||
req.Header.Add("Content-Type", `application/json`) | ||
|
||
return req | ||
} | ||
|
||
func TestApi_CreateRoadmap(t *testing.T) { | ||
t.Run("success", func(t *testing.T) { | ||
// Create request | ||
roadmapRequestData := newRoadmapPayload() | ||
req := newCreateRoadmapRequest(t, roadmapRequestData) | ||
|
||
// Find route in the swagger file | ||
route, pathParams, err := router.FindRoute(req.Method, req.URL) | ||
require.NoError(t, err) | ||
|
||
// Validate request | ||
requestValidationInput := &openapi3filter.RequestValidationInput{ | ||
Request: req, | ||
PathParams: pathParams, | ||
Route: route, | ||
} | ||
err = openapi3filter.ValidateRequest(nil, requestValidationInput) | ||
assert.NoError(t, err) | ||
|
||
// Get response | ||
resp, body := doHttpWithBody(t, req, http.StatusCreated) | ||
|
||
// Validate response | ||
err = openapi3filter.ValidateResponse(nil, &openapi3filter.ResponseValidationInput{ | ||
RequestValidationInput: requestValidationInput, | ||
Status: resp.StatusCode, | ||
Header: resp.Header, | ||
Body: resp.Body, | ||
}) | ||
require.NoError(t, err) | ||
|
||
// Read and parse response | ||
response := roadmap.RoadmapExchange{} | ||
err = json.Unmarshal(body, &response) | ||
require.NoError(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// +build e2e | ||
// +build browser | ||
|
||
package main | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters