internal/store/runners_test.go

7a667c7b40578f9e76675c36ac11f236a882d711
gitbay/internal/store/runners_test.go history · blame · raw

148 lines · 4451 bytes

  1package store
  2
  3import (
  4	"errors"
  5	"testing"
  6)
  7
  8// runnerFixture is one user with a runner key and two repositories.
  9func runnerFixture(t *testing.T) (s *Store, uid, keyID, repoA, repoB int64) {
 10	t.Helper()
 11	s = open(t)
 12	if err := s.MigrateUp(); err != nil {
 13		t.Fatal(err)
 14	}
 15	uid, err := s.CreateUser("alice", false)
 16	if err != nil {
 17		t.Fatal(err)
 18	}
 19	if err := s.AddSSHKey(uid, "SHA256:runnerkey", "ssh-ed25519", []byte("blob"), "runner"); err != nil {
 20		t.Fatal(err)
 21	}
 22	k, err := s.SSHKeyByFingerprint("SHA256:runnerkey")
 23	if err != nil {
 24		t.Fatal(err)
 25	}
 26	repoA, err = s.CreateRepo("user", uid, "a", "public")
 27	if err != nil {
 28		t.Fatal(err)
 29	}
 30	repoB, err = s.CreateRepo("user", uid, "b", "public")
 31	if err != nil {
 32		t.Fatal(err)
 33	}
 34	return s, uid, k.ID, repoA, repoB
 35}
 36
 37// Attaching twice is one row; detaching what is not attached is not found.
 38func TestAttachRunnerIdempotentAndDetach(t *testing.T) {
 39	s, _, keyID, repoA, repoB := runnerFixture(t)
 40	for range 2 {
 41		if err := s.AttachRunner(keyID, repoA); err != nil {
 42			t.Fatal(err)
 43		}
 44	}
 45	ids, err := s.RunnerRepoIDs(keyID)
 46	if err != nil || len(ids) != 1 || ids[0] != repoA {
 47		t.Fatalf("attached repos %v err=%v, want [%d]", ids, err, repoA)
 48	}
 49	if ok, _ := s.RunnerAttached(keyID, repoB); ok {
 50		t.Fatal("attached to a repo it was never attached to")
 51	}
 52	if err := s.DetachRunner(repoB, "SHA256:runnerkey"); !errors.Is(err, ErrNotFound) {
 53		t.Fatalf("detach of an unattached repo: %v, want ErrNotFound", err)
 54	}
 55	if err := s.DetachRunner(repoA, "SHA256:runnerkey"); err != nil {
 56		t.Fatal(err)
 57	}
 58	if ok, _ := s.RunnerAttached(keyID, repoA); ok {
 59		t.Fatal("still attached after detach")
 60	}
 61}
 62
 63// Removing the key or the repository removes the attachment with it.
 64func TestRunnerAttachmentCascades(t *testing.T) {
 65	s, uid, keyID, repoA, repoB := runnerFixture(t)
 66	if err := s.AttachRunner(keyID, repoA); err != nil {
 67		t.Fatal(err)
 68	}
 69	if err := s.AttachRunner(keyID, repoB); err != nil {
 70		t.Fatal(err)
 71	}
 72	if _, err := s.DB.Exec("DELETE FROM repos WHERE id = ?", repoB); err != nil {
 73		t.Fatal(err)
 74	}
 75	if ids, _ := s.RunnerRepoIDs(keyID); len(ids) != 1 {
 76		t.Fatalf("after repo delete: %v, want one attachment", ids)
 77	}
 78	if err := s.RemoveSSHKey(uid, "SHA256:runnerkey"); err != nil {
 79		t.Fatal(err)
 80	}
 81	var n int
 82	if err := s.DB.QueryRow("SELECT count(*) FROM runner_repos").Scan(&n); err != nil || n != 0 {
 83		t.Fatalf("after key delete: %d rows err=%v, want 0", n, err)
 84	}
 85}
 86
 87// The heartbeat is per key: two keys on one account are two rows, and a
 88// repository's runner list shows each key's last poll and the build it holds.
 89func TestRunnerSeenPerKeyAndRepoList(t *testing.T) {
 90	s, uid, keyID, repoA, _ := runnerFixture(t)
 91	if err := s.AddSSHKey(uid, "SHA256:second", "ssh-ed25519", []byte("blob2"), "runner"); err != nil {
 92		t.Fatal(err)
 93	}
 94	k2, _ := s.SSHKeyByFingerprint("SHA256:second")
 95	for _, id := range []int64{keyID, k2.ID} {
 96		if err := s.AttachRunner(id, repoA); err != nil {
 97			t.Fatal(err)
 98		}
 99	}
100	if _, err := s.CreateBuild(repoA, "unit", "abc123", "main", `["true"]`, "", "", true); err != nil {
101		t.Fatal(err)
102	}
103	b, ok, err := s.ClaimBuild(nil, false)
104	if err != nil || !ok {
105		t.Fatalf("claim: %v ok=%v", err, ok)
106	}
107	if err := s.TouchRunner(keyID, uid, "", b.ID); err != nil {
108		t.Fatal(err)
109	}
110	if err := s.TouchRunner(k2.ID, uid, "", 0); err != nil {
111		t.Fatal(err)
112	}
113	runners, err := s.ListRunners()
114	if err != nil || len(runners) != 2 {
115		t.Fatalf("ListRunners: %v err=%v, want two rows", runners, err)
116	}
117	list, err := s.ListRepoRunners(repoA)
118	if err != nil || len(list) != 2 {
119		t.Fatalf("ListRepoRunners: %v err=%v, want two rows", list, err)
120	}
121	var held, idle int
122	for _, r := range list {
123		if r.Username != "alice" || r.LastSeen == "" || r.AddedAt == "" {
124			t.Fatalf("row %+v lacks username, last_seen or added_at", r)
125		}
126		if r.BuildNumber == b.Number && r.BuildJob == "unit" && r.BuildRepo == "alice/a" {
127			held++
128		} else if r.BuildNumber == 0 {
129			idle++
130		}
131	}
132	if held != 1 || idle != 1 {
133		t.Fatalf("held=%d idle=%d, want 1 and 1: %+v", held, idle, list)
134	}
135	if err := s.RunnerDone(keyID); err != nil {
136		t.Fatal(err)
137	}
138	list, _ = s.ListRepoRunners(repoA)
139	for _, r := range list {
140		if r.BuildNumber != 0 {
141			t.Fatalf("build still held after RunnerDone: %+v", r)
142		}
143	}
144	paths, err := s.RunnerRepoPaths(keyID)
145	if err != nil || len(paths) != 1 || paths[0] != "alice/a" {
146		t.Fatalf("RunnerRepoPaths: %v err=%v", paths, err)
147	}
148}