package api
import (
"context"
"testing"
"craftdesign.group/quarry/internal/markup"
)
// fakeCaps answers a fixed letter set.
type fakeCaps string
func (c fakeCaps) HasAny(letters string) bool {
for _, l := range letters {
for _, have := range string(c) {
if l == have {
return true
}
}
}
return false
}
// The resolver table of RFC 0012 Phase 1: each token shape reaches the
// right page, and a token naming a repository the reader cannot see
// reaches nothing at all.
func TestResolveTokenTable(t *testing.T) {
// A scope whose CanRead admits "open" and refuses "secret", and
// whose caps grant everything on what it admits.
d := Deps{
Repos: &Repos{},
CanRead: func(_ context.Context, repo string) bool { return repo == "open" },
CapsFor: func(_, _ string) CapSet { return fakeCaps(anyCapLetters) },
}
r := resolver{repo: "demo", scope: &refScope{ctx: context.Background(), d: d}}
for _, tt := range []struct {
token string
href string // "" means: must not resolve
}{
{token: "#412", href: "/r/demo/chat/412"},
{token: "@alice", href: "/people/alice"},
{token: "/r/demo/ticket/xy9", href: "/r/demo/ticket/xy9"},
{token: "https://q.example/r/demo/landing/ab12", href: "/r/demo/landing/ab12"},
// Cross-repo: "open" is readable, "secret" is not. Neither is
// registered as a repository here, so only the refusal is
// asserted — the admitted one is exercised in the harness.
{token: "secret:#9"},
{token: "secret:[a91f3c2e91]"},
{token: "notatoken"},
} {
tok, ok := markup.ParseRef(tt.token)
if !ok {
if tt.href != "" {
t.Fatalf("%q did not parse", tt.token)
}
continue
}
href, ok := r.ResolveToken(tok)
if tt.href == "" {
if ok {
t.Fatalf("%q resolved to %q; it must stay plain text", tt.token, href)
}
continue
}
if !ok || href != tt.href {
t.Fatalf("%q resolved to %q (ok=%v), want %q", tt.token, href, ok, tt.href)
}
}
}
// A resolver with no scope is the render path that has no request in
// hand: it must not reach across repositories at all.
func TestResolveTokenWithoutScopeStaysHome(t *testing.T) {
r := resolver{repo: "demo"}
tok, _ := markup.ParseRef("other:#7")
if href, ok := r.ResolveToken(tok); ok {
t.Fatalf("scopeless resolver crossed repositories: %q", href)
}
tok, _ = markup.ParseRef("#7")
if href, ok := r.ResolveToken(tok); !ok || href != "/r/demo/chat/7" {
t.Fatalf("same-repo token = %q (ok=%v)", href, ok)
}
}
// refCap reaches for the letter the TARGET's own op is registered with,
// so a preview can never say more than the page it links to would.
func TestRefCapMatchesTheTargetOp(t *testing.T) {
for kind, want := range map[string]string{
markup.RefChat: "C", markup.RefCheckin: "o", markup.RefHash: "o",
markup.RefTicket: "r", markup.RefLanding: "2", markup.RefForum: "2",
markup.RefWiki: "j", markup.RefPerson: "",
} {
if got := refCap(kind); got != want {
t.Fatalf("refCap(%q) = %q, want %q", kind, got, want)
}
}
}
// The quote and the reply count come from one pass over one slice.
func TestChatQuoteFillsBothSides(t *testing.T) {
msgs := []chatMsgOut{
{Seq: 4, Kind: "msg", User: "alice", Text: "first line\nsecond"},
{Seq: 5, Kind: "msg", User: "bob", Ref: 4, Text: "agreed"},
{Seq: 6, Kind: "msg", User: "carol", Ref: 4, Text: "same"},
{Seq: 7, Kind: "msg", User: "dan", Ref: 99, Text: "orphan"},
}
chatQuote(msgs)
if msgs[0].Replies != 2 {
t.Fatalf("replies = %d, want 2", msgs[0].Replies)
}
if msgs[1].RefUser != "alice" || msgs[1].RefLine != "first line" {
t.Fatalf("quote = %q/%q", msgs[1].RefUser, msgs[1].RefLine)
}
if msgs[3].RefUser != "" {
t.Fatalf("a quote was invented for an unsourceable reply")
}
}