Skip to content

Commit 1c7989a

Browse files
authored
refactor(acp-nats): deduplicate agent test helpers (#50)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 82d3e12 commit 1c7989a

16 files changed

Lines changed: 408 additions & 2007 deletions

rsworkspace/crates/acp-nats/src/agent/authenticate.rs

Lines changed: 11 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -39,110 +39,11 @@ pub async fn handle<N: RequestClient, C: GetElapsed>(
3939

4040
#[cfg(test)]
4141
mod tests {
42-
use super::Bridge;
43-
use crate::config::Config;
42+
use crate::agent::test_support::{
43+
has_request_metric, mock_bridge, mock_bridge_with_metrics, set_json_response,
44+
};
4445
use crate::error::AGENT_UNAVAILABLE;
4546
use agent_client_protocol::{Agent, AuthenticateRequest, AuthenticateResponse, ErrorCode};
46-
use opentelemetry::Value;
47-
use opentelemetry::metrics::MeterProvider;
48-
use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData};
49-
use opentelemetry_sdk::metrics::{
50-
PeriodicReader, SdkMeterProvider, in_memory_exporter::InMemoryMetricExporter,
51-
};
52-
use std::time::Duration;
53-
use trogon_nats::AdvancedMockNatsClient;
54-
55-
fn has_authenticate_metric(
56-
finished_metrics: &[opentelemetry_sdk::metrics::data::ResourceMetrics],
57-
expected_success: bool,
58-
) -> bool {
59-
finished_metrics
60-
.iter()
61-
.flat_map(|rm| rm.scope_metrics())
62-
.any(|sm| {
63-
sm.metrics().any(|metric| {
64-
if metric.name() != "acp.requests" {
65-
return false;
66-
}
67-
let data = metric.data();
68-
let sum = match data {
69-
AggregatedMetrics::U64(MetricData::Sum(s)) => s,
70-
_ => return false,
71-
};
72-
sum.data_points().any(|dp| {
73-
let mut method_ok = false;
74-
let mut success_ok = false;
75-
for attr in dp.attributes() {
76-
if attr.key.as_str() == "method" {
77-
method_ok = attr.value.as_str() == "authenticate";
78-
} else if attr.key.as_str() == "success" {
79-
success_ok = attr.value == Value::from(expected_success);
80-
}
81-
}
82-
method_ok && success_ok
83-
})
84-
})
85-
})
86-
}
87-
88-
fn assert_authenticate_metric_recorded(
89-
finished_metrics: &[opentelemetry_sdk::metrics::data::ResourceMetrics],
90-
expected_success: bool,
91-
) {
92-
assert!(
93-
has_authenticate_metric(finished_metrics, expected_success),
94-
"expected acp.requests datapoint with method=authenticate, success={}",
95-
expected_success
96-
);
97-
}
98-
99-
fn mock_bridge_with_metrics() -> (
100-
AdvancedMockNatsClient,
101-
Bridge<AdvancedMockNatsClient, trogon_std::time::SystemClock>,
102-
InMemoryMetricExporter,
103-
SdkMeterProvider,
104-
) {
105-
let exporter = InMemoryMetricExporter::default();
106-
let reader = PeriodicReader::builder(exporter.clone())
107-
.with_interval(Duration::from_millis(100))
108-
.build();
109-
let provider = SdkMeterProvider::builder().with_reader(reader).build();
110-
let meter = provider.meter("acp-nats-test");
111-
112-
let mock = AdvancedMockNatsClient::new();
113-
let bridge = Bridge::new(
114-
mock.clone(),
115-
trogon_std::time::SystemClock,
116-
&meter,
117-
Config::for_test("acp"),
118-
tokio::sync::mpsc::channel(1).0,
119-
);
120-
(mock, bridge, exporter, provider)
121-
}
122-
123-
fn mock_bridge() -> (
124-
AdvancedMockNatsClient,
125-
Bridge<AdvancedMockNatsClient, trogon_std::time::SystemClock>,
126-
) {
127-
let mock = AdvancedMockNatsClient::new();
128-
let bridge = Bridge::new(
129-
mock.clone(),
130-
trogon_std::time::SystemClock,
131-
&opentelemetry::global::meter("acp-nats-test"),
132-
Config::for_test("acp"),
133-
tokio::sync::mpsc::channel(1).0,
134-
);
135-
(mock, bridge)
136-
}
137-
138-
fn set_json_response<T: serde::Serialize>(
139-
mock: &AdvancedMockNatsClient,
140-
subject: &str,
141-
resp: &T,
142-
) {
143-
let bytes = serde_json::to_vec(resp).unwrap();
144-
mock.set_response(subject, bytes.into());
145-
}
14647

14748
#[tokio::test]
14849
async fn authenticate_forwards_request_and_returns_response() {
@@ -192,7 +93,10 @@ mod tests {
19293

19394
provider.force_flush().unwrap();
19495
let finished_metrics = exporter.get_finished_metrics().unwrap();
195-
assert_authenticate_metric_recorded(&finished_metrics, true);
96+
assert!(
97+
has_request_metric(&finished_metrics, "authenticate", true),
98+
"expected acp.requests with method=authenticate, success=true"
99+
);
196100
provider.shutdown().unwrap();
197101
}
198102

@@ -205,28 +109,10 @@ mod tests {
205109

206110
provider.force_flush().unwrap();
207111
let finished_metrics = exporter.get_finished_metrics().unwrap();
208-
assert_authenticate_metric_recorded(&finished_metrics, false);
209-
provider.shutdown().unwrap();
210-
}
211-
212-
#[test]
213-
fn has_authenticate_metric_returns_false_when_metric_is_histogram() {
214-
let exporter = InMemoryMetricExporter::default();
215-
let reader = PeriodicReader::builder(exporter.clone())
216-
.with_interval(Duration::from_millis(100))
217-
.build();
218-
let provider = SdkMeterProvider::builder().with_reader(reader).build();
219-
let meter = provider.meter("test");
220-
let counter = meter.u64_counter("acp.other_metric").build();
221-
counter.add(1, &[]);
222-
let histogram = meter
223-
.f64_histogram("acp.requests")
224-
.with_description("test")
225-
.build();
226-
histogram.record(1.0, &[]);
227-
provider.force_flush().unwrap();
228-
let finished_metrics = exporter.get_finished_metrics().unwrap();
229-
assert!(!has_authenticate_metric(&finished_metrics, true));
112+
assert!(
113+
has_request_metric(&finished_metrics, "authenticate", false),
114+
"expected acp.requests with method=authenticate, success=false"
115+
);
230116
provider.shutdown().unwrap();
231117
}
232118
}

rsworkspace/crates/acp-nats/src/agent/cancel.rs

Lines changed: 3 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -83,57 +83,14 @@ pub async fn handle<N: PublishClient + FlushClient, C: GetElapsed>(
8383
#[cfg(test)]
8484
mod tests {
8585
use super::Bridge;
86+
use crate::agent::test_support::{
87+
has_error_metric, has_request_metric, mock_bridge, mock_bridge_with_metrics,
88+
};
8689
use crate::config::Config;
8790
use agent_client_protocol::{Agent, CancelNotification, ErrorCode};
88-
use opentelemetry::Value;
89-
use opentelemetry::metrics::MeterProvider;
90-
use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData};
91-
use opentelemetry_sdk::metrics::{
92-
PeriodicReader, SdkMeterProvider, in_memory_exporter::InMemoryMetricExporter,
93-
};
94-
use std::time::Duration;
9591
use trogon_nats::AdvancedMockNatsClient;
9692
use trogon_std::time::MockClock;
9793

98-
fn mock_bridge() -> (
99-
AdvancedMockNatsClient,
100-
Bridge<AdvancedMockNatsClient, trogon_std::time::SystemClock>,
101-
) {
102-
let mock = AdvancedMockNatsClient::new();
103-
let bridge = Bridge::new(
104-
mock.clone(),
105-
trogon_std::time::SystemClock,
106-
&opentelemetry::global::meter("acp-nats-test"),
107-
Config::for_test("acp"),
108-
tokio::sync::mpsc::channel(1).0,
109-
);
110-
(mock, bridge)
111-
}
112-
113-
fn mock_bridge_with_metrics() -> (
114-
AdvancedMockNatsClient,
115-
Bridge<AdvancedMockNatsClient, trogon_std::time::SystemClock>,
116-
InMemoryMetricExporter,
117-
SdkMeterProvider,
118-
) {
119-
let exporter = InMemoryMetricExporter::default();
120-
let reader = PeriodicReader::builder(exporter.clone())
121-
.with_interval(Duration::from_millis(100))
122-
.build();
123-
let provider = SdkMeterProvider::builder().with_reader(reader).build();
124-
let meter = provider.meter("acp-nats-test");
125-
126-
let mock = AdvancedMockNatsClient::new();
127-
let bridge = Bridge::new(
128-
mock.clone(),
129-
trogon_std::time::SystemClock,
130-
&meter,
131-
Config::for_test("acp"),
132-
tokio::sync::mpsc::channel(1).0,
133-
);
134-
(mock, bridge, exporter, provider)
135-
}
136-
13794
fn mock_bridge_with_clock() -> (
13895
AdvancedMockNatsClient,
13996
MockClock,
@@ -151,74 +108,6 @@ mod tests {
151108
(mock, clock, bridge)
152109
}
153110

154-
fn has_request_metric(
155-
finished_metrics: &[opentelemetry_sdk::metrics::data::ResourceMetrics],
156-
method: &str,
157-
expected_success: bool,
158-
) -> bool {
159-
finished_metrics
160-
.iter()
161-
.flat_map(|rm| rm.scope_metrics())
162-
.flat_map(|sm| sm.metrics())
163-
.find(|m| m.name() == "acp.requests")
164-
.and_then(|metric| {
165-
let data = metric.data();
166-
if let AggregatedMetrics::U64(MetricData::Sum(s)) = data {
167-
s.data_points()
168-
.find(|dp| {
169-
let mut method_ok = false;
170-
let mut success_ok = false;
171-
for attr in dp.attributes() {
172-
if attr.key.as_str() == "method" {
173-
method_ok = attr.value.as_str() == method;
174-
} else if attr.key.as_str() == "success" {
175-
success_ok = attr.value == Value::from(expected_success);
176-
}
177-
}
178-
method_ok && success_ok
179-
})
180-
.map(|_| ())
181-
} else {
182-
None
183-
}
184-
})
185-
.is_some()
186-
}
187-
188-
fn has_error_metric(
189-
finished_metrics: &[opentelemetry_sdk::metrics::data::ResourceMetrics],
190-
operation: &str,
191-
reason: &str,
192-
) -> bool {
193-
finished_metrics
194-
.iter()
195-
.flat_map(|rm| rm.scope_metrics())
196-
.flat_map(|sm| sm.metrics())
197-
.find(|m| m.name() == "acp.errors")
198-
.and_then(|metric| {
199-
let data = metric.data();
200-
if let AggregatedMetrics::U64(MetricData::Sum(s)) = data {
201-
s.data_points()
202-
.find(|dp| {
203-
let mut operation_ok = false;
204-
let mut reason_ok = false;
205-
for attr in dp.attributes() {
206-
if attr.key.as_str() == "operation" {
207-
operation_ok = attr.value.as_str() == operation;
208-
} else if attr.key.as_str() == "reason" {
209-
reason_ok = attr.value.as_str() == reason;
210-
}
211-
}
212-
operation_ok && reason_ok
213-
})
214-
.map(|_| ())
215-
} else {
216-
None
217-
}
218-
})
219-
.is_some()
220-
}
221-
222111
#[tokio::test]
223112
async fn cancel_publishes_to_correct_subject() {
224113
let (mock, bridge) = mock_bridge();
@@ -314,48 +203,6 @@ mod tests {
314203
provider.shutdown().unwrap();
315204
}
316205

317-
#[test]
318-
fn has_request_metric_returns_false_when_metric_is_histogram() {
319-
let exporter = InMemoryMetricExporter::default();
320-
let reader = PeriodicReader::builder(exporter.clone())
321-
.with_interval(Duration::from_millis(100))
322-
.build();
323-
let provider = SdkMeterProvider::builder().with_reader(reader).build();
324-
let meter = provider.meter("test");
325-
let histogram = meter
326-
.f64_histogram("acp.requests")
327-
.with_description("test")
328-
.build();
329-
histogram.record(1.0, &[]);
330-
provider.force_flush().unwrap();
331-
let finished_metrics = exporter.get_finished_metrics().unwrap();
332-
assert!(!has_request_metric(&finished_metrics, "cancel", true));
333-
provider.shutdown().unwrap();
334-
}
335-
336-
#[test]
337-
fn has_error_metric_returns_false_when_metric_is_histogram() {
338-
let exporter = InMemoryMetricExporter::default();
339-
let reader = PeriodicReader::builder(exporter.clone())
340-
.with_interval(Duration::from_millis(100))
341-
.build();
342-
let provider = SdkMeterProvider::builder().with_reader(reader).build();
343-
let meter = provider.meter("test");
344-
let histogram = meter
345-
.f64_histogram("acp.errors")
346-
.with_description("test")
347-
.build();
348-
histogram.record(1.0, &[]);
349-
provider.force_flush().unwrap();
350-
let finished_metrics = exporter.get_finished_metrics().unwrap();
351-
assert!(!has_error_metric(
352-
&finished_metrics,
353-
"cancel",
354-
"cancel_publish_failed"
355-
));
356-
provider.shutdown().unwrap();
357-
}
358-
359206
#[tokio::test]
360207
async fn cancel_publishes_to_nats() {
361208
let (mock, _clock, bridge) = mock_bridge_with_clock();

0 commit comments

Comments
 (0)