Skip to content
quarry Artifact [f50186f915]
Login

Artifact [f50186f915]

Artifact f50186f91530373655eed82b2aa9ccc0d832d8b8275bbfa5adb2b14c116bda48:


package obs

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
)

type hitSink struct {
	nopSink
	hits []Hit
}

func (s *hitSink) Hit(h Hit) { s.hits = append(s.hits, h) }

// serve runs one request through Hits and returns the hit it filed.
func serve(t *testing.T, req *http.Request, addr func(*http.Request) string, h http.HandlerFunc) Hit {
	t.Helper()
	sink := &hitSink{}
	rec := NewRecorder(10, sink)
	rec.Hits(h, addr).ServeHTTP(httptest.NewRecorder(), req)
	if len(sink.hits) != 1 {
		t.Fatalf("FAIL: filed %d hits, want 1", len(sink.hits))
	}
	return sink.hits[0]
}

func socket(req *http.Request) string { return remoteHost(req.RemoteAddr) }

// A hit carries what the outermost handler sees — status, bytes, the
// path without its query, the referring site — and what the handlers
// inside noted about who it was.
func TestAHitCarriesTheResponseAndWhatTheHandlerNoted(t *testing.T) {
	req := httptest.NewRequest("GET", "/r/quarry/timeline?n=500", nil)
	req.Header.Set("User-Agent", "Mozilla/5.0 Firefox/131.0")
	req.Header.Set("Referer", "https://news.ycombinator.com/item?id=1")
	h := serve(t, req, socket, func(w http.ResponseWriter, r *http.Request) {
		NoteOf(r.Context()).Account("dmestas")
		w.WriteHeader(http.StatusTeapot)
		io.WriteString(w, "twelve bytes")
	})
	if h.Status != http.StatusTeapot || h.Bytes != 12 {
		t.Errorf("FAIL: status %d bytes %d", h.Status, h.Bytes)
	}
	if h.URL != "/r/quarry/timeline" || h.Repo != "quarry" || h.Door != DoorWeb {
		t.Errorf("FAIL: url %q repo %q door %q", h.URL, h.Repo, h.Door)
	}
	if h.Account != "dmestas" || h.Referrer != "news.ycombinator.com" {
		t.Errorf("FAIL: account %q referrer %q", h.Account, h.Referrer)
	}
}

// A sync round notes its verb and client, and that decides the door
// whatever the path looks like.
func TestASyncRoundIsFiledWithItsVerb(t *testing.T) {
	req := httptest.NewRequest("POST", "/r/quarry", nil)
	h := serve(t, req, socket, func(w http.ResponseWriter, r *http.Request) {
		NoteOf(r.Context()).Sync("clone", "", "alice", "fossil", "22700")
	})
	if h.Door != DoorSync || h.Verb != "clone" || h.Claimed != "alice" || h.Account != "" || h.Client != "fossil" {
		t.Errorf("FAIL: %+v", h)
	}
}

// The country is the edge's, and believed only when the edge is who
// answered: a client writing CF-IPCountry to a forge it reaches
// directly is not evidence of where it is.
func TestTheCountryIsBelievedOnlyBehindTheProxy(t *testing.T) {
	behind := func(r *http.Request) string { return r.Header.Get("CF-Connecting-IP") }
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("CF-Connecting-IP", "203.0.113.9")
	req.Header.Set("CF-IPCountry", "de")
	if h := serve(t, req, behind, func(http.ResponseWriter, *http.Request) {}); h.Country != "DE" || h.Addr != "203.0.113.9" {
		t.Errorf("FAIL: behind the proxy: country %q addr %q", h.Country, h.Addr)
	}
	direct := httptest.NewRequest("GET", "/", nil)
	direct.Header.Set("CF-IPCountry", "DE")
	if h := serve(t, direct, socket, func(http.ResponseWriter, *http.Request) {}); h.Country != "" {
		t.Errorf("FAIL: a direct request was believed about its country: %q", h.Country)
	}
}

// A stream is marked, so its open-tab lifetime never reads as a
// response time.
func TestAStreamIsMarked(t *testing.T) {
	req := httptest.NewRequest("GET", "/r/quarry/chat.sse", nil)
	h := serve(t, req, socket, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/event-stream")
		w.WriteHeader(200)
	})
	if !h.Stream {
		t.Error("FAIL: a stream was filed as a response")
	}
}

// The forge's own pages are not a referrer.
func TestTheForgeIsNotItsOwnReferrer(t *testing.T) {
	if got := referrerHost("https://zeitforge.app/r/quarry", "zeitforge.app"); got != "" {
		t.Errorf("FAIL: self-referral reported as %q", got)
	}
	if got := referrerHost("not a url", "zeitforge.app"); got != "" {
		t.Errorf("FAIL: an unparseable referrer reported as %q", got)
	}
}