diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 22:25:39 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 22:25:39 +0000 |
| commit | 504eaf4f5aba93a3e935bbee76042dd35cada666 (patch) | |
| tree | e079d5002c08d655a3c686f1458b8d1e60c4e0b3 /grasp-audit/src/specs/grasp01 | |
| parent | 11870a0f810accf0431d82a74b6fd3adec9d23df (diff) | |
better fixtures: test_head_set_after_state_event_with_existing_commit
Diffstat (limited to 'grasp-audit/src/specs/grasp01')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 251 |
1 files changed, 28 insertions, 223 deletions
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index 0119ab3..9e8597b 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -34,7 +34,6 @@ const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb"; | |||
| 34 | use crate::{ | 34 | use crate::{ |
| 35 | clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref, | 35 | clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref, |
| 36 | AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, | 36 | AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, |
| 37 | MAINTAINER_DETERMINISTIC_COMMIT_HASH, | ||
| 38 | }; | 37 | }; |
| 39 | use nostr_sdk::prelude::*; | 38 | use nostr_sdk::prelude::*; |
| 40 | use std::fs; | 39 | use std::fs; |
| @@ -1292,41 +1291,50 @@ impl PushAuthorizationTests { | |||
| 1292 | /// as soon as the git data related to that branch has been received." | 1291 | /// as soon as the git data related to that branch has been received." |
| 1293 | /// | 1292 | /// |
| 1294 | /// This test verifies the HEAD-setting behavior when: | 1293 | /// This test verifies the HEAD-setting behavior when: |
| 1295 | /// 1. A maintainer commit is pushed to the relay (git data exists) | 1294 | /// 1. Git data has already been pushed via RecursiveMaintainerStateDataPushed |
| 1296 | /// 2. A state event is published pointing to that commit with HEAD="refs/heads/develop" | 1295 | /// 2. A new state event is published with HEAD="refs/heads/develop" |
| 1297 | /// 3. The relay should update the repository's default branch to "develop" | 1296 | /// 3. The relay should update the repository's default branch to "develop" |
| 1298 | /// | 1297 | /// |
| 1299 | /// ## Fixture-First Pattern | 1298 | /// ## Fixture-First Pattern |
| 1300 | /// | 1299 | /// |
| 1301 | /// 1. **Generate**: Create TestContext and get RepoState + MaintainerState fixtures | 1300 | /// Uses HeadSetToDevelopBranch fixture which: |
| 1302 | /// (both commits are pushed as part of the fixture setup) | 1301 | /// 1. **Depends on**: RecursiveMaintainerStateDataPushed (all git data exists) |
| 1303 | /// 2. **Send**: Push maintainer commit to relay first, then publish state event with HEAD=develop | 1302 | /// 2. **Creates**: New state event with HEAD=refs/heads/develop |
| 1304 | /// 3. **Verify**: Query info/refs to verify HEAD symref points to refs/heads/develop | 1303 | /// 3. **Sends**: State event to relay |
| 1304 | /// 4. **Verify**: Query info/refs to verify HEAD symref points to refs/heads/develop | ||
| 1305 | pub async fn test_head_set_after_state_event_with_existing_commit( | 1305 | pub async fn test_head_set_after_state_event_with_existing_commit( |
| 1306 | client: &AuditClient, | 1306 | client: &AuditClient, |
| 1307 | relay_domain: &str, | 1307 | relay_domain: &str, |
| 1308 | ) -> TestResult { | 1308 | ) -> TestResult { |
| 1309 | use std::process::Command; | ||
| 1310 | |||
| 1311 | let test_name = "test_head_set_after_state_event_with_existing_commit"; | 1309 | let test_name = "test_head_set_after_state_event_with_existing_commit"; |
| 1312 | let desc = "HEAD is set when state event published with existing commit"; | 1310 | let desc = "HEAD is set when state event published with existing commit"; |
| 1313 | 1311 | ||
| 1314 | // ============================================================ | 1312 | // ============================================================ |
| 1315 | // Step 1: GENERATE - Create TestContext and get fixtures | 1313 | // Step 1: Get HeadSetToDevelopBranch fixture |
| 1314 | // This sets up everything: repo, maintainer chain, git data, and state event with HEAD=develop | ||
| 1316 | // ============================================================ | 1315 | // ============================================================ |
| 1317 | let ctx = TestContext::new(client); | 1316 | let ctx = TestContext::new(client); |
| 1318 | 1317 | ||
| 1319 | // Get RepoState fixture (owner's repo announcement + state event) | 1318 | let _develop_state_event = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { |
| 1320 | let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { | ||
| 1321 | Ok(e) => e, | 1319 | Ok(e) => e, |
| 1322 | Err(e) => { | 1320 | Err(e) => { |
| 1323 | return TestResult::new(test_name, "GRASP-01", desc) | 1321 | return TestResult::new(test_name, "GRASP-01", desc) |
| 1324 | .fail(format!("Failed to create RepoState fixture: {}", e)); | 1322 | .fail(format!("Failed to create HeadSetToDevelopBranch fixture: {}", e)); |
| 1325 | } | 1323 | } |
| 1326 | }; | 1324 | }; |
| 1327 | 1325 | ||
| 1328 | // Extract repo_id and npub from owner's state event | 1326 | // ============================================================ |
| 1329 | let repo_id = match state_event | 1327 | // Step 2: Extract repo_id and owner npub from ValidRepo (cached by fixture) |
| 1328 | // ============================================================ | ||
| 1329 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | ||
| 1330 | Ok(e) => e, | ||
| 1331 | Err(e) => { | ||
| 1332 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1333 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); | ||
| 1334 | } | ||
| 1335 | }; | ||
| 1336 | |||
| 1337 | let repo_id = match valid_repo | ||
| 1330 | .tags | 1338 | .tags |
| 1331 | .iter() | 1339 | .iter() |
| 1332 | .find(|t| t.kind() == TagKind::d()) | 1340 | .find(|t| t.kind() == TagKind::d()) |
| @@ -1335,11 +1343,11 @@ impl PushAuthorizationTests { | |||
| 1335 | Some(id) => id.to_string(), | 1343 | Some(id) => id.to_string(), |
| 1336 | None => { | 1344 | None => { |
| 1337 | return TestResult::new(test_name, "GRASP-01", desc) | 1345 | return TestResult::new(test_name, "GRASP-01", desc) |
| 1338 | .fail("Missing repo_id in state event"); | 1346 | .fail("Missing repo_id in ValidRepo"); |
| 1339 | } | 1347 | } |
| 1340 | }; | 1348 | }; |
| 1341 | 1349 | ||
| 1342 | let npub = match state_event.pubkey.to_bech32() { | 1350 | let npub = match valid_repo.pubkey.to_bech32() { |
| 1343 | Ok(n) => n, | 1351 | Ok(n) => n, |
| 1344 | Err(e) => { | 1352 | Err(e) => { |
| 1345 | return TestResult::new(test_name, "GRASP-01", desc) | 1353 | return TestResult::new(test_name, "GRASP-01", desc) |
| @@ -1347,211 +1355,8 @@ impl PushAuthorizationTests { | |||
| 1347 | } | 1355 | } |
| 1348 | }; | 1356 | }; |
| 1349 | 1357 | ||
| 1350 | let _maintainer_ann_event = match ctx.get_fixture(FixtureKind::MaintainerAnnouncement).await | ||
| 1351 | { | ||
| 1352 | Ok(e) => e, | ||
| 1353 | Err(e) => { | ||
| 1354 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1355 | "Failed to create MaintainerAnnouncement fixture: {}", | ||
| 1356 | e | ||
| 1357 | )); | ||
| 1358 | } | ||
| 1359 | }; | ||
| 1360 | |||
| 1361 | let _maintainer_state_event = match ctx.get_fixture(FixtureKind::MaintainerState).await { | ||
| 1362 | Ok(e) => e, | ||
| 1363 | Err(e) => { | ||
| 1364 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1365 | .fail(format!("Failed to create MaintainerState fixture: {}", e)); | ||
| 1366 | } | ||
| 1367 | }; | ||
| 1368 | |||
| 1369 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 1370 | |||
| 1371 | // ============================================================ | 1358 | // ============================================================ |
| 1372 | // Step 2: SEND - First push maintainer commit so relay has the git data | 1359 | // Step 3: VERIFY - Query info/refs to check the default branch |
| 1373 | // ============================================================ | ||
| 1374 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | ||
| 1375 | Ok(p) => p, | ||
| 1376 | Err(e) => { | ||
| 1377 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1378 | .fail(format!("Failed to clone repo: {}", e)); | ||
| 1379 | } | ||
| 1380 | }; | ||
| 1381 | |||
| 1382 | let cleanup = || { | ||
| 1383 | let _ = fs::remove_dir_all(&clone_path); | ||
| 1384 | }; | ||
| 1385 | |||
| 1386 | // TODO - this should be pushed inside the MaintainerState fixture. | ||
| 1387 | |||
| 1388 | // Reset to orphan state and create deterministic root commit | ||
| 1389 | // Step 1: Create orphan branch (removes all history) | ||
| 1390 | let _ = Command::new("git") | ||
| 1391 | .args(["checkout", "--orphan", "main"]) | ||
| 1392 | .current_dir(&clone_path) | ||
| 1393 | .output(); | ||
| 1394 | |||
| 1395 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 1396 | let _ = Command::new("git") | ||
| 1397 | .args(["rm", "-rf", "--cached", "."]) | ||
| 1398 | .current_dir(&clone_path) | ||
| 1399 | .output(); | ||
| 1400 | |||
| 1401 | // Step 3: Create deterministic commit using Maintainer variant | ||
| 1402 | let commit_hash = match create_deterministic_commit_with_variant( | ||
| 1403 | &clone_path, | ||
| 1404 | CommitVariant::Maintainer, | ||
| 1405 | ) { | ||
| 1406 | Ok(h) => h, | ||
| 1407 | Err(e) => { | ||
| 1408 | cleanup(); | ||
| 1409 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1410 | .fail(format!("Failed to create maintainer commit: {}", e)); | ||
| 1411 | } | ||
| 1412 | }; | ||
| 1413 | |||
| 1414 | // Verify commit hash matches expected | ||
| 1415 | if commit_hash != MAINTAINER_DETERMINISTIC_COMMIT_HASH { | ||
| 1416 | cleanup(); | ||
| 1417 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1418 | "Maintainer commit hash mismatch: got {}, expected {}", | ||
| 1419 | commit_hash, MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1420 | )); | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | // Push the develop branch with the maintainer commit | ||
| 1424 | let push_output = Command::new("git") | ||
| 1425 | .args(["push", "origin", "main"]) | ||
| 1426 | .current_dir(&clone_path) | ||
| 1427 | .env("GIT_TERMINAL_PROMPT", "0") | ||
| 1428 | .output(); | ||
| 1429 | |||
| 1430 | match push_output { | ||
| 1431 | Err(e) => { | ||
| 1432 | cleanup(); | ||
| 1433 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1434 | .fail(format!("Failed to push develop branch: {}", e)); | ||
| 1435 | } | ||
| 1436 | Ok(output) if !output.status.success() => { | ||
| 1437 | // this will fail when not in isolation - as the Recusive state will be the authorised state | ||
| 1438 | // but we need to do it here so the grasp server has the oid | ||
| 1439 | } | ||
| 1440 | _ => {} | ||
| 1441 | } | ||
| 1442 | |||
| 1443 | let _recursive_maintainer_ann_event = match ctx | ||
| 1444 | .get_fixture(FixtureKind::RecursiveMaintainerAnnouncement) | ||
| 1445 | .await | ||
| 1446 | { | ||
| 1447 | Ok(e) => e, | ||
| 1448 | Err(e) => { | ||
| 1449 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1450 | "Failed to create RecursiveMaintainerAnnouncement fixture: {}", | ||
| 1451 | e | ||
| 1452 | )); | ||
| 1453 | } | ||
| 1454 | }; | ||
| 1455 | |||
| 1456 | let _recursive_maintainer_state_event = | ||
| 1457 | match ctx.get_fixture(FixtureKind::RecursiveMaintainerState).await { | ||
| 1458 | Ok(e) => e, | ||
| 1459 | Err(e) => { | ||
| 1460 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1461 | .fail(format!("Failed to create MaintainerState fixture: {}", e)); | ||
| 1462 | } | ||
| 1463 | }; | ||
| 1464 | |||
| 1465 | // Verify commit hash matches expected | ||
| 1466 | if commit_hash != MAINTAINER_DETERMINISTIC_COMMIT_HASH { | ||
| 1467 | cleanup(); | ||
| 1468 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1469 | "Maintainer commit hash mismatch: got {}, expected {}", | ||
| 1470 | commit_hash, MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1471 | )); | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | // Reset to orphan state and create deterministic root commit | ||
| 1475 | // Step 1: Create orphan branch (removes all history) | ||
| 1476 | let _ = Command::new("git") | ||
| 1477 | .args(["checkout", "--orphan", "develop"]) | ||
| 1478 | .current_dir(&clone_path) | ||
| 1479 | .output(); | ||
| 1480 | |||
| 1481 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 1482 | let _ = Command::new("git") | ||
| 1483 | .args(["rm", "-rf", "--cached", "."]) | ||
| 1484 | .current_dir(&clone_path) | ||
| 1485 | .output(); | ||
| 1486 | |||
| 1487 | // ============================================================ | ||
| 1488 | // Step 3: Publish state event with HEAD pointing to develop branch | ||
| 1489 | // ============================================================ | ||
| 1490 | |||
| 1491 | // Create state event with HEAD=refs/heads/develop and develop branch pointing to maintainer commit | ||
| 1492 | let state_event = match client | ||
| 1493 | .event_builder(Kind::Custom(30618), "") | ||
| 1494 | .tag(Tag::identifier(&repo_id)) | ||
| 1495 | .tag(Tag::custom( | ||
| 1496 | TagKind::custom("HEAD"), | ||
| 1497 | vec!["refs/heads/develop".to_string()], | ||
| 1498 | )) | ||
| 1499 | .tag(Tag::custom( | ||
| 1500 | TagKind::custom("refs/heads/develop"), | ||
| 1501 | vec![MAINTAINER_DETERMINISTIC_COMMIT_HASH.to_string()], | ||
| 1502 | )) | ||
| 1503 | .build(client.maintainer_keys()) | ||
| 1504 | { | ||
| 1505 | Ok(e) => e, | ||
| 1506 | Err(e) => { | ||
| 1507 | cleanup(); | ||
| 1508 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1509 | .fail(format!("Failed to build state event: {}", e)); | ||
| 1510 | } | ||
| 1511 | }; | ||
| 1512 | |||
| 1513 | // Send the state event | ||
| 1514 | if let Err(e) = client.client().send_event(&state_event).await { | ||
| 1515 | cleanup(); | ||
| 1516 | return TestResult::new(test_name, "GRASP-01", desc) | ||
| 1517 | .fail(format!("Failed to send state event: {}", e)); | ||
| 1518 | } | ||
| 1519 | |||
| 1520 | // Wait for relay to process the state event | ||
| 1521 | tokio::time::sleep(std::time::Duration::from_millis(500)).await; | ||
| 1522 | |||
| 1523 | // // Now that state event is published, try pushing again if previous push failed | ||
| 1524 | // let push_output = Command::new("git") | ||
| 1525 | // .args(["push", "-f", "origin", "develop"]) | ||
| 1526 | // .current_dir(&clone_path) | ||
| 1527 | // .env("GIT_TERMINAL_PROMPT", "0") | ||
| 1528 | // .output(); | ||
| 1529 | |||
| 1530 | // match push_output { | ||
| 1531 | // Err(e) => { | ||
| 1532 | // cleanup(); | ||
| 1533 | // return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1534 | // "Failed to push develop branch after state event: {}", | ||
| 1535 | // e | ||
| 1536 | // )); | ||
| 1537 | // } | ||
| 1538 | // Ok(output) if !output.status.success() => { | ||
| 1539 | // cleanup(); | ||
| 1540 | // return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | ||
| 1541 | // "Push of develop branch rejected after state event: {}", | ||
| 1542 | // String::from_utf8_lossy(&output.stderr) | ||
| 1543 | // )); | ||
| 1544 | // } | ||
| 1545 | // _ => {} | ||
| 1546 | // } | ||
| 1547 | |||
| 1548 | cleanup(); | ||
| 1549 | |||
| 1550 | // Wait a bit more for HEAD to be updated | ||
| 1551 | tokio::time::sleep(std::time::Duration::from_millis(300)).await; | ||
| 1552 | |||
| 1553 | // ============================================================ | ||
| 1554 | // Step 4: VERIFY - Query info/refs to check the default branch | ||
| 1555 | // ============================================================ | 1360 | // ============================================================ |
| 1556 | let default_branch = | 1361 | let default_branch = |
| 1557 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { | 1362 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { |
| @@ -1577,8 +1382,8 @@ impl PushAuthorizationTests { | |||
| 1577 | 1382 | ||
| 1578 | /// Test that HEAD is set after git push with oids | 1383 | /// Test that HEAD is set after git push with oids |
| 1579 | pub async fn test_head_set_after_git_push_with_required_oids( | 1384 | pub async fn test_head_set_after_git_push_with_required_oids( |
| 1580 | client: &AuditClient, | 1385 | _client: &AuditClient, |
| 1581 | relay_domain: &str, | 1386 | _relay_domain: &str, |
| 1582 | ) -> TestResult { | 1387 | ) -> TestResult { |
| 1583 | let test_name = "test_head_set_after_git_push_with_required_oids"; | 1388 | let test_name = "test_head_set_after_git_push_with_required_oids"; |
| 1584 | let desc = "HEAD is set to match state event when git push sends required oids to formulate branch"; | 1389 | let desc = "HEAD is set to match state event when git push sends required oids to formulate branch"; |