Skip to content

Commit

Permalink
add support for request header validation
Browse files Browse the repository at this point in the history
  • Loading branch information
optimuspaul committed Nov 28, 2023
1 parent 9affb71 commit 9fa8012
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"golang.go"
]
}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

test:
go test ./pkg/thyrsus
@bash ./scripts/tests.sh

fmt:
go fmt $$(go list ./...)
5 changes: 3 additions & 2 deletions cmd/vbum/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ func main() {
lauge.Printf("patch version number is %d\n", v.Patch)
lauge.Printf("pre version number is %s\n", v.PreRelease)
lauge.Printf("meta version number is %s\n", v.Metadata)
lauge.Printf("tgt is %s\n", tgt)
}
switch tgt {
case "major":
v.BumpMajor()
case "minor":
v.BumpMajor()
v.BumpMinor()
case "patch":
v.BumpMajor()
v.BumpPatch()
}
if pre != "" {
v.PreRelease = semver.PreRelease(pre)
Expand Down
1 change: 1 addition & 0 deletions cover.num
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
97
238 changes: 238 additions & 0 deletions coverage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>thyrsus: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }

</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">

<option value="file0">github.com/cedrus-and-thuja/thyrsus-go/pkg/thyrsus/main.go (97.4%)</option>

</select>
</div>
<div id="legend">
<span>not tracked</span>

<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>

</div>
</div>
<div id="content">

<pre class="file" id="file0" style="display: none">package thyrsus

import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"slices"
"testing"
)

var logger *log.Logger = log.New(os.Stderr, "", 0)

var EmptyHeaders map[string]string = make(map[string]string, 0)

type ExpectHttp struct {
requests []*MockRequest
request_log []*http.Request
Errors []string
testServer *httptest.Server
}

type MockResponse interface {
Status() int
Body() []byte
Headers() map[string]string
}

type BaseMockResponse struct {
status int
body []byte
headers map[string]string
}

func (res BaseMockResponse) Status() int <span class="cov7" title="4">{
return res.status
}</span>

func (res BaseMockResponse) Body() []byte <span class="cov7" title="4">{
return res.body
}</span>

func (res BaseMockResponse) Headers() map[string]string <span class="cov7" title="4">{
return res.headers
}</span>

type MockRequest struct {
Url string
Response MockResponse
RequestHeaders map[string]string
}

func JSONMockResponse(status int, body interface{}, headers map[string]string) MockResponse <span class="cov9" title="6">{
data, err := json.Marshal(body)
if err != nil </span><span class="cov1" title="1">{
data = []byte(err.Error())
}</span>
<span class="cov9" title="6">headers["ContentType"] = "application/json"
return MockResponse(BaseMockResponse{
status: status,
body: data,
headers: headers,
})</span>
}

func NewExpectHttp() *ExpectHttp <span class="cov10" title="7">{
return &amp;ExpectHttp{
requests: make([]*MockRequest, 0),
request_log: make([]*http.Request, 0),
}
}</span>

func (exp *ExpectHttp) ExpectRequest(url string, response MockResponse) <span class="cov7" title="4">{
exp.requests = append(exp.requests, &amp;MockRequest{Url: url, Response: response})
}</span>

func (exp *ExpectHttp) ExpectRequestWithHeaders(url string, response MockResponse, requestHeaders map[string]string) <span class="cov4" title="2">{
exp.requests = append(exp.requests, &amp;MockRequest{Url: url, Response: response, RequestHeaders: requestHeaders})
}</span>

func (exp *ExpectHttp) Start() string <span class="cov9" title="6">{
exp.testServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) </span><span class="cov10" title="7">{
exp.request_log = append(exp.request_log, r)
if len(exp.requests) == 0 </span><span class="cov1" title="1">{
exp.Errors = append(exp.Errors, fmt.Sprintf("no more requests expected, but got %s", r.RequestURI))
w.WriteHeader(500)
}</span> else<span class="cov9" title="6"> {
expected := exp.requests[0]
if expected.Url != r.RequestURI </span><span class="cov1" title="1">{
exp.Errors = append(exp.Errors, fmt.Sprintf("expected %s, but got %s", expected.Url, r.RequestURI))
w.WriteHeader(500)
}</span> else<span class="cov8" title="5"> {
exp.requests = exp.requests[1:]
logger.Printf("request received: %s", r.RequestURI)
for k, v := range expected.RequestHeaders </span><span class="cov4" title="2">{
if val, ok := r.Header[k]; !ok || !slices.Contains(val, v) </span><span class="cov1" title="1">{
exp.Errors = append(exp.Errors, fmt.Sprintf("expected %s header to contain %s, it did not", k, v))
w.WriteHeader(500)
return
}</span>
}
<span class="cov7" title="4">for k, v := range expected.Response.Headers() </span><span class="cov7" title="4">{
logger.Printf("setting response header: %s", k)
w.Header().Set(k, v)
}</span>
<span class="cov7" title="4">w.WriteHeader(expected.Response.Status())
w.Write(expected.Response.Body())</span>
}
}
}))
<span class="cov9" title="6">exp.testServer.EnableHTTP2 = true
exp.testServer.Start()
return exp.testServer.URL</span>
}

