package main
import (
"errors"
"path/filepath"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// A failed import is a record, not a repository: its owner (or their
// agent) asking for the name again starts it over, and anybody else
// still meets the name as taken. An import still copying holds its name
// against everybody.
func TestAFailedImportGivesItsNameBackToItsOwner(t *testing.T) {
db, err := oltp.Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
for _, who := range []string{"dan", "eve"} {
if err := db.AddAccount(who, "a-long-enough-password", false); err != nil {
t.Fatal(err)
}
}
if err := db.NewAgent("dan-bot", "dan", -1); err != nil {
t.Fatal(err)
}
rg := ®istrar{odb: db, deps: &api.Deps{Repos: api.NewRepos(nil)}, repoQuota: 25}
importing := func(name, state string) {
t.Helper()
if err := db.AddImportingRepo(name, filepath.Join(t.TempDir(), name), "", oltp.Public, "https://example.com/"+name); err != nil {
t.Fatal(err)
}
if err := db.SetOwner(name, "dan"); err != nil {
t.Fatal(err)
}
if state != oltp.Cloning {
if err := db.SetRepoState(name, state, "import failed: Corrupt"); err != nil {
t.Fatal(err)
}
}
}
importing("broke", oltp.Failed)
importing("copying", oltp.Cloning)
for _, c := range []struct {
name, who string
free bool
}{
{"broke", "dan", true},
{"broke", "dan-bot", true},
{"broke", "eve", false},
{"copying", "dan", false},
{"fresh", "eve", true},
} {
err := rg.NameFree(c.name, c.who)
if c.free && err != nil {
t.Errorf("FAIL: %s may not name %s: %v", c.who, c.name, err)
}
if !c.free && !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: %s may name %s: %v", c.who, c.name, err)
}
}
// Somebody else's attempt leaves the record alone; the owner's clears it.
if err := rg.retryFailedImport(api.RepoSpec{Name: "broke", Author: "eve"}); err != nil {
t.Fatal(err)
}
if _, found, _ := db.RepoByName("broke"); !found {
t.Fatal("FAIL: another account's attempt cleared the owner's failed import")
}
if err := rg.retryFailedImport(api.RepoSpec{Name: "broke", Author: "dan"}); err != nil {
t.Fatal(err)
}
if _, found, _ := db.RepoByName("broke"); found {
t.Error("FAIL: the owner's new attempt left the failed record holding the name")
}
}
// The forge's namespace is no repository's name: a namespace and a
// repository must never be read for one another in a path (RFC 0025).
func TestNoRepositoryTakesTheForgeNamespace(t *testing.T) {
db, err := oltp.Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
rg := ®istrar{odb: db, deps: &api.Deps{Repos: api.NewRepos(nil)}, repoQuota: 25}
for _, name := range []string{oltp.ForgeNamespace, "ZeitForge"} {
if err := rg.NameFree(name, "dan"); !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: a repository may be called %q: %v", name, err)
}
}
}