package store import ( "context" "testing" "github.com/barerepo/server/internal/token" ) // A rename or a transfer changes the path every table keys on, so every one of them has to follow. func TestMoveCarriesEverythingKeyedByTheRepository(t *testing.T) { ctx := context.Background() db := open(t) for _, who := range []string{"john", "lisa"} { if _, err := db.CreateAccount(ctx, who, keyFor(who), false); err != nil { t.Fatal(err) } } if _, err := db.ExecContext(ctx, `INSERT INTO repos (owner, name, created_at) VALUES ('john', 'johnbot', 0)`); err != nil { t.Fatal(err) } const old, want = "john/johnbot", "lisa/ircbot" _, tok, err := db.CreateToken(ctx, token.Runner, "john", old, "a runner") if err != nil { t.Fatal(err) } if _, err := db.AttachRunner(ctx, tok.ID, old, "uproar.local", "linux", "amd64", nil); err != nil { t.Fatal(err) } if _, err := db.QueueJob(ctx, old, "refs/heads/master", "abc", "go test", ""); err != nil { t.Fatal(err) } if err := db.Record(ctx, Event{Kind: ThreadOpened, Actor: "lisa", Repo: old, Number: 1, Title: "something"}); err != nil { t.Fatal(err) } if err := db.TookPart(ctx, "lisa", old, 1); err != nil { t.Fatal(err) } if err := db.HookFailed(ctx, old, "https://deploy.example/hook", "it went wrong"); err != nil { t.Fatal(err) } if err := db.PutDoc(ctx, Doc{Repo: old, Kind: Code, Path: "a.go", Body: "package main"}, true, Readers("john", nil)); err != nil { t.Fatal(err) } if err := db.Move(ctx, "john", "johnbot", "lisa", "ircbot"); err != nil { t.Fatal(err) } // Every table below is keyed by the path, and a miss in any one of them breaks something. if got, err := db.RunnersOf(ctx, want); err != nil || len(got) != 1 { t.Errorf("the runner did not follow: %d, %v", len(got), err) } if got, err := db.RunnersOf(ctx, old); err != nil || len(got) != 0 { t.Errorf("the runner is still at the old path: %d, %v", len(got), err) } // The token's scope is what a runner is checked against, so it has to move with the repository. var scope string if err := db.QueryRowContext(ctx, `SELECT scope FROM tokens WHERE id = ?`, tok.ID).Scan(&scope); err != nil { t.Fatal(err) } if scope != want { t.Errorf("the runner token is still scoped to %q, so it can never take another job", scope) } if got, err := db.QueuedJobs(ctx, want); err != nil || len(got) != 1 { t.Errorf("the queued job did not follow: %d, %v", len(got), err) } if got, err := db.EventsFor(ctx, want, 10); err != nil || len(got) != 1 { t.Errorf("the events did not follow: %d, %v", len(got), err) } if got, err := db.Inbox(ctx, "lisa", 10); err != nil || len(got) != 1 { t.Errorf("the new owner's inbox lost the thread she took part in: %d, %v", len(got), err) } if got, err := db.HooksOf(ctx, want); err != nil || len(got) != 1 { t.Errorf("the webhook state did not follow: %d, %v", len(got), err) } if got, err := db.FindDocs(ctx, "", "package main", 10); err != nil || len(got) != 1 || got[0].Repo != want { t.Errorf("the search index did not follow: %v, %v", got, err) } } func keyFor(who string) string { if who == "john" { return keyA } return keyB } // seedRepo fills one row in every table that keys on a repository, so a move or a delete has work. func seedRepo(t *testing.T, db *DB, owner, name string) int64 { t.Helper() ctx := context.Background() repo := owner + "/" + name if _, err := db.ExecContext(ctx, `INSERT INTO repos (owner, name, created_at) VALUES (?, ?, 0)`, owner, name); err != nil { t.Fatal(err) } _, tok, err := db.CreateToken(ctx, token.Runner, owner, repo, "a runner") if err != nil { t.Fatal(err) } if _, err := db.AttachRunner(ctx, tok.ID, repo, "uproar.local", "linux", "amd64", nil); err != nil { t.Fatal(err) } if _, err := db.QueueJob(ctx, repo, "refs/heads/master", "abc", "go test", ""); err != nil { t.Fatal(err) } if err := db.Record(ctx, Event{Kind: ThreadOpened, Actor: owner, Repo: repo, Number: 1, Title: "something"}); err != nil { t.Fatal(err) } if err := db.TookPart(ctx, owner, repo, 1); err != nil { t.Fatal(err) } if err := db.HookFailed(ctx, repo, "https://deploy.example/hook", "it went wrong"); err != nil { t.Fatal(err) } if err := db.PutDoc(ctx, Doc{Repo: repo, Kind: Code, Path: "a.go", Body: "package main"}, true, Readers(owner, nil)); err != nil { t.Fatal(err) } return tok.ID } // A deleted repository leaves nothing pointing at it, or a token, a runner and an inbox line outlive it. func TestForgetDropsEverythingKeyedByTheRepository(t *testing.T) { ctx := context.Background() db := open(t) if _, err := db.CreateAccount(ctx, "john", keyA, false); err != nil { t.Fatal(err) } const repo = "john/johnbot" seedRepo(t, db, "john", "johnbot") if err := db.Forget(ctx, "john", "johnbot"); err != nil { t.Fatal(err) } if got, err := db.RunnersOf(ctx, repo); err != nil || len(got) != 0 { t.Errorf("a runner is still attached to a repository that is gone: %d, %v", len(got), err) } if got, err := db.TokensOf(ctx, "john"); err != nil || len(got) != 0 { t.Errorf("the keys page still lists a token for a repository that is gone: %d, %v", len(got), err) } if got, err := db.QueuedJobs(ctx, repo); err != nil || len(got) != 0 { t.Errorf("a job is still queued for a repository that is gone: %d, %v", len(got), err) } if got, err := db.EventsFor(ctx, repo, 10); err != nil || len(got) != 0 { t.Errorf("the inbox still links to a repository that is gone: %d, %v", len(got), err) } if got, err := db.HooksOf(ctx, repo); err != nil || len(got) != 0 { t.Errorf("webhook state outlived its repository: %d, %v", len(got), err) } if got, err := db.FindDocs(ctx, "", "package main", 10); err != nil || len(got) != 0 { t.Errorf("search still finds a repository that is gone: %v, %v", got, err) } }