Files
palette/burnreads_test.go
T

180 lines
5.3 KiB
Go

package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
type anyHandler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
// createBurnReads creates a burn-after-N-reads paste and returns its id.
func createBurnReads(t *testing.T, h anyHandler, reads int) string {
t.Helper()
body, _ := json.Marshal(map[string]any{"content": "limited", "burn_after_read": true, "burn_after_reads": reads})
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(string(body)))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 201 {
t.Fatalf("create burn_after_reads=%d: %d %s", reads, rec.Code, rec.Body.String())
}
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
if created.ID == "" {
t.Fatal("no id in create response")
}
return created.ID
}
func getWithCookie(t *testing.T, h anyHandler, id, viewer string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest("GET", "/api/pastes/"+id, nil)
if viewer != "" {
req.AddCookie(&http.Cookie{Name: "vwr", Value: viewer})
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec
}
func TestBurnAfterNReadsDistinctViewers(t *testing.T) {
s := testServer(t)
h := s.routes()
id := createBurnReads(t, h, 2)
// viewer A: ok (read 1)
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("read 1 (viewer A): %d %s", rec.Code, rec.Body.String())
}
// viewer B: ok (read 2)
if rec := getWithCookie(t, h, id, "bbb"); rec.Code != 200 {
t.Fatalf("read 2 (viewer B): %d %s", rec.Code, rec.Body.String())
}
// viewer C: burned -> 404
if rec := getWithCookie(t, h, id, "ccc"); rec.Code != 404 {
t.Fatalf("read 3 expected 404, got %d", rec.Code)
}
}
func TestBurnReadsSameViewerWithinWindowNoDecrement(t *testing.T) {
s := testServer(t)
h := s.routes()
id := createBurnReads(t, h, 2)
// same viewer reads twice within the window: second is deduped
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("read 1: %d", rec.Code)
}
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("deduped re-read expected 200, got %d", rec.Code)
}
// another viewer still gets read 2 (budget not consumed by re-reads)
if rec := getWithCookie(t, h, id, "bbb"); rec.Code != 200 {
t.Fatalf("read 2: %d", rec.Code)
}
}
func TestBurnReadsWindowExpiryRecounts(t *testing.T) {
s := testServer(t)
h := s.routes()
id := createBurnReads(t, h, 2)
base := time.Now()
timeNow = func() time.Time { return base }
t.Cleanup(func() { timeNow = time.Now })
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("read 1: %d", rec.Code)
}
// 10 minutes later: still within window, deduped
timeNow = func() time.Time { return base.Add(10 * time.Minute) }
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("re-read within window: %d", rec.Code)
}
// 20 minutes after first read: window expired, counts as read 2
timeNow = func() time.Time { return base.Add(20 * time.Minute) }
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 200 {
t.Fatalf("re-read after window expected 200, got %d", rec.Code)
}
// budget exhausted -> 404 even for the same viewer
if rec := getWithCookie(t, h, id, "aaa"); rec.Code != 404 {
t.Fatalf("after budget expected 404, got %d", rec.Code)
}
}
func TestBurnReadsDefaultOne(t *testing.T) {
s := testServer(t)
h := s.routes()
// burn_after_read without burn_after_reads defaults to 1 read
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"one","burn_after_read":true}`))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
if rec := getWithCookie(t, h, created.ID, "aaa"); rec.Code != 200 {
t.Fatalf("read 1: %d", rec.Code)
}
if rec := getWithCookie(t, h, created.ID, "bbb"); rec.Code != 404 {
t.Fatalf("read 2 expected 404, got %d", rec.Code)
}
}
func TestBurnReadsPageViewCounts(t *testing.T) {
if webUIInstance == nil {
ui, err := NewWebUI()
if err != nil {
t.Fatal(err)
}
webUIInstance = ui
}
s := testServer(t)
h := s.routes()
id := createBurnReads(t, h, 2)
// HTML page view counts as a read too (documented decision)
req := httptest.NewRequest("GET", "/"+id, nil)
req.AddCookie(&http.Cookie{Name: "vwr", Value: "aaa"})
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("page view 1: %d", rec.Code)
}
// re-view within window: deduped
req = httptest.NewRequest("GET", "/"+id, nil)
req.AddCookie(&http.Cookie{Name: "vwr", Value: "aaa"})
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("page re-view: %d", rec.Code)
}
// distinct viewer: read 2, page renders with reads remaining
req = httptest.NewRequest("GET", "/"+id, nil)
req.AddCookie(&http.Cookie{Name: "vwr", Value: "bbb"})
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("page view 2: %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "Reads left") {
t.Fatal("stats pill missing 'Reads left'")
}
// third viewer: burned
req = httptest.NewRequest("GET", "/"+id, nil)
req.AddCookie(&http.Cookie{Name: "vwr", Value: "ccc"})
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 404 {
t.Fatalf("page view 3 expected 404, got %d", rec.Code)
}
}