Skip to content

Commit

Permalink
Provide mechanism to add custom metadata to output
Browse files Browse the repository at this point in the history
  • Loading branch information
Stein Fletcher committed Feb 13, 2019
1 parent 999e46f commit b5f8a90
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 48 deletions.
100 changes: 100 additions & 0 deletions .sequence/2895595591_1412177239.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script>
<script src="https://bramp.github.io/js-sequence-diagrams/js/sequence-diagram-min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js"></script>
</head>
<body>

<div class="container-fluid">
<h2>POST /hello</h2>
<span class="badge badge-success">200</span>
<p class="lead">some test</p>
<div class="card text-center">
<div class="card-body">
<div id="d" class="justify-content-center"></div>
</div>
</div>
<br><br>
<p class="lead">Event Log</p>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Payload</th>
</tr>
</thead>
<tbody>

<tr>
<th scope="row">1</th>
<td>
<pre>POST /hello HTTP/1.1
Host: application
Content-Type: application/json

</pre>
<pre><code class="json">{
&#34;a&#34;: 12345
}</code></pre>
</td>
</tr>

<tr>
<th scope="row">2</th>
<td>
<pre>GET / HTTP/1.1
Host: localhost:8080

</pre>

</td>
</tr>

<tr>
<th scope="row">3</th>
<td>
<pre>HTTP/1.1 200 OK
Content-Length: 1
Content-Type: text/plain

</pre>
<pre><code class="json">1</code></pre>
</td>
</tr>

<tr>
<th scope="row">4</th>
<td>
<pre>HTTP/1.1 200 OK
Connection: close

</pre>

</td>
</tr>

