// This file is part of the happyDomain (R) project. // Copyright (c) 2020-2026 happyDomain // Authors: Pierre-Olivier Mercier, et al. package checker import ( "strings" "testing" sdk "git.happydns.org/checker-sdk-go/checker" ) // runHeaderRule looks up a registered rule by name and evaluates it // against an HTTPS probe whose only set header is the one under test. // The collector publishes headers as a lowercase-keyed map (see // collect.go), so we mirror that here regardless of the casing the // caller passed in. func runHeaderRule(t *testing.T, ruleName, header, value string) []sdk.CheckState { t.Helper() p := httpsProbe("a:443") if strings.TrimSpace(value) != "" { p.Headers[strings.ToLower(header)] = value } return runRule(t, ruleByName(t, ruleName), &HTTPData{Probes: []HTTPProbe{p}}, nil) } func TestReferrerPolicyRule(t *testing.T) { cases := []struct { name string value string want sdk.Status code string }{ {"missing", "", sdk.StatusInfo, "http.referrer_policy.missing"}, {"strict-origin-when-cross-origin", "strict-origin-when-cross-origin", sdk.StatusOK, "http.referrer_policy.ok"}, {"no-referrer", "no-referrer", sdk.StatusOK, "http.referrer_policy.ok"}, {"unsafe-url", "unsafe-url", sdk.StatusWarn, "http.referrer_policy.invalid"}, {"no-referrer-when-downgrade", "no-referrer-when-downgrade", sdk.StatusInfo, "http.referrer_policy.invalid"}, {"unrecognised token", "totally-made-up", sdk.StatusWarn, "http.referrer_policy.invalid"}, // Per spec the UA picks the last *recognised* token, so the // `bogus` is ignored and `same-origin` wins. {"list with fallback", "bogus, same-origin", sdk.StatusOK, "http.referrer_policy.ok"}, // Unknown token after a known one: UA falls back to the last // recognised one (`strict-origin`). {"list with unknown trailing", "strict-origin, bogus", sdk.StatusOK, "http.referrer_policy.ok"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { states := runHeaderRule(t, "http.referrer_policy", "Referrer-Policy", c.value) mustStatus(t, states, c.want) if !hasCode(states, c.code) { t.Errorf("value=%q: missing code %q in %+v", c.value, c.code, states) } }) } } func TestPermissionsPolicyRule(t *testing.T) { cases := []struct { name string value string want sdk.Status code string }{ {"missing", "", sdk.StatusInfo, "http.permissions_policy.missing"}, {"restrictive", "camera=(), microphone=()", sdk.StatusOK, "http.permissions_policy.ok"}, {"self only", "geolocation=(self)", sdk.StatusOK, "http.permissions_policy.ok"}, {"empty value treated as missing", " ", sdk.StatusInfo, "http.permissions_policy.missing"}, {"camera wildcard", "camera=*", sdk.StatusWarn, "http.permissions_policy.invalid"}, {"microphone parenthesised wildcard", "microphone=(*)", sdk.StatusWarn, "http.permissions_policy.invalid"}, {"non-dangerous wildcard ignored", "fullscreen=(self), accelerometer=*", sdk.StatusWarn, "http.permissions_policy.invalid"}, {"unknown feature wildcard ignored", "totally-made-up=*", sdk.StatusOK, "http.permissions_policy.ok"}, {"malformed entry", "camera", sdk.StatusWarn, "http.permissions_policy.invalid"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { states := runHeaderRule(t, "http.permissions_policy", "Permissions-Policy", c.value) mustStatus(t, states, c.want) if !hasCode(states, c.code) { t.Errorf("value=%q: missing code %q in %+v", c.value, c.code, states) } }) } } func TestCOOPRule(t *testing.T) { cases := []struct { name string value string want sdk.Status code string }{ {"missing", "", sdk.StatusInfo, "http.coop.missing"}, {"same-origin", "same-origin", sdk.StatusOK, "http.coop.ok"}, {"same-origin-allow-popups", "same-origin-allow-popups", sdk.StatusOK, "http.coop.ok"}, {"unsafe-none", "unsafe-none", sdk.StatusWarn, "http.coop.invalid"}, {"unrecognised", "bogus", sdk.StatusWarn, "http.coop.invalid"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { states := runHeaderRule(t, "http.coop", "Cross-Origin-Opener-Policy", c.value) mustStatus(t, states, c.want) if !hasCode(states, c.code) { t.Errorf("value=%q: missing code %q in %+v", c.value, c.code, states) } }) } } func TestCOEPRule(t *testing.T) { cases := []struct { name string value string want sdk.Status code string }{ {"missing", "", sdk.StatusInfo, "http.coep.missing"}, {"require-corp", "require-corp", sdk.StatusOK, "http.coep.ok"}, {"credentialless", "credentialless", sdk.StatusOK, "http.coep.ok"}, {"unsafe-none", "unsafe-none", sdk.StatusWarn, "http.coep.invalid"}, {"unrecognised", "bogus", sdk.StatusWarn, "http.coep.invalid"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { states := runHeaderRule(t, "http.coep", "Cross-Origin-Embedder-Policy", c.value) mustStatus(t, states, c.want) if !hasCode(states, c.code) { t.Errorf("value=%q: missing code %q in %+v", c.value, c.code, states) } }) } } func TestCORPRule(t *testing.T) { cases := []struct { name string value string want sdk.Status code string }{ {"missing", "", sdk.StatusInfo, "http.corp.missing"}, {"same-origin", "same-origin", sdk.StatusOK, "http.corp.ok"}, {"same-site", "same-site", sdk.StatusOK, "http.corp.ok"}, {"cross-origin", "cross-origin", sdk.StatusOK, "http.corp.ok"}, {"unrecognised", "bogus", sdk.StatusWarn, "http.corp.invalid"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { states := runHeaderRule(t, "http.corp", "Cross-Origin-Resource-Policy", c.value) mustStatus(t, states, c.want) if !hasCode(states, c.code) { t.Errorf("value=%q: missing code %q in %+v", c.value, c.code, states) } }) } } func TestModernHeaders_NoHTTPS(t *testing.T) { // Each modern header rule must emit Unknown when there are no // successful HTTPS probes — the no_https path comes from EvalPerHTTPS. rules := []string{ "http.referrer_policy", "http.permissions_policy", "http.coop", "http.coep", "http.corp", } data := &HTTPData{Probes: []HTTPProbe{httpProbe("a:80")}} for _, name := range rules { t.Run(name, func(t *testing.T) { states := runRule(t, ruleByName(t, name), data, nil) mustStatus(t, states, sdk.StatusUnknown) }) } }