Skip to content

Commit

Permalink
Merge pull request #16 from josephspurrier/dredd
Browse files Browse the repository at this point in the history
Refactor with Dredd and better practices around separating DB from JSON output.
  • Loading branch information
josephspurrier committed Jul 9, 2018
2 parents 9158c1e + e419a9b commit a6a6ba4
Show file tree
Hide file tree
Showing 48 changed files with 1,343 additions and 553 deletions.
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ go:
- "1.10"
- "tip"

cache:
directories:
- $(npm config get prefix)/lib/node_modules

before_install:
- mysql -e 'CREATE DATABASE webapitest;'
- export GOPATH=$HOME/gopath/src/project-root
Expand All @@ -19,6 +23,18 @@ before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
- go get github.com/go-swagger/go-swagger/cmd/swagger
- swagger generate spec -o ./swagger.json
- sed -i'' -e 's/example/x\-example/' ./swagger.json
- swagger validate ./swagger.json
- go get github.com/snikch/goodman/cmd/goodman
- npm install -g dredd@5.1.11
- go build -o ./cmd/webapi/webapi app/webapi/cmd/webapi
- go build -o ./cmd/hooks/hooks app/webapi/cmd/hooks
- cp testdata/config.json ./config.json

before_script:
- dredd

script:
- $GOPATH/bin/goveralls
184 changes: 83 additions & 101 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/app/webapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/config.json
11 changes: 10 additions & 1 deletion src/app/webapi/Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/app/webapi/cmd/cliapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cliapp
cliapp.exe
2 changes: 2 additions & 0 deletions src/app/webapi/cmd/hooks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hooks
hooks.exe
152 changes: 152 additions & 0 deletions src/app/webapi/cmd/hooks/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"

"app/webapi/component"
"app/webapi/component/user"
"app/webapi/internal/testutil"
"app/webapi/pkg/router"
"app/webapi/store"

"github.com/snikch/goodman/hooks"
trans "github.com/snikch/goodman/transaction"
)

/*
Example transaction.
&transaction.Transaction{
Id:"POST (400) /v1/user"
Name:"user > /v1/user > Create a user. > 400 > application/json"
Host:"127.0.0.1"
Port:"8080"
Protocol:"http:"
FullPath:"/v1/user"
Request:(*struct {
Body string "json:\"body,omitempty\"";
Headers map[string]interface {} "json:\"headers,omitempty\"";
URI string "json:\"uri,omitempty\"";
Method string "json:\"method,omitempty\"" })(0xc420150780),
Expected:(*struct { StatusCode string "json:\"statusCode,omitempty\"";
Body string "json:\"body,omitempty\"";
Headers map[string]interface {} "json:\"headers,omitempty\"";
Schema *json.RawMessage "json:\"bodySchema,omitempty\"" })(0xc4201464e0),
Real:(*struct { Body string "json:\"body\"";
Headers map[string]interface {} "json:\"headers\"";
StatusCode int "json:\"statusCode\"" })(nil),
Origin:(*json.RawMessage)(0xc4201584a0),
Test:(*json.RawMessage)(nil),
Results:(*json.RawMessage)(nil),
Skip:true, Fail:interface {}(nil),
TestOrder:[]string(nil)}
*/

// Response returns 200.
type response struct {
// in: body
Body struct {
// Required: true
Status string `json:"status"`
// Required: true
Data struct {
// Required: true
Token string `json:"token"`
} `json:"data"`
}
}

func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
token := ""

h.BeforeAll(func(t []*trans.Transaction) {
// Get the auth token.
r, err := http.Get(fmt.Sprintf("%v//%v:%v/v1/auth", t[0].Protocol, t[0].Host, t[0].Port))
if err != nil {
fmt.Println("Error:", err)
return
}

// Decode the response.
rs := new(response)
err = json.NewDecoder(r.Body).Decode(&rs.Body)
if err != nil {
fmt.Println("Error:", err)
return
}

token = rs.Body.Data.Token
})

h.BeforeEach(func(t *trans.Transaction) {
// Set the Authorization header.
t.Request.Headers["Authorization"] = "Bearer " + token

// Load the tables.
testutil.LoadDatabaseFromFile("../../../migration/tables-only.sql")
core, _ := component.NewCoreMock()

mux := router.New()
user.New(core).Routes(mux)

// Create a new user.
u := store.NewUser(core.DB, core.Q)
id1, err := u.Create("John", "Smith", "jsmith@example.com", "password")
if err != nil {
fmt.Println("Error:", err)
return
}

// Change the email to a real email.
if strings.Contains(t.Request.Body, "email") {
u, err := url.ParseQuery(t.Request.Body)
if err != nil {
fmt.Println("Error:", err)
return
}
u.Set("email", "jsmith2@example.com")
t.Request.Body = u.Encode()
}

// Update the URL for the requests so they have the ID.
if t.Request.URI == "/v1/user/USERID" {
t.FullPath = "/v1/user/" + id1
}
})

if false {
h.BeforeAll(func(t []*trans.Transaction) {
fmt.Println("before all modification")
})
h.BeforeEach(func(t *trans.Transaction) {
fmt.Println("before each modification")
})
h.Before("user > /v1/user/{user_id} > Return one user.", func(t *trans.Transaction) {
fmt.Println("before modification")
})
h.BeforeEachValidation(func(t *trans.Transaction) {
fmt.Println("before each validation modification")
})
h.BeforeValidation("/message > GET", func(t *trans.Transaction) {
fmt.Println("before validation modification")
})
h.After("/message > GET", func(t *trans.Transaction) {
fmt.Println("after modification")
})
h.AfterEach(func(t *trans.Transaction) {
fmt.Println("after each modification")
})
h.AfterAll(func(t []*trans.Transaction) {
fmt.Println("after all modification")
})
}

