mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(reports): add server-driven report flows (#3330)
This commit is contained in:
1 parent
bc17922b02
commit
ece622e800
471 files changed
+56262
-35932
No files matched your search
@@ -3,12 +3,43 @@
|
||||
#[path = "parity/mod.rs"]
|
||||
mod parity_support;
|
||||
|
||||
use fluxer_admin::api::generated::types::{LookupGuildResponse, SearchGuildsResponse};
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Path, RawQuery},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
};
|
||||
use fluxer_admin::api::{
|
||||
generated::types::{
|
||||
AdminReportListResponse, LookupGuildResponse, ReportAdminResponseSchema,
|
||||
SearchGuildsResponse,
|
||||
},
|
||||
types::{ReportEntry, SearchReportsResponse},
|
||||
};
|
||||
use parity_support::{
|
||||
TEST_ACCESS_TOKEN, TEST_ADMIN_SECRET, TEST_ADMIN_USER_ID, api_fixtures, capture,
|
||||
html_normalizer, rust_server,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use std::{error::Error, io};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const SEARCH_REPORTS_V2: &str = include_str!("parity/fixtures/api/search_reports_v2.json");
|
||||
const SEARCH_REPORTS_WEBHOOK: &str =
|
||||
include_str!("parity/fixtures/api/search_reports_webhook.json");
|
||||
const REPORT_WEBHOOK_DETAIL: &str = include_str!("parity/fixtures/api/report_webhook_detail.json");
|
||||
const REPORT_EVIDENCE_DETAIL: &str =
|
||||
include_str!("parity/fixtures/api/report_evidence_detail.json");
|
||||
const EVIDENCE_REPORT_ID: &str = "1556352159220498613";
|
||||
const EVIDENCE_BOT_ID: &str = "1556352159149195428";
|
||||
const EVIDENCE_WEBHOOK_ID: &str = "1556352159132418208";
|
||||
const EVIDENCE_DELETED_AUTHOR_ID: &str = "1556352159216304308";
|
||||
const WEBHOOK_ID: &str = "1556114449176200401";
|
||||
const WEBHOOK_REPORT_ID: &str = "1556114449264280806";
|
||||
const BOT_REPORT_ID: &str = "1556114449268475111";
|
||||
const BOT_USER_ID: &str = "1556114449192977621";
|
||||
const WEBHOOK_CREATOR_ID: &str = "1556114449125868741";
|
||||
|
||||
#[test]
|
||||
fn html_normalizer_canonicalizes_attribute_order_and_csrf_values() {
|
||||
@@ -130,6 +161,473 @@ async fn rust_admin_fixture_routes_cover_default_protected_routes() -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_search_v2_fixture_matches_the_generated_and_hand_written_contracts() {
|
||||
let generated: AdminReportListResponse = serde_json::from_str(SEARCH_REPORTS_V2)
|
||||
.expect("v2 report search fixture must match the generated response contract");
|
||||
let flow = generated.reports[0]
|
||||
.flow
|
||||
.as_ref()
|
||||
.expect("the v2 message report has answers");
|
||||
assert_eq!(flow.surface, "in_app");
|
||||
assert_eq!(flow.steps.len(), 4);
|
||||
assert_eq!(generated.reports[0].reason.as_deref(), Some("csam"));
|
||||
assert_eq!(generated.reports[2].reason, None);
|
||||
|
||||
let response: SearchReportsResponse = serde_json::from_str(SEARCH_REPORTS_V2)
|
||||
.expect("v2 report search fixture must match the hand-written response type");
|
||||
assert_eq!(response.total, 3);
|
||||
let [message, user, legacy] = response.reports.as_slice() else {
|
||||
panic!("expected three reports");
|
||||
};
|
||||
assert_eq!(
|
||||
message.reason_label.as_deref(),
|
||||
Some("Child sexual abuse material")
|
||||
);
|
||||
assert_eq!(message.reason_highest_priority, Some(true));
|
||||
assert_eq!(message.category.as_deref(), Some("child_safety"));
|
||||
let user_flow = user.flow.as_ref().expect("the v2 user report has answers");
|
||||
assert_eq!(user.reason.as_deref(), Some("harassment"));
|
||||
assert_eq!(user_flow.locale.as_deref(), Some("de"));
|
||||
assert_eq!(user_flow.steps[0].screen_id, "profile_intro");
|
||||
assert!(user_flow.steps[0].option_id.is_none());
|
||||
assert!(user_flow.steps[0].items.is_empty());
|
||||
assert_eq!(
|
||||
user_flow.steps[1]
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.id.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["photo", "profile_text"]
|
||||
);
|
||||
assert!(legacy.reason.is_none());
|
||||
assert!(legacy.reason_label.is_none());
|
||||
assert!(legacy.flow.is_none());
|
||||
assert!(legacy.reporter_good_faith_confirmed.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn rust_admin_renders_v2_report_fixture_with_reason_and_answers() -> Result<(), Box<dyn Error>>
|
||||
{
|
||||
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
|
||||
let rust_admin = rust_server::start(&api_endpoint)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
let client = capture::capture_client().map_err(test_error)?;
|
||||
let session = fluxer_admin::session::create_session(
|
||||
TEST_ADMIN_USER_ID,
|
||||
TEST_ACCESS_TOKEN,
|
||||
TEST_ADMIN_SECRET,
|
||||
);
|
||||
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
|
||||
|
||||
let list = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
"/reports?reason=csam",
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(list.status, 200, "{list:#?}");
|
||||
assert!(list.body.contains(r#"name="reason""#), "{}", list.body);
|
||||
assert!(
|
||||
list.body.contains("Priority: Child sexual abuse material"),
|
||||
"{}",
|
||||
list.body
|
||||
);
|
||||
assert!(
|
||||
list.body.contains(r#"data-report-reason="csam""#),
|
||||
"{}",
|
||||
list.body
|
||||
);
|
||||
assert!(
|
||||
!list.body.contains(r#"data-report-reason="harassment""#),
|
||||
"{}",
|
||||
list.body
|
||||
);
|
||||
|
||||
let detail = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
"/reports/1556008115705480394",
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(detail.status, 200, "{detail:#?}");
|
||||
for expected in [
|
||||
"Report Answers",
|
||||
"Report profile",
|
||||
"Which parts of their profile are a problem?: Pictures, Profile text",
|
||||
"Shown to the reporter in de",
|
||||
"Surface: In app",
|
||||
"Harassment or bullying",
|
||||
] {
|
||||
assert!(
|
||||
detail.body.contains(expected),
|
||||
"{expected}\n{}",
|
||||
detail.body
|
||||
);
|
||||
}
|
||||
|
||||
let legacy = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
"/reports/1556008115709674699",
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(legacy.status, 200, "{legacy:#?}");
|
||||
assert!(!legacy.body.contains("Report Answers"), "{}", legacy.body);
|
||||
assert!(
|
||||
!legacy.body.contains("data-report-reason"),
|
||||
"{}",
|
||||
legacy.body
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_webhook_fixtures_match_the_generated_and_hand_written_contracts() {
|
||||
let generated: AdminReportListResponse = serde_json::from_str(SEARCH_REPORTS_WEBHOOK)
|
||||
.expect("webhook report search fixture must match the generated response contract");
|
||||
assert_eq!(generated.reports.len(), 2);
|
||||
let response: SearchReportsResponse = serde_json::from_str(SEARCH_REPORTS_WEBHOOK)
|
||||
.expect("webhook report search fixture must match the hand-written response type");
|
||||
let [webhook, bot] = response.reports.as_slice() else {
|
||||
panic!("expected two reports");
|
||||
};
|
||||
assert_eq!(webhook.report_id, WEBHOOK_REPORT_ID);
|
||||
assert!(webhook.reported_user_id.is_none());
|
||||
assert_eq!(webhook.reported_webhook_id.as_deref(), Some(WEBHOOK_ID));
|
||||
assert_eq!(
|
||||
webhook.reported_webhook_name.as_deref(),
|
||||
Some("Harbor Bulletin")
|
||||
);
|
||||
assert_eq!(bot.reported_user_id.as_deref(), Some(BOT_USER_ID));
|
||||
assert!(bot.reported_webhook_id.is_none());
|
||||
|
||||
let generated_detail: ReportAdminResponseSchema = serde_json::from_str(REPORT_WEBHOOK_DETAIL)
|
||||
.expect("webhook report detail fixture must match the generated response contract");
|
||||
let via_generated: ReportEntry = serde_json::from_value(
|
||||
serde_json::to_value(generated_detail).expect("serialize generated detail"),
|
||||
)
|
||||
.expect("hand-written report type from the generated detail");
|
||||
let direct: ReportEntry = serde_json::from_str(REPORT_WEBHOOK_DETAIL)
|
||||
.expect("webhook report detail fixture must match the hand-written report type");
|
||||
for detail in [&via_generated, &direct] {
|
||||
assert!(detail.reported_user_id.is_none());
|
||||
assert_eq!(detail.reported_webhook_id.as_deref(), Some(WEBHOOK_ID));
|
||||
assert_eq!(
|
||||
detail.reported_webhook_creator_id.as_deref(),
|
||||
Some(WEBHOOK_CREATOR_ID)
|
||||
);
|
||||
assert_eq!(
|
||||
detail.reported_webhook_creator_global_name.as_deref(),
|
||||
Some("Morgan Owner")
|
||||
);
|
||||
assert_eq!(detail.reported_webhook_type, Some(1));
|
||||
assert_eq!(
|
||||
detail.reported_webhook_channel_id,
|
||||
detail.reported_channel_id
|
||||
);
|
||||
assert_eq!(detail.reported_webhook_guild_id, detail.reported_guild_id);
|
||||
assert_eq!(
|
||||
detail.reported_webhook_created_at.as_deref(),
|
||||
Some("2026-10-04T01:23:15.892Z")
|
||||
);
|
||||
assert!(detail.reported_webhook_application_id.is_none());
|
||||
let context = detail.message_context.as_ref().expect("message context");
|
||||
let entry = context
|
||||
.iter()
|
||||
.find(|entry| entry["webhook_id"].as_str() == Some(WEBHOOK_ID))
|
||||
.expect("the webhook message is in the context");
|
||||
assert_eq!(entry["author_id"].as_str(), Some(WEBHOOK_ID));
|
||||
assert_eq!(entry["author_username"].as_str(), Some("Harbor Bulletin"));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn rust_admin_renders_webhook_report_fixture() -> Result<(), Box<dyn Error>> {
|
||||
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
|
||||
let rust_admin = rust_server::start(&api_endpoint)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
let client = capture::capture_client().map_err(test_error)?;
|
||||
let session = fluxer_admin::session::create_session(
|
||||
TEST_ADMIN_USER_ID,
|
||||
TEST_ACCESS_TOKEN,
|
||||
TEST_ADMIN_SECRET,
|
||||
);
|
||||
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
|
||||
let webhook_marker = format!(r#"data-report-webhook="{WEBHOOK_ID}""#);
|
||||
let webhook_user_link = format!("/users/{WEBHOOK_ID}");
|
||||
|
||||
let list = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
&format!("/reports?reported_webhook_id={WEBHOOK_ID}"),
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(list.status, 200, "{list:#?}");
|
||||
assert!(list.body.contains(WEBHOOK_REPORT_ID), "{}", list.body);
|
||||
assert!(!list.body.contains(BOT_REPORT_ID), "{}", list.body);
|
||||
assert!(list.body.contains(&webhook_marker), "{}", list.body);
|
||||
assert!(list.body.contains("Harbor Bulletin"), "{}", list.body);
|
||||
assert!(list.body.contains("Channel: general"), "{}", list.body);
|
||||
assert!(!list.body.contains(&webhook_user_link), "{}", list.body);
|
||||
|
||||
let both = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
"/reports?reporter_id=1556114449113285826",
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(both.status, 200, "{both:#?}");
|
||||
assert!(
|
||||
both.body
|
||||
.contains(&format!(r#"href="/users/{BOT_USER_ID}""#)),
|
||||
"{}",
|
||||
both.body
|
||||
);
|
||||
|
||||
let detail = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
&format!("/reports/{WEBHOOK_REPORT_ID}"),
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(detail.status, 200, "{detail:#?}");
|
||||
for expected in [
|
||||
webhook_marker.clone(),
|
||||
">Webhook ID<".to_owned(),
|
||||
format!(r#"href="/reports?reported_webhook_id={WEBHOOK_ID}""#),
|
||||
"Harbor Commons".to_owned(),
|
||||
"Message Context".to_owned(),
|
||||
format!(r#"data-message-webhook="{WEBHOOK_ID}""#),
|
||||
format!(r#"href="/users/{BOT_USER_ID}""#),
|
||||
">Webhook Creator<".to_owned(),
|
||||
format!(r#"data-report-webhook-creator="{WEBHOOK_CREATOR_ID}""#),
|
||||
format!(r#"href="/users/{WEBHOOK_CREATOR_ID}""#),
|
||||
"Morgan Owner".to_owned(),
|
||||
">Webhook Type<".to_owned(),
|
||||
">Incoming<".to_owned(),
|
||||
">Webhook Created<".to_owned(),
|
||||
"Oct 4, 2026, 1:23 AM UTC".to_owned(),
|
||||
] {
|
||||
assert!(
|
||||
detail.body.contains(&expected),
|
||||
"{expected}\n{}",
|
||||
detail.body
|
||||
);
|
||||
}
|
||||
assert!(!detail.body.contains(&webhook_user_link), "{}", detail.body);
|
||||
assert!(
|
||||
!detail.body.contains("View Reported User"),
|
||||
"{}",
|
||||
detail.body
|
||||
);
|
||||
for absent in [
|
||||
">Webhook Channel ID<",
|
||||
">Webhook Guild ID<",
|
||||
">Webhook Record<",
|
||||
"data-report-webhook-creator-deleted",
|
||||
] {
|
||||
assert!(!detail.body.contains(absent), "{absent}\n{}", detail.body);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_evidence_fixture_matches_the_generated_and_hand_written_contracts() {
|
||||
let generated: ReportAdminResponseSchema = serde_json::from_str(REPORT_EVIDENCE_DETAIL)
|
||||
.expect("evidence report detail fixture must match the generated response contract");
|
||||
let via_generated: ReportEntry = serde_json::from_value(
|
||||
serde_json::to_value(generated).expect("serialize generated detail"),
|
||||
)
|
||||
.expect("hand-written report type from the generated detail");
|
||||
let direct: ReportEntry = serde_json::from_str(REPORT_EVIDENCE_DETAIL)
|
||||
.expect("evidence report detail fixture must match the hand-written report type");
|
||||
for detail in [&via_generated, &direct] {
|
||||
assert_eq!(detail.reported_user_id.as_deref(), Some(EVIDENCE_BOT_ID));
|
||||
assert_eq!(detail.reported_user_bot, Some(true));
|
||||
let snapshot = detail
|
||||
.reported_profile_snapshot
|
||||
.as_ref()
|
||||
.expect("the bot author has a profile snapshot");
|
||||
let user = snapshot.user.as_ref().expect("user snapshot");
|
||||
assert_eq!(user.id, EVIDENCE_BOT_ID);
|
||||
assert_eq!(user.username.as_deref(), Some("Harbor_Helper"));
|
||||
let member = snapshot.member.as_ref().expect("member snapshot");
|
||||
assert_eq!(member.guild_id, "1556352159090475158");
|
||||
assert!(member.joined_at.is_some());
|
||||
assert!(snapshot.guild.is_none());
|
||||
let context = detail.message_context.as_ref().expect("message context");
|
||||
let author_bot = |author_id: &str| {
|
||||
context
|
||||
.iter()
|
||||
.filter(|entry| entry["author_id"].as_str() == Some(author_id))
|
||||
.map(|entry| entry["author_bot"].clone())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(author_bot(EVIDENCE_BOT_ID), [json!(true), json!(true)]);
|
||||
assert_eq!(author_bot(EVIDENCE_WEBHOOK_ID), [Value::Null]);
|
||||
assert_eq!(author_bot(EVIDENCE_DELETED_AUTHOR_ID), [Value::Null]);
|
||||
assert_eq!(author_bot("1556352159069503633"), [json!(false)]);
|
||||
assert!(context.iter().all(|entry| {
|
||||
entry["missing_attachments"]
|
||||
.as_array()
|
||||
.is_none_or(Vec::is_empty)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn rust_admin_renders_the_evidence_report_fixture() -> Result<(), Box<dyn Error>> {
|
||||
let api_endpoint = spawn_v2_report_api().await.map_err(test_error)?;
|
||||
let rust_admin = rust_server::start(&api_endpoint)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
let client = capture::capture_client().map_err(test_error)?;
|
||||
let session = fluxer_admin::session::create_session(
|
||||
TEST_ADMIN_USER_ID,
|
||||
TEST_ACCESS_TOKEN,
|
||||
TEST_ADMIN_SECRET,
|
||||
);
|
||||
let session_cookie = format!("{}={session}", fluxer_admin::session::SESSION_COOKIE_NAME);
|
||||
let detail = capture::fetch_route(
|
||||
&client,
|
||||
rust_admin.base_url(),
|
||||
&format!("/reports/{EVIDENCE_REPORT_ID}"),
|
||||
Some(&session_cookie),
|
||||
)
|
||||
.await
|
||||
.map_err(test_error)?;
|
||||
assert_eq!(detail.status, 200, "{detail:#?}");
|
||||
for expected in [
|
||||
format!(r#"data-report-user-bot="{EVIDENCE_BOT_ID}""#),
|
||||
format!(r#"data-message-author-bot="{EVIDENCE_BOT_ID}""#),
|
||||
format!(r#"data-message-webhook="{EVIDENCE_WEBHOOK_ID}""#),
|
||||
"At Report Time".to_owned(),
|
||||
"Harbor_Helper#3966".to_owned(),
|
||||
">Community Profile<".to_owned(),
|
||||
r#"data-report-legal-hold="none""#.to_owned(),
|
||||
] {
|
||||
assert!(
|
||||
detail.body.contains(&expected),
|
||||
"{expected}\n{}",
|
||||
detail.body
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
detail.body.matches("data-message-author-bot=").count(),
|
||||
1,
|
||||
"{}",
|
||||
detail.body
|
||||
);
|
||||
assert!(
|
||||
!detail.body.contains("data-missing-attachment"),
|
||||
"{}",
|
||||
detail.body
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn spawn_v2_report_api() -> Result<String, String> {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.map_err(|error| format!("failed to bind v2 report API: {error}"))?;
|
||||
let port = listener
|
||||
.local_addr()
|
||||
.map_err(|error| format!("failed to read v2 report API address: {error}"))?
|
||||
.port();
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/admin/users/@me",
|
||||
get(|| async {
|
||||
fixture_json(include_str!("parity/fixtures/api/admin_user_me.json"))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/admin/report-reasons",
|
||||
get(|| async {
|
||||
Json(json!({"reasons": [
|
||||
{"key": "csam", "label": "Child sexual abuse material", "highest_priority": true, "legacy_category_message": "child_safety", "legacy_category_user": "child_safety", "legacy_category_guild": "child_safety"},
|
||||
{"key": "harassment", "label": "Harassment or bullying", "highest_priority": false, "legacy_category_message": "harassment", "legacy_category_user": "harassment", "legacy_category_guild": "harassment"}
|
||||
]}))
|
||||
.into_response()
|
||||
}),
|
||||
)
|
||||
.route("/admin/reports", get(v2_report_search))
|
||||
.route("/admin/reports/{report_id}", get(v2_report_detail));
|
||||
tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, app).await;
|
||||
});
|
||||
Ok(format!("http://127.0.0.1:{port}"))
|
||||
}
|
||||
|
||||
fn fixture_json(body: &'static str) -> Response {
|
||||
let value: Value = serde_json::from_str(body).expect("fixture is JSON");
|
||||
Json(value).into_response()
|
||||
}
|
||||
|
||||
fn v2_reports() -> Vec<Value> {
|
||||
[SEARCH_REPORTS_V2, SEARCH_REPORTS_WEBHOOK]
|
||||
.into_iter()
|
||||
.flat_map(|body| {
|
||||
let value: Value = serde_json::from_str(body).expect("fixture is JSON");
|
||||
value["reports"].as_array().cloned().unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn v2_report_search(RawQuery(query): RawQuery) -> Response {
|
||||
let query = query.unwrap_or_default();
|
||||
let filters = url::form_urlencoded::parse(query.as_bytes())
|
||||
.filter(|(key, value)| {
|
||||
matches!(
|
||||
key.as_ref(),
|
||||
"reason" | "reporter_id" | "reported_webhook_id"
|
||||
) && !value.is_empty()
|
||||
})
|
||||
.map(|(key, value)| (key.into_owned(), value.into_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
let reports = v2_reports()
|
||||
.into_iter()
|
||||
.filter(|report| {
|
||||
filters
|
||||
.iter()
|
||||
.all(|(key, value)| report[key.as_str()].as_str() == Some(value.as_str()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Json(json!({"reports": reports, "total": reports.len(), "offset": 0, "limit": 25}))
|
||||
.into_response()
|
||||
}
|
||||
|
||||
async fn v2_report_detail(Path(report_id): Path<String>) -> Response {
|
||||
if report_id == WEBHOOK_REPORT_ID {
|
||||
return fixture_json(REPORT_WEBHOOK_DETAIL);
|
||||
}
|
||||
if report_id == EVIDENCE_REPORT_ID {
|
||||
return fixture_json(REPORT_EVIDENCE_DETAIL);
|
||||
}
|
||||
match v2_reports()
|
||||
.into_iter()
|
||||
.find(|report| report["report_id"].as_str() == Some(report_id.as_str()))
|
||||
{
|
||||
Some(report) => Json(report).into_response(),
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
fn test_error(message: String) -> Box<dyn Error> {
|
||||
Box::new(io::Error::other(message))
|
||||
}
|
||||
Reference in new issue
Block a user