Skip to content

Commit

Permalink
Merge branch '47-support-testing-in-live' into 'master'
Browse files Browse the repository at this point in the history
Resolve "Support testing in live"

Closes #47

See merge request pace/go-microservice!37
  • Loading branch information
Florian Hübsch committed Feb 11, 2019
2 parents a62503b + aacd328 commit 71719bc
Show file tree
Hide file tree
Showing 22 changed files with 499 additions and 30 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ testserver:
POSTGRES_USER=testserveruser \
POSTGRES_DB=testserver \
POSTGRES_PASSWORD=pace1234! \
PACE_LIVETEST_INTERVAL=1m \
go run ./tools/testserver/main.go

docker.all: docker.jaeger docker.postgres.setup docker.redis
Expand Down
16 changes: 8 additions & 8 deletions client/nominatim/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Client struct {
}

// ErrUnableToGeocode lat/lon don't match to a known address
var ErrUnableToGeocode = errors.New("Unable to geocode")
var ErrUnableToGeocode = errors.New("unable to geocode")

// ErrRequestFailed either the connection was lost or similar
var ErrRequestFailed = errors.New("HTTP request failed")
Expand Down Expand Up @@ -57,7 +57,7 @@ func (c *Client) SolidifiedReverse(ctx context.Context, lat, lon float64) (*Resu
return nil, err
}

