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-11-27 15:55:13 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-27 15:55:13 +0000
commit33a8870b6015fb989430edbbf5810a2d7d1a5247 (patch)
tree48982bc839c2d5167eaccd963530b79acd6729ec /grasp-audit/src
parentbf93f737aeec7b0ba6d007e867a55a8528615c23 (diff)
Task 3: Refactor maintainer push authorization test to fixture-first pattern
- Deprecated setup_repo_for_maintainer helper - test_push_authorized_by_maintainer_state_only now creates own TestContext - Uses FixtureKind::RepoState and FixtureKind::MaintainerState - Uses git helpers from fixtures.rs (clone_repo, create_deterministic_commit_with_variant, try_push) - Uses CommitVariant::Maintainer and MAINTAINER_DETERMINISTIC_COMMIT_HASH - Test compiles and passes: cargo test --lib (25 passed, 0 failed)
Diffstat (limited to 'grasp-audit/src')
-rw-r--r--grasp-audit/src/fixtures.rs30
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs247
2 files changed, 247 insertions, 30 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index 02e9810..571ab20 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -1270,6 +1270,32 @@ pub async fn setup_repo_with_deterministic_commit(
1270 1270
1271/// Set up a maintainer repository with deterministic commit (state only) 1271/// Set up a maintainer repository with deterministic commit (state only)
1272/// 1272///
1273/// # Deprecated
1274///
1275/// This function is deprecated in favor of the fixture-first pattern.
1276/// Tests should create their own TestContext and use `FixtureKind::MaintainerState`
1277/// directly, following the Generate → Send → Verify pattern.
1278///
1279/// See `test_push_authorized_by_maintainer_state_only` in `push_authorization.rs` for
1280/// an example of the fixture-first pattern.
1281///
1282/// ## Migration Guide
1283///
1284/// Instead of:
1285/// ```ignore
1286/// let setup = setup_repo_for_maintainer(client, git_data_dir, relay_domain).await?;
1287/// ```
1288///
1289/// Use:
1290/// ```ignore
1291/// let ctx = TestContext::new(client);
1292/// let _state_event = ctx.get_fixture(FixtureKind::RepoState).await?;
1293/// let _maintainer_state = ctx.get_fixture(FixtureKind::MaintainerState).await?;
1294/// // Then clone, create maintainer deterministic commit, and push inline
1295/// ```
1296///
1297/// ---
1298///
1273/// This performs all the common setup steps needed for maintainer push authorization tests: 1299/// This performs all the common setup steps needed for maintainer push authorization tests:
1274/// 1. Gets RepoState fixture (owner's repo announcement + state event with owner's deterministic commit) 1300/// 1. Gets RepoState fixture (owner's repo announcement + state event with owner's deterministic commit)
1275/// 2. Gets MaintainerState fixture (maintainer's state event ONLY - no announcement) 1301/// 2. Gets MaintainerState fixture (maintainer's state event ONLY - no announcement)
@@ -1286,6 +1312,10 @@ pub async fn setup_repo_with_deterministic_commit(
1286/// which publishes MaintainerAnnouncement separately. 1312/// which publishes MaintainerAnnouncement separately.
1287/// 1313///
1288/// Returns RepoSetup which auto-cleans up the clone_path on drop 1314/// Returns RepoSetup which auto-cleans up the clone_path on drop
1315#[deprecated(
1316 since = "0.1.0",
1317 note = "Use fixture-first pattern with TestContext and FixtureKind::MaintainerState instead. See test_push_authorized_by_maintainer_state_only for example."
1318)]
1289pub async fn setup_repo_for_maintainer( 1319pub async fn setup_repo_for_maintainer(
1290 client: &crate::AuditClient, 1320 client: &crate::AuditClient,
1291 git_data_dir: &Path, 1321 git_data_dir: &Path,
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs
index 7b7e9dc..1de3fe1 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -17,9 +17,10 @@
17//! ``` 17//! ```
18 18
19use crate::{ 19use crate::{
20 clone_repo, create_commit, create_deterministic_commit, setup_repo_for_maintainer, 20 clone_repo, create_commit, create_deterministic_commit, create_deterministic_commit_with_variant,
21 setup_repo_for_recursive_maintainer, setup_repo_with_deterministic_commit, try_push, 21 setup_repo_for_recursive_maintainer, setup_repo_with_deterministic_commit, try_push,
22 AuditClient, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH, 22 AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH,
23 MAINTAINER_DETERMINISTIC_COMMIT_HASH,
23}; 24};
24use nostr_sdk::prelude::*; 25use nostr_sdk::prelude::*;
25use std::fs; 26use std::fs;
@@ -313,57 +314,243 @@ impl PushAuthorizationTests {
313 /// without publishing their own repo announcement. The maintainer is still 314 /// without publishing their own repo announcement. The maintainer is still
314 /// listed in the owner's announcement, so they're a valid maintainer. 315 /// listed in the owner's announcement, so they're a valid maintainer.
315 /// 316 ///
317 /// ## Fixture-First Pattern
318 ///
319 /// 1. **Generate**: Create TestContext, get RepoState (owner) and MaintainerState fixtures
320 /// 2. **Send**: Clone repo, create maintainer deterministic commit, push to relay
321 /// 3. **Verify**: Push should succeed because maintainer's state event authorizes this commit
322 ///
316 /// Scenario: 323 /// Scenario:
317 /// 1. Owner's repo announcement lists maintainer in maintainers tag 324 /// 1. Owner's repo announcement lists maintainer in maintainers tag
318 /// 2. Maintainer publishes ONLY a state event (no announcement) 325 /// 2. Maintainer publishes ONLY a state event (no announcement)
319 /// 3. setup_repo_for_maintainer() clones, creates maintainer commit, verifies hash, pushes 326 /// 3. Clone, create maintainer commit, verify hash, push
320 /// 4. The push should be ACCEPTED because maintainer's state event authorizes it 327 /// 4. The push should be ACCEPTED because maintainer's state event authorizes it
321 pub async fn test_push_authorized_by_maintainer_state_only( 328 pub async fn test_push_authorized_by_maintainer_state_only(
322 client: &AuditClient, 329 client: &AuditClient,
323 git_data_dir: &Path, 330 git_data_dir: &Path,
324 relay_domain: &str, 331 relay_domain: &str,
325 ) -> TestResult { 332 ) -> TestResult {
333 use std::process::Command;
334
326 let test_name = "test_push_authorized_by_maintainer_state_only"; 335 let test_name = "test_push_authorized_by_maintainer_state_only";
327 336
328 // Use setup_repo_for_maintainer which publishes ONLY the state event, no announcement 337 // ============================================================
329 match setup_repo_for_maintainer(client, git_data_dir, relay_domain).await { 338 // Step 1: GENERATE - Create TestContext and get fixtures
330 Ok(_setup) => { 339 // ============================================================
331 // Push succeeded in setup - this means the relay accepted the push 340 let ctx = TestContext::new(client);
332 // authorized by the maintainer's state event alone 341
333 TestResult::new( 342 // Get RepoState fixture (owner's repo announcement + state event)
343 let state_event = match ctx.get_fixture(FixtureKind::RepoState).await {
344 Ok(e) => e,
345 Err(e) => {
346 return TestResult::new(
334 test_name, 347 test_name,
335 "GRASP-01", 348 "GRASP-01",
336 "Push authorized by maintainer state event only (no announcement)", 349 "Push authorized by maintainer state event only (no announcement)",
337 ) 350 )
338 .pass() 351 .fail(&format!("Failed to create RepoState fixture: {}", e));
339 } 352 }
353 };
354
355 // Get MaintainerState fixture (maintainer's state event ONLY - no announcement)
356 // This tests that state-only authorization works without a maintainer announcement
357 match ctx.get_fixture(FixtureKind::MaintainerState).await {
358 Ok(_) => {}
340 Err(e) => { 359 Err(e) => {
341 // Check if this was specifically a push rejection 360 return TestResult::new(
342 if e.contains("Failed to push") { 361 test_name,
343 TestResult::new( 362 "GRASP-01",
344 test_name, 363 "Push authorized by maintainer state event only (no announcement)",
345 "GRASP-01", 364 )
346 "Push authorized by maintainer state event only (no announcement)", 365 .fail(&format!("Failed to create MaintainerState fixture: {}", e));
347 ) 366 }
348 .fail(&format!( 367 };
349 "Push was rejected but should have been accepted. \ 368
350 The maintainer published a state event with a commit hash, \ 369 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
351 and even without a separate announcement, the relay should \ 370
352 authorize pushes matching this state event since the maintainer \ 371 // Extract repo_id and npub from owner's state event
353 is listed in the owner's announcement. \ 372 let repo_id = match state_event
354 Error: {}", 373 .tags
355 e 374 .iter()
356 )) 375 .find(|t| t.kind() == TagKind::d())
357 } else { 376 .and_then(|t| t.content())
358 // Some other error during setup 377 {
359 TestResult::new( 378 Some(id) => id.to_string(),
379 None => {
380 return TestResult::new(
381 test_name,
382 "GRASP-01",
383 "Push authorized by maintainer state event only (no announcement)",
384 )
385 .fail("Missing repo_id in state event");
386 }
387 };
388
389 let npub = match state_event.pubkey.to_bech32() {
390 Ok(n) => n,
391 Err(e) => {
392 return TestResult::new(
393 test_name,
394 "GRASP-01",
395 "Push authorized by maintainer state event only (no announcement)",
396 )
397 .fail(&format!("Failed to convert pubkey to bech32: {}", e));
398 }
399 };
400
401 // Verify repo exists on disk
402 let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id));
403 if !repo_path.exists() {
404 return TestResult::new(
405 test_name,
406 "GRASP-01",
407 "Push authorized by maintainer state event only (no announcement)",
408 )
409 .fail(&format!("Repo not found: {}", repo_path.display()));
410 }
411
412 // ============================================================
413 // Step 2: SEND - Clone, create maintainer commit, push
414 // ============================================================
415 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
416 Ok(p) => p,
417 Err(e) => {
418 return TestResult::new(
419 test_name,
420 "GRASP-01",
421 "Push authorized by maintainer state event only (no announcement)",
422 )
423 .fail(&e);
424 }
425 };
426 let cleanup = || {
427 let _ = fs::remove_dir_all(&clone_path);
428 };
429
430 // Create maintainer deterministic commit
431 let commit_hash =
432 match create_deterministic_commit_with_variant(&clone_path, CommitVariant::Maintainer) {
433 Ok(h) => h,
434 Err(e) => {
435 cleanup();
436 return TestResult::new(
360 test_name, 437 test_name,
361 "GRASP-01", 438 "GRASP-01",
362 "Push authorized by maintainer state event only (no announcement)", 439 "Push authorized by maintainer state event only (no announcement)",
363 ) 440 )
364 .fail(&format!("Setup failed: {}", e)) 441 .fail(&format!("Failed to create maintainer commit: {}", e));
365 } 442 }
443 };
444
445 // Verify commit hash matches expected
446 if commit_hash != MAINTAINER_DETERMINISTIC_COMMIT_HASH {
447 cleanup();
448 return TestResult::new(
449 test_name,
450 "GRASP-01",
451 "Push authorized by maintainer state event only (no announcement)",
452 )
453 .fail(&format!(
454 "Maintainer commit hash mismatch: got {}, expected {}",
455 commit_hash, MAINTAINER_DETERMINISTIC_COMMIT_HASH
456 ));
457 }
458
459 // Create main branch
460 let branch_output = Command::new("git")
461 .args(["branch", "main"])
462 .current_dir(&clone_path)
463 .output();
464
465 match branch_output {
466 Err(e) => {
467 cleanup();
468 return TestResult::new(
469 test_name,
470 "GRASP-01",
471 "Push authorized by maintainer state event only (no announcement)",
472 )
473 .fail(&format!("Failed to create main branch: {}", e));
474 }
475 Ok(output) if !output.status.success() => {
476 cleanup();
477 return TestResult::new(
478 test_name,
479 "GRASP-01",
480 "Push authorized by maintainer state event only (no announcement)",
481 )
482 .fail(&format!(
483 "Failed to create main branch: {}",
484 String::from_utf8_lossy(&output.stderr)
485 ));
486 }
487 _ => {}
488 }
489
490 // Checkout main branch
491 let checkout_output = Command::new("git")
492 .args(["checkout", "main"])
493 .current_dir(&clone_path)
494 .output();
495
496 match checkout_output {
497 Err(e) => {
498 cleanup();
499 return TestResult::new(
500 test_name,
501 "GRASP-01",
502 "Push authorized by maintainer state event only (no announcement)",
503 )
504 .fail(&format!("Failed to checkout main branch: {}", e));
366 } 505 }
506 Ok(output) if !output.status.success() => {
507 cleanup();
508 return TestResult::new(
509 test_name,
510 "GRASP-01",
511 "Push authorized by maintainer state event only (no announcement)",
512 )
513 .fail(&format!(
514 "Failed to checkout main branch: {}",
515 String::from_utf8_lossy(&output.stderr)
516 ));
517 }
518 _ => {}
519 }
520
521 // ============================================================
522 // Step 3: VERIFY - Push should succeed because maintainer's
523 // state event authorizes this commit
524 // ============================================================
525 let push_result = try_push(&clone_path);
526 cleanup();
527
528 match push_result {
529 Ok(true) => TestResult::new(
530 test_name,
531 "GRASP-01",
532 "Push authorized by maintainer state event only (no announcement)",
533 )
534 .pass(),
535 Ok(false) => TestResult::new(
536 test_name,
537 "GRASP-01",
538 "Push authorized by maintainer state event only (no announcement)",
539 )
540 .fail(&format!(
541 "Push was rejected but should have been accepted. \
542 The maintainer published a state event with commit {}, \
543 and even without a separate announcement, the relay should \
544 authorize pushes matching this state event since the maintainer \
545 is listed in the owner's announcement.",
546 MAINTAINER_DETERMINISTIC_COMMIT_HASH
547 )),
548 Err(e) => TestResult::new(
549 test_name,
550 "GRASP-01",
551 "Push authorized by maintainer state event only (no announcement)",
552 )
553 .fail(&format!("Push error: {}", e)),
367 } 554 }
368 } 555 }
369 556