check / check (push) Canceled after 0s
/api/v1/status now gives `error` for each nameserver entry and certificate entry whose status is `error`, copied from the state, which already kept it. The dashboard shows that reason in place of the records for a failed nameserver, which used to show the same `-` as one that answered with no records, and across the CN, issuer and expiry cells for a failed certificate. The dashboard stylesheet is a trimmed build, so the new markup uses only classes the page already had. README Web Dashboard and HTTP API say so. Model: opus-5-5
159 lines
3.8 KiB
Go
159 lines
3.8 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.uber.org/fx/fxtest"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/config"
|
|
"sneak.berlin/go/dnswatcher/internal/globals"
|
|
"sneak.berlin/go/dnswatcher/internal/handlers"
|
|
"sneak.berlin/go/dnswatcher/internal/logger"
|
|
"sneak.berlin/go/dnswatcher/internal/notify"
|
|
"sneak.berlin/go/dnswatcher/internal/state"
|
|
)
|
|
|
|
// The state the handler tests serve: www.example.com has one nameserver
|
|
// that answered and one whose query failed, and its certificate check
|
|
// failed.
|
|
const (
|
|
testHostname = "www.example.com"
|
|
answeringNS = "ns1.example.com."
|
|
failedNS = "ns2.example.com."
|
|
nsFailureReason = "server returned a referral"
|
|
certKey = "192.0.2.1:443:www.example.com"
|
|
certFailedReason = "x509: certificate has expired or is not yet valid"
|
|
)
|
|
|
|
// newHandlersWithFailures builds real Handlers whose state holds the
|
|
// entries described above.
|
|
func newHandlersWithFailures(t *testing.T) *handlers.Handlers {
|
|
t.Helper()
|
|
|
|
glob, err := globals.New(nil)
|
|
if err != nil {
|
|
t.Fatalf("globals.New: %v", err)
|
|
}
|
|
|
|
log, err := logger.New(nil, logger.Params{Globals: glob})
|
|
if err != nil {
|
|
t.Fatalf("logger.New: %v", err)
|
|
}
|
|
|
|
notifier, err := notify.New(fxtest.NewLifecycle(t), notify.Params{
|
|
Logger: log,
|
|
Config: &config.Config{},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("notify.New: %v", err)
|
|
}
|
|
|
|
st, err := state.New(fxtest.NewLifecycle(t), state.Params{
|
|
Logger: log,
|
|
Config: &config.Config{DataDir: t.TempDir()},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("state.New: %v", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
st.SetHostnameState(testHostname, &state.HostnameState{
|
|
RecordsByNameserver: map[string]*state.NameserverRecordState{
|
|
answeringNS: {
|
|
Records: map[string][]string{"A": {"192.0.2.1"}},
|
|
Status: "ok",
|
|
LastChecked: now,
|
|
},
|
|
failedNS: {
|
|
Records: map[string][]string{},
|
|
Status: "error",
|
|
Error: nsFailureReason,
|
|
LastChecked: now,
|
|
},
|
|
},
|
|
LastChecked: now,
|
|
})
|
|
|
|
st.SetCertificateState(certKey, &state.CertificateState{
|
|
Status: "error",
|
|
Error: certFailedReason,
|
|
LastChecked: now,
|
|
})
|
|
|
|
hnd, err := handlers.New(nil, handlers.Params{
|
|
Logger: log,
|
|
Globals: glob,
|
|
State: st,
|
|
Notify: notifier,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("handlers.New: %v", err)
|
|
}
|
|
|
|
return hnd
|
|
}
|
|
|
|
// get serves one GET request to handler and returns the response body.
|
|
func get(t *testing.T, handler http.HandlerFunc) string {
|
|
t.Helper()
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/", nil,
|
|
)
|
|
|
|
handler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", rec.Code)
|
|
}
|
|
|
|
return rec.Body.String()
|
|
}
|
|
|
|
// TestStatusGivesFailureReasons checks that /api/v1/status gives the
|
|
// reason for a failed nameserver entry and a failed certificate entry,
|
|
// and no error for a nameserver that answered.
|
|
func TestStatusGivesFailureReasons(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
body := get(t, newHandlersWithFailures(t).HandleStatus())
|
|
|
|
var resp struct {
|
|
Hostnames map[string]struct {
|
|
Nameservers map[string]map[string]any `json:"nameservers"`
|
|
} `json:"hostnames"`
|
|
Certificates map[string]map[string]any `json:"certificates"`
|
|
}
|
|
|
|
err := json.Unmarshal([]byte(body), &resp)
|
|
if err != nil {
|
|
t.Fatalf("decoding response: %v", err)
|
|
}
|
|
|
|
nameservers := resp.Hostnames[testHostname].Nameservers
|
|
|
|
got := nameservers[failedNS]["error"]
|
|
if got != nsFailureReason {
|
|
t.Errorf("failed nameserver error = %v, want %q",
|
|
got, nsFailureReason)
|
|
}
|
|
|
|
_, has := nameservers[answeringNS]["error"]
|
|
if has {
|
|
t.Errorf("answering nameserver has an error field: %v",
|
|
nameservers[answeringNS])
|
|
}
|
|
|
|
got = resp.Certificates[certKey]["error"]
|
|
if got != certFailedReason {
|
|
t.Errorf("failed certificate error = %v, want %q",
|
|
got, certFailedReason)
|
|
}
|
|
}
|