package oltp
import (
"path/filepath"
"testing"
"time"
)
// Every repository gets an address, namespace/handle, and the handle of
// one made before namespaced names is its name (RFC 0025): nothing that
// holds a name changes meaning. Two repositories may not share an
// address.
func TestEveryRepositoryHasItsNameAsItsHandle(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
d, err := Open(path)
if err != nil {
t.Fatal(err)
}
if err := d.AddRepo("tools", t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
if err := d.AddImportingRepo("mirror", filepath.Join(t.TempDir(), "m"), "", Public, "https://example.com/m"); err != nil {
t.Fatal(err)
}
// A row from before the column: no handle yet.
if _, err := d.w.Exec(`UPDATE repo SET handle = '' WHERE name = 'tools'`); err != nil {
t.Fatal(err)
}
d.Close()
d, err = Open(path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { d.Close() })
for _, name := range []string{"tools", "mirror"} {
var handle string
if err := d.db.QueryRow(`SELECT handle FROM repo WHERE name = ?`, name).Scan(&handle); err != nil || handle != name {
t.Errorf("FAIL: %s has handle %q (%v), want its name", name, handle, err)
}
}
// One address, one repository.
if _, err := d.w.Exec(`UPDATE repo SET handle = 'tools' WHERE name = 'mirror'`); err == nil {
t.Error("FAIL: two repositories took the same address")
}
}
// The forge's namespace is reserved in the pool accounts and teams share.
func TestTheForgeNamespaceIsReserved(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { d.Close() })
for _, name := range []string{ForgeNamespace, "ZeitForge"} {
if err := d.AddAccount(name, "a-long-enough-password", false); err == nil {
t.Errorf("FAIL: an account took %q", name)
}
}
}
// addressRig is two people, a team ada owns, and ada's own "tools".
func addressRig(t *testing.T) *DB {
t.Helper()
d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { d.Close() })
for _, who := range []string{"ada", "bob"} {
if err := d.AddAccount(who, "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
}
if err := d.AddTeam("crew", "Crew", "", "ada"); err != nil {
t.Fatal(err)
}
add := func(key, handle, owner string) {
t.Helper()
if err := d.AddRepo(key, t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
if err := d.SetOwner(key, owner); err != nil {
t.Fatal(err)
}
if err := d.SetNamespace(key, owner, owner, time.Now()); err != nil {
t.Fatal(err)
}
if err := d.SetHandle(key, handle); err != nil {
t.Fatal(err)
}
}
add("tools", "tools", "ada")
add("rp-second", "tools", "bob")
return d
}
// Every way a repository comes to appear somewhere checks that nothing
// else appears there by its name (RFC 0025), and says so in words.
func TestNoTwoRepositoriesShareAnAddress(t *testing.T) {
d := addressRig(t)
// Two tools, under ada and bob: allowed.
var n int
if err := d.db.QueryRow(`SELECT COUNT(*) FROM repo WHERE handle = 'tools'`).Scan(&n); err != nil || n != 2 {
t.Fatalf("FAIL: %d repositories called tools (%v), want ada's and bob's", n, err)
}
// bob's tools cannot join a namespace that has one.
if err := d.SetNamespace("rp-second", "ada", "bob", time.Now()); !IsRefusal(err) {
t.Errorf("FAIL: a second tools moved under ada: %v", err)
}
// A deed handed to ada would put bob's tools beside ada's.
if err := d.DeedMoveRefusal("rp-second", "ada"); !IsRefusal(err) {
t.Errorf("FAIL: handing bob's tools to ada was not refused: %v", err)
}
if err := d.DeedMoveRefusal("rp-second", "bob"); err != nil {
t.Errorf("FAIL: keeping the deed where it is was refused: %v", err)
}
// bob's tools in ada's team is crew/tools: free while crew is public.
if err := d.SetOwner("rp-second", "ada"); err != nil {
t.Fatal(err)
}
if err := d.SetTeamRepo("crew", "rp-second", "ada"); err != nil {
t.Fatalf("FAIL: crew/tools refused: %v", err)
}
// Private, crew's repositories appear under ada, beside ada's tools.
if _, err := d.SetTeamPrivate("crew", true); !IsRefusal(err) {
t.Errorf("FAIL: crew went private with two tools under ada: %v", err)
}
// Dissolved, crew's tools would go back to ada, beside ada's tools.
if err := d.DeleteTeam("crew"); !IsRefusal(err) {
t.Errorf("FAIL: crew dissolved onto ada's tools: %v", err)
}
}
// The start-up repair that holds a namespace to its owner leaves a
// repository where it is rather than put two at one address: refusing
// would stop the forge from starting.
func TestTheStartupRepairNeverSharesAnAddress(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
d, err := Open(path)
if err != nil {
t.Fatal(err)
}
for _, who := range []string{"ada", "bob"} {
if err := d.AddAccount(who, "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
}
for _, key := range []string{"tools", "rp-second"} {
if err := d.AddRepo(key, t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
}
// ada owns both; the second sits under bob's name, which the repair
// would move back to ada, where ada's own tools is.
if _, err := d.w.Exec(`UPDATE repo SET owner = 'ada', namespace = 'ada' WHERE name = 'tools'`); err != nil {
t.Fatal(err)
}
if _, err := d.w.Exec(`UPDATE repo SET owner = 'ada', namespace = 'bob', handle = 'tools' WHERE name = 'rp-second'`); err != nil {
t.Fatal(err)
}
d.Close()
d, err = Open(path)
if err != nil {
t.Fatalf("FAIL: the forge did not start: %v", err)
}
defer d.Close()
if ns, _ := d.NamespaceOf("rp-second"); ns != "bob" {
t.Errorf("FAIL: the repair moved the second tools to %q", ns)
}
}
// The deed backfill gives an unheld repository to its one admin; where
// that admin already has one by its name, the repository keeps no
// namespace rather than stop the forge starting (RFC 0025).
func TestTheDeedBackfillNeverSharesAnAddress(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
d, err := Open(path)
if err != nil {
t.Fatal(err)
}
if err := d.AddAccount("ada", "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
for _, key := range []string{"x", "rp-adasx"} {
if err := d.AddRepo(key, t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
}
if _, err := d.w.Exec(`UPDATE repo SET owner = 'ada', namespace = 'ada', handle = 'x' WHERE name = 'rp-adasx'`); err != nil {
t.Fatal(err)
}
if err := d.SetCaps("ada", "x", "a"); err != nil {
t.Fatal(err)
}
d.Close()
d, err = Open(path)
if err != nil {
t.Fatalf("FAIL: the forge did not start: %v", err)
}
defer d.Close()
if ns, _ := d.NamespaceOf("x"); ns != "" {
t.Errorf("FAIL: the unheld x was put under %q beside ada's own x", ns)
}
}
// A repository nobody holds appears under the forge's namespace from the
// next start (RFC 0025); a held one stays under its owner.
func TestARepositoryNobodyHoldsIsTheForges(t *testing.T) {
path := filepath.Join(t.TempDir(), "oltp.db")
d, err := Open(path)
if err != nil {
t.Fatal(err)
}
if err := d.AddAccount("ada", "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
for _, key := range []string{"tcl", "tools"} {
if err := d.AddRepo(key, t.TempDir(), "", Public); err != nil {
t.Fatal(err)
}
}
if err := d.SetOwner("tools", "ada"); err != nil {
t.Fatal(err)
}
if err := d.SetNamespace("tools", "ada", "ada", time.Now()); err != nil {
t.Fatal(err)
}
d.Close()
d, err = Open(path)
if err != nil {
t.Fatal(err)
}
defer d.Close()
for key, want := range map[string]string{"tcl": ForgeNamespace, "tools": "ada"} {
if ns, _ := d.NamespaceOf(key); ns != want {
t.Errorf("FAIL: %s appears under %q, want %q", key, ns, want)
}
}
}