Skip to content
quarry Artifact [8d5958ef83]
Login

Artifact [8d5958ef83]

Artifact 8d5958ef8330f71d3561b327fc849732585551b41fda3242cd307f51689e7595:


package api

import (
	"context"
	"testing"

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

// heldForumStub is the ForumStore surface this test needs: one author's
// own held submissions, nothing else. The embedded interface leaves
// every other method unimplemented on purpose -- if forum.list starts
// calling one, this test should fail loudly rather than quietly widen.
type heldForumStub struct {
	ForumStore
	staged []oltp.Staged
}

// The other two methods forum.list asks of any Forum it is given. They
// answer empty, so the listing behaves as it does on a forum with no
// categories and no read marks. Everything else on ForumStore stays
// unimplemented on purpose: if forum.list starts calling another, this
// test panics rather than quietly passing on a wider surface.
func (s *heldForumStub) ThreadCategories(string) (map[string]string, error) { return nil, nil }

// A THIRD method, added deliberately rather than to quiet a panic: the
// listing now answers whether the forum is moderated and whether it
// would hold THIS caller's post, so it asks. The tripwire above did its
// job — it caught the widened surface on the first run — and false is
// the honest answer for a stub standing in for a forum with no
// moderation configured.
func (s *heldForumStub) ModerationEnabled(string) bool { return false }
func (s *heldForumStub) ForumRead(_, _ string, _ map[string]string) (map[string]bool, error) {
	return nil, nil
}

func (s *heldForumStub) StagedMine(_, author string) ([]oltp.Staged, error) {
	var out []oltp.Staged
	for _, x := range s.staged {
		if x.Author == author {
			out = append(out, x)
		}
	}
	return out, nil
}

// The forum listing carries the VIEWER'S OWN held submissions, and
// nobody else's.
//
// A staged post is in forum_staged and never in the DAG, and every row
// forum.list assembles comes from the DAG -- so the author's own post
// was invisible to them by construction rather than by any filter
// somebody wrote, which is why the silence was total. jva measured it
// from a non-moderator account and concluded the post had failed
// (G-118). The rule this implements is theirs: the author should always
// see their own held content and its state, because today they see LESS
// than a stranger does (G-119).
//
// Worth knowing for anyone re-measuring this by hand: TrustedAuthor is
// EffectiveCaps.HasAny("45") and Admin expands to everything but sxy,
// so an admin can never produce a held post from their own account. jva
// had to lower an account to Developer to test it at all.
func TestForumListShowsTheViewerTheirOwnHeldPosts(t *testing.T) {
	forum := &heldForumStub{staged: []oltp.Staged{
		{ID: 1, Kind: "post", Title: "eve's held post", Author: "eve", At: "2026-09-19T10:00:00", State: "pending"},
		{ID: 2, Kind: "reply", Root: "abc", Title: "jan's held reply", Author: "jan", At: "2026-09-19T10:05:00", State: "pending"},
	}}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(forumTechIndex(t)), Forum: forum}); err != nil {
		t.Fatal(err)
	}

	// The author sees their own, with its state.
	got, err := r.Invoke(ops.WithUser(context.Background(), "eve"), "forum.list", forumListIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	out := got.(forumListOut)
	if len(out.Held) != 1 {
		t.Fatalf("eve sees %d of her own held posts, want 1: %+v", len(out.Held), out.Held)
	}
	if out.Held[0].Title != "eve's held post" || out.Held[0].State != "pending" {
		t.Errorf("held row = %+v, want eve's post marked pending", out.Held[0])
	}

	// And nobody else's. This is the half that must not become a queue
	// read without the moderation letter.
	for _, h := range out.Held {
		if h.Title == "jan's held reply" {
			t.Error("eve was shown jan's held submission: the listing is a queue read by " +
				"another name")
		}
	}

	// A signed-out reader sees none, and the field is omitted entirely.
	anon, err := r.Invoke(context.Background(), "forum.list", forumListIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	if h := anon.(forumListOut).Held; len(h) != 0 {
		t.Errorf("a signed-out reader was shown held submissions: %+v", h)
	}

	// The threads the listing already carried are untouched: this is
	// additive to what the author could not see, not a filter on what
	// everyone could.
	if out.Count != 1 || len(out.Threads) != 1 {
		t.Errorf("the ordinary listing changed: count=%d threads=%d", out.Count, len(out.Threads))
	}
}