Files
vibedns/internal/blockpage/blockpage_test.go
T
owenandClaude Sonnet 5 4895c8fd1e Add a customizable HTTP/HTTPS block page for sinkholed queries
Serves a page explaining why a domain was blocked instead of leaving a
sinkholed client with a dead connection. Binds its own HTTP/HTTPS
listeners with self-signed, per-hostname TLS certs generated on the
fly, re-evaluates the requesting client against the policy engine per
request, and renders an HTML template editable from Settings with a
live preview.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTKpGMQzpfDsvedu1hvSUf
2026-08-17 00:24:00 -05:00

92 lines
2.5 KiB
Go

package blockpage
import (
"crypto/tls"
"io"
"log/slog"
"strings"
"testing"
)
func discardLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
func TestRenderFillsPlaceholders(t *testing.T) {
out, err := Render(`<h1>{{.Domain}} blocked by {{.ListName}} ({{.PolicyName}})</h1>`)
if err != nil {
t.Fatalf("render: %v", err)
}
if !strings.Contains(out, "ads.example-tracker.com blocked by Advertising (Default Protection)") {
t.Errorf("unexpected output: %s", out)
}
}
func TestRenderRejectsInvalidTemplate(t *testing.T) {
if _, err := Render(`{{.Domain`); err == nil {
t.Fatal("expected a parse error for unterminated action")
}
}
func TestRenderEscapesAttackerControlledDomain(t *testing.T) {
// html/template must escape a hostile-looking hostname; a browser-facing
// error page has no business ever emitting attacker HTML unescaped.
out, err := Render(`<p>{{.Domain}}</p>`)
if err != nil {
t.Fatalf("render: %v", err)
}
if strings.Contains(out, "<script>") {
t.Errorf("output contains an unescaped tag: %s", out)
}
}
func TestCertStoreGeneratesMatchingSAN(t *testing.T) {
store := newCertStore()
cert, err := store.get(&tls.ClientHelloInfo{ServerName: "blocked.example.com"})
if err != nil {
t.Fatalf("get certificate: %v", err)
}
leaf, err := parseLeaf(cert)
if err != nil {
t.Fatalf("parse leaf: %v", err)
}
if len(leaf.DNSNames) != 1 || leaf.DNSNames[0] != "blocked.example.com" {
t.Errorf("DNSNames = %v, want [blocked.example.com]", leaf.DNSNames)
}
}
func TestCertStoreCachesByHostname(t *testing.T) {
store := newCertStore()
a, err := store.get(&tls.ClientHelloInfo{ServerName: "a.example.com"})
if err != nil {
t.Fatalf("get: %v", err)
}
b, err := store.get(&tls.ClientHelloInfo{ServerName: "a.example.com"})
if err != nil {
t.Fatalf("get: %v", err)
}
if a != b {
t.Error("expected the same cached certificate for a repeated hostname")
}
c, err := store.get(&tls.ClientHelloInfo{ServerName: "b.example.com"})
if err != nil {
t.Fatalf("get: %v", err)
}
if a == c {
t.Error("expected a different certificate for a different hostname")
}
}
func TestServerFallsBackOnBrokenTemplate(t *testing.T) {
s := &Server{log: discardLogger()}
tmpl := s.compiled(`{{.Domain`)
var b strings.Builder
if err := tmpl.Execute(&b, pageData{}); err != nil {
t.Fatalf("execute fallback: %v", err)
}
if !strings.Contains(b.String(), "misconfigured") {
t.Errorf("expected the fallback page, got: %s", b.String())
}
}