// 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" ) func TestCookieFlagsRule_NoHTTPS(t *testing.T) { data := &HTTPData{Probes: []HTTPProbe{httpProbe("a:80")}} states := runRule(t, &cookieFlagsRule{}, data, nil) mustStatus(t, states, sdk.StatusUnknown) } func TestCookieFlagsRule_NoCookies(t *testing.T) { data := &HTTPData{Probes: []HTTPProbe{httpsProbe("a:443")}} states := runRule(t, &cookieFlagsRule{}, data, nil) mustStatus(t, states, sdk.StatusOK) if !hasCode(states, "http.cookie_flags.none") { t.Errorf("missing 'none' code: %+v", states) } } func TestCookieFlagsRule_AllOK(t *testing.T) { p := httpsProbe("a:443") p.Cookies = []CookieInfo{ {Name: "sid", Secure: true, HttpOnly: true, SameSite: "Strict"}, {Name: "tok", Secure: true, HttpOnly: true, SameSite: "Lax"}, } states := runRule(t, &cookieFlagsRule{}, &HTTPData{Probes: []HTTPProbe{p}}, nil) mustStatus(t, states, sdk.StatusOK) if !hasCode(states, "http.cookie_flags.ok") { t.Errorf("missing ok code: %+v", states) } } func TestCookieFlagsRule_Issues(t *testing.T) { p := httpsProbe("a:443") p.Cookies = []CookieInfo{ {Name: "no-secure", Secure: false, HttpOnly: true, SameSite: "Lax"}, {Name: "no-httponly", Secure: true, HttpOnly: false, SameSite: "Lax"}, {Name: "no-samesite", Secure: true, HttpOnly: true, SameSite: ""}, {Name: "none-without-secure", Secure: false, HttpOnly: true, SameSite: "None"}, } states := runRule(t, &cookieFlagsRule{}, &HTTPData{Probes: []HTTPProbe{p}}, nil) // Per-cookie diagnostics + a single SameSite aggregate (1 cookie out // of 4 is missing SameSite). if len(states) != len(p.Cookies)+1 { t.Fatalf("got %d states, want %d", len(states), len(p.Cookies)+1) } mustStatus(t, states, sdk.StatusWarn) if !hasCode(states, "http.cookie_flags.samesite_missing") { t.Errorf("missing samesite_missing aggregate: %+v", states) } for _, st := range states { if st.Code == "http.cookie_flags.samesite_missing" { if !strings.Contains(st.Message, "1 of 4") { t.Errorf("aggregate message %q should mention 1 of 4", st.Message) } } } // Check each per-cookie diagnostic mentions the cookie name and a // relevant phrase. wantSubstr := map[string]string{ "no-secure": "missing Secure", "no-httponly": "missing HttpOnly", "no-samesite": "missing SameSite", "none-without-secure": "SameSite=None requires Secure", } for _, st := range states { if st.Code != "http.cookie_flags.weak" { continue } matched := false for name, phrase := range wantSubstr { if strings.Contains(st.Message, name) && strings.Contains(st.Message, phrase) { matched = true break } } if !matched { t.Errorf("unexpected state message: %q", st.Message) } } } func TestCookieFlagsRule_SameSiteAggregateOnly(t *testing.T) { // Two cookies, both otherwise compliant but missing SameSite. We // expect 2 per-cookie warnings + 1 aggregate. p := httpsProbe("a:443") p.Cookies = []CookieInfo{ {Name: "a", Secure: true, HttpOnly: true, SameSite: ""}, {Name: "b", Secure: true, HttpOnly: true, SameSite: ""}, } states := runRule(t, &cookieFlagsRule{}, &HTTPData{Probes: []HTTPProbe{p}}, nil) mustStatus(t, states, sdk.StatusWarn) if !hasCode(states, "http.cookie_flags.samesite_missing") { t.Fatalf("missing aggregate state: %+v", states) } for _, st := range states { if st.Code == "http.cookie_flags.samesite_missing" && !strings.Contains(st.Message, "2 of 2") { t.Errorf("aggregate should report 2 of 2, got %q", st.Message) } } } func TestCookieFlagsRule_SameSiteNoneCaseInsensitive(t *testing.T) { p := httpsProbe("a:443") p.Cookies = []CookieInfo{{Name: "x", Secure: false, HttpOnly: true, SameSite: "none"}} states := runRule(t, &cookieFlagsRule{}, &HTTPData{Probes: []HTTPProbe{p}}, nil) mustStatus(t, states, sdk.StatusWarn) if !strings.Contains(states[0].Message, "SameSite=None requires Secure") { t.Errorf("expected SameSite=None warning regardless of casing, got %q", states[0].Message) } }