upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 22:25:39 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 22:25:39 +0000
commit504eaf4f5aba93a3e935bbee76042dd35cada666 (patch)
treee079d5002c08d655a3c686f1458b8d1e60c4e0b3 /grasp-audit/src
parent11870a0f810accf0431d82a74b6fd3adec9d23df (diff)
better fixtures: test_head_set_after_state_event_with_existing_commit
Diffstat (limited to 'grasp-audit/src')
-rw-r--r--grasp-audit/src/fixtures.rs89
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs251
2 files changed, 117 insertions, 223 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index d054e36..bda7a78 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -200,6 +200,26 @@ pub enum FixtureKind {
200 /// - NOT sent to relay (use `client.send_event()` to publish when ready) 200 /// - NOT sent to relay (use `client.send_event()` to publish when ready)
201 PREventGenerated, 201 PREventGenerated,
202 202
203 /// HEAD set to 'develop' branch via state event
204 ///
205 /// This fixture tests that HEAD is updated when a state event is published
206 /// with HEAD pointing to a different branch that already has git data pushed.
207 ///
208 /// GRASP-01: "MUST set repository HEAD per repository state announcement
209 /// as soon as the git data related to that branch has been received."
210 ///
211 /// Stages:
212 /// 1. **Depends on**: RecursiveMaintainerStateDataPushed (all git data exists on main)
213 /// 2. **Creates**: New state event with HEAD=refs/heads/develop pointing to existing commit
214 /// 3. **Sends**: State event to relay
215 /// 4. **Verifies**: Can be checked via get_default_branch_from_info_refs
216 ///
217 /// - Requires RecursiveMaintainerStateDataPushed (establishes full maintainer chain with git data)
218 /// - Creates state event signed by maintainer keys (`client.maintainer_keys()`)
219 /// - Points refs/heads/develop to RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH
220 /// - Sets HEAD to refs/heads/develop
221 HeadSetToDevelopBranch,
222
203 /// Wrong commit pushed to refs/nostr/<pr-event-id> BEFORE PR event is sent 223 /// Wrong commit pushed to refs/nostr/<pr-event-id> BEFORE PR event is sent
204 /// 224 ///
205 /// This is a "DataPushed" stage fixture for testing pre-event ref behavior. 225 /// This is a "DataPushed" stage fixture for testing pre-event ref behavior.
@@ -327,6 +347,10 @@ impl FixtureKind {
327 // RecursiveMaintainerStateDataPushed depends on MaintainerStateDataPushed 347 // RecursiveMaintainerStateDataPushed depends on MaintainerStateDataPushed
328 // (recursive maintainer force-pushes over maintainer's data) 348 // (recursive maintainer force-pushes over maintainer's data)
329 Self::RecursiveMaintainerStateDataPushed => vec![Self::MaintainerStateDataPushed], 349 Self::RecursiveMaintainerStateDataPushed => vec![Self::MaintainerStateDataPushed],
350
351 // HeadSetToDevelopBranch depends on RecursiveMaintainerStateDataPushed
352 // (all git data already exists, we just publish a new state event)
353 Self::HeadSetToDevelopBranch => vec![Self::RecursiveMaintainerStateDataPushed],
330 } 354 }
331 } 355 }
332 356
@@ -349,6 +373,8 @@ impl FixtureKind {
349 Self::PRWrongCommitPushedBeforeEvent => true, 373 Self::PRWrongCommitPushedBeforeEvent => true,
350 // PREventSentAfterWrongPush sends the PR event internally 374 // PREventSentAfterWrongPush sends the PR event internally
351 Self::PREventSentAfterWrongPush => true, 375 Self::PREventSentAfterWrongPush => true,
376 // HeadSetToDevelopBranch sends its state event internally
377 Self::HeadSetToDevelopBranch => true,
352 // All other fixtures return a single event for the caller to send 378 // All other fixtures return a single event for the caller to send
353 _ => false, 379 _ => false,
354 } 380 }
@@ -868,6 +894,10 @@ impl<'a> TestContext<'a> {
868 FixtureKind::RecursiveMaintainerStateDataPushed => { 894 FixtureKind::RecursiveMaintainerStateDataPushed => {
869 self.build_recursive_maintainer_state_data_pushed().await 895 self.build_recursive_maintainer_state_data_pushed().await
870 } 896 }
897
898 FixtureKind::HeadSetToDevelopBranch => {
899 self.build_head_set_to_develop_branch().await
900 }
871 } 901 }
872 } 902 }
873 903
@@ -1474,6 +1504,65 @@ impl<'a> TestContext<'a> {
1474 } 1504 }
1475 } 1505 }
1476 1506
1507 /// Build HeadSetToDevelopBranch fixture: creates state event with HEAD=develop
1508 ///
1509 /// This tests that HEAD is updated when a state event is published with HEAD
1510 /// pointing to a different branch that already has git data pushed.
1511 ///
1512 /// GRASP-01: "MUST set repository HEAD per repository state announcement
1513 /// as soon as the git data related to that branch has been received."
1514 ///
1515 /// Depends on RecursiveMaintainerStateDataPushed - all git data already exists.
1516 /// We just create a new state event with HEAD=refs/heads/develop pointing to
1517 /// the already-pushed commit.
1518 ///
1519 /// # Returns
1520 /// The state event (kind 30618) with HEAD=refs/heads/develop after it's sent
1521 async fn build_head_set_to_develop_branch(&self) -> Result<Event> {
1522 use nostr_sdk::prelude::*;
1523
1524 // ============================================================
1525 // Stage 1: RecursiveMaintainerStateDataPushed is ensured by ensure_fixture before this is called
1526 // All git data already exists on the relay (main branch with RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH)
1527 // ============================================================
1528 let recursive_state = self.get_cached_dependency(FixtureKind::RecursiveMaintainerStateDataPushed)?;
1529
1530 // Extract repo_id from the recursive maintainer's state event
1531 let repo_id = self.extract_repo_id(&recursive_state)?;
1532
1533 // ============================================================
1534 // Stage 2: Create state event with HEAD=refs/heads/develop
1535 // ============================================================
1536 // Use the same commit hash that's already pushed to the relay
1537 // but point HEAD to develop branch instead of main
1538 let base_time = Timestamp::now().as_u64();
1539 let develop_timestamp = Timestamp::from(base_time - 1); // 1 second ago (most recent)
1540
1541 let develop_state_event = self
1542 .client
1543 .event_builder(Kind::Custom(30618), "")
1544 .tag(Tag::identifier(&repo_id))
1545 .tag(Tag::custom(
1546 TagKind::custom("HEAD"),
1547 vec!["refs/heads/develop".to_string()],
1548 ))
1549 .tag(Tag::custom(
1550 TagKind::custom("refs/heads/develop"),
1551 vec![RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH.to_string()],
1552 ))
1553 .custom_time(develop_timestamp)
1554 .build(self.client.maintainer_keys())
1555 .map_err(|e| anyhow::anyhow!("Failed to build develop state event: {}", e))?;
1556
1557 // Send state event to relay
1558 self.client.send_event(develop_state_event.clone()).await?;
1559
1560 // Wait for relay to process the state event
1561 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
1562
1563 Ok(develop_state_event)
1564 }
1565
1477 /// Build PRWrongCommitPushedBeforeEvent fixture 1566 /// Build PRWrongCommitPushedBeforeEvent fixture
1478 /// 1567 ///
1479 /// This fixture sets up a scenario where: 1568 /// This fixture sets up a scenario where:
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";
34use crate::{ 34use 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};
39use nostr_sdk::prelude::*; 38use nostr_sdk::prelude::*;
40use std::fs; 39use 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";