package main import ( "net/http" "net/http/httptest" "testing" "time" ) func TestParseCookies(t *testing.T) { got := ParseCookies("a=1; b=two; ng_session=abc%20def; empty=; =x; novalue; a=last") want := map[string]string{ "a": "last", // later duplicates overwrite, like Object.fromEntries "b": "two", "ng_session": "abc def", "empty": "", } if len(got) != len(want) { t.Fatalf("ParseCookies = %v, want %v", got, want) } for k, v := range want { if got[k] != v { t.Errorf("cookie %q = %q, want %q", k, got[k], v) } } if _, ok := got[""]; ok { t.Errorf("empty key must be filtered out: %v", got) } if _, ok := got["novalue"]; ok { t.Errorf("pair without '=' must be skipped: %v", got) } } func TestParseCookiesDecodingAndEdges(t *testing.T) { if got := ParseCookies(""); len(got) != 0 { t.Errorf("empty header = %v, want empty", got) } got := ParseCookies("a%20b=c%3Dd; spaced = value ") if got["a b"] != "c=d" { t.Errorf("decoded key/value with '=' in value = %v", got) } if got["spaced"] != "value" { t.Errorf("trimmed pair = %v", got) } // Malformed escape: pair is skipped (documented deviation from JS throw). got = ParseCookies("bad=%zz; good=1") if _, ok := got["bad"]; ok { t.Errorf("malformed pair should be skipped: %v", got) } if got["good"] != "1" { t.Errorf("good pair after malformed = %v", got) } // '+' is literal in decodeURIComponent semantics, not a space. got = ParseCookies("plus=a+b") if got["plus"] != "a+b" { t.Errorf("plus handling = %q, want %q", got["plus"], "a+b") } } func requestWithSession(id string) *http.Request { r := httptest.NewRequest("GET", "/", nil) if id != "" { r.Header.Set("Cookie", "ng_session="+id) } return r } func TestSessionManagerLifecycle(t *testing.T) { m := NewSessionManager(12) if m.TTL != 12*time.Hour { t.Fatalf("TTL = %v, want 12h", m.TTL) } s := m.Create("user-1") if s.ID == "" || s.CSRF == "" || s.UserID != "user-1" { t.Fatalf("created session = %+v", s) } if !s.ExpiresAt.After(time.Now()) { t.Fatalf("ExpiresAt not in future: %v", s.ExpiresAt) } got := m.Get(requestWithSession(s.ID)) if got != s { t.Fatalf("Get = %+v, want the created session", got) } if m.Get(requestWithSession("nope")) != nil { t.Errorf("Get unknown id should be nil") } if m.Get(requestWithSession("")) != nil { t.Errorf("Get without cookie should be nil") } m.Delete(requestWithSession(s.ID)) if m.Get(requestWithSession(s.ID)) != nil { t.Errorf("Get after Delete should be nil") } } func TestSessionManagerExpiryAndPrune(t *testing.T) { m := NewSessionManager(1) live := m.Create("live") dead := m.Create("dead") dead.ExpiresAt = time.Now().Add(-time.Minute) if m.Get(requestWithSession(dead.ID)) != nil { t.Errorf("expired session should read as nil") } // Expired session is also deleted by Get. dead2 := m.Create("dead2") dead2.ExpiresAt = time.Now().Add(-time.Minute) m.Prune() if m.Get(requestWithSession(dead2.ID)) != nil { t.Errorf("pruned session should be gone") } if m.Get(requestWithSession(live.ID)) != live { t.Errorf("live session must survive Prune") } }