package api
import (
"context"
"encoding/json"
"sync"
"testing"
"craftdesign.group/quarry/internal/ops"
)
// remarshalT re-decodes an op's output into its typed form.
func remarshalT(t *testing.T, in, out any) {
t.Helper()
b, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(b, out); err != nil {
t.Fatal(err)
}
}
type fakeStamp struct {
mu sync.Mutex
set map[string]map[string]bool // repo -> account -> stamped
}
func newFakeStamp() *fakeStamp {
return &fakeStamp{set: map[string]map[string]bool{}}
}
func (f *fakeStamp) SetStamp(repo, account string, on bool) (bool, error) {
f.mu.Lock()
defer f.mu.Unlock()
if f.set[repo] == nil {
f.set[repo] = map[string]bool{}
}
if on {
f.set[repo][account] = true
} else {
delete(f.set[repo], account)
}
return on, nil
}
func (f *fakeStamp) Stamped(repo, account string) bool { return f.set[repo][account] }
func (f *fakeStamp) StampCount(repo string) int { return len(f.set[repo]) }
func (f *fakeStamp) StampedBy(account string) ([]string, error) {
var out []string
for _, repo := range []string{"alpha", "beta", "secret"} {
if f.set[repo][account] {
out = append(out, repo)
}
}
return out, nil
}
// The rule: any signed-in account may stamp any repository it can see.
// No capability letter is involved, so an account with no grant at all
// must succeed.
func TestStampNeedsOnlyASignedInAccount(t *testing.T) {
r := ops.NewRegistry()
repos := testRepos(wikiIndex(t))
mustRepo(t, repos, "test").Code = "c1"
if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Stamp: newFakeStamp()}); err != nil {
t.Fatal(err)
}
ctx := ops.WithUser(context.Background(), "grantless")
out, err := r.Invoke(ctx, "stamp.set", stampSetIn{Repo: "test", On: true})
if err != nil {
t.Fatalf("a signed-in account with no grant could not stamp: %v", err)
}
var got stampSetOut
remarshalT(t, out, &got)
if !got.Stamped || got.Count != 1 {
t.Errorf("stamped=%v count=%d, want true 1", got.Stamped, got.Count)
}
}
// Stamp must be attributable, so anonymous is refused rather than
// pooling every stranger into one shared stamp.
func TestAnonymousCannotStamp(t *testing.T) {
r := ops.NewRegistry()
repos := testRepos(wikiIndex(t))
mustRepo(t, repos, "test").Code = "c1"
if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Stamp: newFakeStamp()}); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(context.Background(), "stamp.set",
stampSetIn{Repo: "test", On: true}); err == nil {
t.Error("anonymous stamp was accepted")
}
}
// On is a desired state, not a toggle: a retried request must land where
// the reader clicked rather than on the opposite of it.
func TestStampIsIdempotent(t *testing.T) {
r := ops.NewRegistry()
repos := testRepos(wikiIndex(t))
mustRepo(t, repos, "test").Code = "c1"
if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Stamp: newFakeStamp()}); err != nil {
t.Fatal(err)
}
ctx := ops.WithUser(context.Background(), "dan")
for i := 0; i < 3; i++ {
out, err := r.Invoke(ctx, "stamp.set", stampSetIn{Repo: "test", On: true})
if err != nil {
t.Fatal(err)
}
var got stampSetOut
remarshalT(t, out, &got)
if !got.Stamped || got.Count != 1 {
t.Fatalf("attempt %d: stamped=%v count=%d", i, got.Stamped, got.Count)
}
}
}
// A listing is filtered by the VIEWER. Stamp is public; the existence
// of a private repository is not.
func TestStampListHidesWhatTheViewerCannotRead(t *testing.T) {
r := ops.NewRegistry()
ix := wikiIndex(t)
repos := NewRepos(map[string]*Repo{
"alpha": {Index: ix, Code: "c-alpha"},
"beta": {Index: ix, Code: "c-beta"},
// Registered, and deliberately unreadable to the viewer below.
"secret": {Index: ix, Code: "c-secret"},
})
fp := newFakeStamp()
for _, n := range []string{"alpha", "beta", "secret"} {
fp.SetStamp(n, "dan", true)
}
if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Stamp: fp,
CanRead: func(_ context.Context, repo string) bool { return repo != "secret" },
}); err != nil {
t.Fatal(err)
}
out, err := r.Invoke(context.Background(), "stamp.list", stampListIn{User: "dan"})
if err != nil {
t.Fatal(err)
}
var got stampListOut
remarshalT(t, out, &got)
for _, repo := range got.Repos {
if repo == "secret" {
t.Fatalf("a private repository leaked through a stamp listing: %v", got.Repos)
}
}
if len(got.Repos) != 2 {
t.Errorf("listed %v, want the two readable ones", got.Repos)
}
}