func (exp *ExpectHttp) Close() <span class="cov9" title="6">{
exp.testServer.Close()
}</span>

func (exp *ExpectHttp) ValidateExpectations(T *testing.T) <span class="cov4" title="2">{
for _, err := range exp.Errors </span><span class="cov0" title="0">{
T.Error(err)
}</span>
}
</pre>

</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
20 changes: 15 additions & 5 deletions pkg/thyrsus/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"slices"
"testing"
)

Expand Down Expand Up @@ -46,18 +47,16 @@ func (res BaseMockResponse) Headers() map[string]string {
}

type MockRequest struct {
Url string
Response MockResponse
Url string
Response MockResponse
RequestHeaders map[string]string
}

func JSONMockResponse(status int, body interface{}, headers map[string]string) MockResponse {
data, err := json.Marshal(body)
if err != nil {
data = []byte(err.Error())
}
if headers == nil {
headers = make(map[string]string)
}
headers["ContentType"] = "application/json"
return MockResponse(BaseMockResponse{
status: status,
Expand All @@ -77,6 +76,10 @@ func (exp *ExpectHttp) ExpectRequest(url string, response MockResponse) {
exp.requests = append(exp.requests, &MockRequest{Url: url, Response: response})
}

func (exp *ExpectHttp) ExpectRequestWithHeaders(url string, response MockResponse, requestHeaders map[string]string) {
exp.requests = append(exp.requests, &MockRequest{Url: url, Response: response, RequestHeaders: requestHeaders})
}

func (exp *ExpectHttp) Start() string {
exp.testServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
exp.request_log = append(exp.request_log, r)
Expand All @@ -91,6 +94,13 @@ func (exp *ExpectHttp) Start() string {
} else {
exp.requests = exp.requests[1:]
logger.Printf("request received: %s", r.RequestURI)
for k, v := range expected.RequestHeaders {
if val, ok := r.Header[k]; !ok || !slices.Contains(val, v) {
exp.Errors = append(exp.Errors, fmt.Sprintf("expected %s header to contain %s, it did not", k, v))
w.WriteHeader(500)
return
}
}
for k, v := range expected.Response.Headers() {
logger.Printf("setting response header: %s", k)
w.Header().Set(k, v)
Expand Down
50 changes: 50 additions & 0 deletions pkg/thyrsus/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package thyrsus_test
import (
"fmt"
"io"
"math"
"net/http"
"testing"

Expand Down Expand Up @@ -61,3 +62,52 @@ func TestBasicJSONMismatchURL(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "expected /api/v1/sunshine, but got /api/v1/moonlight", string(expect.Errors[0]))
}

func TestNoFail(t *testing.T) {
expect := thyrsus.NewExpectHttp()
expect.ValidateExpectations(t)
}

func TestRequestHeaders(t *testing.T) {
expect := thyrsus.NewExpectHttp()
url := expect.Start()
defer expect.Close()
headers := map[string]string{"Authorization": "bearer 8888"}
expect.ExpectRequestWithHeaders("/api/flatulence/v1/fart", thyrsus.JSONMockResponse(200, &TestData{Status: "ok"}, thyrsus.EmptyHeaders), headers)
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/flatulence/v1/fart", url), nil)
for k, v := range headers {
req.Header[k] = []string{v}
}
assert.NoError(t, err, "reguest not made")
_, err = http.DefaultClient.Do(req)
assert.NoError(t, err)
// assert.Equal(t, "expected /api/v1/sunshine, but got /api/v1/moonlight", string(expect.Errors[0]))

expect.ValidateExpectations(t)
}

func TestRequestHeadersMissingHeader(t *testing.T) {
expect := thyrsus.NewExpectHttp()
url := expect.Start()
defer expect.Close()
headers := map[string]string{"Authorization": "bearer 8888"}
expect.ExpectRequestWithHeaders("/api/flatulence/v1/fart", thyrsus.JSONMockResponse(200, &TestData{Status: "ok"}, thyrsus.EmptyHeaders), headers)
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/flatulence/v1/fart", url), nil)
assert.NoError(t, err, "reguest not made")
_, err = http.DefaultClient.Do(req)
assert.NoError(t, err)
assert.Equal(t, "expected Authorization header to contain bearer 8888, it did not", string(expect.Errors[0]))
}

func TestFailJSONMarshal(t *testing.T) {
expect := thyrsus.NewExpectHttp()
url := expect.Start()
defer expect.Close()
expect.ExpectRequest("/api/v1/moonlight", thyrsus.JSONMockResponse(200, math.NaN(), thyrsus.EmptyHeaders))
res, err := http.DefaultClient.Get(fmt.Sprintf("%s/api/v1/moonlight", url))
assert.NoError(t, err)
body, err := io.ReadAll(res.Body)
res.Body.Close()
assert.NoError(t, err)
assert.Equal(t, "json: unsupported value: NaN", string(body))
}
Loading

0 comments on commit 9fa8012

Please sign in to comment.