vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Identify accounts by their registered name
62576fa8157d431dcf2d360f3ebe481de77bda03 · Keenan Nemetz · 14d ago · parent 642e5994
Identify accounts by their registered name Drop the internal account id and display name. Users, keys, passkeys, invites, sessions, ACL entries, approvals, locks and CI records now all use the registered name as the single identity. The roster migrates in place: old id-keyed tables are rebuilt around the name, and access-rule/lock references are rewritten through the same mapping. Display-name editing and whoami --id are removed.
server/integration_test.go +61−82
| @@ -169,7 +169,7 @@ func newFixtureWith(t *testing.T, tweak func(*config.Config)) *fixture { | |||
| 169 | 169 | if err != nil { | |
| 170 | 170 | t.Fatal(err) | |
| 171 | 171 | } | |
| 172 | owner, err := ident.Create("keenan", "", pub, "test") | ||
| 172 | owner, err := ident.Create("keenan", pub, "test") | ||
| 173 | 173 | if err != nil { | |
| 174 | 174 | t.Fatal(err) | |
| 175 | 175 | } | |
| @@ -188,7 +188,7 @@ func newFixtureWith(t *testing.T, tweak func(*config.Config)) *fixture { | |||
| 188 | 188 | ||
| 189 | 189 | f := &fixture{ | |
| 190 | 190 | t: t, cfg: cfg, srv: srv, http: ts, sshAddr: ln.Addr().String(), | |
| 191 | keyPath: priv, keyPub: pub, repo: "demo", ownerID: owner.ID, | ||
| 191 | keyPath: priv, keyPub: pub, repo: "demo", ownerID: owner.Name, | ||
| 192 | 192 | } | |
| 193 | 193 | t.Cleanup(func() { | |
| 194 | 194 | ts.Close() | |
| @@ -196,7 +196,7 @@ func newFixtureWith(t *testing.T, tweak func(*config.Config)) *fixture { | |||
| 196 | 196 | srv.Close() | |
| 197 | 197 | }) | |
| 198 | 198 | ||
| 199 | m, err := srv.Repos.Create(f.repo, owner.ID, "demo repo") | ||
| 199 | m, err := srv.Repos.Create(f.repo, owner.Name, "demo repo") | ||
| 200 | 200 | if err != nil { | |
| 201 | 201 | t.Fatal(err) | |
| 202 | 202 | } | |
| @@ -230,7 +230,7 @@ func keypair(t *testing.T, path string) (pubLine, privPath string) { | |||
| 230 | 230 | func (f *fixture) addWriter(t *testing.T) string { | |
| 231 | 231 | t.Helper() | |
| 232 | 232 | pub, priv := keypair(t, filepath.Join(f.cfg.DataDir, "id_writer")) | |
| 233 | writer, err := f.srv.Identity.Create("wilbur", "", pub, "test") | ||
| 233 | writer, err := f.srv.Identity.Create("wilbur", pub, "test") | ||
| 234 | 234 | if err != nil { | |
| 235 | 235 | t.Fatal(err) | |
| 236 | 236 | } | |
| @@ -240,24 +240,25 @@ func (f *fixture) addWriter(t *testing.T) string { | |||
| 240 | 240 | t.Fatal(err) | |
| 241 | 241 | } | |
| 242 | 242 | // ACL entries are keyed by account id, not by name. | |
| 243 | if err := a.SetEntry(writer.ID, "maintain"); err != nil { | ||
| 243 | if err := a.SetEntry(writer.Name, "maintain"); err != nil { | ||
| 244 | 244 | t.Fatal(err) | |
| 245 | 245 | } | |
| 246 | 246 | if err := m.SetACL(a, "test"); err != nil { | |
| 247 | 247 | t.Fatal(err) | |
| 248 | 248 | } | |
| 249 | f.writerID = writer.ID | ||
| 249 | f.writerID = writer.Name | ||
| 250 | 250 | return priv | |
| 251 | 251 | } | |
| 252 | 252 | ||
| 253 | // accountID resolves a name to the account id that approvals and ACLs store. | ||
| 254 | func (f *fixture) accountID(name string) string { | ||
| 253 | // accountName checks that a fixture account is active and returns its name, the | ||
| 254 | // identifier ACLs and approvals store. | ||
| 255 | func (f *fixture) accountName(name string) string { | ||
| 255 | 256 | f.t.Helper() | |
| 256 | accountID, ok := f.srv.Identity.ResolveID(name) | ||
| 257 | if !ok { | ||
| 258 | f.t.Fatalf("no account %q", name) | ||
| 257 | a, err := f.srv.Identity.Find(name) | ||
| 258 | if err != nil || !a.Active() { | ||
| 259 | f.t.Fatalf("no active account %q", name) | ||
| 259 | 260 | } | |
| 260 | return accountID | ||
| 261 | return a.Name | ||
| 261 | 262 | } | |
| 262 | 263 | ||
| 263 | 264 | // gitWithKey is f.git with a specific identity. | |
| @@ -999,14 +1000,6 @@ func TestWhoAmIAnswersFromTheInstance(t *testing.T) { | |||
| 999 | 1000 | if got := strings.TrimSpace(out); got != "keenan" { | |
| 1000 | 1001 | t.Fatalf("whoami = %q, want the account name keenan", got) | |
| 1001 | 1002 | } | |
| 1002 | out, _, err = f.cli(clone, "whoami", "--id") | ||
| 1003 | if err != nil { | ||
| 1004 | t.Fatalf("whoami --id: %v", err) | ||
| 1005 | } | ||
| 1006 | if got := strings.TrimSpace(out); got != f.ownerID { | ||
| 1007 | t.Fatalf("whoami --id = %q, want %q", got, f.ownerID) | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | 1003 | // A filed issue is authored by the account, not by the guess, and the command | |
| 1011 | 1004 | // says where to read it. | |
| 1012 | 1005 | out, errs, err = f.cli(clone, "issue", "new", "--title", "who am I") | |
| @@ -1114,8 +1107,8 @@ func TestRequestAuthorComesFromTheKey(t *testing.T) { | |||
| 1114 | 1107 | if !strings.Contains(raw, "author: keenan\n") { | |
| 1115 | 1108 | t.Fatalf("request author is not the account:\n%s", raw) | |
| 1116 | 1109 | } | |
| 1117 | if !strings.Contains(raw, "author_id: "+f.ownerID) { | ||
| 1118 | t.Fatalf("request author_id is not the account id %s:\n%s", f.ownerID, raw) | ||
| 1110 | if strings.Contains(raw, "author_id:") { | ||
| 1111 | t.Fatalf("request still carries a second identifier:\n%s", raw) | ||
| 1119 | 1112 | } | |
| 1120 | 1113 | if strings.Contains(raw, "keenan-nemetz") { | |
| 1121 | 1114 | t.Fatalf("the git-config guess reached the request:\n%s", raw) | |
| @@ -1212,7 +1205,7 @@ func TestRequestPageSaysWhenItWouldConflict(t *testing.T) { | |||
| 1212 | 1205 | ||
| 1213 | 1206 | meta := f.newMetaRepo(t) | |
| 1214 | 1207 | if err := meta.apply(map[string]string{ | |
| 1215 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", f.ownerID, "why this change\n"), | ||
| 1208 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", "why this change\n"), | ||
| 1216 | 1209 | }, "request"); err != nil { | |
| 1217 | 1210 | t.Fatalf("request push: %v", err) | |
| 1218 | 1211 | } | |
| @@ -1236,7 +1229,7 @@ func TestRequestPageSaysWhenItWouldConflict(t *testing.T) { | |||
| 1236 | 1229 | } | |
| 1237 | 1230 | const cleanID = "01M5JR2JK29KKMAA1T9MDNTR02" | |
| 1238 | 1231 | if err := meta.apply(map[string]string{ | |
| 1239 | "requests/" + cleanID + "/request.md": requestFile("main", cleanHead, "keenan", f.ownerID, "clean change\n"), | ||
| 1232 | "requests/" + cleanID + "/request.md": requestFile("main", cleanHead, "keenan", "clean change\n"), | ||
| 1240 | 1233 | }, "clean request"); err != nil { | |
| 1241 | 1234 | t.Fatalf("clean request push: %v", err) | |
| 1242 | 1235 | } | |
| @@ -1280,7 +1273,7 @@ func TestRequestPageSaysWhenItWouldConflict(t *testing.T) { | |||
| 1280 | 1273 | const ghostID = "01M5JR2JK29KKMAA1T9MDNTR03" | |
| 1281 | 1274 | if err := meta.apply(map[string]string{ | |
| 1282 | 1275 | "requests/" + ghostID + "/request.md": requestFile("main", | |
| 1283 | "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "keenan", f.ownerID, "ghost head\n"), | ||
| 1276 | "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "keenan", "ghost head\n"), | ||
| 1284 | 1277 | }, "ghost request"); err != nil { | |
| 1285 | 1278 | t.Fatalf("ghost request push: %v", err) | |
| 1286 | 1279 | } | |
| @@ -1310,7 +1303,7 @@ func TestRequestDiscussionIsPushedAndOwned(t *testing.T) { | |||
| 1310 | 1303 | } | |
| 1311 | 1304 | meta := f.newMetaRepo(t) | |
| 1312 | 1305 | if err := meta.apply(map[string]string{ | |
| 1313 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", f.ownerID, "rationale\n"), | ||
| 1306 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", "rationale\n"), | ||
| 1314 | 1307 | }, "request"); err != nil { | |
| 1315 | 1308 | t.Fatalf("request push: %v", err) | |
| 1316 | 1309 | } | |
| @@ -1431,7 +1424,7 @@ func TestReviewCommentAnchorsALine(t *testing.T) { | |||
| 1431 | 1424 | } | |
| 1432 | 1425 | meta := f.newMetaRepo(t) | |
| 1433 | 1426 | if err := meta.apply(map[string]string{ | |
| 1434 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", f.ownerID, "rationale\n"), | ||
| 1427 | "requests/" + reqID + "/request.md": requestFile("main", head, "keenan", "rationale\n"), | ||
| 1435 | 1428 | }, "request"); err != nil { | |
| 1436 | 1429 | t.Fatalf("request push: %v", err) | |
| 1437 | 1430 | } | |
| @@ -2132,12 +2125,11 @@ const reqID = "01M5JR2JK29KKMAA1T9MDNTR01" | |||
| 2132 | 2125 | ||
| 2133 | 2126 | // requestFile renders requests/<id>/request.md. The body is the request's | |
| 2134 | 2127 | // description, so a fixture cannot drift from the format the reader expects. | |
| 2135 | func requestFile(base, head, author, authorID, body string) string { | ||
| 2128 | func requestFile(base, head, author, body string) string { | ||
| 2136 | 2129 | d := metadata.NewDoc() | |
| 2137 | 2130 | d.Text("title", "Land the feature") | |
| 2138 | 2131 | d.Word("state", "open") | |
| 2139 | 2132 | d.Word("author", author) | |
| 2140 | d.Word("author_id", authorID) | ||
| 2141 | 2133 | d.Word("base", base) | |
| 2142 | 2134 | d.Word("head", head) | |
| 2143 | 2135 | d.Word("created", "2026-10-22T22:00:00Z") | |
| @@ -2149,8 +2141,8 @@ func requestFile(base, head, author, authorID, body string) string { | |||
| 2149 | 2141 | } | |
| 2150 | 2142 | ||
| 2151 | 2143 | // requestMeta is requestFile with no description: the common fixture shape. | |
| 2152 | func requestMeta(base, head, author, authorID string) string { | ||
| 2153 | return requestFile(base, head, author, authorID, "") | ||
| 2144 | func requestMeta(base, head, author string) string { | ||
| 2145 | return requestFile(base, head, author, "") | ||
| 2154 | 2146 | } | |
| 2155 | 2147 | ||
| 2156 | 2148 | func approvalFile(base, head string) string { | |
| @@ -2222,7 +2214,7 @@ func TestProtectedBranchRequiresApprovedRequest(t *testing.T) { | |||
| 2222 | 2214 | // 2. Request open, no approvals: still refused. | |
| 2223 | 2215 | meta := f.newMetaRepo(t) | |
| 2224 | 2216 | if err := meta.apply(map[string]string{ | |
| 2225 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2217 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2226 | 2218 | }, "request"); err != nil { | |
| 2227 | 2219 | t.Fatalf("request push: %v", err) | |
| 2228 | 2220 | } | |
| @@ -2235,7 +2227,7 @@ func TestProtectedBranchRequiresApprovedRequest(t *testing.T) { | |||
| 2235 | 2227 | // 3. Approval by another principal: allowed. | |
| 2236 | 2228 | approver := f.newMetaRepo(t) | |
| 2237 | 2229 | if err := approver.apply(map[string]string{ | |
| 2238 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2230 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2239 | 2231 | }, "approve"); err != nil { | |
| 2240 | 2232 | t.Fatalf("approval push: %v", err) | |
| 2241 | 2233 | } | |
| @@ -2274,13 +2266,13 @@ func TestApprovalIsBoundToTheCommit(t *testing.T) { | |||
| 2274 | 2266 | ||
| 2275 | 2267 | meta := f.newMetaRepo(t) | |
| 2276 | 2268 | if err := meta.apply(map[string]string{ | |
| 2277 | "requests/" + reqID + "/request.md": requestMeta("main", approved, "wilbur", f.accountID("wilbur")), | ||
| 2269 | "requests/" + reqID + "/request.md": requestMeta("main", approved, "wilbur"), | ||
| 2278 | 2270 | }, "request"); err != nil { | |
| 2279 | 2271 | t.Fatalf("request: %v", err) | |
| 2280 | 2272 | } | |
| 2281 | 2273 | approver := f.newMetaRepo(t) | |
| 2282 | 2274 | if err := approver.apply(map[string]string{ | |
| 2283 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", approved), | ||
| 2275 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", approved), | ||
| 2284 | 2276 | }, "approve"); err != nil { | |
| 2285 | 2277 | t.Fatalf("approve: %v", err) | |
| 2286 | 2278 | } | |
| @@ -2309,8 +2301,8 @@ func TestSelfApprovalRejected(t *testing.T) { | |||
| 2309 | 2301 | ||
| 2310 | 2302 | meta := f.newMetaRepo(t) | |
| 2311 | 2303 | err := meta.apply(map[string]string{ | |
| 2312 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2313 | "requests/" + reqID + "/approvals/" + f.accountID("wilbur") + ".md": approvalFile("main", head), | ||
| 2304 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2305 | "requests/" + reqID + "/approvals/" + f.accountName("wilbur") + ".md": approvalFile("main", head), | ||
| 2314 | 2306 | }, "self approve") | |
| 2315 | 2307 | if err == nil { | |
| 2316 | 2308 | t.Fatal("a self-approval was accepted") | |
| @@ -2346,13 +2338,13 @@ func TestMergeCommitShapeAllowed(t *testing.T) { | |||
| 2346 | 2338 | } | |
| 2347 | 2339 | meta := f.newMetaRepo(t) | |
| 2348 | 2340 | if err := meta.apply(map[string]string{ | |
| 2349 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2341 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2350 | 2342 | }, "request"); err != nil { | |
| 2351 | 2343 | t.Fatalf("request: %v", err) | |
| 2352 | 2344 | } | |
| 2353 | 2345 | approver := f.newMetaRepo(t) | |
| 2354 | 2346 | if err := approver.apply(map[string]string{ | |
| 2355 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2347 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2356 | 2348 | }, "approve"); err != nil { | |
| 2357 | 2349 | t.Fatalf("approve: %v", err) | |
| 2358 | 2350 | } | |
| @@ -2391,7 +2383,7 @@ func TestImpersonatedApprovalRejected(t *testing.T) { | |||
| 2391 | 2383 | ||
| 2392 | 2384 | meta := f.newMetaRepo(t) | |
| 2393 | 2385 | if err := meta.apply(map[string]string{ | |
| 2394 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2386 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2395 | 2387 | }, "request"); err != nil { | |
| 2396 | 2388 | t.Fatalf("request: %v", err) | |
| 2397 | 2389 | } | |
| @@ -2399,7 +2391,7 @@ func TestImpersonatedApprovalRejected(t *testing.T) { | |||
| 2399 | 2391 | impostor := f.newMetaRepo(t) | |
| 2400 | 2392 | impostor.key = writerKey | |
| 2401 | 2393 | err := impostor.apply(map[string]string{ | |
| 2402 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2394 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2403 | 2395 | }, "approve as someone else") | |
| 2404 | 2396 | if err == nil { | |
| 2405 | 2397 | t.Fatal("a writer approved in another principal's name") | |
| @@ -2434,13 +2426,13 @@ func TestApprovalIsBoundToTheBase(t *testing.T) { | |||
| 2434 | 2426 | feature, head := f.cloneWithChange(t, map[string]string{"a.md": "v2\n"}, "feature") | |
| 2435 | 2427 | meta := f.newMetaRepo(t) | |
| 2436 | 2428 | if err := meta.apply(map[string]string{ | |
| 2437 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2429 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2438 | 2430 | }, "request"); err != nil { | |
| 2439 | 2431 | t.Fatalf("request: %v", err) | |
| 2440 | 2432 | } | |
| 2441 | 2433 | approver := f.newMetaRepo(t) | |
| 2442 | 2434 | if err := approver.apply(map[string]string{ | |
| 2443 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2435 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2444 | 2436 | }, "approve"); err != nil { | |
| 2445 | 2437 | t.Fatalf("approve: %v", err) | |
| 2446 | 2438 | } | |
| @@ -2448,7 +2440,7 @@ func TestApprovalIsBoundToTheBase(t *testing.T) { | |||
| 2448 | 2440 | // Repoint the request at release. The approval named main. | |
| 2449 | 2441 | repoint := f.newMetaRepo(t) | |
| 2450 | 2442 | if err := repoint.apply(map[string]string{ | |
| 2451 | "requests/" + reqID + "/request.md": requestMeta("release", head, "wilbur", f.accountID("wilbur")), | ||
| 2443 | "requests/" + reqID + "/request.md": requestMeta("release", head, "wilbur"), | ||
| 2452 | 2444 | }, "repoint"); err != nil { | |
| 2453 | 2445 | t.Fatalf("repoint: %v", err) | |
| 2454 | 2446 | } | |
| @@ -2475,8 +2467,8 @@ func TestFirstPushCannotSeedForeignApprovals(t *testing.T) { | |||
| 2475 | 2467 | meta := f.newMetaRepo(t) | |
| 2476 | 2468 | meta.key = writerKey | |
| 2477 | 2469 | err := meta.apply(map[string]string{ | |
| 2478 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2479 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2470 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2471 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2480 | 2472 | }, "seed") | |
| 2481 | 2473 | if err == nil { | |
| 2482 | 2474 | t.Fatal("the first push seeded an approval in another principal's name") | |
| @@ -2511,13 +2503,13 @@ func TestModifiedApprovalCannotBeForged(t *testing.T) { | |||
| 2511 | 2503 | } | |
| 2512 | 2504 | meta := f.newMetaRepo(t) | |
| 2513 | 2505 | if err := meta.apply(map[string]string{ | |
| 2514 | "requests/" + reqID + "/request.md": requestMeta("main", approvedHead, "wilbur", f.accountID("wilbur")), | ||
| 2506 | "requests/" + reqID + "/request.md": requestMeta("main", approvedHead, "wilbur"), | ||
| 2515 | 2507 | }, "request"); err != nil { | |
| 2516 | 2508 | t.Fatalf("request: %v", err) | |
| 2517 | 2509 | } | |
| 2518 | 2510 | approver := f.newMetaRepo(t) | |
| 2519 | 2511 | if err := approver.apply(map[string]string{ | |
| 2520 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", approvedHead), | ||
| 2512 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", approvedHead), | ||
| 2521 | 2513 | }, "approve"); err != nil { | |
| 2522 | 2514 | t.Fatalf("approve: %v", err) | |
| 2523 | 2515 | } | |
| @@ -2533,8 +2525,8 @@ func TestModifiedApprovalCannotBeForged(t *testing.T) { | |||
| 2533 | 2525 | forger := f.newMetaRepo(t) | |
| 2534 | 2526 | forger.key = writerKey | |
| 2535 | 2527 | err := forger.apply(map[string]string{ | |
| 2536 | "requests/" + reqID + "/request.md": requestMeta("main", sneaky, "wilbur", f.accountID("wilbur")), | ||
| 2537 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", sneaky), | ||
| 2528 | "requests/" + reqID + "/request.md": requestMeta("main", sneaky, "wilbur"), | ||
| 2529 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", sneaky), | ||
| 2538 | 2530 | }, "repoint approval") | |
| 2539 | 2531 | if err == nil { | |
| 2540 | 2532 | t.Fatal("a writer rewrote another principal's approval and the push was accepted") | |
| @@ -2561,13 +2553,13 @@ func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) { | |||
| 2561 | 2553 | meta := f.newMetaRepo(t) | |
| 2562 | 2554 | meta.key = writerKey | |
| 2563 | 2555 | if err := meta.apply(map[string]string{ | |
| 2564 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur", f.accountID("wilbur")), | ||
| 2556 | "requests/" + reqID + "/request.md": requestMeta("main", head, "wilbur"), | ||
| 2565 | 2557 | }, "request"); err != nil { | |
| 2566 | 2558 | t.Fatalf("setup: %v", err) | |
| 2567 | 2559 | } | |
| 2568 | 2560 | approver := f.newMetaRepo(t) | |
| 2569 | 2561 | if err := approver.apply(map[string]string{ | |
| 2570 | "requests/" + reqID + "/approvals/" + f.accountID("keenan") + ".md": approvalFile("main", head), | ||
| 2562 | "requests/" + reqID + "/approvals/" + f.accountName("keenan") + ".md": approvalFile("main", head), | ||
| 2571 | 2563 | }, "approve"); err != nil { | |
| 2572 | 2564 | t.Fatalf("approve: %v", err) | |
| 2573 | 2565 | } | |
| @@ -2576,7 +2568,7 @@ func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) { | |||
| 2576 | 2568 | deleter := f.newMetaRepo(t) | |
| 2577 | 2569 | deleter.key = writerKey | |
| 2578 | 2570 | deleter.sync() | |
| 2579 | if err := os.Remove(filepath.Join(deleter.dir, "requests", reqID, "approvals", f.accountID("keenan")+".md")); err != nil { | ||
| 2571 | if err := os.Remove(filepath.Join(deleter.dir, "requests", reqID, "approvals", f.accountName("keenan")+".md")); err != nil { | ||
| 2580 | 2572 | t.Fatal(err) | |
| 2581 | 2573 | } | |
| 2582 | 2574 | if _, errs, err := runGit(t, deleter.dir, "add", "-A"); err != nil { | |
| @@ -2596,7 +2588,7 @@ func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) { | |||
| 2596 | 2588 | // keenan withdraws his own. | |
| 2597 | 2589 | own := f.newMetaRepo(t) | |
| 2598 | 2590 | own.sync() | |
| 2599 | if err := os.Remove(filepath.Join(own.dir, "requests", reqID, "approvals", f.accountID("keenan")+".md")); err != nil { | ||
| 2591 | if err := os.Remove(filepath.Join(own.dir, "requests", reqID, "approvals", f.accountName("keenan")+".md")); err != nil { | ||
| 2600 | 2592 | t.Fatal(err) | |
| 2601 | 2593 | } | |
| 2602 | 2594 | if _, errs, cerr := runGit(t, own.dir, "add", "-A"); cerr != nil { | |
| @@ -2610,11 +2602,9 @@ func TestApprovalDeletionIsOwnerOrAdmin(t *testing.T) { | |||
| 2610 | 2602 | } | |
| 2611 | 2603 | } | |
| 2612 | 2604 | ||
| 2613 | // TestDisplayNameChangeKeepsAccessAndApprovalOwnership: ACL entries and approval | ||
| 2614 | // files hold the account id, metadata authors hold the fixed account name, and a | ||
| 2615 | // display name is only a label — so relabelling a person must not change what | ||
| 2616 | // they may do, who owns their past approvals, or whose name their comments carry. | ||
| 2617 | func TestDisplayNameChangeKeepsAccessAndApprovalOwnership(t *testing.T) { | ||
| 2605 | // TestAccessAndApprovalOwnershipFollowTheName: ACL entries and approval files | ||
| 2606 | // are keyed by the same registered name metadata authors carry. | ||
| 2607 | func TestAccessAndApprovalOwnershipFollowTheName(t *testing.T) { | ||
| 2618 | 2608 | t.Parallel() | |
| 2619 | 2609 | f := newFixture(t) | |
| 2620 | 2610 | writerKey := f.addWriter(t) | |
| @@ -2628,49 +2618,38 @@ func TestDisplayNameChangeKeepsAccessAndApprovalOwnership(t *testing.T) { | |||
| 2628 | 2618 | // keenan authors a request; wilbur (the writer) approves it. | |
| 2629 | 2619 | meta := f.newMetaRepo(t) | |
| 2630 | 2620 | if err := meta.apply(map[string]string{ | |
| 2631 | "requests/" + reqID + "/request.md": requestMeta("main", head, "keenan", f.ownerID), | ||
| 2621 | "requests/" + reqID + "/request.md": requestMeta("main", head, "keenan"), | ||
| 2632 | 2622 | }, "request"); err != nil { | |
| 2633 | 2623 | t.Fatalf("request: %v", err) | |
| 2634 | 2624 | } | |
| 2635 | 2625 | approver := f.newMetaRepo(t) | |
| 2636 | 2626 | approver.key = writerKey | |
| 2637 | 2627 | if err := approver.apply(map[string]string{ | |
| 2638 | "requests/" + reqID + "/approvals/" + f.writerID + ".md": approvalFile("main", head), | ||
| 2628 | "requests/" + reqID + "/approvals/wilbur.md": approvalFile("main", head), | ||
| 2639 | 2629 | }, "approve"); err != nil { | |
| 2640 | 2630 | t.Fatalf("approve: %v", err) | |
| 2641 | 2631 | } | |
| 2642 | 2632 | ||
| 2643 | if _, err := f.srv.Identity.SetDisplay("wilbur", "Wilbur Wright", "test"); err != nil { | ||
| 2644 | t.Fatal(err) | ||
| 2645 | } | ||
| 2646 | if got := f.srv.Identity.Label(f.writerID); got != "Wilbur Wright" { | ||
| 2647 | t.Fatalf("label = %q, want the display name", got) | ||
| 2648 | } | ||
| 2649 | if got := f.srv.Identity.Name(f.writerID); got != "wilbur" { | ||
| 2650 | t.Fatalf("name changed to %q; the name is not a display label", got) | ||
| 2651 | } | ||
| 2652 | |||
| 2653 | // Access follows the id: the same key still has write, under the same name. | ||
| 2633 | // The same key still has write, under the same name. | ||
| 2654 | 2634 | issue := f.newMetaRepo(t) | |
| 2655 | 2635 | issue.key = writerKey | |
| 2656 | 2636 | if err := issue.apply(map[string]string{ | |
| 2657 | "issues/01M5JR2JK29KKMAA1T9MDNTZ01/issue.md": issueFile("after display change", "wilbur", "open", "2026-10-22T22:00:00Z", nil, ""), | ||
| 2658 | }, "issue after display change"); err != nil { | ||
| 2637 | "issues/01M5JR2JK29KKMAA1T9MDNTZ01/issue.md": issueFile("after approval", "wilbur", "open", "2026-10-22T22:00:00Z", nil, ""), | ||
| 2638 | }, "issue after approval"); err != nil { | ||
| 2659 | 2639 | t.Fatalf("the account lost its access: %v", err) | |
| 2660 | 2640 | } | |
| 2661 | 2641 | ||
| 2662 | // Ownership follows the id too: wilbur may withdraw the approval they wrote, | ||
| 2663 | // because the file is named after the account, not the display name. | ||
| 2642 | // wilbur may withdraw the approval they wrote. | ||
| 2664 | 2643 | withdraw := f.newMetaRepo(t) | |
| 2665 | 2644 | withdraw.key = writerKey | |
| 2666 | 2645 | withdraw.sync() | |
| 2667 | if err := os.Remove(filepath.Join(withdraw.dir, "requests", reqID, "approvals", f.writerID+".md")); err != nil { | ||
| 2646 | if err := os.Remove(filepath.Join(withdraw.dir, "requests", reqID, "approvals", "wilbur.md")); err != nil { | ||
| 2668 | 2647 | t.Fatal(err) | |
| 2669 | 2648 | } | |
| 2670 | 2649 | if _, errs, err := runGit(t, withdraw.dir, "add", "-A"); err != nil { | |
| 2671 | 2650 | t.Fatalf("add: %v: %s", err, errs) | |
| 2672 | 2651 | } | |
| 2673 | if _, errs, err := runGit(t, withdraw.dir, "commit", "-qm", "withdraw after display change"); err != nil { | ||
| 2652 | if _, errs, err := runGit(t, withdraw.dir, "commit", "-qm", "withdraw approval"); err != nil { | ||
| 2674 | 2653 | t.Fatalf("commit: %v: %s", err, errs) | |
| 2675 | 2654 | } | |
| 2676 | 2655 | if _, errs, err := fgit(t, writerKey, withdraw.dir, "push", "origin", "HEAD:refs/heads/vole"); err != nil { | |
| @@ -2694,7 +2673,7 @@ func TestRetiredNameCannotBeReissued(t *testing.T) { | |||
| 2694 | 2673 | t.Fatal(err) | |
| 2695 | 2674 | } | |
| 2696 | 2675 | reissue, _ := keypair(t, filepath.Join(f.cfg.DataDir, "id_reissued")) | |
| 2697 | if _, err := f.srv.Identity.Create("wilbur", "", reissue, "test"); err == nil { | ||
| 2676 | if _, err := f.srv.Identity.Create("wilbur", reissue, "test"); err == nil { | ||
| 2698 | 2677 | t.Fatal("the retired name was reissued") | |
| 2699 | 2678 | } | |
| 2700 | 2679 | // And the retired key no longer authenticates. | |
| @@ -3254,7 +3233,7 @@ func (f *fixture) openAndApprove(t *testing.T, head, author string) { | |||
| 3254 | 3233 | t.Helper() | |
| 3255 | 3234 | meta := f.newMetaRepo(t) | |
| 3256 | 3235 | if err := meta.apply(map[string]string{ | |
| 3257 | "requests/" + reqID + "/request.md": requestMeta("main", head, author, f.accountID(author)), | ||
| 3236 | "requests/" + reqID + "/request.md": requestMeta("main", head, author), | ||
| 3258 | 3237 | }, "request"); err != nil { | |
| 3259 | 3238 | t.Fatalf("request: %v", err) | |
| 3260 | 3239 | } | |
| @@ -3307,7 +3286,7 @@ func TestOnlyAllowedRunnersGreenLightABranch(t *testing.T) { | |||
| 3307 | 3286 | t.Parallel() | |
| 3308 | 3287 | f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 2 }) | |
| 3309 | 3288 | writerKey := f.addWriter(t) | |
| 3310 | wilbur := f.accountID("wilbur") | ||
| 3289 | wilbur := f.accountName("wilbur") | ||
| 3311 | 3290 | f.publish(f.repo) | |
| 3312 | 3291 | if _, errs, err := f.git(workRepo(t, map[string]string{"a.md": "a\n"}), | |
| 3313 | 3292 | "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | |
| @@ -3401,7 +3380,7 @@ func TestACLRunnersEditsTheList(t *testing.T) { | |||
| 3401 | 3380 | } else if !strings.Contains(out, "wilbur") { | |
| 3402 | 3381 | t.Fatalf("add did not report the account:\n%s", out) | |
| 3403 | 3382 | } | |
| 3404 | if got := runners(); len(got) != 1 || got[0] != f.accountID("wilbur") { | ||
| 3383 | if got := runners(); len(got) != 1 || got[0] != f.accountName("wilbur") { | ||
| 3405 | 3384 | t.Fatalf("runners after --add wilbur = %#v", got) | |
| 3406 | 3385 | } | |
| 3407 | 3386 | ||