if resultHigh != nil {
if resultHigh != nil && resultHigh.Address != nil && resultLow != nil && resultLow.Address != nil {
if resultLow.Address.City == "" {
resultLow.Address.City = resultHigh.Address.City
}
Expand All @@ -82,6 +82,8 @@ func (c *Client) SolidifiedReverse(ctx context.Context, lat, lon float64) (*Resu
if resultLow.Address.State == "" {
resultLow.Address.State = resultHigh.Address.State
}
} else {
return nil, ErrUnableToGeocode
}

return resultLow, nil
Expand Down Expand Up @@ -125,7 +127,7 @@ func (c *Client) Reverse(ctx context.Context, lat, lon float64, zoom int) (*Resu
// prepate request
u, err := url.Parse(c.Endpoint)
if err != nil {
return nil, fmt.Errorf("Failed to parse nominatim endpoint URL %q: %v", c.Endpoint, err)
return nil, fmt.Errorf("failed to parse nominatim endpoint URL %q: %v", c.Endpoint, err)
}
u.Path = "nominatim/reverse"
values := make(url.Values)
Expand All @@ -147,8 +149,7 @@ func (c *Client) Reverse(ctx context.Context, lat, lon float64, zoom int) (*Resu
defer resp.Body.Close() // nolint: errcheck

if resp.StatusCode != http.StatusOK {
err = ErrRequestFailed
return nil, err
return nil, ErrRequestFailed
}

// parse response
Expand All @@ -158,9 +159,8 @@ func (c *Client) Reverse(ctx context.Context, lat, lon float64, zoom int) (*Resu
}

// Handle geocoding error
if result.Error == ErrUnableToGeocode.Error() {
err = ErrUnableToGeocode
return nil, err
if result.Error == "Unable to geocode" {
return nil, ErrUnableToGeocode
}

return &result, nil
Expand Down
6 changes: 4 additions & 2 deletions client/nominatim/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ func TestIntegrationReverse(t *testing.T) {
ctx := context.Background()
ctx = log.With().Logger().WithContext(ctx)

_, err := DefaultClient.Reverse(ctx, 0, 0, 18)
d, err := DefaultClient.Reverse(ctx, 0, 0, 18)
if err != ErrUnableToGeocode {
t.Error("expected unable to geocode error, got: ", err)
t.Errorf("expected unable to geocode error, got: %v; %+v", err, d)
return
}

res, err := DefaultClient.Reverse(ctx, 49.01251, 8.42636, 18)
if err != nil {
t.Error("expected error, got: ", err)
return
}

expected := "Haid-und-Neu-Straße 18, 76131 Karlsruhe"
Expand Down
6 changes: 3 additions & 3 deletions client/pace/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
)

// ErrNotFound error for HTTP 404 responses
var ErrNotFound = errors.New("Resource not found (HTTP 404)")
var ErrNotFound = errors.New("resource not found (HTTP 404)")

// ErrRequest contains error details
type ErrRequest struct {
Expand Down Expand Up @@ -61,7 +61,7 @@ func New(endpoint string) *Client {
func (c *Client) URL(path string, values url.Values) (*url.URL, error) {
u, err := url.Parse(c.Endpoint)
if err != nil {
return nil, fmt.Errorf("Endpoint URL %q can't be parsed: %v", c.Endpoint, err)
return nil, fmt.Errorf("endpoint URL %q can't be parsed: %v", c.Endpoint, err)
}

u.Path = path
Expand Down Expand Up @@ -156,7 +156,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, err

// handle error if request failed
if err != nil {
return nil, fmt.Errorf("Failed to %s %q: %v", req.Method, req.URL.String(), err)
return nil, fmt.Errorf("failed to %s %q: %v", req.Method, req.URL.String(), err)
}

// return not found
Expand Down
2 changes: 1 addition & 1 deletion client/pace/client/unixtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (r UnixTime) MarshalJSON() ([]byte, error) {
func (r *UnixTime) UnmarshalJSON(data []byte) error {
var timestamp int64
if err := json.Unmarshal(data, &timestamp); err != nil {
return fmt.Errorf("Unix timestamp should be an number, got %s", data)
return fmt.Errorf("unix timestamp should be an number, got %s", data)
}
*r = UnixTime(time.Unix(timestamp, 0))
return nil
Expand Down
2 changes: 1 addition & 1 deletion http/jsonapi/generator/generate_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (g *Generator) generateResponseInterface(route *route, schema *openapi3.Swa
// error responses have an error message parameter
codeNum, err := strconv.Atoi(code)
if err != nil {
return fmt.Errorf("Failed to parse response code %s: %v", code, err)
return fmt.Errorf("failed to parse response code %s: %v", code, err)
}

// generate method name
Expand Down
2 changes: 1 addition & 1 deletion http/jsonapi/generator/generate_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (g *Generator) goType(stmt *jen.Statement, schema *openapi3.Schema, tags ma
return err
}
default:
return fmt.Errorf("Unknown type: %s", schema.Type)
return fmt.Errorf("unknown type: %s", schema.Type)
}

// add enum validation
Expand Down
2 changes: 1 addition & 1 deletion http/jsonapi/generator/generate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func (g *Generator) generateStructRelationships(prefix string, schema *openapi3.
// check for data
data := relSchema.Value.Properties["data"]
if data == nil || data.Value == nil {
return nil, fmt.Errorf("No data for relationship %s context %s", relName, prefix)
return nil, fmt.Errorf("no data for relationship %s context %s", relName, prefix)
}

// generate relationship field
Expand Down
4 changes: 3 additions & 1 deletion http/jsonapi/generator/internal/poi/poi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,15 @@ func TestHandler(t *testing.T) {
err := json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
t.Fatal(err)
return
}
if len(data.Data) != 10 {
t.Error("Expected 10 apps")
return
}
fmt.Printf("%#v", data)
if data.Data[0]["type"] != "locationBasedApp" {
t.Error("Expected type locationBasedApp")
return
}
meta, ok := data.Data[0]["meta"].(map[string]interface{})
if !ok {
Expand Down
8 changes: 4 additions & 4 deletions http/jsonapi/runtime/marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ func Unmarshal(w http.ResponseWriter, r *http.Request, data interface{}) bool {
accept := r.Header.Get("Accept")
if accept != JSONAPIContentType {
WriteError(w, http.StatusNotAcceptable,
fmt.Errorf("Request needs to be send with %q header, containing value: %q", "Accept", JSONAPIContentType))
fmt.Errorf("request needs to be send with %q header, containing value: %q", "Accept", JSONAPIContentType))
return false
}

// if the client didn't send a content type, don't verify
contentType := r.Header.Get("Content-Type")
if contentType != JSONAPIContentType {
WriteError(w, http.StatusUnsupportedMediaType,
fmt.Errorf("Request needs to be send with %q header, containing value: %q", "Accept", JSONAPIContentType))
fmt.Errorf("request needs to be send with %q header, containing value: %q", "Accept", JSONAPIContentType))
return false
}

// parse request
err := jsonapi.UnmarshalPayload(r.Body, data)
if err != nil {
WriteError(w, http.StatusUnprocessableEntity,
fmt.Errorf("Can't parse content: %v", err))
fmt.Errorf("can't parse content: %v", err))
return false
}

Expand All @@ -59,6 +59,6 @@ func Marshal(w http.ResponseWriter, data interface{}, code int) {
// write marshaled response body
err := jsonapi.MarshalPayload(w, data)
if err != nil {
panic(fmt.Errorf("Failed to marshal jsonapi response for %#v: %s", data, err))
panic(fmt.Errorf("failed to marshal jsonapi response for %#v: %s", data, err))
}
}
2 changes: 1 addition & 1 deletion http/jsonapi/runtime/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func ScanParameters(w http.ResponseWriter, r *http.Request, parameters ...*ScanP
// single parameter scanning
scanData = strings.Join(input, " ")
default:
panic(fmt.Errorf("Impossible scanning location: %d", param.Location))
panic(fmt.Errorf("impossible scanning location: %d", param.Location))
}

n, _ := fmt.Sscan(scanData, param.Data) // nolint: gosec
Expand Down
4 changes: 2 additions & 2 deletions http/jsonapi/runtime/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func ValidateStruct(w http.ResponseWriter, r *http.Request, data interface{}, so
case error:
panic(err) // programming error, e.g. not used with struct
default:
panic(fmt.Errorf("Unhandled error case: %s", err))
panic(fmt.Errorf("unhandled error case: %s", err))
}

return false
Expand All @@ -59,7 +59,7 @@ func generateValidationErrors(validErrors valid.Errors, jsonapiErrors *Errors, s
case valid.Error:
*jsonapiErrors = append(*jsonapiErrors, generateValidationError(e, source))
default:
panic(fmt.Errorf("Unhandled error case: %s", e))
panic(fmt.Errorf("unhandled error case: %s", e))
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions http/oauth2/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (

type introspecter func(mdw *Middleware, token string, resp *introspectResponse) error

var errInvalidToken = errors.New("User token is invalid")
var errUpstreamConnection = errors.New("Problem connecting to the introspection endpoint")
var errBadUpstreamResponse = errors.New("Bad upstream response when introspecting token")
var errInvalidToken = errors.New("user token is invalid")
var errUpstreamConnection = errors.New("problem connecting to the introspection endpoint")
var errBadUpstreamResponse = errors.New("bad upstream response when introspecting token")

type introspectResponse struct {
Active bool `json:"active"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/synctx/work_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (queue *WorkQueue) Add(description string, fn WorkFunc) {
// if one of the work queue items fails the whole
// queue will be canceled
if err != nil {
queue.setErr(fmt.Errorf("Failed to %s: %v", description, err))
queue.setErr(fmt.Errorf("failed to %s: %v", description, err))
queue.cancel()
}
queue.wg.Done()
Expand Down
2 changes: 1 addition & 1 deletion pkg/synctx/work_queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestWorkQueueOneTaskWithErr(t *testing.T) {
t.Error("expected error")
return
}
expected := "Failed to some work: Some error"
expected := "failed to some work: Some error"
if q.Err().Error() != expected {
t.Errorf("expected error %q, got: %q", q.Err().Error(), expected)
}
Expand Down
23 changes: 23 additions & 0 deletions testing/livetest/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright © 2018 by PACE Telematics GmbH. All rights reserved.
// Created at 2019/02/01 by Vincent Landgraf

// Package livetest implements a set of helpers that ease writing of a
// sidecar that tests the functions of a service.
//
// Assuring the functional correctness of a service is an important
// task for a production grade system. This package aims to provide
// helpers that allow a go test like experience for building functional
// health tests in production.
//
// Test functions need to be written similarly to the regular go test
// function format. Only difference is the use of the testing.TB
// interface.
//
// If a test failed, all other tests are still executed. So tests
// should not build on each other. Sub tests should be used for that
// purpose.
//
// The result for the tests is exposed via prometheus metrics.
//
// The interval is configured using PACE_LIVETEST_INTERVAL (duration format).
package livetest
48 changes: 48 additions & 0 deletions testing/livetest/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright © 2018 by PACE Telematics GmbH. All rights reserved.
// Created at 2019/02/04 by Vincent Landgraf

package livetest

import (
"log"
"time"

"github.com/caarlos0/env"
"github.com/prometheus/client_golang/prometheus"
)

type config struct {
Interval time.Duration `env:"PACE_LIVETEST_INTERVAL" envDefault:"1h"`
ServiceName string `env:"JAEGER_SERVICE_NAME" envDefault:"go-microservice"`
}

var (
paceLivetestTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "pace_livetest_total",
Help: "Collects stats about the number of live tests made",
},
[]string{"service", "result"},
)
paceLivetestDurationSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "pace_livetest_duration_seconds",
Help: "Collect performance metrics for each live test",
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 60},
},
[]string{"service"},
)
)

var cfg config

func init() {
prometheus.MustRegister(paceLivetestTotal)
prometheus.MustRegister(paceLivetestDurationSeconds)

// parse log config
err := env.Parse(&cfg)
if err != nil {
log.Fatalf("Failed to parse livetest environment: %v", err)
}
}
Loading

0 comments on commit 71719bc

Please sign in to comment.