Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 18 additions & 6 deletions packages/core/src/api/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,16 @@ mod tests {
avg_fee: "213".into(),
percentiles: PercentileFees {
p10: "100".into(),
p25: "100".into(),
p20: "100".into(),
p30: "120".into(),
p40: "140".into(),
p50: "150".into(),
p75: "300".into(),
p60: "200".into(),
p70: "300".into(),
p80: "400".into(),
p90: "500".into(),
p95: "800".into(),
p99: "1200".into(),
},
};

Expand All @@ -527,17 +532,24 @@ mod tests {
}

#[test]
fn percentile_fees_has_all_six_fields() {
fn percentile_fees_has_all_fields() {
let p = PercentileFees {
p10: "100".into(),
p25: "100".into(),
p20: "100".into(),
p30: "120".into(),
p40: "140".into(),
p50: "150".into(),
p75: "300".into(),
p60: "200".into(),
p70: "300".into(),
p80: "400".into(),
p90: "500".into(),
p95: "800".into(),
p99: "1200".into(),
};
let json = serde_json::to_value(&p).unwrap();
for field in &["p10", "p25", "p50", "p75", "p90", "p95"] {
for field in &[
"p10", "p20", "p30", "p40", "p50", "p60", "p70", "p80", "p90", "p95", "p99",
] {
assert!(json.get(field).is_some(), "missing field: {}", field);
assert!(!json[field].as_str().unwrap().is_empty());
}
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,11 +494,16 @@ mod tests {
max: "5000".into(),
avg: "213".into(),
p10: "100".into(),
p25: "100".into(),
p20: "100".into(),
p30: "120".into(),
p40: "140".into(),
p50: "150".into(),
p75: "300".into(),
p60: "200".into(),
p70: "300".into(),
p80: "400".into(),
p90: "500".into(),
p95: "800".into(),
p99: "1200".into(),
},
};

Expand Down
36 changes: 26 additions & 10 deletions packages/core/src/services/horizon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,24 +260,34 @@ mod tests {
let json = r#"{
"min": "100",
"max": "5000",
"avg": "213",
"mode": "213",
"p10": "100",
"p25": "100",
"p20": "100",
"p30": "120",
"p40": "140",
"p50": "150",
"p75": "300",
"p60": "200",
"p70": "300",
"p80": "400",
"p90": "500",
"p95": "800"
"p95": "800",
"p99": "1200"
}"#;
let fc: FeeCharged = serde_json::from_str(json).unwrap();
assert_eq!(fc.min, "100");
assert_eq!(fc.max, "5000");
assert_eq!(fc.avg, "213");
assert_eq!(fc.p10, "100");
assert_eq!(fc.p25, "100");
assert_eq!(fc.p20, "100");
assert_eq!(fc.p30, "120");
assert_eq!(fc.p40, "140");
assert_eq!(fc.p50, "150");
assert_eq!(fc.p75, "300");
assert_eq!(fc.p60, "200");
assert_eq!(fc.p70, "300");
assert_eq!(fc.p80, "400");
assert_eq!(fc.p90, "500");
assert_eq!(fc.p95, "800");
assert_eq!(fc.p99, "1200");
}

#[test]
Expand All @@ -287,18 +297,24 @@ mod tests {
"fee_charged": {
"min": "100",
"max": "5000",
"avg": "213",
"mode": "213",
"p10": "100",
"p25": "100",
"p20": "100",
"p30": "120",
"p40": "140",
"p50": "150",
"p75": "300",
"p60": "200",
"p70": "300",
"p80": "400",
"p90": "500",
"p95": "800"
"p95": "800",
"p99": "1200"
}
}"#;
let stats: HorizonFeeStats = serde_json::from_str(json).unwrap();
assert_eq!(stats.last_ledger_base_fee, "100");
assert_eq!(stats.fee_charged.p50, "150");
assert_eq!(stats.fee_charged.p95, "800");
assert_eq!(stats.fee_charged.p99, "1200");
}
}
32 changes: 32 additions & 0 deletions packages/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,38 @@ cargo test -p stellar-devkit
cargo test -p stellar-devkit --test harness_congested
```

## Mock Horizon Server

The harness exposes canned `GET /fee_stats` payloads through `HorizonMock` and the JSON fixtures in `src/harness/scenarios/`.

```bash
# Start with the baseline fixture
cargo test -p stellar-devkit --test harness_normal -- --nocapture

# Swap to a higher-pressure fixture
cargo test -p stellar-devkit --test harness_congested -- --nocapture
```

Scenario flags map directly to the fixture you load in your test setup:

- `normal` for a low-fee baseline
- `congested` for sustained high-fee demand
- `spike` for a sudden short-lived fee jump
- `recovery` for a return from congestion toward baseline

```rust
use std::path::Path;

use stellar_devkit::harness::{
horizon_mock::HorizonMock,
scenarios::load_from_file,
};

let payload = load_from_file(Path::new("src/harness/scenarios/spike.json"))?;
let mock = HorizonMock::new(payload);
assert!(mock.fee_stats_payload().contains("\"scenario\": \"spike\""));
```

## Adding to Your Crate

```toml
Expand Down
4 changes: 3 additions & 1 deletion packages/devkit/src/harness/horizon_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ pub struct HorizonMock {

impl HorizonMock {
pub fn new(fee_stats_response: impl Into<String>) -> Self {
Self { fee_stats_response: fee_stats_response.into() }
Self {
fee_stats_response: fee_stats_response.into(),
}
}

/// Returns the canned response body for `GET /fee_stats`.
Expand Down
9 changes: 7 additions & 2 deletions packages/devkit/src/harness/scenarios/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Pre-built test scenarios for the Stellar fee tracker harness.

use std::path::Path;

/// Cycles through a list of scenario names, returning the next one each call.
pub struct ScenarioRotator {
scenarios: Vec<String>,
Expand All @@ -8,7 +10,10 @@ pub struct ScenarioRotator {

impl ScenarioRotator {
pub fn new(scenarios: Vec<String>) -> Self {
Self { scenarios, index: 0 }
Self {
scenarios,
index: 0,
}
}

/// Returns the current scenario name and advances to the next.
Expand All @@ -20,7 +25,7 @@ impl ScenarioRotator {
self.index = (self.index + 1) % self.scenarios.len();
Some(current)
}
use std::path::Path;
}

/// Loads a scenario JSON file from the given path and returns its contents.
pub fn load_from_file(path: &Path) -> std::io::Result<String> {
Expand Down
Loading
Loading