Skip to content
quarry Artifact [0ff2b05839]
Login

Artifact [0ff2b05839]

Artifact 0ff2b05839e7d198501bd5dc5674aec22eeb7624e453df1ce3535e071556ab8d:


package api

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"

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

// memMirrors is a MirrorStore in memory, enough to drive posture.
type memMirrors map[string]Mirror

func (m memMirrors) Mirrors() ([]Mirror, error) {
	var out []Mirror
	for _, v := range m {
		out = append(out, v)
	}
	return out, nil
}
func (m memMirrors) MirrorFor(repo string) (Mirror, bool, error) { v, ok := m[repo]; return v, ok, nil }
func (m memMirrors) SetMirror(repo, url string, every int) error {
	m[repo] = Mirror{Repo: repo, URL: url, Every: every, Enabled: true}
	return nil
}
func (m memMirrors) DetachMirror(repo string) error {
	v := m[repo]
	v.Enabled, v.DetachedFrom = false, v.URL
	m[repo] = v
	return nil
}
func (m memMirrors) SetMirrorWrites(repo string, accept bool) error {
	v := m[repo]
	v.AcceptWrites = accept
	m[repo] = v
	return nil
}
func (m memMirrors) RecordMirrorRun(string, time.Duration, bool, error) (int, error) { return 0, nil }
func (m memMirrors) MirrorPulls(string, int) ([]MirrorPull, error)                   { return nil, nil }
func (m memMirrors) MirrorDiverged(string, string) error                             { return nil }

func TestMirrorState(t *testing.T) {
	now := time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC)
	stamp := func(ago time.Duration) string { return now.Add(-ago).UTC().Format(time.RFC3339) }
	cases := []struct {
		name string
		m    Mirror
		want string
	}{
		{"fresh", Mirror{Enabled: true, Every: 3600, LastOK: stamp(10 * time.Minute)}, MirrorCurrent},
		{"edge", Mirror{Enabled: true, Every: 3600, LastOK: stamp(119 * time.Minute)}, MirrorCurrent},
		{"quiet too long", Mirror{Enabled: true, Every: 3600, LastOK: stamp(3 * time.Hour)}, MirrorLagging},
		{"never ran", Mirror{Enabled: true, Every: 3600}, MirrorLagging},
		{"failing", Mirror{Enabled: true, Every: 3600, LastOK: stamp(time.Minute), Failures: 1}, MirrorFailing},
		{"detached", Mirror{Enabled: false, DetachedFrom: "https://x/home"}, MirrorDetached},
	}
	for _, c := range cases {
		if got := mirrorState(c.m, now); got != c.want {
			t.Errorf("%s: got %s, want %s", c.name, got, c.want)
		}
	}
}

// A mirror refuses local writes until the operator allows them, and a
// detached store is native again. repo.get carries the posture.
func TestMirrorRefusesWrites(t *testing.T) {
	ix := wikiIndex(t)
	ms := memMirrors{}
	_ = ms.SetMirror("test", "https://upstream.example/home", 3600)
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(ix), Mirrors: ms}); err != nil {
		t.Fatal(err)
	}
	ctx := context.Background()
	newTicket := func() error {
		_, err := r.Invoke(ctx, "ticket.new", ticketNewIn{Repo: "test", Title: "t"})
		return err
	}
	err := newTicket()
	if !errors.Is(err, ops.ErrForbidden) || !strings.Contains(err.Error(), "upstream.example") {
		t.Fatalf("ticket on a mirror: want forbidden naming the upstream, got %v", err)
	}
	for _, op := range []struct {
		name string
		in   any
	}{
		{"wiki.save", wikiSaveIn{Repo: "test", Name: "p", Body: "b"}},
		{"forum.post", forumPostIn{Repo: "test", Title: "t", Body: "b"}},
		{"technote.add", technoteAddIn{Repo: "test", Body: "b"}},
	} {
		if _, err := r.Invoke(ctx, op.name, op.in); !errors.Is(err, ops.ErrForbidden) {
			t.Errorf("%s on a mirror: want forbidden, got %v", op.name, err)
		}
	}
	got, err := r.Invoke(ctx, "repo.get", repoGetIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	info := got.(repoInfo)
	if !info.Mirror.Tracking || info.Mirror.URL != "https://upstream.example/home" || info.Mirror.State != MirrorLagging {
		t.Fatalf("repo.get posture: %+v", info.Mirror)
	}

	if _, err := r.Invoke(ctx, "mirror.writes", mirrorWritesIn{Repo: "test", Accept: true}); err != nil {
		t.Fatal(err)
	}
	// (This harness has no store to write through, so the gate is
	// asserted directly once writes are allowed.)
	if ok, _ := (Deps{Mirrors: ms}).Writable("test"); !ok {
		t.Fatal("writes allowed, still refused")
	}

	if _, err := r.Invoke(ctx, "mirror.detach", mirrorDetachIn{Repo: "test"}); err != nil {
		t.Fatal(err)
	}
	got, _ = r.Invoke(ctx, "repo.get", repoGetIn{Repo: "test"})
	info = got.(repoInfo)
	if info.Mirror.Tracking || info.DetachedFrom != "https://upstream.example/home" {
		t.Fatalf("detached posture: %+v detachedFrom=%q", info.Mirror, info.DetachedFrom)
	}
	if ok, _ := (Deps{Mirrors: ms}).Writable("test"); !ok {
		t.Fatal("a detached store is native and writable")
	}
}