Skip to content
quarry Artifact [cb18a81501]
Login

Artifact [cb18a81501]

Artifact cb18a81501dc827cac22d04f399212dd8249764d10a2ba5e8fee8b04d158f76f:


package api

import (
	"context"
	"net/http"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/ops"
)

type said struct {
	user, kind, room string
	ref              uint64
	text             string
}

// chatRig is the forum rig with a room to talk into. lou may chat on
// quarry; mallory may not, and neither is an admin — replying by mail
// is an ordinary account's business.
func chatRig(t *testing.T) (*ReplyReceiver, *fakeReplyStore, *[]said) {
	t.Helper()
	var log []said
	store := &fakeReplyStore{secret: "s3cret"}
	h := &ReplyReceiver{
		Store: store,
		CapsFor: func(account, repo string) CapSet {
			if account == "lou" {
				return capsOf("C3")
			}
			return capsOf("")
		},
		Say: func(ctx context.Context, kind, room string, ref uint64, text string) (uint64, error) {
			log = append(log, said{ops.User(ctx), kind, room, ref, text})
			return 42, nil
		},
		Now: func() time.Time { return time.Unix(1_800_000_000, 0) },
	}
	return h, store, &log
}

// The chat transaction ids were minted from the day chat had mentions
// and nothing ever read them back: a reply to one answered "no thread
// behind transaction", so the mail was accepted, understood and
// dropped. Both rooms now land.
func TestAMailedReplyLandsInTheRoomItAnswers(t *testing.T) {
	for _, c := range []struct {
		name, txn, kind, room string
		ref                   uint64
	}{
		{"a repository's room", "mention:chat:quarry:1234:lou", "chat", "quarry", 1234},
		{"a team's room", "mention:team:grove:99:lou", "team", "grove", 99},
	} {
		h, store, log := chatRig(t)
		w := deliver(h, "s3cret", 1_800_000_000, replyEvent(c.txn, "lou", "  On it.\n"))
		if w.Code != http.StatusCreated {
			t.Errorf("FAIL: %s: status %d: %s", c.name, w.Code, w.Body)
			continue
		}
		if len(*log) != 1 {
			t.Errorf("FAIL: %s: %d lines, want 1", c.name, len(*log))
			continue
		}
		got := (*log)[0]
		if got.user != "lou" || got.kind != c.kind || got.room != c.room ||
			got.ref != c.ref || got.text != "On it." {
			t.Errorf("FAIL: %s: said %+v", c.name, got)
		} else {
			t.Logf("ok: %s — %s reply as %s into %s, answering %d", c.name, got.kind, got.user, got.room, got.ref)
		}
		if store.seen["<[email protected]>"] == "" {
			t.Errorf("FAIL: %s: outcome not recorded", c.name)
		}
	}
}

// Every gate the room has, the mail meets too. Replying by email is a
// different DOOR, never a different set of rules.
func TestAMailedChatReplyMeetsTheRoomsGates(t *testing.T) {
	// Somebody else's notification is not yours to answer.
	h, _, log := chatRig(t)
	w := deliver(h, "s3cret", 1_800_000_000, replyEvent("mention:chat:quarry:7:lou", "mallory", "hello"))
	if w.Code != http.StatusForbidden || len(*log) != 0 {
		t.Errorf("FAIL: mallory answered lou's notification (%d, %d lines)", w.Code, len(*log))
	} else {
		t.Log("ok: a reply from the wrong account is refused")
	}

	// No chat capability on that repository, no line.
	h2, _, log2 := chatRig(t)
	w2 := deliver(h2, "s3cret", 1_800_000_000, replyEvent("mention:chat:quarry:7:mallory", "mallory", "hello"))
	if w2.Code != http.StatusForbidden || len(*log2) != 0 {
		t.Errorf("FAIL: mallory chatted without C (%d, %d lines)", w2.Code, len(*log2))
	} else {
		t.Log("ok: the room's capability is checked")
	}

	// An empty reply — everything quoted stripped — is not a message.
	h3, _, log3 := chatRig(t)
	w3 := deliver(h3, "s3cret", 1_800_000_000, replyEvent("mention:chat:quarry:7:lou", "lou", "   \n  "))
	if w3.Code < 400 || len(*log3) != 0 {
		t.Errorf("FAIL: an empty reply became a line (%d)", w3.Code)
	} else {
		t.Log("ok: an empty reply is refused")
	}
}

// The id is parsed from the RIGHT: who is always last, a seq always
// sits before it, and a room is the field whose grammar we do not own.
func TestChatTransactionIdsAreReadBack(t *testing.T) {
	for _, c := range []struct {
		txn, kind, room string
		ref             uint64
		who             string
		ok              bool
	}{
		{"mention:chat:quarry:1234:lou", "chat", "quarry", 1234, "lou", true},
		{"mention:team:grove:99:ann", "team", "grove", 99, "ann", true},
		{"mention:chat:my:repo:7:lou", "chat", "my:repo", 7, "lou", true},
		{"mention:chat:quarry:notaseq:lou", "", "", 0, "", false},
		{"mention:chat:quarry:lou", "", "", 0, "", false},
		{"landing:abc:lou", "", "", 0, "", false},
	} {
		got, ok := replyTarget(c.txn)
		if ok != c.ok {
			t.Errorf("FAIL: %q read=%v want=%v", c.txn, ok, c.ok)
			continue
		}
		if !ok {
			t.Logf("ok: %q is not a room reply", c.txn)
			continue
		}
		if got.Kind != c.kind || got.Room != c.room || got.Ref != c.ref || got.Who != c.who {
			t.Errorf("FAIL: %q -> %+v", c.txn, got)
		} else {
			t.Logf("ok: %q -> %s %s #%d for %s", c.txn, got.Kind, got.Room, got.Ref, got.Who)
		}
	}
	// The forum shapes still read as they did.
	if got, ok := replyTarget("forum-reply:abc123:lou"); !ok || got.Kind != "forum" || got.Hash != "abc123" || got.Who != "lou" {
		t.Errorf("FAIL: the forum shape broke: %+v %v", got, ok)
	} else {
		t.Log("ok: the forum shapes are unchanged")
	}
	if got, ok := replyTarget("mention:forum:abc123:lou"); !ok || got.Kind != "forum" || got.Hash != "abc123" {
		t.Errorf("FAIL: the forum mention shape broke: %+v %v", got, ok)
	} else {
		t.Log("ok: the forum mention shape is unchanged")
	}
}