check / check (push) Canceled after 0s
Tests that waited a few seconds for something to happen now wait 15 seconds. The wait ends as soon as the condition holds, so a passing run takes no longer. TestStreamNotDroppedWhileLinesArrive, and the tests whose requests pass a 2- or 4-second limit in the server, run on a fake clock, so a stall on the host cannot use up the limit. The checkpoint tests give the writing connection a 30-second busy timeout, so their limit can be 15 seconds and a checkpoint that waits for a read still fails them. Model: opus-5-5
121 lines
3.7 KiB
Go
121 lines
3.7 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"testing/synctest"
|
|
)
|
|
|
|
// TestRoutesAnswerHEAD sends HEAD for a page and JSON routes through the
|
|
// server's router and checks that each answers as GET does. /ip and /ip/, the
|
|
// lookup of the caller's own address, are in the list because chi's GetHead
|
|
// does not reach them. Before the fix every route answered HEAD with 405
|
|
// (https://git.eeqj.de/sneak/routewatch/issues/111).
|
|
//
|
|
// The requests run in a synctest bubble, whose clock is fake, so a stall on
|
|
// the host does not use up the stats handlers' 4-second limit. The resolver
|
|
// is fake because no time limit would cut off a real DNS lookup in a bubble.
|
|
func TestRoutesAnswerHEAD(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := newServerWithTestRoutes(t)
|
|
s.resolver = &fakeResolver{}
|
|
|
|
for _, path := range []string{"/", statusJSONPath, statsPath, "/ip", "/ip/"} {
|
|
rec := httptest.NewRecorder()
|
|
|
|
synctest.Test(t, func(t *testing.T) {
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodHead, path, nil))
|
|
})
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("HEAD %s: status %d, want %d; body %s",
|
|
path, rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestJSONRoutesAnswerUnmatchedRequestsWithJSONErrors sends a method the JSON
|
|
// routes do not take, and a path none of them matches, to each group of JSON
|
|
// routes through the server's router. It also sends a method they do not take
|
|
// to /status.json and the health check. Each must answer with the JSON error
|
|
// body, and a 405 must name the methods the routes take in its Allow header.
|
|
// Before the fix a 405 said "Internal server error" and a 404 was chi's plain
|
|
// text answer (https://git.eeqj.de/sneak/routewatch/issues/111).
|
|
// /status.json and the health check gave chi's empty 405
|
|
// (https://git.eeqj.de/sneak/routewatch/issues/201).
|
|
func TestJSONRoutesAnswerUnmatchedRequestsWithJSONErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := newServerWithTestRoutes(t)
|
|
|
|
tests := []struct {
|
|
method string
|
|
path string
|
|
wantCode int
|
|
wantMsg string
|
|
}{
|
|
{http.MethodPost, statsPath, http.StatusMethodNotAllowed, methodNotAllowedMessage},
|
|
{http.MethodPost, "/ip/", http.StatusMethodNotAllowed, methodNotAllowedMessage},
|
|
{
|
|
http.MethodDelete, statusJSONPath,
|
|
http.StatusMethodNotAllowed, methodNotAllowedMessage,
|
|
},
|
|
{
|
|
http.MethodOptions, healthCheckPath,
|
|
http.StatusMethodNotAllowed, methodNotAllowedMessage,
|
|
},
|
|
{http.MethodGet, "/api/v1/nothing", http.StatusNotFound, notFoundMessage},
|
|
{http.MethodGet, "/ip/192.0.2.1/nothing", http.StatusNotFound, notFoundMessage},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
name := tt.method + " " + tt.path
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), tt.method, tt.path, nil))
|
|
|
|
if rec.Code != tt.wantCode {
|
|
t.Errorf("%s: status %d, want %d; body %s",
|
|
name, rec.Code, tt.wantCode, rec.Body)
|
|
}
|
|
|
|
checkJSONErrorCode(t, name, rec, tt.wantCode)
|
|
checkJSONErrorMessage(t, name, rec, tt.wantMsg)
|
|
|
|
allow := rec.Header().Get("Allow")
|
|
if tt.wantCode == http.StatusMethodNotAllowed && allow != jsonRoutesAllow {
|
|
t.Errorf("%s: Allow %q, want %q", name, allow, jsonRoutesAllow)
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkJSONErrorMessage checks that rec holds the JSON error body with the
|
|
// given message.
|
|
func checkJSONErrorMessage(
|
|
t *testing.T, name string, rec *httptest.ResponseRecorder, want string,
|
|
) {
|
|
t.Helper()
|
|
|
|
var body struct {
|
|
Error struct {
|
|
Msg string `json:"msg"`
|
|
} `json:"error"`
|
|
}
|
|
|
|
err := json.Unmarshal(rec.Body.Bytes(), &body)
|
|
if err != nil {
|
|
t.Errorf("%s: decoding the answer %q: %v", name, rec.Body, err)
|
|
|
|
return
|
|
}
|
|
|
|
if body.Error.Msg != want {
|
|
t.Errorf("%s: error message %q, want %q", name, body.Error.Msg, want)
|
|
}
|
|
}
|