Tests for the log's fields and the metrics Grafana reads (#403)
Three tests for what the dashboard and the alert rules read and nothing checked: every ipx_ metric named in grafana/dashboard.py and alerts.py (read out of them at compile time) is served by render, with the ones written only once known (counts, the last scan, a job under way) set first; ipx's JSON log lines for a scan's events carry ev, feed, new, bytes, msg and error.type, and inside a span trace_id and span_id; the directory's snapshot line carries instance, accepted and the refused_ counts. The JSON layer was built the same way in both services' setup; it is otel::json_layer now, so the tests run the layer production does. The log tests set the global subscriber, as production does: under a scoped one WithTrace finds no current span (#408). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -393,6 +393,46 @@ mod tests {
|
||||
assert_eq!(call(&app, "DELETE", &format!("/feed/{id}"), None, None).await.0, 405);
|
||||
}
|
||||
|
||||
/// What the log layer wrote, kept for the test to read.
|
||||
struct Lines(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
|
||||
|
||||
impl std::io::Write for Lines {
|
||||
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.lock().unwrap().extend_from_slice(b);
|
||||
Ok(b.len())
|
||||
}
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_snapshots_log_line_has_the_fields_grafana_reads() {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
let buf = std::sync::Arc::new(std::sync::Mutex::new(vec![]));
|
||||
let b = buf.clone();
|
||||
// Global, as the directory's own is; other tests' snapshots land here too, and each of
|
||||
// their lines must carry the same fields.
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::registry().with(crate::otel::json_layer(move || Lines(b.clone()), false)),
|
||||
)
|
||||
.unwrap();
|
||||
let (app, _dir) = app().await;
|
||||
let snap = serde_json::json!([{ "url": "https://x.test/log.xml", "subscribers": 1 }]);
|
||||
assert_eq!(call(&app, "POST", "/v1/snapshot", Some("rw"), Some(snap)).await.0, 200);
|
||||
|
||||
let text = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
|
||||
let lines: Vec<serde_json::Value> = text.lines().map(|l| serde_json::from_str(l).expect(l)).collect();
|
||||
let snaps: Vec<&serde_json::Value> = lines.iter().filter(|l| l["message"] == "snapshot").collect();
|
||||
assert!(!snaps.is_empty(), "no snapshot line in {text}");
|
||||
for l in snaps {
|
||||
assert!(l["instance"].is_string(), "{l}");
|
||||
for n in ["accepted", "refused_private", "refused_blocked", "refused_invalid"] {
|
||||
assert!(l[n].is_u64(), "{n} in {l}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn each_access_level_does_what_it_may_and_no_more() {
|
||||
let (app, dir) = app().await;
|
||||
|
||||
Reference in New Issue
Block a user