Skip to content
quarry Artifact [1d66cd4eef]
Login

Artifact [1d66cd4eef]

Artifact 1d66cd4eef7ba7de4a76cf9f2137f8c30570fce68a8771c97ac252965d06c706:


package web

import (
	"net/http"
	"net/url"
	"slices"
	"strings"
	"testing"
)

// Two accounts in one browser, the way GitHub does it: signing in while
// signed in keeps the first session in a stash; the menu offers a
// switch; switching swaps which session is active; signing out wakes
// the stashed one; a third account pushes the oldest out.
func TestAccountSwitcherHoldsTwoAndSwaps(t *testing.T) {
	srv, accts, sess := betaForge(t)
	_ = accts.AddAccount("eve", "longenough1", false)
	_ = accts.AddAccount("bob", "longenough1", false)
	cookies := func(res *http.Response) (session, others string, othersCleared bool) {
		for _, c := range res.Cookies() {
			switch c.Name {
			case sessionCookie:
				session = c.Value
			case othersCookie:
				others, othersCleared = c.Value, c.MaxAge < 0
			}
		}
		return
	}
	post := func(path string, form url.Values, jar ...*http.Cookie) *http.Response {
		req, _ := http.NewRequest("POST", srv.URL+path, strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		for _, c := range jar {
			req.AddCookie(c)
		}
		res, err := (&http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}).Do(req)
		if err != nil {
			t.Fatal(err)
		}
		res.Body.Close()
		return res
	}
	// First sign-in: one cookie, no stash.
	res := post("/login", url.Values{"user": {"root"}, "password": {"root-pw"}})
	s1, o1, _ := cookies(res)
	if s1 != "tok-root" || o1 != "" {
		t.Fatalf("first sign-in: session %q others %q", s1, o1)
	}
	// Second account while signed in: the first is stashed, not lost.
	res = post("/login", url.Values{"user": {"eve"}, "password": {"longenough1"}}, &http.Cookie{Name: sessionCookie, Value: s1})
	s2, o2, _ := cookies(res)
	if s2 != "tok-eve" || o2 != "tok-root" {
		t.Fatalf("second sign-in: session %q others %q", s2, o2)
	}
	// The menu offers the switch, by name, and no room for a third.
	req, _ := http.NewRequest("GET", srv.URL+"/", nil)
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: s2})
	req.AddCookie(&http.Cookie{Name: othersCookie, Value: o2})
	pres, _ := http.DefaultClient.Do(req)
	page := body(pres)
	if !strings.Contains(page, `action="/switch"`) || !strings.Contains(page, `name="to" value="root"`) {
		t.Errorf("the menu offers no switch to root")
	}
	if strings.Contains(page, `href="/login?add=1"`) {
		t.Errorf("a full stash still offers Add account")
	}
	// Switching swaps active and stashed; a token never crosses the wire.
	res = post("/switch", url.Values{"to": {"root"}, "back": {"/r/alpha"}}, &http.Cookie{Name: sessionCookie, Value: s2}, &http.Cookie{Name: othersCookie, Value: o2})
	s3, o3, _ := cookies(res)
	if s3 != "tok-root" || o3 != "tok-eve" || res.Header.Get("Location") != "/r/alpha" {
		t.Fatalf("switch: session %q others %q -> %s", s3, o3, res.Header.Get("Location"))
	}
	// A third account: the oldest stashed session is ended, not kept.
	res = post("/login", url.Values{"user": {"bob"}, "password": {"longenough1"}}, &http.Cookie{Name: sessionCookie, Value: s3}, &http.Cookie{Name: othersCookie, Value: o3})
	s4, o4, _ := cookies(res)
	if s4 != "tok-bob" || o4 != "tok-root" {
		t.Fatalf("third sign-in: session %q others %q", s4, o4)
	}
	if !slices.Contains(sess.out, "tok-eve") {
		t.Errorf("the pushed-out session was not ended server-side: %v", sess.out)
	}
	// Signing out ends the active session and wakes the stashed one.
	res = post("/logout", nil, &http.Cookie{Name: sessionCookie, Value: s4}, &http.Cookie{Name: othersCookie, Value: o4})
	s5, _, cleared := cookies(res)
	if s5 != "tok-root" || !cleared {
		t.Fatalf("sign out: session %q, stash cleared %v", s5, cleared)
	}
	if !slices.Contains(sess.out, "tok-bob") {
		t.Errorf("sign out did not end the active session: %v", sess.out)
	}
	// Signing out with nothing stashed clears the browser as before.
	res = post("/logout", nil, &http.Cookie{Name: sessionCookie, Value: s5})
	if s6, _, _ := cookies(res); s6 != "" {
		t.Fatalf("final sign out left a session %q", s6)
	}
	// The add-account door tells the reader the first account stays.
	req, _ = http.NewRequest("GET", srv.URL+"/login?add=1", nil)
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "tok-root"})
	pres, _ = http.DefaultClient.Do(req)
	if !strings.Contains(body(pres), `data-hook="login-add"`) {
		t.Errorf("the add-account login page does not say the first account stays")
	}
}