Skip to content
quarry Artifact [3ac61335ec]
Login

Artifact [3ac61335ec]

Artifact 3ac61335ec931941cc26636c228b389fbfb089e1ae3415762f0971e73d320422:


package harness

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

// TestWriteOps drives the wiki/ticket write path the way a browser
// does: form POSTs through the web handler, gated by Basic auth, with
// read-your-writes (the op syncs before answering).
func TestWriteOps(t *testing.T) {
	stores := map[string]string{"alpha": buildStore(t, "writeseed")}
	dataDir := t.TempDir()
	srv := ingestAndServeAuth(t, stores, dataDir, func(repo, user, pass string) bool {
		return repo == "alpha" && user == "dan" && pass == "hunter2"
	})

	client := srv.Client()
	client.CheckRedirect = func(*http.Request, []*http.Request) error {
		return http.ErrUseLastResponse
	}
	post := func(path string, form url.Values, auth bool) *http.Response {
		req, _ := http.NewRequest("POST", srv.URL+path, strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		if auth {
			req.SetBasicAuth("dan", "hunter2")
		}
		resp, err := client.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		resp.Body.Close()
		return resp
	}
	get := func(path string) string {
		resp, err := client.Get(srv.URL + path)
		if err != nil {
			t.Fatal(err)
		}
		defer resp.Body.Close()
		b, _ := io.ReadAll(resp.Body)
		return string(b)
	}

	// Unauthenticated write: challenged, nothing recorded.
	if resp := post("/r/alpha/wiki/Guide/edit", url.Values{"body": {"nope"}}, false); resp.StatusCode != http.StatusUnauthorized {
		t.Fatalf("unauthenticated wiki save: status %d", resp.StatusCode)
	}

	// Authenticated wiki save: redirect, then the page serves the body
	// and the history records the author.
	if resp := post("/r/alpha/wiki/Guide/edit", url.Values{"body": {"# Guide\n\nwritten from the web\n"}}, true); resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("wiki save: status %d", resp.StatusCode)
	}
	if body := get("/r/alpha/wiki/Guide"); !strings.Contains(body, "written from the web") {
		t.Fatalf("saved wiki not served:\n%s", body)
	}
	if body := get("/r/alpha/wiki/Guide/history"); !strings.Contains(body, "dan") {
		t.Fatalf("author missing from history:\n%s", body)
	}

	// A second save adds a revision. We assert the history records both,
	// not which is "current": zit's D card has whole-second resolution,
	// so two saves in the same second tie and the deterministic hash
	// tiebreak — not wall-clock order — decides the winner (tracked
	// upstream; needs sub-second D cards from zit). The count is what's
	// guaranteed regardless.
	post("/r/alpha/wiki/Guide/edit", url.Values{"body": {"revised from the web\n"}}, true)
	hist := get("/r/alpha/wiki/Guide/history")
	if n := strings.Count(hist, "<li>"); n < 2 {
		t.Fatalf("second revision not recorded (%d in history):\n%s", n, hist)
	}
	if body := get("/r/alpha/wiki/Guide"); !strings.Contains(body, "written from the web") && !strings.Contains(body, "revised from the web") {
		t.Fatalf("current revision is neither save:\n%s", body)
	}

	// Ticket: new, then set, both visible immediately.
	resp := post("/r/alpha/ticket/new", url.Values{
		"title": {"web-born ticket"}, "comment": {"filed from the form"},
	}, true)
	if resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("ticket new: status %d", resp.StatusCode)
	}
	loc := resp.Header.Get("Location")
	if body := get(loc); !strings.Contains(body, "web-born ticket") || !strings.Contains(body, "Open") {
		t.Fatalf("new ticket page:\n%s", body)
	}
	id := strings.TrimPrefix(loc, "/r/alpha/ticket/")
	if resp := post("/r/alpha/ticket/"+id[:10]+"/edit", url.Values{
		"status": {"Closed"}, "resolution": {"Fixed"},
	}, true); resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("ticket set: status %d", resp.StatusCode)
	}
	if body := get(loc); !strings.Contains(body, "Closed") || !strings.Contains(body, "Fixed") {
		t.Fatalf("ticket change not folded:\n%s", body)
	}
	if body := get("/r/alpha/timeline"); !strings.Contains(body, "web-born ticket") && !strings.Contains(body, "Ticket") {
		t.Fatalf("ticket missing from timeline:\n%s", body)
	}
}