server.Serve()
defer server.Listener.Close()
}
13 changes: 8 additions & 5 deletions src/app/webapi/cmd/webapi/webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"app/webapi"
"app/webapi/pkg/jsonconfig"
"app/webapi/pkg/logger"
)

func init() {
Expand All @@ -19,18 +20,20 @@ func init() {

func main() {
// Create the logger.
appLogger := log.New(os.Stderr, "", log.LstdFlags)
l := logger.New(log.New(os.Stderr, "", log.LstdFlags))

// Load the configuration file.
config := new(webapi.AppConfig)
err := jsonconfig.Load("config.json", config)
if err != nil {
appLogger.Fatalf("%v", err)
l.Fatalf("%v", err)
}

// Set up the routes.
_, httpServer, httpsServer := webapi.Routes(config, appLogger)
// Set up the service, routes, and the handlers.
core := webapi.Services(config, l)
mux := webapi.Routes(core)
httpServer, httpsServer := webapi.Handlers(config, l, mux)

// Start the listeners based on the config.
config.Server.Run(httpServer, httpsServer, appLogger)
config.Server.Run(httpServer, httpsServer, l)
}
24 changes: 6 additions & 18 deletions src/app/webapi/component/auth/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package auth
import (
"net/http"
"time"

"app/webapi/model"
)

// Index .
Expand All @@ -13,27 +15,13 @@ import (
// Responses:
// 200: AuthIndexResponse
func (p *Endpoint) Index(w http.ResponseWriter, r *http.Request) (int, error) {
t, err := p.Token.Generate("jsmith", 8*time.Hour)
t, err := p.Token.Generate("1", 8*time.Hour)
if err != nil {
return http.StatusInternalServerError, err
}

// Response returns 200.
// swagger:response AuthIndexResponse
type response struct {
// in: body
Body struct {
// Required: true
Status string `json:"status"`
// Required: true
Data struct {
// Required: true
Token string `json:"token"`
} `json:"data"`
}
}

resp := new(response)
resp := new(model.AuthIndexResponse)
resp.Body.Status = http.StatusText(http.StatusOK)
resp.Body.Data.Token = t
return p.Response.Results(w, &resp.Body, nil)
return p.Response.JSON(w, resp.Body)
}
30 changes: 15 additions & 15 deletions src/app/webapi/component/auth/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package auth_test

import (
"encoding/base64"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"

"app/webapi/component"
"app/webapi/component/auth"
"app/webapi/pkg/router"
"app/webapi/internal/testrequest"
"app/webapi/model"

"github.com/stretchr/testify/assert"
)
Expand All @@ -24,15 +24,15 @@ func TestIndex(t *testing.T) {
return enc, nil
}

mux := router.New()
auth.New(core).Routes(mux)
w := testrequest.SendForm(t, core, "GET", "/v1/auth", nil)

r := httptest.NewRequest("GET", "/v1/auth", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)
r := new(model.AuthIndexResponse)
err := json.Unmarshal(w.Body.Bytes(), &r.Body)
assert.Nil(t, err)

assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, `{"status":"","data":{"token":"MDEyMzQ1Njc4OUFCQ0RFRjAxMjM0NTY3ODlBQkNERUY="}}`+"\n", w.Body.String())
assert.Equal(t, "OK", r.Body.Status)
assert.Equal(t, "MDEyMzQ1Njc4OUFCQ0RFRjAxMjM0NTY3ODlBQkNERUY=", r.Body.Data.Token)
}

func TestIndexError(t *testing.T) {
Expand All @@ -42,13 +42,13 @@ func TestIndexError(t *testing.T) {
return "", errors.New("generate error")
}

mux := router.New()
auth.New(core).Routes(mux)
w := testrequest.SendForm(t, core, "GET", "/v1/auth", nil)

r := httptest.NewRequest("GET", "/v1/auth", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)
r := new(model.InternalServerErrorResponse)
err := json.Unmarshal(w.Body.Bytes(), &r.Body)
assert.Nil(t, err)

assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, `generate error`+"\n", w.Body.String())
assert.Equal(t, "Internal Server Error", r.Body.Status)
assert.Equal(t, "generate error", r.Body.Message)
}
4 changes: 2 additions & 2 deletions src/app/webapi/component/interface.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package component

import (
"app/webapi/pkg/router"
"database/sql"
"net/http"
"time"

"app/webapi/pkg/query"
"app/webapi/pkg/router"
)

// IDatabase provides data query capabilities.
Expand Down Expand Up @@ -51,8 +51,8 @@ type IBind interface {

// IResponse provides outputs for data.
type IResponse interface {
JSON(w http.ResponseWriter, body interface{}) (int, error)
Created(w http.ResponseWriter, recordID string) (int, error)
Results(w http.ResponseWriter, body interface{}, data interface{}) (int, error)
OK(w http.ResponseWriter, message string) (int, error)
}

Expand Down
Loading

0 comments on commit a6a6ba4

Please sign in to comment.