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)
}
}
// An earlier build of the backfill adopted mirrors before it knew to
// skip them. Those are released, identified by having no provenance:
// every deliberate act that sets an owner records who did it, so an
// empty namespace_by means a migration guessed.
func TestReleaseReturnsAGuessedMirrorToTheForge(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
db, err := Open(path)
if err != nil {
t.Fatal(err)
}
for _, n := range []string{"guessed", "claimed"} {
if err := db.AddRepo(n, t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
if err := db.SetMirror(n, "https://example.invalid/r/"+n, time.Hour); err != nil {
t.Fatal(err)
}
if err := db.SetOwner(n, "importer"); err != nil {
t.Fatal(err)
}
}
// "claimed" was taken deliberately, so it carries provenance and
// must survive the release.
if err := db.SetNamespace("claimed", "importer", "importer", time.Now()); err != nil {
t.Fatal(err)
}
db = reopen(t, db, path)
guessed, err := db.OwnerOf("guessed")
if err != nil {
t.Fatal(err)
}
if guessed != "" {
t.Fatalf("owner = %q, want empty — a guessed mirror goes back to the forge", guessed)
}
claimed, err := db.OwnerOf("claimed")
if err != nil {
t.Fatal(err)
}
if claimed != "importer" {
t.Fatalf("owner = %q, want importer — a claimed mirror is somebody's and stays theirs", claimed)
}
}