diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-19 14:25:27 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-19 15:04:00 +0000 |
| commit | 9372ad649b6c438b1e4645f1dbe95c0f648bb80d (patch) | |
| tree | a2f95431711bde64713aeb72f3a7dcc65ffe58cc /tests | |
| parent | 16833501a1004a5a661a729e4fd2dbcbeaecd1d5 (diff) | |
fix: archive_read_only creates bare repos for archived announcements
Combined Accept and AcceptArchive match arms in builder.rs to ensure
bare repositories are created for both cases. Previously AcceptArchive
had duplicate code that didn't call ensure_bare_repository().
Also includes:
- Config fix: effective_git_data_path() respects explicit paths with memory backend
- TestRelay: Added git_data_path() and archive config support for testing
- Integration tests for archive_read_only behavior
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/archive_read_only.rs | 368 | ||||
| -rw-r--r-- | tests/common/relay.rs | 92 |
2 files changed, 459 insertions, 1 deletions
diff --git a/tests/archive_read_only.rs b/tests/archive_read_only.rs new file mode 100644 index 0000000..be6959b --- /dev/null +++ b/tests/archive_read_only.rs | |||
| @@ -0,0 +1,368 @@ | |||
| 1 | //! Archive Read-Only Mode Integration Tests | ||
| 2 | //! | ||
| 3 | //! Tests that verify archive_read_only mode behavior: | ||
| 4 | //! - Bare git repositories are created for announcements | ||
| 5 | //! - Git data is synced via relay-to-relay sync (purgatory sync) | ||
| 6 | //! - Git pushes are rejected (read-only mode) | ||
| 7 | //! | ||
| 8 | //! # Test Strategy | ||
| 9 | //! | ||
| 10 | //! These tests verify the GRASP-05 archive mode with read_only flag: | ||
| 11 | //! 1. Source relay has full repository (announcement + state events + git data) | ||
| 12 | //! 2. Archive relay syncs from source relay (relay-to-relay sync) | ||
| 13 | //! 3. State events trigger purgatory sync which fetches git data | ||
| 14 | //! 4. Git data is validated against Nostr state events | ||
| 15 | //! 5. Git pushes are rejected (read-only enforcement) | ||
| 16 | //! | ||
| 17 | //! # Security Model | ||
| 18 | //! | ||
| 19 | //! Archive mode uses the existing purgatory sync infrastructure to ensure: | ||
| 20 | //! - Git data is validated against Nostr state events | ||
| 21 | //! - "Naughty git servers" can't provide incorrect state | ||
| 22 | //! - Same security guarantees as normal relay operation | ||
| 23 | //! | ||
| 24 | //! # Running Tests | ||
| 25 | //! | ||
| 26 | //! ```bash | ||
| 27 | //! # Run all archive read-only tests | ||
| 28 | //! cargo test --test archive_read_only | ||
| 29 | //! | ||
| 30 | //! # Run specific test | ||
| 31 | //! cargo test --test archive_read_only test_archive_read_only_creates_bare_repo | ||
| 32 | //! | ||
| 33 | //! # With output for debugging | ||
| 34 | //! cargo test --test archive_read_only -- --nocapture | ||
| 35 | //! ``` | ||
| 36 | |||
| 37 | mod common; | ||
| 38 | |||
| 39 | use common::{ | ||
| 40 | check_ref_at_commit, create_repo_announcement, create_state_event, | ||
| 41 | create_test_repo_with_commit, push_to_relay, wait_for_event_served, wait_for_sync_connection, | ||
| 42 | CommitVariant, TestRelay, | ||
| 43 | }; | ||
| 44 | use nostr_sdk::prelude::*; | ||
| 45 | use std::time::Duration; | ||
| 46 | |||
| 47 | /// Test that archive_read_only mode creates bare git repositories and syncs data | ||
| 48 | /// via relay-to-relay sync (purgatory sync infrastructure). | ||
| 49 | /// | ||
| 50 | /// Scenario: | ||
| 51 | /// 1. Start source relay with full repository (announcement + state + git data) | ||
| 52 | /// 2. Start archive relay with archive_all=true, archive_read_only=true, syncing from source | ||
| 53 | /// 3. Archive relay syncs announcement and state events from source | ||
| 54 | /// 4. State events trigger purgatory sync which fetches git data from source's clone URL | ||
| 55 | /// 5. Verify bare repository is created and git data is synced | ||
| 56 | /// 6. Verify git pushes are rejected (read-only mode) | ||
| 57 | #[tokio::test] | ||
| 58 | async fn test_archive_read_only_creates_bare_repo() { | ||
| 59 | // 1. Start source relay | ||
| 60 | let source_relay = TestRelay::start().await; | ||
| 61 | let keys = Keys::generate(); | ||
| 62 | let identifier = "archive-test-repo"; | ||
| 63 | |||
| 64 | // Pre-allocate archive relay port so we can include it in announcement | ||
| 65 | let archive_port = TestRelay::find_free_port(); | ||
| 66 | let archive_domain = format!("127.0.0.1:{}", archive_port); | ||
| 67 | |||
| 68 | // 2. Create test repository locally with deterministic commit | ||
| 69 | let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); | ||
| 70 | let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest) | ||
| 71 | .expect("Failed to create test repo"); | ||
| 72 | |||
| 73 | let npub = keys.public_key().to_bech32().expect("Failed to get npub"); | ||
| 74 | |||
| 75 | // 3. Create and send announcement listing BOTH relays | ||
| 76 | // This ensures the archive relay will accept the state event when it syncs | ||
| 77 | let announcement = create_repo_announcement( | ||
| 78 | &keys, | ||
| 79 | &[&source_relay.domain(), &archive_domain], | ||
| 80 | identifier, | ||
| 81 | ); | ||
| 82 | |||
| 83 | let source_client = Client::new(keys.clone()); | ||
| 84 | source_client | ||
| 85 | .add_relay(source_relay.url()) | ||
| 86 | .await | ||
| 87 | .expect("Failed to add source relay"); | ||
| 88 | source_client.connect().await; | ||
| 89 | |||
| 90 | // Wait for connection | ||
| 91 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 92 | |||
| 93 | // Send announcement to source relay | ||
| 94 | source_client | ||
| 95 | .send_event(&announcement) | ||
| 96 | .await | ||
| 97 | .expect("Failed to send announcement to source"); | ||
| 98 | |||
| 99 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 100 | |||
| 101 | // 4. Create and send state event | ||
| 102 | let clone_urls = [ | ||
| 103 | format!( | ||
| 104 | "http://{}/{}/{}.git", | ||
| 105 | source_relay.domain(), | ||
| 106 | npub, | ||
| 107 | identifier | ||
| 108 | ), | ||
| 109 | format!("http://{}/{}/{}.git", archive_domain, npub, identifier), | ||
| 110 | ]; | ||
| 111 | let relay_urls = [ | ||
| 112 | source_relay.url().to_string(), | ||
| 113 | format!("ws://{}", archive_domain), | ||
| 114 | ]; | ||
| 115 | |||
| 116 | let state_event = create_state_event( | ||
| 117 | &keys, | ||
| 118 | identifier, | ||
| 119 | &[("main", &commit_hash)], | ||
| 120 | &[], | ||
| 121 | &[&clone_urls[0], &clone_urls[1]], | ||
| 122 | &[&relay_urls[0], &relay_urls[1]], | ||
| 123 | ) | ||
| 124 | .expect("Failed to create state event"); | ||
| 125 | |||
| 126 | let state_event_id = state_event.id; | ||
| 127 | |||
| 128 | // Send state event to source relay (goes to purgatory - no git data yet) | ||
| 129 | source_client | ||
| 130 | .send_event(&state_event) | ||
| 131 | .await | ||
| 132 | .expect("Failed to send state event to source"); | ||
| 133 | |||
| 134 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 135 | |||
| 136 | // 5. Push git data to source relay | ||
| 137 | // The state event in purgatory authorizes this push | ||
| 138 | push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier) | ||
| 139 | .expect("Push to source should succeed"); | ||
| 140 | |||
| 141 | // After push, state event should be released from purgatory on source relay | ||
| 142 | wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5)) | ||
| 143 | .await | ||
| 144 | .expect("State event should be served on source relay after push"); | ||
| 145 | |||
| 146 | // 6. Start archive relay with archive_all=true, archive_read_only=true, syncing from source | ||
| 147 | let archive_relay = TestRelay::start_with_archive_and_sync( | ||
| 148 | archive_port, | ||
| 149 | Some(source_relay.url().to_string()), | ||
| 150 | false, // negentropy enabled | ||
| 151 | true, // archive_all | ||
| 152 | true, // archive_read_only | ||
| 153 | ) | ||
| 154 | .await; | ||
| 155 | |||
| 156 | // Wait for sync connection to establish | ||
| 157 | wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5)) | ||
| 158 | .await | ||
| 159 | .expect("Sync connection should establish"); | ||
| 160 | |||
| 161 | // 7. Wait for state event to be released on archive relay | ||
| 162 | // The sync should: | ||
| 163 | // a) Fetch the announcement and state event from source relay | ||
| 164 | // b) Accept announcement (creates bare repo structure) - via archive mode | ||
| 165 | // c) Put state event in purgatory (git data missing on archive relay) | ||
| 166 | // d) Fetch git data from source relay's clone URL | ||
| 167 | // e) Release the state event from purgatory | ||
| 168 | let found = wait_for_event_served( | ||
| 169 | archive_relay.url(), | ||
| 170 | &state_event_id, | ||
| 171 | Duration::from_secs(30), // Allow time for sync + git fetch | ||
| 172 | ) | ||
| 173 | .await; | ||
| 174 | |||
| 175 | assert!( | ||
| 176 | found.is_ok(), | ||
| 177 | "State event should be served after sync fetches git data: {:?}", | ||
| 178 | found.err() | ||
| 179 | ); | ||
| 180 | |||
| 181 | // 8. Verify bare repository was created | ||
| 182 | let repo_path = archive_relay | ||
| 183 | .git_data_path() | ||
| 184 | .join(format!("{}/{}.git", npub, identifier)); | ||
| 185 | |||
| 186 | assert!( | ||
| 187 | repo_path.exists(), | ||
| 188 | "Bare repository should be created at {:?} for archive announcement", | ||
| 189 | repo_path | ||
| 190 | ); | ||
| 191 | |||
| 192 | // 9. Verify it's a bare repository (check for config file with bare = true) | ||
| 193 | let config_path = repo_path.join("config"); | ||
| 194 | assert!( | ||
| 195 | config_path.exists(), | ||
| 196 | "Git config should exist at {:?}", | ||
| 197 | config_path | ||
| 198 | ); | ||
| 199 | |||
| 200 | let config_content = tokio::fs::read_to_string(&config_path) | ||
| 201 | .await | ||
| 202 | .expect("Should read git config"); | ||
| 203 | assert!( | ||
| 204 | config_content.contains("bare = true"), | ||
| 205 | "Repository at {:?} should be bare (config should contain 'bare = true')", | ||
| 206 | repo_path | ||
| 207 | ); | ||
| 208 | |||
| 209 | // 10. Verify refs are correct on archive relay | ||
| 210 | let ref_correct = check_ref_at_commit( | ||
| 211 | &archive_domain, | ||
| 212 | &npub, | ||
| 213 | identifier, | ||
| 214 | "refs/heads/main", | ||
| 215 | &commit_hash, | ||
| 216 | ) | ||
| 217 | .await | ||
| 218 | .expect("Failed to check ref"); | ||
| 219 | |||
| 220 | assert!(ref_correct, "main branch should point to correct commit"); | ||
| 221 | |||
| 222 | // 11. Verify git pushes are rejected (read-only mode) | ||
| 223 | // Create a new commit in the source repo | ||
| 224 | tokio::fs::write(temp_dir.path().join("new_file.txt"), "new content") | ||
| 225 | .await | ||
| 226 | .expect("Failed to write new file"); | ||
| 227 | |||
| 228 | let output = tokio::process::Command::new("git") | ||
| 229 | .args(["add", "."]) | ||
| 230 | .current_dir(temp_dir.path()) | ||
| 231 | .output() | ||
| 232 | .await | ||
| 233 | .expect("Failed to git add"); | ||
| 234 | assert!(output.status.success()); | ||
| 235 | |||
| 236 | let output = tokio::process::Command::new("git") | ||
| 237 | .args(["commit", "-m", "New commit for push test"]) | ||
| 238 | .current_dir(temp_dir.path()) | ||
| 239 | .output() | ||
| 240 | .await | ||
| 241 | .expect("Failed to git commit"); | ||
| 242 | assert!(output.status.success()); | ||
| 243 | |||
| 244 | // Try to push to archive relay (should fail in read-only mode) | ||
| 245 | let push_url = format!("http://{}/{}/{}.git", archive_domain, npub, identifier); | ||
| 246 | let output = tokio::process::Command::new("git") | ||
| 247 | .args(["push", &push_url, "main"]) | ||
| 248 | .current_dir(temp_dir.path()) | ||
| 249 | .output() | ||
| 250 | .await | ||
| 251 | .expect("Failed to run git push"); | ||
| 252 | |||
| 253 | assert!( | ||
| 254 | !output.status.success(), | ||
| 255 | "Git push should be rejected in archive_read_only mode. stderr: {}", | ||
| 256 | String::from_utf8_lossy(&output.stderr) | ||
| 257 | ); | ||
| 258 | |||
| 259 | // Cleanup | ||
| 260 | source_client.disconnect().await; | ||
| 261 | archive_relay.stop().await; | ||
| 262 | source_relay.stop().await; | ||
| 263 | } | ||
| 264 | |||
| 265 | /// Test that archive mode without state events does NOT sync git data. | ||
| 266 | /// | ||
| 267 | /// This verifies the security model: archive mode only syncs git data | ||
| 268 | /// when there are state events to validate against. | ||
| 269 | /// | ||
| 270 | /// Scenario: | ||
| 271 | /// 1. Start source relay with announcement only (no state events) | ||
| 272 | /// 2. Start archive relay syncing from source | ||
| 273 | /// 3. Archive relay syncs announcement (creates bare repo) | ||
| 274 | /// 4. Verify git data is NOT synced (no state events to trigger purgatory sync) | ||
| 275 | #[tokio::test] | ||
| 276 | async fn test_archive_without_state_events_does_not_sync_git() { | ||
| 277 | // 1. Start source relay | ||
| 278 | let source_relay = TestRelay::start().await; | ||
| 279 | let keys = Keys::generate(); | ||
| 280 | let identifier = "archive-no-state-repo"; | ||
| 281 | |||
| 282 | // Pre-allocate archive relay port | ||
| 283 | let archive_port = TestRelay::find_free_port(); | ||
| 284 | let archive_domain = format!("127.0.0.1:{}", archive_port); | ||
| 285 | |||
| 286 | // 2. Create test repository locally | ||
| 287 | let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); | ||
| 288 | let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest) | ||
| 289 | .expect("Failed to create test repo"); | ||
| 290 | |||
| 291 | let npub = keys.public_key().to_bech32().expect("Failed to get npub"); | ||
| 292 | |||
| 293 | // 3. Create and send announcement listing BOTH relays (but NO state event) | ||
| 294 | let announcement = create_repo_announcement( | ||
| 295 | &keys, | ||
| 296 | &[&source_relay.domain(), &archive_domain], | ||
| 297 | identifier, | ||
| 298 | ); | ||
| 299 | |||
| 300 | let source_client = Client::new(keys.clone()); | ||
| 301 | source_client | ||
| 302 | .add_relay(source_relay.url()) | ||
| 303 | .await | ||
| 304 | .expect("Failed to add source relay"); | ||
| 305 | source_client.connect().await; | ||
| 306 | |||
| 307 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 308 | |||
| 309 | // Send announcement to source relay | ||
| 310 | source_client | ||
| 311 | .send_event(&announcement) | ||
| 312 | .await | ||
| 313 | .expect("Failed to send announcement to source"); | ||
| 314 | |||
| 315 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 316 | |||
| 317 | // 4. Push git data to source relay (but no state event to authorize it) | ||
| 318 | // This push will fail because there's no state event in purgatory | ||
| 319 | // That's expected - we're testing that archive mode doesn't blindly fetch git data | ||
| 320 | |||
| 321 | // 5. Start archive relay | ||
| 322 | let archive_relay = TestRelay::start_with_archive_and_sync( | ||
| 323 | archive_port, | ||
| 324 | Some(source_relay.url().to_string()), | ||
| 325 | false, | ||
| 326 | true, | ||
| 327 | true, | ||
| 328 | ) | ||
| 329 | .await; | ||
| 330 | |||
| 331 | // Wait for sync | ||
| 332 | wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5)) | ||
| 333 | .await | ||
| 334 | .expect("Sync connection should establish"); | ||
| 335 | |||
| 336 | // Give time for any potential git sync to happen | ||
| 337 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 338 | |||
| 339 | // 6. Verify bare repository was created (announcement was accepted) | ||
| 340 | let repo_path = archive_relay | ||
| 341 | .git_data_path() | ||
| 342 | .join(format!("{}/{}.git", npub, identifier)); | ||
| 343 | |||
| 344 | assert!( | ||
| 345 | repo_path.exists(), | ||
| 346 | "Bare repository should be created for archive announcement" | ||
| 347 | ); | ||
| 348 | |||
| 349 | // 7. Verify git data was NOT synced (no state events to trigger purgatory sync) | ||
| 350 | // Check that the commit does NOT exist in the archive relay's repo | ||
| 351 | let output = tokio::process::Command::new("git") | ||
| 352 | .args(["cat-file", "-t", &commit_hash]) | ||
| 353 | .current_dir(&repo_path) | ||
| 354 | .output() | ||
| 355 | .await; | ||
| 356 | |||
| 357 | let commit_exists = output.map(|o| o.status.success()).unwrap_or(false); | ||
| 358 | |||
| 359 | assert!( | ||
| 360 | !commit_exists, | ||
| 361 | "Git data should NOT be synced without state events (security: validates against Nostr state)" | ||
| 362 | ); | ||
| 363 | |||
| 364 | // Cleanup | ||
| 365 | source_client.disconnect().await; | ||
| 366 | archive_relay.stop().await; | ||
| 367 | source_relay.stop().await; | ||
| 368 | } | ||
diff --git a/tests/common/relay.rs b/tests/common/relay.rs index fb5d421..227849a 100644 --- a/tests/common/relay.rs +++ b/tests/common/relay.rs | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | //! Provides automatic relay lifecycle management for integration tests. | 3 | //! Provides automatic relay lifecycle management for integration tests. |
| 4 | 4 | ||
| 5 | use nostr_sdk::ToBech32; | 5 | use nostr_sdk::ToBech32; |
| 6 | use std::path::PathBuf; | ||
| 6 | use std::process::{Child, Command, Stdio}; | 7 | use std::process::{Child, Command, Stdio}; |
| 7 | use std::time::Duration; | 8 | use std::time::Duration; |
| 8 | use tokio::time::sleep; | 9 | use tokio::time::sleep; |
| @@ -15,6 +16,11 @@ pub struct TestRelay { | |||
| 15 | process: Child, | 16 | process: Child, |
| 16 | url: String, | 17 | url: String, |
| 17 | port: u16, | 18 | port: u16, |
| 19 | /// Temporary directory for git repositories | ||
| 20 | /// Kept alive for the lifetime of the relay | ||
| 21 | _git_data_dir: tempfile::TempDir, | ||
| 22 | /// Path to git data directory (for test assertions) | ||
| 23 | git_data_path: PathBuf, | ||
| 18 | } | 24 | } |
| 19 | 25 | ||
| 20 | impl TestRelay { | 26 | impl TestRelay { |
| @@ -98,6 +104,37 @@ impl TestRelay { | |||
| 98 | Self::start_with_full_options(Self::find_free_port(), bootstrap_relay_url, true).await | 104 | Self::start_with_full_options(Self::find_free_port(), bootstrap_relay_url, true).await |
| 99 | } | 105 | } |
| 100 | 106 | ||
| 107 | /// Start relay with archive configuration | ||
| 108 | /// | ||
| 109 | /// This is useful for testing GRASP-05 archive mode behavior. | ||
| 110 | /// | ||
| 111 | /// # Arguments | ||
| 112 | /// * `archive_all` - Accept all repository announcements (GRASP-05) | ||
| 113 | /// * `archive_read_only` - Reject git pushes (read-only archive mode) | ||
| 114 | /// | ||
| 115 | /// # Example | ||
| 116 | /// | ||
| 117 | /// ```no_run | ||
| 118 | /// use common::TestRelay; | ||
| 119 | /// | ||
| 120 | /// #[tokio::test] | ||
| 121 | /// async fn test_archive_mode() { | ||
| 122 | /// let relay = TestRelay::start_with_archive_config(true, true).await; | ||
| 123 | /// // ... test archive behavior ... | ||
| 124 | /// relay.stop().await; | ||
| 125 | /// } | ||
| 126 | /// ``` | ||
| 127 | pub async fn start_with_archive_config(archive_all: bool, archive_read_only: bool) -> Self { | ||
| 128 | Self::start_with_archive_and_sync( | ||
| 129 | Self::find_free_port(), | ||
| 130 | None, | ||
| 131 | false, | ||
| 132 | archive_all, | ||
| 133 | archive_read_only, | ||
| 134 | ) | ||
| 135 | .await | ||
| 136 | } | ||
| 137 | |||
| 101 | /// Start relay with options (internal, maintains backward compatibility) | 138 | /// Start relay with options (internal, maintains backward compatibility) |
| 102 | async fn start_with_options(port: u16, bootstrap_relay_url: Option<String>) -> Self { | 139 | async fn start_with_options(port: u16, bootstrap_relay_url: Option<String>) -> Self { |
| 103 | Self::start_with_full_options(port, bootstrap_relay_url, false).await | 140 | Self::start_with_full_options(port, bootstrap_relay_url, false).await |
| @@ -109,6 +146,34 @@ impl TestRelay { | |||
| 109 | bootstrap_relay_url: Option<String>, | 146 | bootstrap_relay_url: Option<String>, |
| 110 | disable_negentropy: bool, | 147 | disable_negentropy: bool, |
| 111 | ) -> Self { | 148 | ) -> Self { |
| 149 | Self::start_with_archive_and_sync( | ||
| 150 | port, | ||
| 151 | bootstrap_relay_url, | ||
| 152 | disable_negentropy, | ||
| 153 | false, | ||
| 154 | false, | ||
| 155 | ) | ||
| 156 | .await | ||
| 157 | } | ||
| 158 | |||
| 159 | /// Start relay with all options including archive configuration and sync | ||
| 160 | /// | ||
| 161 | /// This is the most flexible method for starting a test relay with all options. | ||
| 162 | /// Use this when you need both archive mode AND sync from a bootstrap relay. | ||
| 163 | /// | ||
| 164 | /// # Arguments | ||
| 165 | /// * `port` - Port to bind to | ||
| 166 | /// * `bootstrap_relay_url` - URL of relay to sync from (optional) | ||
| 167 | /// * `disable_negentropy` - Whether to disable NIP-77 negentropy sync | ||
| 168 | /// * `archive_all` - Accept all repository announcements (GRASP-05) | ||
| 169 | /// * `archive_read_only` - Reject git pushes (read-only archive mode) | ||
| 170 | pub async fn start_with_archive_and_sync( | ||
| 171 | port: u16, | ||
| 172 | bootstrap_relay_url: Option<String>, | ||
| 173 | disable_negentropy: bool, | ||
| 174 | archive_all: bool, | ||
| 175 | archive_read_only: bool, | ||
| 176 | ) -> Self { | ||
| 112 | let bind_address = format!("127.0.0.1:{}", port); | 177 | let bind_address = format!("127.0.0.1:{}", port); |
| 113 | let url = format!("ws://127.0.0.1:{}", port); | 178 | let url = format!("ws://127.0.0.1:{}", port); |
| 114 | 179 | ||
| @@ -161,9 +226,26 @@ impl TestRelay { | |||
| 161 | cmd.env("NGIT_SYNC_DISABLE_NEGENTROPY", "true"); | 226 | cmd.env("NGIT_SYNC_DISABLE_NEGENTROPY", "true"); |
| 162 | } | 227 | } |
| 163 | 228 | ||
| 229 | // Add archive configuration if requested | ||
| 230 | if archive_all { | ||
| 231 | cmd.env("NGIT_ARCHIVE_ALL", "true"); | ||
| 232 | } | ||
| 233 | if archive_read_only { | ||
| 234 | cmd.env("NGIT_ARCHIVE_READ_ONLY", "true"); | ||
| 235 | } | ||
| 236 | |||
| 164 | let process = cmd.spawn().expect("Failed to start relay process"); | 237 | let process = cmd.spawn().expect("Failed to start relay process"); |
| 165 | 238 | ||
| 166 | let relay = Self { process, url, port }; | 239 | // Store git data path for test assertions |
| 240 | let git_data_path = git_data_dir.path().to_path_buf(); | ||
| 241 | |||
| 242 | let relay = Self { | ||
| 243 | process, | ||
| 244 | url, | ||
| 245 | port, | ||
| 246 | _git_data_dir: git_data_dir, | ||
| 247 | git_data_path, | ||
| 248 | }; | ||
| 167 | 249 | ||
| 168 | // Wait for relay to be ready | 250 | // Wait for relay to be ready |
| 169 | relay.wait_for_ready().await; | 251 | relay.wait_for_ready().await; |
| @@ -181,6 +263,14 @@ impl TestRelay { | |||
| 181 | format!("127.0.0.1:{}", self.port) | 263 | format!("127.0.0.1:{}", self.port) |
| 182 | } | 264 | } |
| 183 | 265 | ||
| 266 | /// Get the git data directory path | ||
| 267 | /// | ||
| 268 | /// This is useful for test assertions that need to verify | ||
| 269 | /// git repositories were created correctly. | ||
| 270 | pub fn git_data_path(&self) -> &PathBuf { | ||
| 271 | &self.git_data_path | ||
| 272 | } | ||
| 273 | |||
| 184 | /// Wait for the relay to be ready to accept connections | 274 | /// Wait for the relay to be ready to accept connections |
| 185 | async fn wait_for_ready(&self) { | 275 | async fn wait_for_ready(&self) { |
| 186 | let max_attempts = 50; // 5 seconds total | 276 | let max_attempts = 50; // 5 seconds total |