// SPDX-License-Identifier: AGPL-3.0-or-later #![recursion_limit = "256"] use axum::{ Json, Router, body::{Body, to_bytes}, http::{HeaderMap, Method, Request, StatusCode, Uri, header}, response::{IntoResponse, Response}, }; use fluxer_admin::{ api::{generated::types as generated_types, types::LookupGuildResponse}, build_router, config::{AdminConfig, ProxyConfig, RuntimeEnv}, session, }; use serde_json::{Value, json}; use tokio::net::TcpListener; use tower::ServiceExt; const SECRET_KEY: &str = "htmx-acceptance-test-secret"; const ADMIN_API_KEY_SECRET: &str = "fa_1900000000000000001_OneTimeSecretForAcceptance"; struct TestApp { router: Router, session_cookie: String, } #[tokio::test] async fn admin_api_key_create_form_renders_the_one_time_secret() { let app = setup().await; let (headers, page) = get_with_headers(&app, "/admin-api-keys", &[]).await; let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("Admin API keys page did not set csrf_token cookie\n{page}")); assert!(page.contains(r#"data-admin-result-form="true""#), "{page}"); let (status, _, response_body) = post_form_with_headers( &app, "/admin-api-keys?action=create", &[ ("HX-Request", "true"), ("HX-Boosted", "true"), ("HX-Target", "body"), ( "Cookie", &format!("{}; csrf_token={}", app.session_cookie, csrf_token), ), ], &format!("_csrf={csrf_token}&name=Acceptance+Key&acls=*"), ) .await; assert_eq!(status, StatusCode::OK, "{response_body}"); assert_full_layout(&response_body); assert!(response_body.contains(r#"hx-history="false""#)); assert!(response_body.contains(ADMIN_API_KEY_SECRET)); } #[tokio::test] async fn search_routes_return_layout_or_fragments_by_hx_target() { let app = setup().await; let cases = [ SearchCase { path: "/users?ids=1500000000000000001", result_target: "users-results", mismatch_target: "guilds-results", result_text: "SearchedUser", }, SearchCase { path: "/guilds?ids=1600000000000000001", result_target: "guilds-results", mismatch_target: "users-results", result_text: "Searched Guild", }, SearchCase { path: "/applications?owner_id=1500000000000000001", result_target: "applications-results", mismatch_target: "users-results", result_text: "Mock Application", }, ]; for case in cases { let full = get(&app, case.path, &[]).await; assert_full_layout(&full); assert!( full.contains(case.result_text), "full response for {}", case.path ); let boosted = get( &app, case.path, &[ ("HX-Request", "true"), ("HX-Boosted", "true"), ("HX-Target", "body"), ], ) .await; assert_full_layout(&boosted); assert!( boosted.contains(case.result_text), "boosted response for {}", case.path ); let fragment = get( &app, case.path, &[("HX-Request", "true"), ("HX-Target", case.result_target)], ) .await; assert_fragment(&fragment); assert!( fragment.contains(case.result_text), "fragment response for {}", case.path ); let mismatch = get( &app, case.path, &[("HX-Request", "true"), ("HX-Target", case.mismatch_target)], ) .await; assert_full_layout(&mismatch); assert!( mismatch.contains(case.result_text), "mismatched target response for {}", case.path ); } } #[tokio::test] async fn detail_tab_routes_return_layout_or_fragments_by_route_shape() { let app = setup().await; let cases = [ TabCase { full_path: "/users/1500000000000000001?tab=applications", fragment_path: "/users/1500000000000000001/tabs/applications", detail_text: "SearchedUser", tab_text: "Mock Application", }, TabCase { full_path: "/guilds/1600000000000000001?tab=applications", fragment_path: "/guilds/1600000000000000001/tabs/applications", detail_text: "Searched Guild", tab_text: "Mock Application", }, ]; for case in cases { let full = get(&app, case.full_path, &[]).await; assert_full_layout(&full); assert!( full.contains(case.detail_text), "full tab URL for {}", case.full_path ); assert!( full.contains(case.tab_text), "full tab URL for {}", case.full_path ); let fragment = get(&app, case.fragment_path, &[]).await; assert_fragment(&fragment); assert!( fragment.contains(case.tab_text), "tab endpoint fragment for {}", case.fragment_path ); } } #[tokio::test] async fn target_audit_log_tabs_request_write_entries_only() { let app = setup().await; for path in [ "/users/1500000000000000001/tabs/audit_logs", "/guilds/1600000000000000001/tabs/audit_logs", ] { let fragment = get(&app, path, &[]).await; assert!(fragment.contains("Temp ban"), "{path}\n{fragment}"); assert!(!fragment.contains("Get user"), "{path}\n{fragment}"); } } #[tokio::test] async fn audit_log_page_forwards_the_access_filter() { let app = setup().await; let all = get(&app, "/audit-logs", &[]).await; assert!(all.contains("Temp ban"), "{all}"); assert!(all.contains("Get user"), "{all}"); assert!( all.contains(r#""#), "{all}" ); let reads = get(&app, "/audit-logs?access=read", &[]).await; assert!(reads.contains("Get user"), "{reads}"); assert!(!reads.contains("Temp ban"), "{reads}"); assert!( reads.contains(r#""#), "{reads}" ); } #[tokio::test] async fn report_routes_keep_layout_and_fragment_contract() { let app = setup().await; let reports = get(&app, "/reports?q=mock", &[]).await; assert_full_layout(&reports); assert!(reports.contains("1800000000000000001"), "{reports}"); assert!( !reports.contains(r#"hx-post="/reports/bulk-resolve""#), "{reports}" ); assert!(!reports.contains(r#"name="report_ids[]""#), "{reports}"); let boosted = get( &app, "/reports?q=mock", &[ ("HX-Request", "true"), ("HX-Boosted", "true"), ("HX-Target", "body"), ], ) .await; assert_full_layout(&boosted); assert!(boosted.contains("1800000000000000001"), "{boosted}"); let detail = get(&app, "/reports/1800000000000000001", &[]).await; assert_full_layout(&detail); assert!(detail.contains("Mock report details"), "{detail}"); let fragment = get(&app, "/reports/1800000000000000001/fragment", &[]).await; assert_fragment(&fragment); assert!(fragment.contains("Mock report details"), "{fragment}"); let message_fragment = get(&app, "/reports/1800000000000000002/fragment", &[]).await; assert_fragment(&message_fragment); assert!( message_fragment.contains("Message Context"), "{message_fragment}" ); assert!( message_fragment.contains("Reported message in drawer"), "{message_fragment}" ); } #[tokio::test] async fn user_fragment_alias_returns_drawer_fragment() { let app = setup().await; let fragment = get(&app, "/users/1500000000000000001/fragment", &[]).await; assert_fragment(&fragment); assert!(fragment.contains("SearchedUser"), "{fragment}"); } #[tokio::test] async fn user_peek_alias_is_gone() { let app = setup().await; assert_eq!( get_status(&app, "/users/1500000000000000001/peek").await, StatusCode::NOT_FOUND ); } #[tokio::test] async fn drawer_triggers_use_htmx_and_native_popover() { let app = setup().await; let body = get(&app, "/users?ids=1500000000000000001", &[]).await; assert!(body.contains(r#"popovertarget="user-peek""#), "{body}"); assert!(body.contains(r##"hx-target="#user-peek-body""##), "{body}"); assert!(!body.contains("__fluxerDrawerInit"), "{body}"); } #[tokio::test] async fn jobs_poll_with_htmx_fragment_instead_of_fetch_loop() { let app = setup().await; let body = get(&app, "/jobs?status=running", &[]).await; assert_full_layout(&body); assert!(body.contains(r#"id="jobs-results""#), "{body}"); assert!(body.contains(r#"hx-trigger="every 3s""#), "{body}"); assert!(body.contains("mockJobSync"), "{body}"); assert!(body.contains(r##"hx-target="#main-content""##), "{body}"); assert!(body.contains(r#"hx-swap="innerHTML""#), "{body}"); assert!(body.contains(r#"hx-push-url="true""#), "{body}"); assert!(!body.contains("/jobs/active.json"), "{body}"); let fragment = get( &app, "/jobs?status=running", &[("HX-Request", "true"), ("HX-Target", "jobs-results")], ) .await; assert_fragment(&fragment); assert!(fragment.contains(r#"id="jobs-results""#), "{fragment}"); assert!(fragment.contains("mockJobSync"), "{fragment}"); } #[tokio::test] async fn detail_pages_return_fragment_for_main_content_htmx_target() { let app = setup().await; let guild_full = get(&app, "/guilds/1600000000000000001", &[]).await; assert_full_layout(&guild_full); assert!( guild_full.contains("Searched Guild"), "guild detail full: {guild_full}" ); let guild_htmx = get( &app, "/guilds/1600000000000000001", &[("HX-Request", "true"), ("HX-Target", "main-content")], ) .await; assert_fragment(&guild_htmx); assert!( guild_htmx.contains("Searched Guild"), "guild detail htmx fragment: {guild_htmx}" ); let report_full = get(&app, "/reports/1800000000000000001", &[]).await; assert_full_layout(&report_full); let report_htmx = get( &app, "/reports/1800000000000000001", &[("HX-Request", "true"), ("HX-Target", "main-content")], ) .await; assert_fragment(&report_htmx); let job_full = get(&app, "/jobs/1900000000000000001", &[]).await; assert_full_layout(&job_full); assert!( job_full.contains("mockJobSync"), "job detail full: {job_full}" ); let job_htmx = get( &app, "/jobs/1900000000000000001", &[("HX-Request", "true"), ("HX-Target", "main-content")], ) .await; assert_fragment(&job_htmx); assert!( job_htmx.contains("mockJobSync"), "job detail htmx fragment: {job_htmx}" ); } #[tokio::test] async fn user_account_actions_use_no_swap_htmx_toasts() { let app = setup().await; let account_path = "/users/1500000000000000001?tab=account"; let (headers, body) = get_with_headers(&app, account_path, &[]).await; let native_confirm = format!("{}{}", "confirm", "("); assert_full_layout(&body); assert!(body.contains("__fluxerAdminActionForms"), "{body}"); assert!(!body.contains(&native_confirm), "{body}"); assert!( body.contains( r#"hx-post="/users/1500000000000000001?action=send_password_reset&tab=account""# ), "{body}" ); assert!(body.contains(r##"hx-target="#flash-container""##), "{body}"); assert!(body.contains(r#"hx-swap="none""#), "{body}"); assert!(body.contains(r#"hx-push-url="false""#), "{body}"); let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("account page did not set csrf_token cookie\n{body}")); let (status, response_headers, response_body) = post_form_with_headers( &app, "/users/1500000000000000001?action=send_password_reset&tab=account", &[ ("HX-Request", "true"), ("HX-Target", "flash-container"), ( "Cookie", &format!("{}; csrf_token={}", app.session_cookie, csrf_token), ), ], &format!("_csrf={csrf_token}"), ) .await; assert_eq!(status, StatusCode::NO_CONTENT, "{response_body}"); assert_eq!( response_headers .get("HX-Reswap") .and_then(|value| value.to_str().ok()), Some("none") ); let toast = response_headers .get("X-Fluxer-Admin-Toast") .and_then(|value| value.to_str().ok()) .unwrap_or_else(|| panic!("missing toast header\n{response_body}")); assert!(toast.contains("success"), "{toast}"); assert!( toast.contains("Password reset sent successfully"), "{toast}" ); } #[tokio::test] async fn redirect_flash_actions_convert_to_htmx_toasts() { let app = setup().await; let report_path = "/reports/1800000000000000001"; let (headers, body) = get_with_headers(&app, report_path, &[]).await; let native_confirm = format!("{}{}", "confirm", "("); assert_full_layout(&body); assert!(!body.contains(&native_confirm), "{body}"); let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("report page did not set csrf_token cookie\n{body}")); let (status, response_headers, response_body) = post_form_with_headers( &app, "/reports/1800000000000000001/resolve", &[ ("HX-Request", "true"), ("HX-Target", "flash-container"), ( "Cookie", &format!("{}; csrf_token={}", app.session_cookie, csrf_token), ), ], &format!("_csrf={csrf_token}&resolution=done"), ) .await; assert_eq!(status, StatusCode::NO_CONTENT, "{response_body}"); assert_eq!( response_headers .get("HX-Reswap") .and_then(|value| value.to_str().ok()), Some("none") ); let toast = response_headers .get("X-Fluxer-Admin-Toast") .and_then(|value| value.to_str().ok()) .unwrap_or_else(|| panic!("missing toast header\n{response_body}")); assert!(toast.contains("success"), "{toast}"); assert!(toast.contains("Report resolved"), "{toast}"); } #[tokio::test] async fn mutating_admin_pages_render_usable_csrf_tokens() { let app = setup().await; let cases = [ ("/system-dms", &["/system-dms"][..]), ( "/messages", &[ "/messages?action=browse", "/messages?action=lookup", "/messages?action=lookup-by-attachment", "/messages?action=delete", ][..], ), ("/gift-codes", &["/gift-codes"][..]), ("/search-index", &["/search-index?action=reindex"][..]), ("/gateway", &["/gateway?action=reload_all"][..]), ( "/instance-config", &[ "/instance-config?action=update_gateway_rollout", "/instance-config?action=update_sso", "/instance-config?action=update_domain_migration", "/instance-config?action=update_plutonium_page", "/instance-config?action=update_experiment_delivery", ][..], ), ]; for (path, form_actions) in cases { let (headers, body) = get_with_headers(&app, path, &[]).await; let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("{path}: response did not set csrf_token cookie\n{body}")); for form_action in form_actions { assert_form_has_csrf(&body, form_action, &csrf_token); } } } #[tokio::test] async fn instance_config_registration_tables_show_copyable_urls_and_compact_pending_actions() { let app = setup().await; let body = get(&app, "/instance-config", &[]).await; assert_full_layout(&body); assert!(body.contains(r#"id="registration-url-list""#), "{body}"); assert!( body.contains( r#"value="https://app.example.test/register?registration_url=11111111-1111-4111-8111-111111111111""# ), "{body}" ); assert!( body.contains( r#"data-copy-value="https://app.example.test/register?registration_url=11111111-1111-4111-8111-111111111111""# ), "{body}" ); assert!(body.contains(r#"id="pending-registration-list""#), "{body}"); assert!(body.contains(">Applicant<"), "{body}"); assert!(!body.contains(">User ID<"), "{body}"); assert!(!body.contains(">Link ID<"), "{body}"); assert!( body.contains(r##"hx-target="#pending-registration-list""##), "{body}" ); assert!(body.contains(r#"hx-swap="outerHTML""#), "{body}"); } #[tokio::test] async fn pending_registration_actions_swap_pending_list_fragment() { let app = setup().await; let (headers, body) = get_with_headers(&app, "/instance-config", &[]).await; let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("instance config did not set csrf_token cookie\n{body}")); let (status, response_headers, response_body) = post_form_with_headers( &app, "/instance-config?action=approve_pending_registration", &[ ("HX-Request", "true"), ("HX-Target", "pending-registration-list"), ( "Cookie", &format!("{}; csrf_token={}", app.session_cookie, csrf_token), ), ], &format!("_csrf={csrf_token}&user_id=1500000000000000002"), ) .await; assert_eq!(status, StatusCode::OK, "{response_body}"); assert_fragment(&response_body); assert!(response_body.contains(r#"id="pending-registration-list""#)); assert!(response_body.contains("No pending registrations.")); assert!(!response_body.contains("PendingUser"), "{response_body}"); let toast = response_headers .get("X-Fluxer-Admin-Toast") .and_then(|value| value.to_str().ok()) .unwrap_or_else(|| panic!("missing toast header\n{response_body}")); assert!(toast.contains("success"), "{toast}"); assert!(toast.contains("Registration approved"), "{toast}"); } #[tokio::test] async fn creating_registration_url_swaps_copyable_url_list_fragment() { let app = setup().await; let (headers, body) = get_with_headers(&app, "/instance-config", &[]).await; let csrf_token = csrf_cookie(&headers) .unwrap_or_else(|| panic!("instance config did not set csrf_token cookie\n{body}")); let (status, response_headers, response_body) = post_form_with_headers( &app, "/instance-config?action=create_registration_url", &[ ("HX-Request", "true"), ("HX-Target", "registration-url-list"), ( "Cookie", &format!("{}; csrf_token={}", app.session_cookie, csrf_token), ), ], &format!("_csrf={csrf_token}®istration_url_label=Support®istration_url_max_uses=1"), ) .await; assert_eq!(status, StatusCode::OK, "{response_body}"); assert_fragment(&response_body); assert!(response_body.contains(r#"id="registration-url-list""#)); assert!( response_body.contains( "https://app.example.test/register?registration_url=11111111-1111-4111-8111-111111111111" ), "{response_body}" ); let toast = response_headers .get("X-Fluxer-Admin-Toast") .and_then(|value| value.to_str().ok()) .unwrap_or_else(|| panic!("missing toast header\n{response_body}")); assert!(toast.contains("success"), "{toast}"); assert!(toast.contains("Registration URL created"), "{toast}"); } #[tokio::test] async fn fonts_are_served_locally_content_hashed_and_immutable() { let app = setup().await; let stylesheet_path = format!( "/static/fonts/{}", fluxer_admin::fonts::STYLESHEET_FILE_NAME ); let (headers, css) = get_with_headers(&app, &stylesheet_path, &[]).await; assert_eq!( headers.get(header::CACHE_CONTROL).unwrap(), "public, max-age=31536000, immutable" ); for fragment in css.split("url('").skip(1) { let file_name = fragment.split('\'').next().unwrap(); let response = app .router .clone() .oneshot( Request::builder() .uri(format!("/static/fonts/{file_name}")) .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!( response.status(), StatusCode::OK, "missing font {file_name}" ); assert_eq!( response.headers().get(header::CONTENT_TYPE).unwrap(), "font/woff2" ); assert_eq!( response.headers().get(header::CACHE_CONTROL).unwrap(), "public, max-age=31536000, immutable" ); } let response = app .router .clone() .oneshot( Request::builder() .uri("/static/fonts/does-not-exist.woff2") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn rendered_heads_never_reference_the_static_cdn_for_fonts() { let app = setup().await; let (headers, page) = get_with_headers(&app, "/users", &[]).await; assert!( !page.contains("/fonts/ibm-plex.css"), "the admin layout still links the CDN font stylesheets" ); assert!(page.contains("/static/fonts/"), "{page}"); let csp = headers .get(header::CONTENT_SECURITY_POLICY) .and_then(|value| value.to_str().ok()) .expect("missing CSP"); assert!(csp.contains("font-src 'self';"), "font-src was {csp}"); assert!( csp.contains("style-src 'self' 'unsafe-inline';"), "style-src was {csp}" ); } struct SearchCase { path: &'static str, result_target: &'static str, mismatch_target: &'static str, result_text: &'static str, } struct TabCase { full_path: &'static str, fragment_path: &'static str, detail_text: &'static str, tab_text: &'static str, } async fn setup() -> TestApp { let api_endpoint = spawn_mock_api().await; let router = build_router(test_config(api_endpoint)); let session_value = session::create_session("1500000000000000000", "test-token", SECRET_KEY); TestApp { router, session_cookie: format!("{}={session_value}", session::SESSION_COOKIE_NAME), } } async fn get(app: &TestApp, uri: &str, headers: &[(&str, &str)]) -> String { get_with_headers(app, uri, headers).await.1 } async fn get_status(app: &TestApp, uri: &str) -> StatusCode { let response = app .router .clone() .oneshot( Request::builder() .method(Method::GET) .uri(uri) .header(header::COOKIE, &app.session_cookie) .body(Body::empty()) .unwrap(), ) .await .unwrap(); response.status() } async fn get_with_headers( app: &TestApp, uri: &str, headers: &[(&str, &str)], ) -> (HeaderMap, String) { let mut builder = Request::builder() .method(Method::GET) .uri(uri) .header(header::COOKIE, &app.session_cookie); for (name, value) in headers { builder = builder.header(*name, *value); } let response = app .router .clone() .oneshot(builder.body(Body::empty()).unwrap()) .await .unwrap(); let status = response.status(); let headers = response.headers().clone(); let body = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let text = String::from_utf8(body.to_vec()).unwrap(); assert_eq!(status, StatusCode::OK, "{text}"); (headers, text) } async fn post_form_with_headers( app: &TestApp, uri: &str, headers: &[(&str, &str)], body: &str, ) -> (StatusCode, HeaderMap, String) { let has_cookie_header = headers .iter() .any(|(name, _)| name.eq_ignore_ascii_case("cookie")); let mut builder = Request::builder() .method(Method::POST) .uri(uri) .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); if !has_cookie_header { builder = builder.header(header::COOKIE, &app.session_cookie); } for (name, value) in headers { builder = builder.header(*name, *value); } let response = app .router .clone() .oneshot(builder.body(Body::from(body.to_owned())).unwrap()) .await .unwrap(); let status = response.status(); let headers = response.headers().clone(); let body = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let text = String::from_utf8(body.to_vec()).unwrap(); (status, headers, text) } fn csrf_cookie(headers: &HeaderMap) -> Option { headers .get_all(header::SET_COOKIE) .iter() .filter_map(|value| value.to_str().ok()) .find_map(|value| { let pair = value.split(';').next()?; let token = pair .strip_prefix("__Host-csrf_token=") .or_else(|| pair.strip_prefix("csrf_token="))?; (!token.is_empty()).then(|| token.to_owned()) }) } fn assert_form_has_csrf(body: &str, action: &str, csrf_token: &str) { let marker = format!(r#"action="{action}""#); let Some(action_index) = body.find(&marker) else { panic!("missing form action {action:?}\n{body}"); }; let csrf_marker = format!(r#"name="_csrf" value="{csrf_token}""#); let tail = &body[action_index..]; let form = tail.find("").map(|end| &tail[..end]).unwrap_or(tail); assert!( form.contains(&csrf_marker), "form {action:?} did not include matching CSRF token {csrf_token:?}\n{body}" ); } fn assert_full_layout(body: &str) { assert!(body.contains(r#"id="admin-sidebar""#), "{body}"); assert!(body.contains(r#"id="main-content""#), "{body}"); } fn assert_fragment(body: &str) { assert!(!body.contains(r#"id="admin-sidebar""#), "{body}"); assert!(!body.contains(r#"id="main-content""#), "{body}"); } async fn spawn_mock_api() -> String { let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { axum::serve(listener, Router::new().fallback(mock_api)) .await .unwrap(); }); format!("http://{addr}") } async fn mock_api(method: Method, uri: Uri) -> Response { let path = uri.path().to_owned(); if method == Method::PATCH && path == "/admin/instance/config" { return json_response(instance_config()); } match (method, path.as_str()) { (Method::GET, "/admin/users/@me") => json_response(json!({ "user": admin_user() })), (Method::GET, "/admin/api-keys") => json_response(json!([])), (Method::POST, "/admin/api-keys") => json_response(json!({ "key_id": "1900000000000000001", "key": ADMIN_API_KEY_SECRET, "name": "Acceptance key", "created_at": "2026-07-10T15:00:00.000Z", "expires_at": null, "acls": ["*"] })), (Method::GET, "/admin/users") => { json_response(json!({ "users": [searched_user()], "total": 1 })) } (Method::GET, "/admin/users/1500000000000000001") => { json_response(json!({ "users": [searched_user()] })) } (Method::POST, "/admin/users/1500000000000000001/password-reset") => { StatusCode::NO_CONTENT.into_response() } (Method::GET, "/admin/guilds") => { json_response(json!({ "guilds": [searched_guild()], "total": 1 })) } (Method::GET, "/admin/guilds/1600000000000000001") => { json_response(json!({ "guild": searched_guild_detail() })) } (Method::GET, "/admin/applications") => { json_response(json!({ "applications": [searched_application()] })) } (Method::GET, "/admin/reports") => json_response( json!({ "reports": [searched_report()], "total": 1, "offset": 0, "limit": 25 }), ), (Method::GET, "/admin/reports/1800000000000000001") => json_response(searched_report()), (Method::GET, "/admin/reports/1800000000000000002") => { json_response(searched_message_report()) } (Method::PATCH, "/admin/reports/1800000000000000001") => json_response(json!({ "report_id": "1800000000000000001", "status": 1, "resolved_at": "2026-05-26T12:03:00.000Z", "public_comment": "done" })), (Method::GET, "/admin/jobs") => { json_response(json!({ "jobs": [searched_job()], "next_cursor": null, "cursor": null })) } (Method::GET, "/admin/jobs/1900000000000000001") => { json_response(json!({ "job": searched_job() })) } (Method::GET, "/admin/instance/config") => json_response(instance_config()), (Method::POST, "/admin/instance/registration-urls") => json_response(json!({ "registration_url": registration_url_fixture(), "code": "11111111-1111-4111-8111-111111111111", "url": "https://app.example.test/register?registration_url=11111111-1111-4111-8111-111111111111" })), (Method::DELETE, path) if path.starts_with("/admin/instance/registration-urls/") => { json_response(instance_config_without_registration_urls()) } (Method::PATCH, path) if path.starts_with("/admin/instance/pending-registrations/") => { json_response(instance_config_without_pending_registrations()) } (Method::GET, "/admin/limit-config") => json_response(limit_config()), (Method::GET, "/admin/audit-logs") => { let access = uri.query().and_then(|query| { url::form_urlencoded::parse(query.as_bytes()) .find(|(key, _)| key == "access") .map(|(_, value)| value.into_owned()) }); let logs = [ audit_log_entry("1900000000000000101", "get_user", "read"), audit_log_entry("1900000000000000102", "temp_ban", "write"), ] .into_iter() .filter(|entry| { access .as_deref() .is_none_or(|access| entry.access.to_string() == access) }) .collect::>(); json_response(json!({ "total": logs.len(), "logs": logs })) } _ => (StatusCode::NOT_FOUND, Json(json!({ "error": "not found" }))).into_response(), } } fn json_response(value: Value) -> Response { Json(value).into_response() } fn admin_user() -> Value { let mut user = user("1500000000000000000", "AdminUser"); user["acls"] = json!(["*"]); user } fn searched_user() -> Value { user("1500000000000000001", "SearchedUser") } fn user(id: &str, username: &str) -> Value { json!({ "id": id, "username": username, "discriminator": 1, "avatar": null, "banner": null, "email": "admin@example.com", "email_verified": true, "email_bounced": false, "global_name": username, "bio": null, "pronouns": null, "accent_color": null, "date_of_birth": null, "locale": "en-US", "acls": [], "traits": [], "flags": "0", "premium_flags": 0, "bot": false, "system": false, "premium_type": null, "premium_since": null, "premium_until": null, "premium_grace_ends_at": null, "premium_lifetime_sequence": null, "has_totp": false, "authenticator_types": [], "temp_banned_until": null, "pending_deletion_at": null, "pending_bulk_message_deletion_at": null, "deletion_reason_code": null, "deletion_public_reason": null, "deletion_audit_log_reason": null, "deletion_scheduled_by": null, "deletion_scheduled_at": null, "last_active_at": null, "last_active_ip": null, "last_active_ip_reverse": null, "last_active_location": null }) } fn searched_guild() -> generated_types::GuildAdminResponse { serde_json::from_value(json!({ "id": "1600000000000000001", "name": "Searched Guild", "icon": null, "banner": null, "owner_id": "1500000000000000001", "owner_username": "SearchedUser", "owner_global_name": "SearchedUser", "owner_discriminator": "0001", "member_count": 12, "features": ["COMMUNITY"], "nsfw_level": 0, "nsfw": false, "content_warning_level": 0, "content_warning_text": null })) .expect("guild search fixture must match the generated response contract") } fn searched_guild_detail() -> generated_types::LookupGuildResponseGuild { serde_json::from_value(json!({ "id": "1600000000000000001", "owner_id": "1500000000000000001", "owner_username": "SearchedUser", "owner_global_name": "SearchedUser", "owner_discriminator": "0001", "name": "Searched Guild", "vanity_url_code": null, "icon": null, "banner": null, "splash": null, "embed_splash": null, "features": ["COMMUNITY"], "verification_level": 1, "mfa_level": 0, "nsfw_level": 0, "nsfw": false, "content_warning_level": 0, "content_warning_text": null, "explicit_content_filter": 0, "default_message_notifications": 0, "afk_channel_id": null, "afk_timeout": 0, "system_channel_id": null, "system_channel_flags": 0, "rules_channel_id": null, "disabled_operations": 0, "member_count": 12, "channels": [], "roles": [] })) .expect("guild detail fixture must match the generated response contract") } #[test] fn guild_fixtures_match_generated_response_contracts() { let search = searched_guild(); assert_eq!(search.name, "Searched Guild"); assert_eq!(*search.member_count, 12); let response: LookupGuildResponse = serde_json::from_value(json!({"guild": searched_guild_detail()})).unwrap(); let detail = response.guild.unwrap(); assert_eq!(detail.name, "Searched Guild"); assert_eq!(detail.id, "1600000000000000001"); assert_eq!(detail.member_count, 12); } fn audit_log_entry( log_id: &str, action: &str, access: &str, ) -> generated_types::AdminAuditLogResponseSchema { serde_json::from_value(json!({ "log_id": log_id, "admin_user_id": "1500000000000000000", "admin_user": null, "target_type": "user", "target_id": "1500000000000000001", "target_user": null, "target_guild": null, "target_channel": null, "related_users": {}, "related_guilds": {}, "related_channels": {}, "action": action, "access": access, "audit_log_reason": null, "metadata": {}, "created_at": "2026-09-16T12:00:00.000Z" })) .expect("audit log fixture must match the generated response contract") } fn searched_application() -> Value { json!({ "id": "1700000000000000001", "name": "Mock Application", "owner_user_id": "1500000000000000001", "owner_username": "SearchedUser", "owner_global_name": "SearchedUser", "owner_discriminator": "0001", "bot_user_id": null, "bot_username": null, "bot_global_name": null, "bot_discriminator": null, "bot_is_public": true, "bot_require_code_grant": false, "oauth2_redirect_uris": [], "has_client_secret": false, "has_bot_token": false, "bot_token_preview": null, "bot_token_created_at": null, "client_secret_created_at": null, "version": 1 }) } fn searched_report() -> Value { json!({ "report_id": "1800000000000000001", "reporter_id": "1500000000000000000", "reporter_tag": "AdminUser#0001", "reported_at": "2026-05-26T12:00:00.000Z", "status": 0, "report_type": 1, "category": "other", "additional_info": "Mock report details", "reported_user_id": "1500000000000000001", "reported_user_tag": "SearchedUser#0001" }) } fn searched_message_report() -> Value { json!({ "report_id": "1800000000000000002", "reporter_id": "1500000000000000000", "reporter_tag": "AdminUser#0001", "reported_at": "2026-05-26T12:00:00.000Z", "status": 0, "report_type": 0, "category": "spam", "additional_info": "Mock message report details", "reported_user_id": "1500000000000000001", "reported_user_tag": "SearchedUser#0001", "reported_message_id": "1800000000000001001", "reported_channel_id": "1600000000000000101", "reported_channel_name": "general", "message_context": [{ "id": "1800000000000001001", "content": "Reported message in drawer", "timestamp": "2026-05-26T12:01:00.000Z", "author_id": "1500000000000000001", "author_username": "SearchedUser", "author_global_name": "SearchedUser", "author_discriminator": "0001", "author_avatar": null, "channel_id": "1600000000000000101", "attachments": [] }] }) } fn searched_job() -> Value { json!({ "job_id": "1900000000000000001", "task_type": "mockJobSync", "status": "running", "created_at": "2026-05-26T12:00:00.000Z", "progress_current": 4, "progress_total": 10, "progress_message": null, "error_message": null, "started_at": "2026-05-26T12:00:05.000Z", "completed_at": null, "attempts": 1, "max_attempts": 3, "requested_by_user_id": "1500000000000000000", "audit_log_reason": null, "jet_stream_lane": null, "jet_stream_seq": null, "run_at": null, "cancel_requested": false, "context_link": null, "payload": null, "result": null }) } fn instance_config() -> Value { json!({ "sso": { "enabled": false, "enforced": false, "display_name": null, "issuer": null, "authorization_url": null, "token_url": null, "userinfo_url": null, "jwks_url": null, "client_id": null, "client_secret_set": false, "scope": null, "allowed_domains": [], "auto_provision": false, "redirect_uri": "https://admin.example.test/oauth2_callback" }, "gateway_rollout": { "session_rollout_percentage": 100, "session_rollout_mode": "modulo", "guild_rollout_percentage": 100, "rpc_request_timeout_ms": 5000, "max_concurrent_session_starts": 16, "max_concurrent_guild_starts": 16, "voice_e2ee_scope": "guild_feature_only" }, "domain_migration": { "enabled": false, "config_version": 0, "rollout_basis_points": 0, "rollout_salt": "domain-migration-v1", "included_user_ids": [], "excluded_user_ids": [], "anonymous_rollout_basis_points": 0, "standalone_forwarding": false }, "plutonium_page": { "enabled": false, "config_version": 0, "rollout_basis_points": 0, "rollout_salt": "plutonium-page-v1", "included_user_ids": [], "excluded_user_ids": [] }, "experiment_delivery": { "poll_interval_seconds": 300, "poll_jitter_percent": 15 }, "registration": registration_config(), "self_hosted": false }) } fn registration_config() -> Value { json!({ "mode": "approval", "admin_registration_urls_enabled": true, "urls": [registration_url_fixture()], "pending_registrations": [pending_registration_fixture()] }) } fn registration_url_fixture() -> Value { json!({ "id": "11111111-1111-4111-8111-111111111111", "label": "Support batch", "created_by_user_id": "1500000000000000000", "created_at": "2026-05-26T12:00:00.000Z", "expires_at": null, "max_uses": 5, "use_count": 0, "revoked_at": null, "approval_required": true, "last_used_at": null, "last_used_by_user_id": null }) } fn pending_registration_fixture() -> Value { json!({ "user_id": "1500000000000000002", "username": "PendingUser", "discriminator": 0, "global_name": "Pending User", "email": "pending.user.with.a.long.address@example.test", "requested_at": "2026-05-26T12:10:00.000Z", "registration_url_id": "11111111-1111-4111-8111-111111111111", "client_ip": "203.0.113.24" }) } fn instance_config_without_pending_registrations() -> Value { let mut config = instance_config(); config["registration"]["pending_registrations"] = json!([]); config } fn instance_config_without_registration_urls() -> Value { let mut config = instance_config(); config["registration"]["urls"] = json!([]); config } fn limit_config() -> Value { json!({ "limit_config": { "traitDefinitions": [], "rules": [{ "id": "default", "filters": null, "limits": { "maxGuilds": 100 } }] }, "limit_config_json": "{\"traitDefinitions\":[],\"rules\":[]}", "self_hosted": false, "defaults": { "default": { "maxGuilds": 100 } }, "metadata": { "maxGuilds": { "key": "maxGuilds", "label": "Max Guilds", "description": "Maximum guild memberships.", "category": "account", "scope": "user", "isToggle": false, "unit": null, "min": 0, "max": 1000 } }, "categories": { "account": "Account" }, "limit_keys": ["maxGuilds"], "bounds": null }) } fn test_config(api_endpoint: String) -> AdminConfig { AdminConfig { env: RuntimeEnv::Test, host: "127.0.0.1".to_owned(), port: 0, secret_key_base: SECRET_KEY.to_owned(), base_path: String::new(), api_endpoint, media_endpoint: "https://media.example.test".to_owned(), static_cdn_endpoint: "https://static.example.test".to_owned(), admin_endpoint: "https://admin.example.test".to_owned(), web_app_endpoint: "https://app.example.test".to_owned(), oauth_client_id: "admin-client".to_owned(), oauth_client_secret: "admin-secret".to_owned(), oauth_redirect_uri: "https://admin.example.test/callback".to_owned(), build_version: "test".to_owned(), self_hosted: false, proxy: ProxyConfig { trust_client_ip_header: false, client_ip_header_name: "x-forwarded-for".to_owned(), }, } }