Skip to content
quarry Artifact [21c4da6ca7]
Login

Artifact [21c4da6ca7]

Artifact 21c4da6ca7245a6da41ef7417cdd604aafc9fb5fdc0b4b8bf2134932aac9e938:


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 supersedes the first: zit's millisecond D card
	// (zit#75) gives same-second writes real submission order, so
	// last-write-wins holds and the history records both revisions.
	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, "revised from the web") {
		t.Fatalf("latest save is not current:\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)
	}
}