Skip to content
quarry Artifact [d5b66b8fe9]
Login

Artifact [d5b66b8fe9]

Artifact d5b66b8fe9a8bf37940a739e1cc9ec8ae9eb8e9792a8f9d2d7f63c1f53fc6c88:


package oltp

import (
	"path/filepath"
	"testing"
	"time"
)

// reopen closes and reopens the registry at the same path, so the
// migration list runs a second time against rows already in it. That
// is the only honest way to exercise a backfill: the fresh schema
// already has the columns, so the UPDATE has to be what fills them.
func reopen(t *testing.T, db *DB, path string) *DB {
	t.Helper()
	if err := db.Close(); err != nil {
		t.Fatal(err)
	}
	next, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { next.Close() })
	return next
}

func TestOwnerAndNamespaceRoundTrip(t *testing.T) {
	db := testDB(t)
	if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	owner, err := db.OwnerOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "" {
		t.Fatalf("a fresh repository starts forge-held, got owner %q", owner)
	}
	if err := db.SetOwner("proj", "ada"); err != nil {
		t.Fatal(err)
	}
	at := time.Date(2026, 9, 13, 1, 2, 3, 0, time.UTC)
	if err := db.SetNamespace("proj", "hoppers", "grace", at); err != nil {
		t.Fatal(err)
	}
	owner, err = db.OwnerOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	ns, err := db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	// The deed and the appearance are separate facts: placing a
	// repository under a team must not change who holds it.
	if owner != "ada" || ns != "hoppers" {
		t.Fatalf("owner/namespace = %q/%q, want ada/hoppers", owner, ns)
	}
	by, when, err := db.NamespacePlacement("proj")
	if err != nil {
		t.Fatal(err)
	}
	if by != "grace" || when != "2026-09-13T01:02:03Z" {
		t.Fatalf("placement = %q at %q, want grace at 2026-09-13T01:02:03Z", by, when)
	}
}

func TestOwnerOfUnknownRepoIsEmptyNotAnError(t *testing.T) {
	db := testDB(t)
	owner, err := db.OwnerOf("nope")
	if err != nil || owner != "" {
		t.Fatalf("OwnerOf(unknown) = %q, %v; want empty and no error", owner, err)
	}
}

