Skip to content
quarry Artifact [21a52138ff]
Login

Artifact [21a52138ff]

Artifact 21a52138ff56ac89c6662055d2a3aac98051b114661a0d96ddee2f976d050f95:


package api

import (
	"context"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

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

// A mention completion must not carry capability letters.
//
// people.mention returned each handle with the account's full capability
// string, "so a composer can say who it is about to name" - and nothing
// ever read it. What it did instead was answer any signed-in account
// with a map of who holds what on the repository. jva measured it as a
// Reader on quarry: nine handles, and the rows for the two site
// administrators carried the whole alphabet,
// abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.
// Anonymous was refused, so the door was "any member", and the letters
// came back on every keystroke of an autocomplete.
//
// Asserted on the source because the property is an ABSENCE. A response
// test can only show that today's fixture has no letters in it; this
// fails if the field comes back at all, which is the thing that must not
// happen again.
func TestAMentionCompletionCarriesNoCapabilityLetters(t *testing.T) {
	src, err := os.ReadFile("refs.go")
	if err != nil {
		t.Fatal(err)
	}
	decl := string(src)
	i := strings.Index(decl, "type mentionHandle struct {")
	if i < 0 {
		t.Fatal("mentionHandle is gone - if it moved, move this guard with it")
	}
	body := decl[i:]
	if end := strings.Index(body, "}"); end >= 0 {
		body = body[:end]
	}
	if strings.Contains(body, "Caps") || strings.Contains(body, `json:"caps"`) {
		t.Errorf("mentionHandle carries capability letters again:\n%s\n"+
			"the letters may decide who is completable, but they must not leave the server", body)
	}
}

// The filter is the half that has to stay: an account with no letters on
// the repository is not worth completing. Removing the field by removing
// the lookup would quietly start offering strangers.
func TestTheCompletionStillFiltersOnTheLetters(t *testing.T) {
	src, err := os.ReadFile("refs.go")
	if err != nil {
		t.Fatal(err)
	}
	loc := regexp.MustCompile(`Name:\s+"people.mention"`).FindStringIndex(string(src))
	if loc == nil {
		t.Fatal("people.mention is gone")
	}
	body := string(src)[loc[0]:]
	if end := strings.Index(body, "\n\tif err := ops.Register"); end > 0 {
		body = body[:end]
	}
	if !strings.Contains(body, "anyCapLetters") {
		t.Error("people.mention no longer filters candidates on their letters: " +
			"it will complete accounts with nothing on this repository")
	}
}

// The @ picker offers this repository's people, never the forge's
// whole account list (#372): on a public repository every signed-in
// account holds the default letters, so the letters alone named
// everyone, searchable by anyone. A contributor and an account granted
// a role here are offered; an account with only the default is not.
func TestAMentionCompletionNamesOnlyTheRepositorysPeople(t *testing.T) {
	ix, z, _ := attachStore(t)
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	for _, who := range []string{"lou", "eve", "stranger"} {
		if err := db.AddAccount(who, "a-long-enough-password", false); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.Grant("eve", "test", oltp.RepoAdmin); err != nil {
		t.Fatal(err)
	}
	d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: z, Sync: zitSync(z, ix)}}),
		Accounts: acctAdapter{db}, Forum: db,
		// Every account holds the public default, as on a public repo.
		CapsFor: func(string, string) CapSet { return oltp.CapSet("hjor23") }}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(as("lou", "3"), "forum.post", forumPostIn{Repo: "test", Title: "hello", Body: "first\n"}); err != nil {
		t.Fatal(err)
	}
	out, err := r.Invoke(ops.WithUser(context.Background(), "stranger"), "people.mention", peopleMentionIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	got := map[string]bool{}
	for _, h := range out.(peopleMentionOut).Handles {
		got[h.Handle] = true
	}
	if !got["lou"] || !got["eve"] || got["stranger"] {
		t.Fatalf("FAIL: completion offers %v; want the contributor and the granted account, not a stranger", got)
	}
	t.Log("ok: the picker names the repository's people, not the forge's accounts")
}