Files
selfpost/internal/web/session_test.go
T
mix d49351c022 chore/docs: move to GitHub as the single home; drop archived-spec references
Codeberg is being retired as the project's public site, so every reference now
points at GitHub. That includes the Go module path (codeberg.org/mix/selfpost →
github.com/mixeme/selfpost): leaving an import path on a host that is going
away would break `go get` and `go install`, so this is not only a docs change.
Touches go.mod, test/e2e/go.mod, all imports, Makefile MODULE, the -ldflags
version stamp in build/Dockerfile and docs/development.md, the licence headers
in the SVG/HTML assets, and README (no more primary/mirror pair).

Comments no longer cite the archived specification. "spec 7.6.1", "spec 5.1"
and friends pointed into docs/archive/specification-v1.0.md, which is marked as
not a source of truth; each is now a reference to the live document that owns
the subject — architecture.md (with section), product.md, security.md or the
README. The review only asked for the 7.x refs (code-review.md § 4), but 4/5/6/
8/9 had the same defect, so they went too. Comments only, no behaviour change.

Also closes the remaining review items: architecture.md gained a Code layers
section with the layer diagram (A2), and TestParseDelivery gained the exotic
mail.log cases (§ 3).

Fixes a bug that last test found: the delivery-line pattern matched status=
greedily, taking the *last* occurrence on the line. Postfix appends the remote
server's reply verbatim, so a rejection whose reply quoted "status=sent" was
filed as a delivered message in the send log. It now takes the first status=
after the recipient, which is the real field.

R7 (CONTRIBUTING.md) moved to roadmap 2.x — one developer, no external PR flow,
so the file would have no audience yet. R1 (compose image tag) and the git tag
stay in roadmap § v1.x as the release-commit steps.

gofmt/go vet clean on both modules; go test ./... green except the three known
Windows-only failures (file perms, backslash paths, renaming an open file).
Not exercised on the dev server — no Docker locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:14:13 +03:00

85 lines
2.2 KiB
Go

package web
import (
"path/filepath"
"testing"
"time"
"github.com/mixeme/selfpost/internal/store"
)
func newTestSessionStore(t *testing.T) *sessionStore {
t.Helper()
st, err := store.Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatalf("open store: %v", err)
}
t.Cleanup(func() { st.Close() })
return newSessionStore(st, 7*24*time.Hour)
}
func TestSessionRename(t *testing.T) {
s := newTestSessionStore(t)
token := s.Create("admin")
s.Rename(token, "operator")
name, ok := s.Lookup(token)
if !ok {
t.Fatal("session lost after rename")
}
if name != "operator" {
t.Fatalf("session username = %q, want %q", name, "operator")
}
}
// A password change must invalidate every other session (so a cookie captured
// under the old password stops working) while keeping the one performing the
// change signed in.
func TestSessionDestroyOthers(t *testing.T) {
s := newTestSessionStore(t)
keep := s.Create("admin")
other := s.Create("admin")
s.DestroyOthers(keep)
if _, ok := s.Lookup(keep); !ok {
t.Fatal("current session was destroyed")
}
if _, ok := s.Lookup(other); ok {
t.Fatal("other session survived")
}
}
// A session past its sliding idle expiry must not be honoured.
func TestSessionLookupRejectsExpired(t *testing.T) {
s := newTestSessionStore(t)
s.idle = -time.Minute // already expired the instant it's created
token := s.Create("admin")
if _, ok := s.Lookup(token); ok {
t.Fatal("expired session was accepted")
}
}
// Touch must not rewrite the expiry (or report a renewal) inside the
// once-an-hour throttle window, so an active tab's polling doesn't turn into
// a database write per request.
func TestSessionTouchThrottled(t *testing.T) {
s := newTestSessionStore(t)
token := s.Create("admin")
if s.Touch(token) {
t.Fatal("touch renewed a session created moments ago")
}
// Back-date the session's last renewal by rewriting its expiry, as if it
// had been created (or last renewed) 2 hours ago rather than moments ago.
if err := s.store.RenewSession(hashToken(token), time.Now().Add(-2*time.Hour).Add(s.idle)); err != nil {
t.Fatalf("renew session: %v", err)
}
if !s.Touch(token) {
t.Fatal("touch did not renew a session past the throttle window")
}
}