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.
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package dav
|
|
|
|
import (
|
|
"testing"
|
|
|
|
tlsct "git.happydns.org/checker-tls/contract"
|
|
)
|
|
|
|
// parseAll decodes DiscoverEntries output via the TLS contract. Malformed
|
|
// entries fail the test so we notice drift quickly.
|
|
func parseAll(t *testing.T, obs *Observation) []tlsct.TLSEndpoint {
|
|
t.Helper()
|
|
entries := DiscoverEntries(obs)
|
|
eps, warnings := tlsct.ParseEntries(entries)
|
|
if len(warnings) != 0 {
|
|
t.Fatalf("unexpected decode warnings: %v", warnings)
|
|
}
|
|
out := make([]tlsct.TLSEndpoint, len(eps))
|
|
for i, e := range eps {
|
|
if e.Ref == "" {
|
|
t.Errorf("entry %d has empty Ref", i)
|
|
}
|
|
out[i] = e.Endpoint
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestDiscoverEntries_contextURLOnly(t *testing.T) {
|
|
obs := &Observation{
|
|
Discovery: DiscoveryResult{ContextURL: "https://dav.example.com/caldav/"},
|
|
}
|
|
got := parseAll(t, obs)
|
|
if len(got) != 1 {
|
|
t.Fatalf("got %d endpoints, want 1: %+v", len(got), got)
|
|
}
|
|
if got[0].Host != "dav.example.com" || got[0].Port != 443 {
|
|
t.Errorf("unexpected endpoint: %+v", got[0])
|
|
}
|
|
// Direct TLS; no STARTTLS upgrade.
|
|
if got[0].STARTTLS != "" {
|
|
t.Errorf("STARTTLS = %q, want empty (direct TLS)", got[0].STARTTLS)
|
|
}
|
|
// SNI must be set unconditionally, even when it is equal to Host.
|
|
if got[0].SNI != "dav.example.com" {
|
|
t.Errorf("SNI = %q, want dav.example.com", got[0].SNI)
|
|
}
|
|
}
|
|
|
|
func TestDiscoverEntries_nonDefaultPort(t *testing.T) {
|
|
obs := &Observation{
|
|
Discovery: DiscoveryResult{ContextURL: "https://dav.example.com:8443/caldav/"},
|
|
}
|
|
got := parseAll(t, obs)
|
|
if len(got) != 1 || got[0].Port != 8443 {
|
|
t.Fatalf("unexpected: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestDiscoverEntries_srvTargets(t *testing.T) {
|
|
// SRV pointing to a different name than the domain → we must surface
|
|
// the SRV target too, because that's the hostname the cert needs to
|
|
// cover.
|
|
obs := &Observation{
|
|
Discovery: DiscoveryResult{
|
|
ContextURL: "https://dav.example.com/caldav/",
|
|
SecureSRV: []SRVRecord{
|
|
{Target: "dav-backend-1.example.net", Port: 443},
|
|
{Target: "dav-backend-2.example.net", Port: 443},
|
|
{Target: "dav.example.com", Port: 443}, // duplicate of context → deduped
|
|
},
|
|
},
|
|
}
|
|
got := parseAll(t, obs)
|
|
if len(got) != 3 {
|
|
t.Fatalf("expected 3 unique endpoints, got %d: %+v", len(got), got)
|
|
}
|
|
hosts := map[string]bool{}
|
|
for _, e := range got {
|
|
hosts[e.Host] = true
|
|
if e.SNI != e.Host {
|
|
t.Errorf("endpoint %+v: SNI=%q, want %q (equal to Host)", e, e.SNI, e.Host)
|
|
}
|
|
}
|
|
for _, want := range []string{"dav.example.com", "dav-backend-1.example.net", "dav-backend-2.example.net"} {
|
|
if !hosts[want] {
|
|
t.Errorf("missing host %q in %+v", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiscoverEntries_emptyOnNoContextURL(t *testing.T) {
|
|
if got := DiscoverEntries(&Observation{}); got != nil {
|
|
t.Errorf("expected nil, got %+v", got)
|
|
}
|
|
if got := DiscoverEntries(nil); got != nil {
|
|
t.Errorf("expected nil for nil obs, got %+v", got)
|
|
}
|
|
}
|