</tbody>
</table>
</div>
<script>
Diagram.parse("\x22cli\x22-\x3e\x22sut\x22: (1) POST \/hello\n\x22sut\x22-\x3e\x22localhost:8080\x22: (2) GET http:\/\/localhost:8080\n\x22localhost:8080\x22-\x3e\x3e\x22sut\x22: (3) 200\n\x22sut\x22-\x3e\x3e\x22cli\x22: (4) 200\n").drawSVG("d", {theme: 'simple', 'font-size': 14});
</script>
<style>
body {
padding-top: 2rem;
padding-bottom: 2rem;
}
</style>
<script type="application/json" id="metaJson">{"hash":"2895595591_1412177239","method":"POST","name":"some test","path":"/hello","status_code":200}</script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</body>
</html>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ func TestApi(t *testing.T) {

func TestApi(t *testing.T) {
apitest.New().
Report(apitest.SequenceDiagram()).
Mocks(getUser, getPreferences).
Handler(handler).
Get("/hello").
Expand Down
76 changes: 39 additions & 37 deletions apitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var responseDebugPrefix = fmt.Sprintf("<%s", divider)
// APITest is the top level struct holding the test spec
type APITest struct {
debugEnabled bool
reporter ReportFormatter
name string
request *Request
response *Response
Expand All @@ -39,6 +40,7 @@ type APITest struct {
t *testing.T
httpClient *http.Client
transport *Transport
meta map[string]interface{}
}

type InboundRequest struct {
Expand All @@ -54,11 +56,13 @@ type FinalResponse struct {
// Observe will be called by with the request and response on completion
type Observe func(*http.Response, *http.Request, *APITest)

type RecorderHook func(*Recorder, string)
type RecorderHook func(*Recorder)

// New creates a new api test. The name is optional and will appear in test reports
func New(name ...string) *APITest {
apiTest := &APITest{}
apiTest := &APITest{
meta: map[string]interface{}{},
}

request := &Request{
apiTest: apiTest,
Expand All @@ -85,6 +89,18 @@ func (a *APITest) Debug() *APITest {
return a
}

// Reporter provides a hook to add custom formatting to the output of the test
func (a *APITest) Report(reporter ReportFormatter) *APITest {
a.reporter = reporter
return a
}

// Meta provides a hook to add custom meta data to the test which can be picked up when defining a custom reporter
func (a *APITest) Meta(meta map[string]interface{}) *APITest {
a.meta = meta
return a
}

// Mocks is a builder method for setting the mocks
func (a *APITest) Mocks(mocks ...*Mock) *APITest {
var m []*Mock
Expand Down Expand Up @@ -144,7 +160,6 @@ func (a *APITest) Handler(handler http.Handler) *Request {
type Request struct {
handler http.Handler
interceptor Intercept
host string
method string
url string
body string
Expand All @@ -170,12 +185,6 @@ func (r *Request) Intercept(interceptor Intercept) *Request {
return r
}

// Host is a builder method for setting the Host of the request
func (r *Request) Host(host string) *Request {
r.host = host
return r
}

// Method is a builder method for setting the http method of the request
func (r *Request) Method(method string) *Request {
r.method = method
Expand Down Expand Up @@ -299,14 +308,6 @@ func (r *Request) Expect(t *testing.T) *Response {
return r.apiTest.response
}

// GetHost returns the host or the default name if not set
func (r *Request) GetHost() string {
if r.host == "" {
return systemUnderTestDefaultName
}
return r.host
}

// Response is the user defined expected response from the application under test
type Response struct {
status int
Expand Down Expand Up @@ -380,6 +381,10 @@ func (r *Response) Assert(fn func(*http.Response, *http.Request) error) *Respons

// End runs the test and all defined assertions
func (r *Response) End() {
if r.apiTest.reporter != nil {
r.apiTest.report()
return
}
r.execute()
}

Expand All @@ -389,20 +394,18 @@ type mockInteraction struct {
timestamp time.Time
}

func (r *Response) Report(formatter ...ReportFormatter) {
apiTest := r.apiTest

func (a *APITest) report() {
var capturedInboundReq *http.Request
var capturedFinalRes *http.Response
var capturedMockInteractions []*mockInteraction

apiTest.observers = []Observe{
a.observers = []Observe{
func(finalRes *http.Response, inboundReq *http.Request, a *APITest) {
capturedFinalRes = finalRes
capturedInboundReq = inboundReq
},
}
apiTest.mocksObserver = func(mockRes *http.Response, mockReq *http.Request, a *APITest) {
a.mocksObserver = func(mockRes *http.Response, mockReq *http.Request, a *APITest) {
capturedMockInteractions = append(capturedMockInteractions, &mockInteraction{
request: copyHttpRequest(mockReq),
response: copyHttpResponse(mockRes),
Expand All @@ -411,43 +414,43 @@ func (r *Response) Report(formatter ...ReportFormatter) {
}

recorder := NewTestRecorder()
if apiTest.recorderHook != nil {
apiTest.recorderHook(recorder, apiTest.request.GetHost())
if a.recorderHook != nil {
a.recorderHook(recorder)
}

execTime := time.Now().UTC()
r.execute()
a.response.execute()
finishTime := time.Now().UTC()

recorder.
AddTitle(fmt.Sprintf("%s %s", capturedInboundReq.Method, capturedInboundReq.URL.String())).
AddSubTitle(apiTest.name).
AddSubTitle(a.name).
AddHttpRequest(HttpRequest{
Source: quoted(consumerName),
Target: quoted(apiTest.request.GetHost()),
Target: quoted(systemUnderTestDefaultName),
Value: capturedInboundReq,
Timestamp: execTime,
})

for _, interaction := range capturedMockInteractions {
recorder.AddHttpRequest(HttpRequest{
Source: quoted(apiTest.request.GetHost()),
Source: quoted(systemUnderTestDefaultName),
Target: quoted(interaction.request.Host),
Value: interaction.request,
Timestamp: interaction.timestamp,
})
if interaction.response != nil {
recorder.AddHttpResponse(HttpResponse{
Source: quoted(interaction.request.Host),
Target: quoted(apiTest.request.GetHost()),
Target: quoted(systemUnderTestDefaultName),
Value: interaction.response,
Timestamp: interaction.timestamp,
})
}
}

recorder.AddHttpResponse(HttpResponse{
Source: quoted(apiTest.request.GetHost()),
Source: quoted(systemUnderTestDefaultName),
Target: quoted(consumerName),
Value: capturedFinalRes,
Timestamp: finishTime,
Expand All @@ -461,17 +464,16 @@ func (r *Response) Report(formatter ...ReportFormatter) {
meta["status_code"] = capturedFinalRes.StatusCode
meta["path"] = capturedInboundReq.URL.String()
meta["method"] = capturedInboundReq.Method
meta["name"] = apiTest.name
meta["host"] = apiTest.request.GetHost()
meta["name"] = a.name
meta["hash"] = createHash(meta)

for k, v := range a.meta {
meta[k] = v
}

recorder.AddMeta(meta)

if len(formatter) == 0 {
NewSequenceDiagramFormatter().Format(recorder)
} else {
formatter[0].Format(recorder)
}
a.reporter.Format(recorder)
}

func createHash(meta map[string]interface{}) string {
Expand Down
9 changes: 5 additions & 4 deletions apitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,23 +418,24 @@ func TestApiTest_Report(t *testing.T) {
Times(2).
End()

recorder := &RecorderCaptor{}
reporter := &RecorderCaptor{}

New("some test").
Meta(map[string]interface{}{"host": "abc.com"}).
Report(reporter).
Mocks(getUser).
Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
getUserData()
w.WriteHeader(http.StatusOK)
})).
Host("abc.com").
Post("/hello").
Body(`{"a": 12345}`).
Headers(map[string]string{"Content-Type": "application/json"}).
Expect(t).
Status(http.StatusOK).
Report(recorder)
End()

r := recorder.capturedRecorder
r := reporter.capturedRecorder
assert.Equal(t, "POST /hello", r.Title)
assert.Equal(t, "some test", r.SubTitle)
assert.Len(t, r.Events, 4)
Expand Down
2 changes: 1 addition & 1 deletion diagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (r *SequenceDiagramFormatter) Format(recorder *Recorder) {
fmt.Printf("Created sequence diagram (%s): %s\n", fileName, filepath.FromSlash(s))
}

func NewSequenceDiagramFormatter(path ...string) *SequenceDiagramFormatter {
func SequenceDiagram(path ...string) *SequenceDiagramFormatter {
var storagePath string
if len(path) == 0 {
storagePath = ".sequence"
Expand Down
4 changes: 2 additions & 2 deletions diagram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ func TestWebSequenceDiagram_GeneratesDSL(t *testing.T) {
}

func TestNewSequenceDiagramFormatter_SetsDefaultPath(t *testing.T) {
formatter := NewSequenceDiagramFormatter()
formatter := SequenceDiagram()

assert.Equal(t, ".sequence", formatter.storagePath)
}

func TestNewSequenceDiagramFormatter_OverridesPath(t *testing.T) {
formatter := NewSequenceDiagramFormatter(".sequence-diagram")
formatter := SequenceDiagram(".sequence-diagram")

assert.Equal(t, ".sequence-diagram", formatter.storagePath)
}
Expand Down
10 changes: 6 additions & 4 deletions examples/sequence-diagrams/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,32 @@ import (

func TestGetUser_With_Default_Report_Formatter(t *testing.T) {
apitest.New("gets the user 1").
Report(apitest.SequenceDiagram()).
Meta(map[string]interface{}{"host": "user-service"}).
Mocks(getPreferencesMock, getUserMock).
Handler(newApp().Router).
Get("/user").
Host("user-service").
Query("name", "jan").
Expect(t).
Status(http.StatusOK).
Header("Content-Type", "application/json").
Body(`{"name": "jon", "is_contactable": true}`).
Report()
End()
}

func TestGetUser_With_Default_Report_Formatter_Overriding_Path(t *testing.T) {
apitest.New("gets the user 2").
Meta(map[string]interface{}{"host": "user-service"}).
Report(apitest.SequenceDiagram(".sequence-diagrams")).
Mocks(getPreferencesMock, getUserMock).
Handler(newApp().Router).
Get("/user").
Host("user-service").
Query("name", "jan").
Expect(t).
Status(http.StatusOK).
Header("Content-Type", "application/json").
Body(`{"name": "jon", "is_contactable": true}`).
Report(apitest.NewSequenceDiagramFormatter(".sequence-diagrams"))
End()
}

var getPreferencesMock = apitest.NewMock().
Expand Down

0 comments on commit b5f8a90

Please sign in to comment.