package api import ( "net/http" "net/http/httptest" "strings" "testing" "time" "palette/internal/store" ) // #95: HTML paste views dedupe per viewer (vwr cookie) within the burn viewer // window, reusing the paste_views table. Repeated views by the same viewer do // not inflate view_count; a different viewer counts. func TestHTMLViewsDedupedPerViewer(t *testing.T) { s := testServer(t) h := s.routes() req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"vd"}`)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) id := jsonField(t, rec.Body.String(), "id") view := func(vwr string) { req := httptest.NewRequest("GET", "/"+id, nil) if vwr != "" { req.AddCookie(&http.Cookie{Name: "vwr", Value: vwr}) } rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("view: got %d", rec.Code) } } count := func() string { req := httptest.NewRequest("GET", "/api/pastes/"+id, nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec.Body.String() } // same viewer (same cookie) renders twice: still 1 view view("v-a") view("v-a") if got := count(); !strings.Contains(got, `"view_count":1`) { t.Fatalf("expected view_count 1 after duplicate viewer views, got: %s", got) } // a different viewer counts view("v-b") if got := count(); !strings.Contains(got, `"view_count":2`) { t.Fatalf("expected view_count 2 after second viewer, got: %s", got) } // viewer without cookie counts every time (plain API clients, #49 decision) view("") view("") if got := count(); !strings.Contains(got, `"view_count":4`) { t.Fatalf("expected view_count 4 after uncoookied views, got: %s", got) } } // #95: raw views always count, even from a cookie-holding viewer (#49 decision). func TestRawViewsAlwaysCount(t *testing.T) { s := testServer(t) h := s.routes() req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"rv"}`)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) id := jsonField(t, rec.Body.String(), "id") for i := 0; i < 3; i++ { req := httptest.NewRequest("GET", "/raw/"+id, nil) req.AddCookie(&http.Cookie{Name: "vwr", Value: "same-viewer"}) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("raw view: got %d", rec.Code) } } req = httptest.NewRequest("GET", "/api/pastes/"+id, nil) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if got := rec.Body.String(); !strings.Contains(got, `"view_count":3`) { t.Fatalf("expected view_count 3 after 3 raw views by one viewer, got: %s", got) } } // #95: after the dedupe window passes, the same viewer counts again. func TestViewDedupeWindowExpires(t *testing.T) { s := testServer(t) h := s.routes() req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"we"}`)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) id := jsonField(t, rec.Body.String(), "id") view := func() { req := httptest.NewRequest("GET", "/"+id, nil) req.AddCookie(&http.Cookie{Name: "vwr", Value: "v-w"}) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != 200 { t.Fatalf("view: got %d", rec.Code) } } view() view() // jump past the 15-minute window realNow := store.TimeNow store.TimeNow = func() time.Time { return realNow().Add(16 * time.Minute) } defer func() { store.TimeNow = realNow }() view() req = httptest.NewRequest("GET", "/api/pastes/"+id, nil) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if got := rec.Body.String(); !strings.Contains(got, `"view_count":2`) { t.Fatalf("expected view_count 2 after window expiry, got: %s", got) } }