func TestBackfillAdoptsTheSoleAdmin(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("solo", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("ada", "solo", "a"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("solo")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "ada" {
		t.Fatalf("owner = %q, want ada — one admin is an unambiguous owner", owner)
	}
	ns, err := db.NamespaceOf("solo")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "ada" {
		t.Fatalf("namespace = %q, want ada — with no team, a repository appears under its owner", ns)
	}
}

// The case this rule exists for. On the production forge the agent
// account that imported every mirror holds `a` on all of them, and the
// human owner was granted `a` afterwards. Any rule that picks one of
// two admins — earliest, latest, alphabetical — can hand the deed to
// the wrong account silently. Declining is the only safe answer.
func TestBackfillDeclinesWhenTwoAccountsHoldAdmin(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("shared", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("importer", "shared", "a"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("ada", "shared", "a"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("shared")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "" {
		t.Fatalf("owner = %q, want empty — two admins is ambiguous and must be left to a person", owner)
	}
}

func TestBackfillIgnoresNonAdminGrants(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("readonly", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("lou", "readonly", "u3bBC78"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("readonly")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "" {
		t.Fatalf("owner = %q, want empty — a reader is not an owner", owner)
	}
}

func TestBackfillLeavesAnExistingOwnerAlone(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("held", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("held", "ada"); err != nil {
		t.Fatal(err)
	}
	// A later admin grant to somebody else must not re-derive the deed.
	if err := db.SetCaps("grace", "held", "a"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("held")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "ada" {
		t.Fatalf("owner = %q, want ada — the backfill adopts only the unclaimed", owner)
	}
}

func TestReposOwnedByIsOwnershipNotReach(t *testing.T) {
	db := testDB(t)
	for _, n := range []string{"one", "two", "other"} {
		if err := db.AddRepo(n, t.TempDir(), "", Public); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.SetOwner("one", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("two", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("other", "grace"); err != nil {
		t.Fatal(err)
	}
	// Admin on somebody else's repository is reach, not ownership.
	if err := db.SetCaps("ada", "other", "a"); err != nil {
		t.Fatal(err)
	}
	got, err := db.ReposOwnedBy("ada")
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 2 || got[0] != "one" || got[1] != "two" {
		t.Fatalf("ReposOwnedBy(ada) = %v, want [one two]", got)
	}
	n, err := db.OwnedRepoCount("ada")
	if err != nil {
		t.Fatal(err)
	}
	if n != 2 {
		t.Fatalf("OwnedRepoCount(ada) = %d, want 2 — the quota counts what you own, not what you can reach", n)
	}
}

// Load-bearing for the team union in EffectiveCaps. Team capabilities
// are resolved by matching team_member.team against the repository's
// namespace, so if a team could take a name an account already holds,
// a personal namespace would start matching a team's grants. The two
// name kinds share one pool and refuse each other in both directions;
// this asserts that rather than trusting it.
func TestTeamAndAccountNamesCannotCollide(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("ada", "Not Ada", "", "ada"); err == nil {
		t.Fatal("a team took a name an account already holds; a personal namespace would then match team grants")
	}
	if err := db.AddAccount("hoppers", "correct horse battery staple", false); err == nil {
		t.Fatal("an account took a name a team already holds; the same collision, from the other side")
	}
}

// A tracking store is something the forge serves, not something a
// person is hosting here, and its sole admin is usually the operator
// account that ran the import. Adopting that account would hand it the
// deed to code it merely copied.
func TestBackfillDoesNotAdoptAMirror(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("upstream", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetMirror("upstream", "https://example.invalid/r/upstream", time.Hour); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("importer", "upstream", "a"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("upstream")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "" {
		t.Fatalf("owner = %q, want empty — a mirror stays forge-held", owner)
	}
}

// The migration that released wrongly-adopted mirrors has been removed,
// and this is the interaction that made keeping it unsafe.
//
// It identified rows to release by namespace_by being empty. That now
// also means "the segment fell back to its owner", which is what
// leaving a team produces -- so a claimed mirror that had been in a
// team would have been quietly un-owned by a migration on the next
// restart, and nobody would have connected the two.
func TestLeavingATeamDoesNotUnownAMirror(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("tracked", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetMirror("tracked", "https://example.invalid/r/tracked", time.Hour); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("tracked", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetTeamRepo("hoppers", "tracked", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.RemoveTeamRepo("tracked"); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	owner, err := db.OwnerOf("tracked")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "ada" {
		t.Fatalf("owner = %q, want ada -- a restart must not take a claimed mirror away", owner)
	}
}

// team_repo decides which team holds a repository; repo.namespace is
// where it appears. They were written in different places, so a
// repository could sit in a team while its path still named the
// previous holder. The two now move together.
func TestNamespaceFollowsTeamPlacement(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("proj", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetNamespace("proj", "ada", "ada", time.Now()); err != nil {
		t.Fatal(err)
	}

	if err := db.SetTeamRepo("hoppers", "proj", "ada"); err != nil {
		t.Fatal(err)
	}
	ns, err := db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "hoppers" {
		t.Fatalf("namespace = %q, want hoppers — a repository in a team appears under the team", ns)
	}
	// The deed never moves. That is what lets a team be dissolved
	// without putting anybody's code at risk.
	owner, err := db.OwnerOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if owner != "ada" {
		t.Fatalf("owner = %q, want ada — placing a repository in a team must not move its deed", owner)
	}
	by, _, err := db.NamespacePlacement("proj")
	if err != nil {
		t.Fatal(err)
	}
	if by != "ada" {
		t.Fatalf("placement recorded %q, want ada", by)
	}

	if err := db.RemoveTeamRepo("proj"); err != nil {
		t.Fatal(err)
	}
	ns, err = db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "ada" {
		t.Fatalf("namespace = %q, want ada — out of the team, it appears under its owner again", ns)
	}
}

// A forge-held repository has no owner to fall back to, so leaving a
// team leaves it with no namespace rather than stranding the team's
// name on a repository the team no longer holds.
func TestLeavingATeamWithNoOwnerLeavesNoNamespace(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("served", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetTeamRepo("hoppers", "served", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.RemoveTeamRepo("served"); err != nil {
		t.Fatal(err)
	}
	ns, err := db.NamespaceOf("served")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "" {
		t.Fatalf("namespace = %q, want empty — nobody holds this store", ns)
	}
}

// A path segment that disagrees with the team holding the repository is
// the path gone stale, never the other way round, so a reopen re-seats
// it. This is the state rows were left in before SetTeamRepo wrote both.
func TestReopenReseatsAStalePathSegment(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("proj", "ada"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetTeamRepo("hoppers", "proj", "ada"); err != nil {
		t.Fatal(err)
	}
	// Force the stale shape the old code produced: team_repo says the
	// team, the path still names the previous holder.
	if err := db.SetNamespace("proj", "ada", "ada", time.Now()); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	ns, err := db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "hoppers" {
		t.Fatalf("namespace = %q, want hoppers — the team table is the authority", ns)
	}
}

// Dissolving a team must not strand its repositories' paths. The team
// held the path, never the deed, which is the whole reason dissolution
// is safe -- and it is only true if the path actually falls back.
func TestDissolvingATeamReturnsItsRepositoriesToTheirOwners(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("hoppers", "Hoppers", "", "ada"); err != nil {
		t.Fatal(err)
	}
	for _, n := range []string{"one", "two"} {
		if err := db.AddRepo(n, t.TempDir(), "", Public); err != nil {
			t.Fatal(err)
		}
		if err := db.SetOwner(n, "ada"); err != nil {
			t.Fatal(err)
		}
		if err := db.SetTeamRepo("hoppers", n, "ada"); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.DeleteTeam("hoppers"); err != nil {
		t.Fatal(err)
	}
	for _, n := range []string{"one", "two"} {
		ns, err := db.NamespaceOf(n)
		if err != nil {
			t.Fatal(err)
		}
		if ns != "ada" {
			t.Fatalf("%s namespace = %q, want ada — a dissolved team must not keep naming the path", n, ns)
		}
		owner, err := db.OwnerOf(n)
		if err != nil {
			t.Fatal(err)
		}
		if owner != "ada" {
			t.Fatalf("%s owner = %q, want ada — dissolving a team must not touch the deed", n, owner)
		}
		by, _, err := db.NamespacePlacement(n)
		if err != nil {
			t.Fatal(err)
		}
		if by != "" {
			t.Fatalf("%s placement = %q, want empty — nobody put it where it now is", n, by)
		}
	}
}

// A forge carrying the damage a dissolve used to leave repairs itself
// on the next start. The re-seat above cannot see these rows: they are
// no longer in any team, which is exactly why they were unrecoverable.
func TestReopenRepairsASegmentNamingADeadTeam(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AddAccount("ada", "correct horse battery staple", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("proj", "ada"); err != nil {
		t.Fatal(err)
	}
	// The exact shape the old dissolve left: a segment naming a team
	// that does not exist, and no team_repo row to find it by.
	if err := db.SetNamespace("proj", "dissolved-team", "ada", time.Now()); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	ns, err := db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "ada" {
		t.Fatalf("namespace = %q, want ada", ns)
	}
}

// The repair must never touch a personal namespace. Account handles and
// team slugs share one pool, so a segment naming a live account is a
// repository sitting in its owner's namespace, not a dead team.
func TestRepairLeavesAPersonalNamespaceAlone(t *testing.T) {
	path := filepath.Join(t.TempDir(), "oltp.db")
	db, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	for _, who := range []string{"ada", "grace"} {
		if err := db.AddAccount(who, "correct horse battery staple", false); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddRepo("proj", t.TempDir(), "", Public); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOwner("proj", "ada"); err != nil {
		t.Fatal(err)
	}
	// Deliberately in another account's namespace, not a team's.
	if err := db.SetNamespace("proj", "grace", "ada", time.Now()); err != nil {
		t.Fatal(err)
	}
	db = reopen(t, db, path)
	ns, err := db.NamespaceOf("proj")
	if err != nil {
		t.Fatal(err)
	}
	if ns != "grace" {
		t.Fatalf("namespace = %q, want grace — a live account is not a dead team", ns)
	}
}