CalDAV and CardDAV checkers sharing a single Go module. Discovery follows RFC 6764 (/.well-known + SRV/TXT), authenticated probes cover principal, home-set, collections and a minimal REPORT query on top of go-webdav. Common shape in internal/dav/; CalDAV adds a scheduling rule. Surfaces its context URL (and each secure-SRV target) as TLS endpoints via the EndpointDiscoverer interface, so the dedicated TLS checker can pick them up without re-parsing observations. HTML report foregrounds common misconfigs (well-known returning 200, missing SRV, plaintext-only SRV, missing DAV capability, skipped auth phase) as action-item callouts before the full phase breakdown.
115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
package dav
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestProbeOptions_parsesHeaders(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodOptions {
|
|
t.Fatalf("expected OPTIONS, got %s", r.Method)
|
|
}
|
|
w.Header().Set("DAV", "1, 2, calendar-access, calendar-schedule")
|
|
w.Header().Set("Allow", "OPTIONS, PROPFIND, REPORT, PUT")
|
|
w.Header().Set("Server", "TestSrv/1.0")
|
|
w.Header().Add("WWW-Authenticate", `Basic realm="test"`)
|
|
w.Header().Add("WWW-Authenticate", `Digest realm="test", nonce="abc"`)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
res, err := ProbeOptions(context.Background(), srv.Client(), srv.URL)
|
|
if err != nil {
|
|
t.Fatalf("ProbeOptions: %v", err)
|
|
}
|
|
if !res.HasCapability("calendar-access") {
|
|
t.Errorf("expected calendar-access in %v", res.DAVClasses)
|
|
}
|
|
if !res.HasCapability("CALENDAR-SCHEDULE") {
|
|
t.Errorf("case-insensitive match failed for calendar-schedule")
|
|
}
|
|
if !res.AllowsMethod("REPORT") || !res.AllowsMethod("PROPFIND") {
|
|
t.Errorf("expected REPORT and PROPFIND in %v", res.AllowMethods)
|
|
}
|
|
if len(res.AuthSchemes) != 2 {
|
|
t.Errorf("expected 2 auth schemes, got %v", res.AuthSchemes)
|
|
}
|
|
if res.Server != "TestSrv/1.0" {
|
|
t.Errorf("Server header: %q", res.Server)
|
|
}
|
|
}
|
|
|
|
func TestProbeOptions_missingDAVHeader(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
res, err := ProbeOptions(context.Background(), srv.Client(), srv.URL)
|
|
if err != nil {
|
|
t.Fatalf("unexpected transport error: %v", err)
|
|
}
|
|
if res.HasCapability("calendar-access") {
|
|
t.Error("expected capability absent")
|
|
}
|
|
if len(res.DAVClasses) != 0 {
|
|
t.Errorf("expected empty DAV classes, got %v", res.DAVClasses)
|
|
}
|
|
}
|
|
|
|
func TestProbeOptions_errorStatus(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
res, err := ProbeOptions(context.Background(), srv.Client(), srv.URL)
|
|
if err != nil {
|
|
t.Fatalf("transport err: %v", err)
|
|
}
|
|
if res.StatusCode != 503 {
|
|
t.Errorf("status: %d", res.StatusCode)
|
|
}
|
|
if res.Error == "" {
|
|
t.Error("expected Error to be set for 503")
|
|
}
|
|
}
|
|
|
|
func TestParseCSVHeader_mergeAndTrim(t *testing.T) {
|
|
got := parseCSVHeader([]string{"1, 2 ,calendar-access", " calendar-schedule"})
|
|
want := []string{"1", "2", "calendar-access", "calendar-schedule"}
|
|
if !equalSlices(got, want) {
|
|
t.Errorf("got %v want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAuthScheme(t *testing.T) {
|
|
cases := map[string]string{
|
|
`Basic realm="x"`: "Basic",
|
|
"Bearer": "Bearer",
|
|
`Digest realm="r", nonce="n"`: "Digest",
|
|
"": "",
|
|
" ": "",
|
|
}
|
|
for in, want := range cases {
|
|
if got := authScheme(in); got != want {
|
|
t.Errorf("authScheme(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func equalSlices(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if !strings.EqualFold(a[i], b[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|