Skip to content
quarry Artifact [9831de222b]
Login

Artifact [9831de222b]

Artifact 9831de222ba7e5ae930e1b9d6542820415b7c7f40ce4ef37de72a879b7843b28:


// promote_test.go proves RFC 0011 §1's `chat.promote` end to end on a
// REAL repository: a run of channel messages becomes a forum thread in
// the DAG, the channel is told where its conversation went, and the
// reference somebody typed in chat — which the window would have
// forgotten in seven days — is a durable backlink on the artifact's own
// page afterwards (§14).
package harness

import (
	"encoding/json"
	"net/http"
	"net/url"
	"strings"
	"testing"
)

// promoted folds [from,to] through the op and answers the thread root.
func (f *refForge) promote(from, to uint64, title string) (string, string) {
	f.t.Helper()
	form := url.Values{"from": {itoa(from)}, "to": {itoa(to)}}
	if title != "" {
		form.Set("title", title)
	}
	code, body := f.do("POST", "/api/v1/r/alpha/chat/promote", "dan", form)
	if code != http.StatusOK {
		f.t.Fatalf("chat.promote: status %d (%s)", code, body)
	}
	var out struct {
		Hash  string `json:"hash"`
		Title string `json:"title"`
		Count int    `json:"count"`
		Seq   uint64 `json:"seq"`
	}
	if err := json.Unmarshal([]byte(body), &out); err != nil {
		f.t.Fatal(err)
	}
	if out.Hash == "" {
		f.t.Fatalf("promote opened no thread: %s", body)
	}
	return out.Hash, body
}

func (f *refForge) chat(text string) uint64 {
	f.t.Helper()
	code, body := f.do("POST", "/api/v1/r/alpha/chat", "dan", url.Values{"text": {text}})
	if code != http.StatusOK {
		f.t.Fatalf("chat post: status %d (%s)", code, body)
	}
	var posted struct {
		Seq uint64 `json:"seq"`
	}
	if err := json.Unmarshal([]byte(body), &posted); err != nil {
		f.t.Fatal(err)
	}
	return posted.Seq
}

// The whole promotion: the thread carries the folded messages and a
// link back to the channel, the channel carries a robot line pointing
// at the thread, and the ticket somebody named in chat now has a
// durable "Referenced by" row that outlives the chat window.
func TestPromoteFoldsChatIntoADurableThread(t *testing.T) {
	f := newRefForge(t)
	id := f.newTicket("the bug being discussed")
	prefix := id[:8]

	first := f.chat("the lease is per store, not per node")
	f.chat("agreed — see [" + prefix + "] for the failing case")
	last := f.chat("let us write it down")

	root, _ := f.promote(first, last, "")

	// The thread exists and reads as the conversation it folded.
	_, page := f.do("GET", "/r/alpha/forum/"+root, "dan", nil)
	if page == "" {
		t.Fatal("the promoted thread has no page")
	}
	for _, want := range []string{
		"the lease is per store, not per node",
		"agreed",
		"let us write it down",
	} {
		if !strings.Contains(page, want) {
			t.Fatalf("the thread is missing the message %q:\n%s", want, page)
		}
	}
	// The opener links back to the channel it came from, and to the
	// ends of the run: a reader who wants the live version can get
	// there without guessing sequences.
	if !strings.Contains(page, `href="/r/alpha/chat"`) {
		t.Fatalf("the promoted thread has no back-link to the channel:\n%s", page)
	}
	if !strings.Contains(page, `href="/r/alpha/chat/`+itoa(first)+`"`) {
		t.Fatalf("the promoted thread does not link the run's first message:\n%s", page)
	}

	// The channel is told, once, as a robot line.
	_, history := f.do("GET", "/r/alpha/chat", "dan", nil)
	if !strings.Contains(history, `data-kind="robot"`) {
		t.Fatalf("the channel was never told about the promotion:\n%s", history)
	}
	// The line names the run and the thread by REFERENCE, so the
	// resolver has already turned both into links by the time a reader
	// sees them — which is the point of writing it as tokens.
	if !strings.Contains(history, "promoted") ||
		!strings.Contains(history, `/r/alpha/forum/`+root[:12]) {
		t.Fatalf("the robot line does not point at the promoted thread:\n%s", history)
	}
	if !strings.Contains(history, `data-hook="robot-mark"`) {
		t.Fatalf("the robot line is not drawn as one:\n%s", history)
	}

	// And the point of the whole exercise: the reference typed in chat
	// is now a DURABLE backlink derived from the thread's own text,
	// beside the window-bounded chat row rather than instead of it.
	_, ticket := f.do("GET", "/r/alpha/ticket/"+id, "dan", nil)
	if !strings.Contains(ticket, `id="tkt-refs"`) {
		t.Fatalf("no durable Referenced-by section on the ticket page:\n%s", ticket)
	}
	if !strings.Contains(ticket, `/r/alpha/forum/`+root[:12]) {
		t.Fatalf("the durable backlink does not point at the promoted thread:\n%s", ticket)
	}
}

// A title given explicitly is the thread's; an omitted one is the
// first line of the first message promoted.
func TestPromoteTitles(t *testing.T) {
	f := newRefForge(t)
	seq := f.chat("why the window is seven days")
	_, body := f.promote(seq, seq, "")
	if !strings.Contains(body, `"title":"why the window is seven days"`) {
		t.Fatalf("default title did not come from the first message: %s", body)
	}
	seq2 := f.chat("another thing entirely")
	_, body2 := f.promote(seq2, seq2, "Window retention")
	if !strings.Contains(body2, `"title":"Window retention"`) {
		t.Fatalf("explicit title was not used: %s", body2)
	}
}

// A range the window no longer holds — or one that never held
// anything — is refused in words, not archived as an empty thread.
func TestPromoteRefusesAnUnpromotableRange(t *testing.T) {
	f := newRefForge(t)
	seq := f.chat("the only message")
	code, body := f.do("POST", "/api/v1/r/alpha/chat/promote", "dan",
		url.Values{"from": {itoa(seq + 50)}, "to": {itoa(seq + 60)}})
	if code != http.StatusBadRequest {
		t.Fatalf("promoting an empty range: status %d (%s)", code, body)
	}
	code, body = f.do("POST", "/api/v1/r/alpha/chat/promote", "dan",
		url.Values{"from": {itoa(seq)}, "to": {itoa(seq - 1)}})
	if code != http.StatusBadRequest {
		t.Fatalf("promoting a reversed range: status %d (%s)", code, body)
	}
}