go-challenge/src/numbers/numbers_test.go

222 lines
6.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
// Tests valid and invalid URLs via 'validateURL'.
func TestValidateURL(t *testing.T) {
urls_valid := []string{
"http://www.bar.com",
"https://86.31.3.9.de",
"http://localhost:8080",
"https://baz.org",
}
urls_invalid := []string{
"http:/www.bar.com",
"ftp://86.31.3.9.de",
"localhost:8080",
"ssh://foo.bar",
}
for _, url := range urls_valid {
err := validateURL(url)
if err != nil {
t.Errorf("URL %s invalid\nError was: %s", url, err)
}
}
for _, url := range urls_invalid {
err := validateURL(url)
if err == nil {
t.Errorf("URL %s valid", url)
}
}
}
// Tests specific JSON that is accepted by 'parseJson', e.g.
// {"Numbers": [1,2,5]}
func TestParseJson(t *testing.T) {
validJSON := [][]byte{
[]byte("{\"Numbers\": []}"),
[]byte("{\"Numbers\": [7]}"),
[]byte("{\"Numbers\": [1,2,5]}"),
[]byte("{\"Numbers\" : [1 , 2 ,5]}"),
}
invalidJSON := [][]byte{
[]byte("{\"Numbers\": [}"),
[]byte("\"Numbers\": [7]}"),
[]byte("{\"umbers\": [1,2,5]}"),
[]byte("{\"umbers\": [1,2,5]"),
[]byte("{\"Numbers\" [1,2,5]}"),
}
for _, json := range validJSON {
_, err := parseJson(json)
if err != nil {
t.Errorf("JSON \"%s\" invalid\nError was: %s", json, err)
}
}
for _, json := range invalidJSON {
res, err := parseJson(json)
if err == nil {
t.Errorf("JSON \"%s\" valid\nResult was: %s", json, res)
}
}
}
// Test the actual backend handler. Because we have no mocking framework,
// we just do a few very basic tests.
func TestHandler(t *testing.T) {
// no url parameters => status code 400
{
req, err := http.NewRequest("GET", "/numbers", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(numbersHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Handler returned status code %v, expected %v", status, http.StatusOK)
}
}
// invalid url => empty result, status code 200
{
req, err := http.NewRequest("GET", "/numbers?u=ftp://a.b.c.d.e.f", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(numbersHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned status code %v, expected %v", status, http.StatusOK)
}
body := rr.Body.String()
expected := "{\"Numbers\":[]}\n"
if body != expected {
t.Errorf("Body not as expected, got %s, expected %s", body, expected)
}
}
// valid numbers, all in time
{
ts1 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{2, 3, 5, 7, 11, 13}, 0)))
defer ts1.Close()
ts2 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{1, 1, 2, 3, 5, 8, 13, 21}, 0)))
defer ts2.Close()
ts3 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23}, 0)))
defer ts3.Close()
ts4 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{5, 17, 3, 19, 76, 24, 1, 5, 10, 34, 8, 27, 7}, 0)))
defer ts4.Close()
req, err := http.NewRequest("GET", fmt.Sprintf("/numbers?u=%s&u=%s&u=%s&u=%s", ts1.URL, ts2.URL, ts3.URL, ts4.URL), nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(numbersHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned status code %v, expected %v", status, http.StatusOK)
}
body := rr.Body.String()
expected := "{\"Numbers\":[1,2,3,5,7,8,9,10,11,13,15,17,19,21,23,24,27,34,76]}\n"
if body != expected {
t.Errorf("Body not as expected, got %s, expected %s", body, expected)
}
}
// valid numbers, skipping those that are not in time or respond with "service unavailable"
{
ts1 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{2, 3, 5, 7, 11, 13}, 200)))
defer ts1.Close()
ts2 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{1, 1, 2, 3, 5, 8, 13, 21}, 300)))
defer ts2.Close()
ts3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
}))
defer ts3.Close()
ts4 := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{5, 17, 3, 19, 76, 24, 1, 5, 10, 34, 8, 27, 7}, 500)))
defer ts4.Close()
req, err := http.NewRequest("GET", fmt.Sprintf("/numbers?u=%s&u=%s&u=%s&u=%s", ts1.URL, ts2.URL, ts3.URL, ts4.URL), nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(numbersHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned status code %v, expected %v", status, http.StatusOK)
}
body := rr.Body.String()
expected := "{\"Numbers\":[1,2,3,5,7,8,11,13,21]}\n"
if body != expected {
t.Errorf("Body not as expected, got %s, expected %s", body, expected)
}
}
// ensure response time is below 500ms
{
ts := httptest.NewServer(http.HandlerFunc(numbersHandlerInTime([]int{5, 17, 3, 19, 76, 24, 1, 5, 10, 34, 8, 27, 7}, 500)))
defer ts.Close()
req, err := http.NewRequest("GET", fmt.Sprintf("/numbers?u=%s", ts.URL), nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(numbersHandler)
start := time.Now()
handler.ServeHTTP(rr, req)
end := time.Now()
elapsed := end.Sub(start)
fmt.Println(elapsed)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned status code %v, expected %v", status, http.StatusOK)
}
body := rr.Body.String()
expected := "{\"Numbers\":[]}\n"
if body != expected {
t.Errorf("Body not as expected, got %s, expected %s", body, expected)
}
if elapsed > 500000000 {
t.Errorf("Time response not within 500ms, got %d", elapsed)
}
}
}
func numbersHandlerInTime(numbers []int, waitPeriod int) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Duration(waitPeriod) * time.Millisecond)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"Numbers": numbers})
}
}