-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathscaffold.rs
More file actions
151 lines (131 loc) · 5.78 KB
/
Copy pathscaffold.rs
File metadata and controls
151 lines (131 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Scaffolding integration test — demo and sample.
//!
//! This file is a **scaffolding sample** that demonstrates the integration-test
//! pattern adopted by this project. It is not intended to provide unique test
//! coverage. Instead, its purpose is to:
//!
//! - Verify that multiple top-level integration-test binaries can run
//! concurrently without port or configuration conflicts.
//! - Show future contributors how to add a new integration-test binary for
//! a different tracker configuration or lifecycle scenario.
//!
//! # Architecture
//!
//! Each top-level `tests/*.rs` file is a **separate OS process** (Cargo
//! integration-test binary). A binary runs **one tracker application
//! instance** with a fixed initial configuration. Scenario functions run
//! sequentially against that instance.
//!
//! A different initial configuration belongs in another binary.
//! For example, `tests/bootstrap.rs` would exercise the startup/shutdown
//! lifecycle, while `tests/stats.rs` exercises the global statistics API
//! under one configuration.
//!
//! ## Shared Helpers
//!
//! Common utilities live in [`tests/common/`](../common/index.html).
//! Import with `mod common;`.
//!
//! ## Requirements
//!
//! - Port `0` for all service bind addresses.
//! - Isolated temporary workspace per suite (`EphemeralTrackerWorkspace`).
//! - Registration-acknowledgement readiness for every configured service.
//! - Sequential scenarios that account for accumulated state.
//!
//! ## Endpoint Discovery
//!
//! Endpoint discovery uses side-effect-free runtime-registry snapshots. Helpers
//! select services by canonical role or exact configuration identity rather
//! than bind-IP conventions, registration delays, or registry-map ordering.
//!
//! # Example: Running this test
//!
//! ```text
//! cargo test --test scaffold
//! ```
//!
//! Both `stats` and `scaffold` binaries can run in parallel:
//!
//! ```text
//! cargo test --test stats --test scaffold
//! ```
mod common;
use serde::Deserialize;
use torrust_tracker_rest_api_client::connection_info::{ConnectionInfo, Origin};
use torrust_tracker_rest_api_client::v1::client::ApiHttpClient as TrackerApiClient;
use url::Url;
use crate::common::EphemeralTrackerWorkspace;
/// Demo: the stats API should aggregate announces across multiple trackers.
///
/// This is a scaffolding sample that reproduces the global-stats scenario
/// to demonstrate that a second integration-test binary can boot its own
/// tracker application without conflicting with the main suite.
#[tokio::test]
async fn the_stats_api_endpoint_should_aggregate_announces_across_multiple_trackers() {
// ── 1. Configuration ──────────────────────────────────────────────
let config_toml = r#"
[metadata]
app = "torrust-tracker"
purpose = "configuration"
schema_version = "2.0.0"
[logging]
threshold = "off"
[core]
listed = false
private = false
[core.database]
driver = "sqlite3"
path = "{STORAGE_PATH}/sqlite3.db"
[[http_trackers]]
bind_address = "0.0.0.0:0"
tracker_usage_statistics = true
[[http_trackers]]
bind_address = "0.0.0.0:0"
tracker_usage_statistics = true
[http_api]
bind_address = "127.0.0.1:0"
[http_api.access_tokens]
admin = "MyAccessToken"
[health_check_api]
bind_address = "127.0.0.2:0"
"#;
// ── 2. Start tracker on isolated workspace ───────────────────────
let workspace = EphemeralTrackerWorkspace::new(config_toml);
let (app_container, _jobs) = common::start_tracker_with_config(&workspace).await;
// ── 3. Discover bound addresses ──────────────────────────────────
let tracker_urls = common::http_tracker_urls(&app_container).await;
assert_eq!(tracker_urls.len(), 2, "expected two HTTP trackers");
let api_url = common::http_api_url(&app_container).await.expect("expected an HTTP API URL");
// ── 4. Scenario: announce to both trackers ───────────────────────
let client = reqwest::Client::new();
for url in &tracker_urls {
let announce_url = url
.join("/announce?info_hash=%9c8b%22%13%e3%0b%ff%21%2b0%c3%60%d2o%9a%02%13d%22&peer_id=-qB00000000000000001&port=17548&ip=127.0.0.1&event=started&compact=0")
.expect("announce URL should be valid");
let resp = client.get(announce_url.as_str()).send().await.unwrap();
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
panic!("announce to {url} failed: status {status}, body: {body}");
}
}
// ── 5. Scenario: verify global stats ─────────────────────────────
let stats = get_stats(&api_url, "MyAccessToken").await;
assert_eq!(stats.tcp4_announces_handled, 2, "two announces should be aggregated");
// The tracker application and its temporary workspace are cleaned up
// when `workspace` and `_jobs` are dropped at the end of this scope.
}
/// Statistics subset relevant to this demo.
#[derive(Deserialize)]
struct DemoStats {
tcp4_announces_handled: u64,
}
async fn get_stats(api_url: &Url, token: &str) -> DemoStats {
let response = TrackerApiClient::new(ConnectionInfo::authenticated(Origin::new(api_url.as_str()).unwrap(), token))
.unwrap()
.get_tracker_statistics(None)
.await
.expect("failed to get tracker statistics");
response.json::<DemoStats>().await.expect("failed to parse JSON response")
}