diff --git a/tests/fixtures/labeler/http/empty/first_page.json b/tests/fixtures/labeler/http/empty/first_page.json new file mode 100644 index 0000000..f984261 --- /dev/null +++ b/tests/fixtures/labeler/http/empty/first_page.json @@ -0,0 +1 @@ +{"cursor":null,"labels":[]} diff --git a/tests/fixtures/labeler/http/healthy/second_page.json b/tests/fixtures/labeler/http/healthy/second_page.json new file mode 100644 index 0000000..1f9000f --- /dev/null +++ b/tests/fixtures/labeler/http/healthy/second_page.json @@ -0,0 +1 @@ +{"cursor":null,"labels":[{"ver":1,"src":"did:plc:test123456789abcdefghijklmnop","uri":"at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/def1","val":"inappropriate","neg":false,"cts":"2026-01-03T00:00:00.000Z"}]} diff --git a/tests/fixtures/labeler/http/ignored_cursor/first_page.json b/tests/fixtures/labeler/http/ignored_cursor/first_page.json new file mode 100644 index 0000000..11cbff0 --- /dev/null +++ b/tests/fixtures/labeler/http/ignored_cursor/first_page.json @@ -0,0 +1 @@ +{"cursor":"cursor1","labels":[{"ver":1,"src":"did:plc:test123456789abcdefghijklmnop","uri":"at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/abc1","val":"spam","neg":false,"cts":"2026-01-01T00:00:00.000Z"}]} diff --git a/tests/fixtures/labeler/http/ignored_cursor/second_page.json b/tests/fixtures/labeler/http/ignored_cursor/second_page.json new file mode 100644 index 0000000..b3f1c91 --- /dev/null +++ b/tests/fixtures/labeler/http/ignored_cursor/second_page.json @@ -0,0 +1 @@ +{"cursor":null,"labels":[{"ver":1,"src":"did:plc:test123456789abcdefghijklmnop","uri":"at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/abc1","val":"spam","neg":false,"cts":"2026-01-01T00:00:00.000Z"}]} diff --git a/tests/fixtures/labeler/http/malformed/first_page.json b/tests/fixtures/labeler/http/malformed/first_page.json new file mode 100644 index 0000000..ea644b2 --- /dev/null +++ b/tests/fixtures/labeler/http/malformed/first_page.json @@ -0,0 +1 @@ +{"cursor":null,"labels":42} diff --git a/tests/labeler_http.rs b/tests/labeler_http.rs new file mode 100644 index 0000000..0526745 --- /dev/null +++ b/tests/labeler_http.rs @@ -0,0 +1,162 @@ +//! Integration tests for the labeler HTTP stage using snapshot tests. + +mod common; + +use atproto_devtool::commands::test::labeler::http::run; +use common::FakeRawHttpTee; + +/// Helper to render a report to a string for snapshot testing. +fn render_report_to_string( + report: &atproto_devtool::commands::test::labeler::report::LabelerReport, +) -> String { + let mut buf = Vec::new(); + report + .render( + &mut buf, + &atproto_devtool::commands::test::labeler::report::RenderConfig { no_color: true }, + ) + .expect("render failed"); + String::from_utf8(buf).expect("invalid utf-8") +} + +#[tokio::test] +async fn http_healthy_renders_all_ok() { + let tee = FakeRawHttpTee::new(); + // Load fixture pages with real labels and pagination. + let first_page = include_bytes!("fixtures/labeler/http/healthy/first_page.json").to_vec(); + let second_page = include_bytes!("fixtures/labeler/http/healthy/second_page.json").to_vec(); + + tee.add_response(None, 200, first_page); + tee.add_response(Some("cursor1"), 200, second_page); + + let output = run(&tee).await; + + // Build a minimal report for snapshot testing. + let mut report = atproto_devtool::commands::test::labeler::report::LabelerReport::new( + atproto_devtool::commands::test::labeler::report::ReportHeader { + target: "https://example.com".to_string(), + resolved_did: None, + pds_endpoint: None, + labeler_endpoint: Some("https://example.com".to_string()), + }, + ); + + for result in output.results { + report.record(result); + } + report.finish(); + + let rendered = render_report_to_string(&report); + insta::assert_snapshot!(rendered); +} + +#[tokio::test] +async fn http_empty_labeler_emits_advisory() { + let tee = FakeRawHttpTee::new(); + let empty_page = include_bytes!("fixtures/labeler/http/empty/first_page.json").to_vec(); + + tee.add_response(None, 200, empty_page); + + let output = run(&tee).await; + + let mut report = atproto_devtool::commands::test::labeler::report::LabelerReport::new( + atproto_devtool::commands::test::labeler::report::ReportHeader { + target: "https://example.com".to_string(), + resolved_did: None, + pds_endpoint: None, + labeler_endpoint: Some("https://example.com".to_string()), + }, + ); + + for result in output.results { + report.record(result); + } + report.finish(); + + let rendered = render_report_to_string(&report); + insta::assert_snapshot!(rendered); +} + +#[tokio::test] +async fn http_malformed_schema_fails_with_source_span() { + let tee = FakeRawHttpTee::new(); + let malformed = include_bytes!("fixtures/labeler/http/malformed/first_page.json").to_vec(); + + tee.add_response(None, 200, malformed.clone()); + + let output = run(&tee).await; + + let mut report = atproto_devtool::commands::test::labeler::report::LabelerReport::new( + atproto_devtool::commands::test::labeler::report::ReportHeader { + target: "https://example.com".to_string(), + resolved_did: None, + pds_endpoint: None, + labeler_endpoint: Some("https://example.com".to_string()), + }, + ); + + for result in output.results { + report.record(result); + } + report.finish(); + + let rendered = render_report_to_string(&report); + insta::assert_snapshot!(rendered); +} + +#[tokio::test] +async fn http_ignored_cursor_fails() { + let tee = FakeRawHttpTee::new(); + // Load fixture with identical pages (cursor ignored). + let first_page = + include_bytes!("fixtures/labeler/http/ignored_cursor/first_page.json").to_vec(); + let second_page = + include_bytes!("fixtures/labeler/http/ignored_cursor/second_page.json").to_vec(); + + tee.add_response(None, 200, first_page); + tee.add_response(Some("cursor1"), 200, second_page); + + let output = run(&tee).await; + + let mut report = atproto_devtool::commands::test::labeler::report::LabelerReport::new( + atproto_devtool::commands::test::labeler::report::ReportHeader { + target: "https://example.com".to_string(), + resolved_did: None, + pds_endpoint: None, + labeler_endpoint: Some("https://example.com".to_string()), + }, + ); + + for result in output.results { + report.record(result); + } + report.finish(); + + let rendered = render_report_to_string(&report); + insta::assert_snapshot!(rendered); +} + +#[tokio::test] +async fn http_transport_error_renders_network_error() { + let tee = FakeRawHttpTee::new(); + tee.set_transport_error(); + + let output = run(&tee).await; + + let mut report = atproto_devtool::commands::test::labeler::report::LabelerReport::new( + atproto_devtool::commands::test::labeler::report::ReportHeader { + target: "https://example.com".to_string(), + resolved_did: None, + pds_endpoint: None, + labeler_endpoint: Some("https://example.com".to_string()), + }, + ); + + for result in output.results { + report.record(result); + } + report.finish(); + + let rendered = render_report_to_string(&report); + insta::assert_snapshot!(rendered); +} diff --git a/tests/snapshots/labeler_http__http_empty_labeler_emits_advisory.snap b/tests/snapshots/labeler_http__http_empty_labeler_emits_advisory.snap new file mode 100644 index 0000000..ab72667 --- /dev/null +++ b/tests/snapshots/labeler_http__http_empty_labeler_emits_advisory.snap @@ -0,0 +1,15 @@ +--- +source: tests/labeler_http.rs +expression: rendered +--- +Target: https://example.com + Labeler endpoint: https://example.com + elapsed: 0ms + +== HTTP == +[OK] Labeler endpoint is reachable +[OK] First page schema is valid +[WARN] Labeler has no published labels +[OK] First page was complete; pagination not exercised + +Summary: 3 passed, 0 failed (spec), 0 network errors, 1 advisories, 0 skipped. Exit code: 0 diff --git a/tests/snapshots/labeler_http__http_healthy_renders_all_ok.snap b/tests/snapshots/labeler_http__http_healthy_renders_all_ok.snap new file mode 100644 index 0000000..c43079a --- /dev/null +++ b/tests/snapshots/labeler_http__http_healthy_renders_all_ok.snap @@ -0,0 +1,15 @@ +--- +source: tests/labeler_http.rs +expression: rendered +--- +Target: https://example.com + Labeler endpoint: https://example.com + elapsed: 0ms + +== HTTP == +[OK] Labeler endpoint is reachable +[OK] First page schema is valid +[OK] Second page schema is valid +[OK] Pagination round-trip successful + +Summary: 4 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0 diff --git a/tests/snapshots/labeler_http__http_ignored_cursor_fails.snap b/tests/snapshots/labeler_http__http_ignored_cursor_fails.snap new file mode 100644 index 0000000..d22228b --- /dev/null +++ b/tests/snapshots/labeler_http__http_ignored_cursor_fails.snap @@ -0,0 +1,15 @@ +--- +source: tests/labeler_http.rs +expression: rendered +--- +Target: https://example.com + Labeler endpoint: https://example.com + elapsed: 0ms + +== HTTP == +[OK] Labeler endpoint is reachable +[OK] First page schema is valid +[OK] Second page schema is valid +[FAIL] Labeler ignored the cursor parameter + +Summary: 3 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1 diff --git a/tests/snapshots/labeler_http__http_malformed_schema_fails_with_source_span.snap b/tests/snapshots/labeler_http__http_malformed_schema_fails_with_source_span.snap new file mode 100644 index 0000000..683943b --- /dev/null +++ b/tests/snapshots/labeler_http__http_malformed_schema_fails_with_source_span.snap @@ -0,0 +1,21 @@ +--- +source: tests/labeler_http.rs +expression: rendered +--- +Target: https://example.com + Labeler endpoint: https://example.com + elapsed: 0ms + +== HTTP == +[OK] Labeler endpoint is reachable +[FAIL] Schema validation failed + labeler::http::schema_failure + + × Failed to decode query_labels response: invalid type: integer `42`, expected a sequence at line 1 column 27 + ╭─[https://example.com/xrpc/com.atproto.label.queryLabels:1:27] + 1 │ {"cursor":null,"labels":42} + · ┬ + · ╰── JSON error + ╰──── + +Summary: 1 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1 diff --git a/tests/snapshots/labeler_http__http_transport_error_renders_network_error.snap b/tests/snapshots/labeler_http__http_transport_error_renders_network_error.snap new file mode 100644 index 0000000..55d0d3a --- /dev/null +++ b/tests/snapshots/labeler_http__http_transport_error_renders_network_error.snap @@ -0,0 +1,13 @@ +--- +source: tests/labeler_http.rs +assertion_line: 162 +expression: rendered +--- +Target: https://example.com + Labeler endpoint: https://example.com + elapsed: 0ms + +== HTTP == +[NET] Network error: tcp connect: connection refused + +Summary: 0 passed, 0 failed (spec), 1 network errors, 0 advisories, 0 skipped. Exit code: 0