Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package db 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "path/filepath" 8 "testing" 9 10 "tangled.org/core/appview/models" 11 "tangled.org/core/orm" 12) 13 14func newTestDB(t *testing.T) *DB { 15 t.Helper() 16 path := filepath.Join(t.TempDir(), "test.db") 17 d, err := Make(context.Background(), path) 18 if err != nil { 19 t.Fatalf("Make: %v", err) 20 } 21 t.Cleanup(func() { d.Close() }) 22 return d 23} 24 25func seedRepo(t *testing.T, d *DB, did, knot, name, rkey, repoDid string) *models.Repo { 26 t.Helper() 27 tx, err := d.Begin() 28 if err != nil { 29 t.Fatalf("Begin: %v", err) 30 } 31 repo := &models.Repo{ 32 Did: did, 33 Name: name, 34 Knot: knot, 35 Rkey: rkey, 36 RepoDid: repoDid, 37 } 38 if err := AddRepo(tx, repo); err != nil { 39 t.Fatalf("AddRepo: %v", err) 40 } 41 if err := tx.Commit(); err != nil { 42 t.Fatalf("Commit: %v", err) 43 } 44 return repo 45} 46 47func TestRenameRepo_HappyPath(t *testing.T) { 48 d := newTestDB(t) 49 seedRepo(t, d, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") 50 51 tx, err := d.Begin() 52 if err != nil { 53 t.Fatalf("Begin: %v", err) 54 } 55 defer tx.Rollback() 56 57 if err := RenameRepo(tx, "did:plc:akshay", "foo", "bar", "Bar"); err != nil { 58 t.Fatalf("RenameRepo: %v", err) 59 } 60 if err := tx.Commit(); err != nil { 61 t.Fatalf("Commit: %v", err) 62 } 63 64 got, err := GetRepoByDid(d, "did:plc:repo1") 65 if err != nil { 66 t.Fatalf("GetRepoByDid: %v", err) 67 } 68 if got.Rkey != "bar" { 69 t.Errorf("rkey = %q, want %q", got.Rkey, "bar") 70 } 71 if got.Name != "Bar" { 72 t.Errorf("name = %q, want %q", got.Name, "Bar") 73 } 74} 75 76func TestUpdateRepoDisplayName_HappyPath(t *testing.T) { 77 d := newTestDB(t) 78 seedRepo(t, d, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") 79 80 if err := UpdateRepoDisplayName(d, "did:plc:akshay", "foo", "Foo"); err != nil { 81 t.Fatalf("UpdateRepoDisplayName: %v", err) 82 } 83 84 got, err := GetRepoByDid(d, "did:plc:repo1") 85 if err != nil { 86 t.Fatalf("GetRepoByDid: %v", err) 87 } 88 if got.Name != "Foo" { 89 t.Errorf("name = %q, want %q", got.Name, "Foo") 90 } 91 if got.Rkey != "foo" { 92 t.Errorf("rkey should be unchanged but got %q, want %q", got.Rkey, "foo") 93 } 94} 95 96func TestRecordAndLookupRepoRename(t *testing.T) { 97 d := newTestDB(t) 98 seedRepo(t, d, "did:plc:akshay", "knot.example", "bar", "rkey1", "did:plc:repo1") 99 100 if err := RecordRepoRename(d, "did:plc:akshay", "foo", "did:plc:repo1"); err != nil { 101 t.Fatalf("RecordRepoRename: %v", err) 102 } 103 104 repo, err := LookupRepoRename(d, "did:plc:akshay", "foo") 105 if err != nil { 106 t.Fatalf("LookupRepoRename: %v", err) 107 } 108 if repo.RepoDid != "did:plc:repo1" { 109 t.Errorf("repoDid = %q, want %q", repo.RepoDid, "did:plc:repo1") 110 } 111 if repo.Name != "bar" { 112 t.Errorf("name = %q, want %q", repo.Name, "bar") 113 } 114} 115 116func TestLookupRepoRename_MultipleOldNamesResolveToCurrent(t *testing.T) { 117 d := newTestDB(t) 118 seedRepo(t, d, "did:plc:akshay", "knot.example", "baz", "baz", "did:plc:repo1") 119 120 if err := RecordRepoRename(d, "did:plc:akshay", "foo", "did:plc:repo1"); err != nil { 121 t.Fatalf("record foo: %v", err) 122 } 123 if err := RecordRepoRename(d, "did:plc:akshay", "bar", "did:plc:repo1"); err != nil { 124 t.Fatalf("record bar: %v", err) 125 } 126 127 for _, oldName := range []string{"foo", "bar"} { 128 repo, err := LookupRepoRename(d, "did:plc:akshay", oldName) 129 if err != nil { 130 t.Fatalf("lookup %q: %v", oldName, err) 131 } 132 if repo.Name != "baz" { 133 t.Errorf("lookup %q: name = %q, want %q", oldName, repo.Name, "baz") 134 } 135 } 136} 137 138func TestRecordRepoRename_UpsertRefreshesTarget(t *testing.T) { 139 d := newTestDB(t) 140 seedRepo(t, d, "did:plc:akshay", "knot.example", "current", "rkey1", "did:plc:repo1") 141 seedRepo(t, d, "did:plc:akshay", "knot.example", "other", "rkey2", "did:plc:repo2") 142 143 if err := RecordRepoRename(d, "did:plc:akshay", "shared", "did:plc:repo1"); err != nil { 144 t.Fatalf("first record: %v", err) 145 } 146 if err := RecordRepoRename(d, "did:plc:akshay", "shared", "did:plc:repo2"); err != nil { 147 t.Fatalf("second record: %v", err) 148 } 149 150 repo, err := LookupRepoRename(d, "did:plc:akshay", "shared") 151 if err != nil { 152 t.Fatalf("LookupRepoRename: %v", err) 153 } 154 if repo.RepoDid != "did:plc:repo2" { 155 t.Errorf("latest record should win: repoDid = %q, want %q", repo.RepoDid, "did:plc:repo2") 156 } 157} 158 159func TestLookupRepoRename_StaleSelfHeal(t *testing.T) { 160 d := newTestDB(t) 161 162 if err := RecordRepoRename(d, "did:plc:akshay", "foo", "did:plc:ghost"); err != nil { 163 t.Fatalf("RecordRepoRename: %v", err) 164 } 165 166 _, err := LookupRepoRename(d, "did:plc:akshay", "foo") 167 if !errors.Is(err, sql.ErrNoRows) { 168 t.Errorf("target should be gone and fall through to 404: err = %v, want sql.ErrNoRows", err) 169 } 170} 171 172func TestLookupRepoRename_NoRow(t *testing.T) { 173 d := newTestDB(t) 174 175 _, err := LookupRepoRename(d, "did:plc:akshay", "nothing") 176 if !errors.Is(err, sql.ErrNoRows) { 177 t.Errorf("err = %v, want sql.ErrNoRows", err) 178 } 179} 180 181func TestDuplicateRkeyUnderSameDID_Rejected(t *testing.T) { 182 d := newTestDB(t) 183 seedRepo(t, d, "did:plc:akshay", "knot.example", "myrepo", "myrepo", "did:plc:repo1") 184 185 tx, err := d.Begin() 186 if err != nil { 187 t.Fatalf("Begin: %v", err) 188 } 189 defer tx.Rollback() 190 191 err = AddRepo(tx, &models.Repo{ 192 Did: "did:plc:akshay", 193 Name: "myrepo", 194 Knot: "knot.example", 195 Rkey: "myrepo", 196 RepoDid: "did:plc:repo2", 197 }) 198 if err == nil { 199 t.Fatal("expected unique violation for duplicate (did, rkey), got nil") 200 } 201 if !orm.IsUniqueViolation(err) { 202 t.Errorf("err = %v, want unique violation", err) 203 } 204} 205 206func TestRenameRepo_OldRkeyRowGone(t *testing.T) { 207 d := newTestDB(t) 208 seedRepo(t, d, "did:plc:akshay", "knot.example", "old", "old", "did:plc:repo1") 209 210 tx, err := d.Begin() 211 if err != nil { 212 t.Fatalf("Begin: %v", err) 213 } 214 defer tx.Rollback() 215 216 if err := RenameRepo(tx, "did:plc:akshay", "old", "new", "New"); err != nil { 217 t.Fatalf("RenameRepo: %v", err) 218 } 219 if err := tx.Commit(); err != nil { 220 t.Fatalf("Commit: %v", err) 221 } 222 223 got, err := GetRepoByDid(d, "did:plc:repo1") 224 if err != nil { 225 t.Fatalf("GetRepoByDid: %v", err) 226 } 227 if got.Rkey != "new" { 228 t.Errorf("rkey = %q, want %q", got.Rkey, "new") 229 } 230 231 var dummy int 232 err = d.QueryRow(`select 1 from repos where did = ? and rkey = ?`, "did:plc:akshay", "old").Scan(&dummy) 233 if !errors.Is(err, sql.ErrNoRows) { 234 t.Errorf("old rkey row should be gone, got err = %v", err) 235 } 236} 237 238func TestRenameRepo_PipelineRenamed(t *testing.T) { 239 d := newTestDB(t) 240 seedRepo(t, d, "did:plc:akshay", "knot.example", "old", "old", "did:plc:repo1") 241 242 if _, err := d.Exec( 243 `insert into triggers (kind) values (?)`, "push", 244 ); err != nil { 245 t.Fatalf("seed trigger: %v", err) 246 } 247 if _, err := d.Exec( 248 `insert into pipelines (rkey, knot, repo_owner, repo_name, sha, trigger_id, repo_did) 249 values (?, ?, ?, ?, ?, ?, ?)`, 250 "pipe1", "knot.example", "did:plc:akshay", "old", 251 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1, "did:plc:repo1", 252 ); err != nil { 253 t.Fatalf("seed pipeline: %v", err) 254 } 255 256 tx, err := d.Begin() 257 if err != nil { 258 t.Fatalf("Begin: %v", err) 259 } 260 defer tx.Rollback() 261 262 if err := RenameRepo(tx, "did:plc:akshay", "old", "new", "New"); err != nil { 263 t.Fatalf("RenameRepo: %v", err) 264 } 265 if err := tx.Commit(); err != nil { 266 t.Fatalf("Commit: %v", err) 267 } 268 269 var repoName string 270 if err := d.QueryRow( 271 `select repo_name from pipelines where repo_owner = ? and rkey = ?`, 272 "did:plc:akshay", "pipe1", 273 ).Scan(&repoName); err != nil { 274 t.Fatalf("query pipeline: %v", err) 275 } 276 if repoName != "new" { 277 t.Errorf("pipeline repo_name = %q, want %q", repoName, "new") 278 } 279}