mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
chore(repo): remove dead code and useless tests (#3375)
This commit is contained in:
1 parent
7e177a1c90
commit
1cd62eb661
1391 files changed
+783
-120415
No files matched your search
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
use crate::api::generated::types as generated_types;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{BanAvatarResult, BanCheckResult, BlocklistEntryPage, BulkBanResult};
|
||||
use super::types::{BanCheckResult, BlocklistEntryPage, BulkBanResult};
|
||||
|
||||
impl AdminApiClient {
|
||||
pub async fn ban_email(&self, email: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
@@ -249,16 +249,6 @@ impl AdminApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ban_user_avatar(&self, user_id: &str) -> ApiResult<BanAvatarResult> {
|
||||
let body = generated_types::BanUserAvatarRequest::default();
|
||||
let response = self
|
||||
.generated()
|
||||
.ban_admin_user_avatar(&snowflake(user_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn ban_profile_substring(
|
||||
&self,
|
||||
scope: &str,
|
||||
|
||||
@@ -251,22 +251,6 @@ impl AdminApiClient {
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn put_void_with_reason(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let response = self
|
||||
.send_json(Method::PUT, path, body, audit_log_reason)
|
||||
.await?;
|
||||
Self::parse_void_response(response).await
|
||||
}
|
||||
|
||||
pub async fn delete_void(&self, path: &str, body: Option<&serde_json::Value>) -> ApiResult<()> {
|
||||
self.delete_void_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn delete_void_with_reason(
|
||||
&self,
|
||||
path: &str,
|
||||
|
||||
@@ -284,11 +284,6 @@ pub struct BulkBanResult {
|
||||
pub job_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct BanAvatarResult {
|
||||
pub hash_short: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct SuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
@@ -78,8 +78,3 @@ pub struct CreateVoiceServerResponse {
|
||||
pub struct UpdateVoiceServerResponse {
|
||||
pub server: VoiceServer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct GetVoiceServerResponse {
|
||||
pub server: Option<VoiceServer>,
|
||||
}
|
||||
@@ -41,16 +41,6 @@ impl AdminApiClient {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn lookup_user(&self, query: &str) -> ApiResult<Option<AdminUser>> {
|
||||
let response = self
|
||||
.generated()
|
||||
.list_admin_users(None, None, None, None, None, Some(query), None)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
let resp: LookupUserResponse = self.generated_value(response.into_inner())?;
|
||||
Ok(resp.users.into_iter().next())
|
||||
}
|
||||
|
||||
pub async fn lookup_users_by_ids(&self, user_ids: &[String]) -> ApiResult<Vec<AdminUser>> {
|
||||
if user_ids.is_empty() {
|
||||
return Ok(vec![]);
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::api::generated::types as generated_types;
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{
|
||||
CreateVoiceRegionResponse, CreateVoiceServerResponse, DeleteVoiceResponse,
|
||||
GetVoiceRegionResponse, GetVoiceServerResponse, ListVoiceRegionsResponse,
|
||||
ListVoiceServersResponse, UpdateVoiceRegionResponse, UpdateVoiceServerResponse,
|
||||
GetVoiceRegionResponse, ListVoiceRegionsResponse, ListVoiceServersResponse,
|
||||
UpdateVoiceRegionResponse, UpdateVoiceServerResponse,
|
||||
};
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -83,19 +83,6 @@ impl AdminApiClient {
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_voice_server(
|
||||
&self,
|
||||
region_id: &str,
|
||||
server_id: &str,
|
||||
) -> ApiResult<GetVoiceServerResponse> {
|
||||
let response = self
|
||||
.generated()
|
||||
.get_admin_voice_server(region_id, server_id)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn create_voice_server(
|
||||
&self,
|
||||
params: &serde_json::Value,
|
||||
|
||||
@@ -10,7 +10,6 @@ const DEFAULT_REPORTS_BUCKET_ORIGIN: &str = "https://fluxer-reports.ewr1.vultrob
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AdminConfig {
|
||||
pub env: RuntimeEnv,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub secret_key_base: String,
|
||||
@@ -35,13 +34,6 @@ pub struct ProxyConfig {
|
||||
pub client_ip_header_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RuntimeEnv {
|
||||
Development,
|
||||
Production,
|
||||
Test,
|
||||
}
|
||||
|
||||
impl AdminConfig {
|
||||
pub fn from_env() -> anyhow::Result<Self> {
|
||||
let base_path = normalize_base_path(&read_env("FLUXER_ADMIN_BASE_PATH", ""));
|
||||
@@ -60,7 +52,6 @@ impl AdminConfig {
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
env: RuntimeEnv::from_env_value(&read_env("FLUXER_ENV", "development")),
|
||||
host: read_env("FLUXER_ADMIN_HOST", "0.0.0.0"),
|
||||
port: read_env("FLUXER_ADMIN_PORT", "3020")
|
||||
.parse()
|
||||
@@ -105,14 +96,6 @@ impl AdminConfig {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_dev(&self) -> bool {
|
||||
self.env == RuntimeEnv::Development
|
||||
}
|
||||
|
||||
pub fn is_production(&self) -> bool {
|
||||
self.env == RuntimeEnv::Production
|
||||
}
|
||||
|
||||
pub fn secure_cookies(&self) -> bool {
|
||||
self.admin_endpoint.starts_with("https://")
|
||||
}
|
||||
@@ -189,16 +172,6 @@ fn is_virtual_hostable_bucket(bucket: &str, allow_dots: bool) -> bool {
|
||||
&& !bucket.ends_with('-')
|
||||
}
|
||||
|
||||
impl RuntimeEnv {
|
||||
pub(crate) fn from_env_value(value: &str) -> Self {
|
||||
match value {
|
||||
"production" => Self::Production,
|
||||
"test" => Self::Test,
|
||||
_ => Self::Development,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -207,8 +180,7 @@ mod tests {
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
const MANAGED_ENV: [&str; 15] = [
|
||||
"FLUXER_ENV",
|
||||
const MANAGED_ENV: [&str; 14] = [
|
||||
"FLUXER_ADMIN_HOST",
|
||||
"FLUXER_ADMIN_PORT",
|
||||
"FLUXER_ADMIN_ENDPOINT",
|
||||
@@ -243,135 +215,6 @@ mod tests {
|
||||
config
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_base_path_strips_trailing_slashes() {
|
||||
assert_eq!(normalize_base_path("admin/"), "/admin");
|
||||
assert_eq!(normalize_base_path("admin///"), "/admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_base_path_adds_leading_slash() {
|
||||
assert_eq!(normalize_base_path("admin"), "/admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_base_path_empty_stays_empty() {
|
||||
assert_eq!(normalize_base_path(""), "");
|
||||
assert_eq!(normalize_base_path(" "), "");
|
||||
assert_eq!(normalize_base_path("/"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_base_path_preserves_inner() {
|
||||
assert_eq!(normalize_base_path("/foo/bar/"), "/foo/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_trailing_slash_removes_trailing() {
|
||||
assert_eq!(
|
||||
trim_trailing_slash("https://example.com/"),
|
||||
"https://example.com"
|
||||
);
|
||||
assert_eq!(
|
||||
trim_trailing_slash("https://example.com"),
|
||||
"https://example.com"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_trailing_slash_empty_string() {
|
||||
assert_eq!(trim_trailing_slash(""), "");
|
||||
assert_eq!(trim_trailing_slash("/"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_env_from_env_value() {
|
||||
assert_eq!(
|
||||
RuntimeEnv::from_env_value("production"),
|
||||
RuntimeEnv::Production
|
||||
);
|
||||
assert_eq!(RuntimeEnv::from_env_value("test"), RuntimeEnv::Test);
|
||||
assert_eq!(
|
||||
RuntimeEnv::from_env_value("development"),
|
||||
RuntimeEnv::Development
|
||||
);
|
||||
assert_eq!(
|
||||
RuntimeEnv::from_env_value("anything"),
|
||||
RuntimeEnv::Development
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_production_returns_true_for_production() {
|
||||
let config = AdminConfig {
|
||||
env: RuntimeEnv::Production,
|
||||
host: String::new(),
|
||||
port: 3020,
|
||||
secret_key_base: String::new(),
|
||||
base_path: String::new(),
|
||||
api_endpoint: String::new(),
|
||||
media_endpoint: String::new(),
|
||||
static_cdn_endpoint: String::new(),
|
||||
reports_bucket_origin: String::new(),
|
||||
|
||||
admin_endpoint: String::new(),
|
||||
web_app_endpoint: String::new(),
|
||||
oauth_client_id: String::new(),
|
||||
oauth_client_secret: String::new(),
|
||||
oauth_redirect_uri: String::new(),
|
||||
build_version: String::new(),
|
||||
self_hosted: false,
|
||||
proxy: ProxyConfig {
|
||||
trust_client_ip_header: false,
|
||||
client_ip_header_name: String::new(),
|
||||
},
|
||||
};
|
||||
assert!(config.is_production());
|
||||
assert!(!config.is_dev());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_dev_returns_true_for_development() {
|
||||
let config = AdminConfig {
|
||||
env: RuntimeEnv::Development,
|
||||
host: String::new(),
|
||||
port: 3020,
|
||||
secret_key_base: String::new(),
|
||||
base_path: String::new(),
|
||||
api_endpoint: String::new(),
|
||||
media_endpoint: String::new(),
|
||||
static_cdn_endpoint: String::new(),
|
||||
reports_bucket_origin: String::new(),
|
||||
|
||||
admin_endpoint: String::new(),
|
||||
web_app_endpoint: String::new(),
|
||||
oauth_client_id: String::new(),
|
||||
oauth_client_secret: String::new(),
|
||||
oauth_redirect_uri: String::new(),
|
||||
build_version: String::new(),
|
||||
self_hosted: false,
|
||||
proxy: ProxyConfig {
|
||||
trust_client_ip_header: false,
|
||||
client_ip_header_name: String::new(),
|
||||
},
|
||||
};
|
||||
assert!(config.is_dev());
|
||||
assert!(!config.is_production());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_env_uses_defaults() {
|
||||
let config = config_from_env(&[]);
|
||||
assert_eq!(config.env, RuntimeEnv::Development);
|
||||
assert_eq!(config.host, "0.0.0.0");
|
||||
assert_eq!(config.port, 3020);
|
||||
assert_eq!(config.oauth_client_id, DEFAULT_ADMIN_OAUTH_CLIENT_ID);
|
||||
assert_eq!(
|
||||
config.oauth_redirect_uri,
|
||||
"https://admin.fluxer.app/oauth2_callback"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_non_default_public_port_reaches_the_public_endpoints() {
|
||||
let config = config_from_env(&[
|
||||
|
||||
@@ -13,43 +13,6 @@ pub fn asset(file_name: &str) -> Option<(&'static str, &'static [u8])> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn stylesheet_is_served_and_content_hashed() {
|
||||
let (content_type, bytes) =
|
||||
asset(STYLESHEET_FILE_NAME).expect("the generated stylesheet must be servable");
|
||||
assert_eq!(content_type, "text/css; charset=utf-8");
|
||||
let css = std::str::from_utf8(bytes).expect("stylesheet must be UTF-8");
|
||||
assert!(css.contains("font-family: 'Fluxer Sans'"));
|
||||
assert!(css.contains("font-family: 'Fluxer Mono'"));
|
||||
assert!(
|
||||
!css.contains("?v="),
|
||||
"content hashing replaces cache-bust tokens"
|
||||
);
|
||||
assert!(
|
||||
!css.contains("fluxerstatic"),
|
||||
"fonts must not be fetched from the static CDN"
|
||||
);
|
||||
assert!(
|
||||
STYLESHEET_FILE_NAME.starts_with("fonts.") && STYLESHEET_FILE_NAME.ends_with(".css"),
|
||||
"unexpected stylesheet name {STYLESHEET_FILE_NAME}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_face_the_stylesheet_references_is_served() {
|
||||
let (_, bytes) = asset(STYLESHEET_FILE_NAME).expect("stylesheet");
|
||||
let css = std::str::from_utf8(bytes).expect("stylesheet must be UTF-8");
|
||||
let mut referenced = 0;
|
||||
for fragment in css.split("url('").skip(1) {
|
||||
let file_name = fragment.split('\'').next().expect("unterminated url()");
|
||||
let (content_type, _) = asset(file_name)
|
||||
.unwrap_or_else(|| panic!("stylesheet references unserved font {file_name}"));
|
||||
assert_eq!(content_type, "font/woff2");
|
||||
referenced += 1;
|
||||
}
|
||||
assert_eq!(referenced, 16, "expected the 16 bundled Latin-core faces");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ofl_attribution_ships_with_the_binaries() {
|
||||
let notice = ASSETS
|
||||
@@ -63,10 +26,4 @@ mod tests {
|
||||
.any(|(name, _, _)| name.starts_with("LICENSE-IBM-PLEX."))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_files_are_not_served() {
|
||||
assert!(asset("fonts.css").is_none());
|
||||
assert!(asset("../../../etc/passwd").is_none());
|
||||
}
|
||||
}
|
||||
@@ -198,14 +198,13 @@ pub fn get_csrf_token(request: &Request) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::{AdminConfig, ProxyConfig, RuntimeEnv};
|
||||
use crate::config::{AdminConfig, ProxyConfig};
|
||||
use crate::state::AppState;
|
||||
use axum::{Router, middleware::from_fn_with_state, routing::get};
|
||||
use tower::ServiceExt;
|
||||
|
||||
fn state_with_admin_endpoint(admin_endpoint: &str) -> AppState {
|
||||
AppState::new(AdminConfig {
|
||||
env: RuntimeEnv::Production,
|
||||
host: String::new(),
|
||||
port: 3020,
|
||||
secret_key_base: "test-secret".to_owned(),
|
||||
|
||||
@@ -108,66 +108,3 @@ pub fn set_flash_cookie(response: &mut Response, flash: &FlashData, secure: bool
|
||||
response.headers_mut().append(header::SET_COOKIE, v);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_then_parse_round_trips() {
|
||||
let flash = FlashData::success("Item saved");
|
||||
let encoded = serialize_flash(&flash);
|
||||
let decoded = parse_flash(&encoded).expect("must parse");
|
||||
assert_eq!(decoded.message, "Item saved");
|
||||
assert_eq!(decoded.flash_type, "success");
|
||||
assert!(decoded.detail.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_flash_returns_none_for_invalid_base64() {
|
||||
assert!(parse_flash("!!!not-base64!!!").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_flash_returns_none_for_invalid_json() {
|
||||
let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"not json");
|
||||
assert!(parse_flash(&encoded).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flash_success_sets_correct_type() {
|
||||
let f = FlashData::success("ok");
|
||||
assert_eq!(f.flash_type, "success");
|
||||
assert_eq!(f.message, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flash_error_sets_correct_type() {
|
||||
let f = FlashData::error("fail");
|
||||
assert_eq!(f.flash_type, "error");
|
||||
assert_eq!(f.message, "fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flash_info_sets_correct_type() {
|
||||
let f = FlashData::info("note");
|
||||
assert_eq!(f.flash_type, "info");
|
||||
assert_eq!(f.message, "note");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_flash_message_maps_levels() {
|
||||
assert!(matches!(
|
||||
FlashData::success("").to_flash_message().level,
|
||||
FlashLevel::Success
|
||||
));
|
||||
assert!(matches!(
|
||||
FlashData::error("").to_flash_message().level,
|
||||
FlashLevel::Error
|
||||
));
|
||||
assert!(matches!(
|
||||
FlashData::info("").to_flash_message().level,
|
||||
FlashLevel::Info
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -127,181 +127,3 @@ fn compare_message_ids(left: &Message, right: &Message) -> Ordering {
|
||||
_ => left.id.cmp(&right.id),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn maps_message_and_attachment_fields() {
|
||||
let value = json!({
|
||||
"id": "9007199254740993",
|
||||
"content": "Message content",
|
||||
"timestamp": "2026-09-11T12:00:00Z",
|
||||
"author_id": 42,
|
||||
"author_username": "alice",
|
||||
"author_global_name": "Alice",
|
||||
"author_discriminator": 1234,
|
||||
"author_avatar": "avatar-hash",
|
||||
"channel_id": "100",
|
||||
"channel_nsfw": false,
|
||||
"channel_content_warning_level": 2,
|
||||
"channel_content_warning_text": "Content warning",
|
||||
"guild_nsfw": true,
|
||||
"attachments": [{
|
||||
"id": 9007199254740993_u64,
|
||||
"url": "https://cdn.example.com/image.png",
|
||||
"filename": "image.png",
|
||||
"nsfw": true,
|
||||
"content_type": "image/png",
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"size": 4096
|
||||
}]
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
message_from_value(&value),
|
||||
Message {
|
||||
id: "9007199254740993".into(),
|
||||
content: "Message content".into(),
|
||||
timestamp: "2026-09-11T12:00:00Z".into(),
|
||||
author_id: "42".into(),
|
||||
author_username: "alice".into(),
|
||||
author_global_name: Some("Alice".into()),
|
||||
author_discriminator: "1234".into(),
|
||||
author_avatar: Some("avatar-hash".into()),
|
||||
webhook_id: None,
|
||||
author_bot: None,
|
||||
channel_id: "100".into(),
|
||||
channel_nsfw: Some(false),
|
||||
channel_content_warning_level: Some(2),
|
||||
channel_content_warning_text: Some("Content warning".into()),
|
||||
guild_nsfw: Some(true),
|
||||
attachments: vec![Attachment {
|
||||
id: "9007199254740993".into(),
|
||||
url: "https://cdn.example.com/image.png".into(),
|
||||
filename: "image.png".into(),
|
||||
nsfw: Some(true),
|
||||
content_type: Some("image/png".into()),
|
||||
width: Some(640),
|
||||
height: Some(480),
|
||||
size: Some(4096),
|
||||
}],
|
||||
missing_attachments: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_author_bot_flags() {
|
||||
let bot = message_from_value(&json!({"author_id": "42", "author_bot": true}));
|
||||
assert_eq!(bot.author_bot, Some(true));
|
||||
let human = message_from_value(&json!({"author_id": "43", "author_bot": false}));
|
||||
assert_eq!(human.author_bot, Some(false));
|
||||
let webhook = message_from_value(
|
||||
&json!({"author_id": "500", "webhook_id": "500", "author_bot": null}),
|
||||
);
|
||||
assert_eq!(webhook.author_bot, None);
|
||||
let old = message_from_value(&json!({"author_id": "44"}));
|
||||
assert_eq!(old.author_bot, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_missing_attachments() {
|
||||
let message = message_from_value(&json!({
|
||||
"id": "10",
|
||||
"attachments": [],
|
||||
"missing_attachments": [{
|
||||
"id": 9007199254740993_u64,
|
||||
"filename": "evidence.png",
|
||||
"nsfw": null,
|
||||
"content_type": "image/png",
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"size": 4096
|
||||
}]
|
||||
}));
|
||||
assert!(message.attachments.is_empty());
|
||||
assert_eq!(
|
||||
message.missing_attachments,
|
||||
vec![MissingAttachment {
|
||||
id: "9007199254740993".into(),
|
||||
filename: "evidence.png".into(),
|
||||
content_type: Some("image/png".into()),
|
||||
size: Some(4096),
|
||||
}]
|
||||
);
|
||||
let old = message_from_value(&json!({"id": "11", "attachments": []}));
|
||||
assert!(old.missing_attachments.is_empty());
|
||||
let null = message_from_value(&json!({"id": "12", "missing_attachments": null}));
|
||||
assert!(null.missing_attachments.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_webhook_authors() {
|
||||
let webhook = message_from_value(&json!({
|
||||
"author_id": "500",
|
||||
"author_username": "Harbor Bulletin",
|
||||
"webhook_id": "500"
|
||||
}));
|
||||
assert_eq!(webhook.webhook_id.as_deref(), Some("500"));
|
||||
assert_eq!(webhook.author_id, "500");
|
||||
let user = message_from_value(&json!({"author_id": "42", "webhook_id": null}));
|
||||
assert_eq!(user.webhook_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_display_defaults_for_incomplete_snapshots() {
|
||||
let message = message_from_value(&json!({"attachments": [{}]}));
|
||||
|
||||
assert_eq!(message.author_username, "Unknown");
|
||||
assert_eq!(message.author_discriminator, "0000");
|
||||
assert_eq!(message.content, "");
|
||||
assert_eq!(message.author_global_name, None);
|
||||
assert_eq!(message.webhook_id, None);
|
||||
assert_eq!(message.channel_nsfw, None);
|
||||
assert_eq!(
|
||||
message.attachments,
|
||||
vec![Attachment {
|
||||
id: String::new(),
|
||||
url: String::new(),
|
||||
filename: String::new(),
|
||||
nsfw: None,
|
||||
content_type: None,
|
||||
width: None,
|
||||
height: None,
|
||||
size: None,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orders_string_and_numeric_snowflakes_without_rounding() {
|
||||
let values = vec![
|
||||
json!({"id": "9007199254740993"}),
|
||||
json!({"id": "10"}),
|
||||
json!({"id": 2}),
|
||||
json!({"id": 9007199254740992_u64}),
|
||||
];
|
||||
let messages = ordered_messages(&values);
|
||||
let ids: Vec<&str> = messages.iter().map(|message| message.id.as_str()).collect();
|
||||
|
||||
assert_eq!(ids, ["2", "10", "9007199254740992", "9007199254740993"]);
|
||||
assert_eq!(values[0]["id"], "9007199254740993");
|
||||
assert!(ordered_messages(&[]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_message_order_when_ids_are_equal() {
|
||||
let values = [
|
||||
json!({"id": "10", "content": "first"}),
|
||||
json!({"id": 10, "content": "second"}),
|
||||
];
|
||||
let messages = ordered_messages(&values);
|
||||
|
||||
assert_eq!(messages[0].content, "first");
|
||||
assert_eq!(messages[1].content, "second");
|
||||
}
|
||||
}
|
||||
@@ -51,94 +51,3 @@ pub fn report_category(category: &str) -> Markup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn known_categories_get_their_label() {
|
||||
assert_eq!(
|
||||
report_category_label("harassment"),
|
||||
"Harassment or bullying"
|
||||
);
|
||||
assert_eq!(report_category_label("spam"), "Spam");
|
||||
assert_eq!(report_category_label("spam_account"), "Spam account");
|
||||
assert_eq!(
|
||||
report_category_label("extremist_community"),
|
||||
"Extremist community"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_categories_fall_back_to_the_key() {
|
||||
assert_eq!(report_category_label("future_value"), "future_value");
|
||||
assert_eq!(report_category_label(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasons_that_repeat_the_category_label_are_detected() {
|
||||
assert!(reason_repeats_category("spam", "Spam"));
|
||||
assert!(reason_repeats_category("spam", "spam"));
|
||||
assert!(reason_repeats_category(
|
||||
"harassment",
|
||||
"Harassment or bullying"
|
||||
));
|
||||
assert!(reason_repeats_category(
|
||||
"harassment",
|
||||
" harassment OR bullying "
|
||||
));
|
||||
assert!(!reason_repeats_category("harassment", "Hate speech"));
|
||||
assert!(!reason_repeats_category(
|
||||
"child_safety",
|
||||
"Child sexual abuse material"
|
||||
));
|
||||
assert!(reason_repeats_category("future_value", "future_value"));
|
||||
assert!(!reason_repeats_category("future_value", "Spam"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_category_key_is_unique_and_labelled() {
|
||||
let mut keys = REPORT_CATEGORIES
|
||||
.iter()
|
||||
.map(|(key, _)| *key)
|
||||
.collect::<Vec<_>>();
|
||||
keys.sort_unstable();
|
||||
keys.dedup();
|
||||
assert_eq!(keys.len(), REPORT_CATEGORIES.len());
|
||||
assert_eq!(keys.len(), 18);
|
||||
for (key, label) in REPORT_CATEGORIES {
|
||||
assert_ne!(key, label);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options_start_with_all_and_keep_an_unknown_selection() {
|
||||
let options = report_category_options("");
|
||||
assert_eq!(options.first(), Some(&("", "All")));
|
||||
assert_eq!(options.len(), 19);
|
||||
assert_eq!(report_category_options("spam").len(), 19);
|
||||
let unknown = report_category_options("future_value");
|
||||
assert_eq!(unknown.len(), 20);
|
||||
assert_eq!(unknown.last(), Some(&("future_value", "future_value")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_markup_shows_the_label_and_keeps_the_key() {
|
||||
let markup = report_category("doxxing").into_string();
|
||||
assert!(
|
||||
markup.contains(r#"data-report-category="doxxing""#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(
|
||||
markup.contains(">Sharing personal information<"),
|
||||
"{markup}"
|
||||
);
|
||||
let unknown = report_category("future_value").into_string();
|
||||
assert!(
|
||||
unknown.contains(r#"data-report-category="future_value""#),
|
||||
"{unknown}"
|
||||
);
|
||||
assert!(unknown.contains(">future_value<"), "{unknown}");
|
||||
}
|
||||
}
|
||||
@@ -74,20 +74,3 @@ pub fn info(base: &str, hint: &Hint<'_>) -> Markup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HintLink;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "anchor")]
|
||||
fn rejects_a_link_that_points_at_no_anchor() {
|
||||
let _ = HintLink::new("/instance-config", "Instance policy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "label")]
|
||||
fn rejects_a_link_with_no_label() {
|
||||
let _ = HintLink::new("/instance-config#community-creation", " ");
|
||||
}
|
||||
}
|
||||
@@ -16,31 +16,3 @@ pub fn format_user_display(
|
||||
_ => "Unknown".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::user_tag::sync_with_unique_usernames;
|
||||
|
||||
#[test]
|
||||
fn username_instances_show_bare_human_names_and_keep_bot_tags() {
|
||||
sync_with_unique_usernames(true, || {
|
||||
assert_eq!(
|
||||
format_user_display(None, Some("alice"), Some("0000"), false),
|
||||
"alice"
|
||||
);
|
||||
assert_eq!(
|
||||
format_user_display(None, Some("helper"), Some("4363"), true),
|
||||
"helper#4363"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_instances_keep_the_zero_tag() {
|
||||
assert_eq!(
|
||||
format_user_display(None, Some("alice"), Some("0000"), false),
|
||||
"alice#0000"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -103,38 +103,3 @@ pub fn user_profile_badges(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn render(self_hosted: bool, name: Option<&str>, premium_type: i32) -> String {
|
||||
user_profile_badges(
|
||||
"https://static.example.com",
|
||||
0,
|
||||
Some(premium_type),
|
||||
Some("2026-01-01"),
|
||||
self_hosted,
|
||||
name,
|
||||
false,
|
||||
)
|
||||
.into_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hosted_premium_badges_keep_their_fluxer_labels() {
|
||||
assert!(
|
||||
render(false, Some("Gold"), 1).contains("Fluxer Plutonium subscriber since 2026-01-01")
|
||||
);
|
||||
assert!(render(false, None, 2).contains("Fluxer Visionary since 2026-01-01"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_hosted_premium_badges_use_the_configured_name() {
|
||||
let markup = render(true, Some("Gold"), 1);
|
||||
assert!(markup.contains("Gold subscriber since 2026-01-01"));
|
||||
assert!(!markup.contains("Plutonium"));
|
||||
assert!(render(true, Some("Gold"), 2).contains("Gold subscriber since"));
|
||||
assert!(!render(true, None, 1).contains("img"));
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ pub fn render_sidebar(
|
||||
nav data-sidebar-nav="" class="sidebar-scrollbar flex-1 space-y-4 overflow-y-auto p-4" aria-label="Admin sections" {
|
||||
@for section in NAV_SECTIONS {
|
||||
@let visible_items: Vec<_> = section.items.iter()
|
||||
.filter(|item| !(item.hosted_only && config.self_hosted))
|
||||
.filter(|item| acl::has_any_permission(admin_acls, item.required_acls))
|
||||
.filter(|item| {
|
||||
item.active_key != "voice-servers" || inspected_voice_region_id.is_some()
|
||||
|
||||
@@ -7,7 +7,6 @@ pub struct NavItem {
|
||||
pub path: &'static str,
|
||||
pub active_key: &'static str,
|
||||
pub required_acls: &'static [&'static str],
|
||||
pub hosted_only: bool,
|
||||
}
|
||||
|
||||
pub struct NavSection {
|
||||
@@ -17,10 +16,7 @@ pub struct NavSection {
|
||||
|
||||
macro_rules! item {
|
||||
($t:expr, $p:expr, $k:expr, [ $($a:expr),+ $(,)? ]) => {
|
||||
NavItem { title: $t, path: $p, active_key: $k, required_acls: &[$($a),+], hosted_only: false }
|
||||
};
|
||||
($t:expr, $p:expr, $k:expr, [ $($a:expr),+ $(,)? ], hosted) => {
|
||||
NavItem { title: $t, path: $p, active_key: $k, required_acls: &[$($a),+], hosted_only: true }
|
||||
NavItem { title: $t, path: $p, active_key: $k, required_acls: &[$($a),+] }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -253,26 +249,3 @@ pub const NAV_SECTIONS: &[NavSection] = &[
|
||||
)],
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bulk_actions_nav_covers_every_acl_the_page_renders_a_section_for() {
|
||||
let item = NAV_SECTIONS
|
||||
.iter()
|
||||
.flat_map(|section| section.items)
|
||||
.find(|item| item.active_key == "bulk-actions")
|
||||
.expect("bulk actions nav item");
|
||||
for required in [
|
||||
acl::BULK_UPDATE_USER_FLAGS,
|
||||
acl::BULK_UPDATE_GUILD_FEATURES,
|
||||
acl::BULK_ADD_GUILD_MEMBERS,
|
||||
acl::BULK_DELETE_USERS,
|
||||
acl::BULK_DELETE_USER_MESSAGES,
|
||||
] {
|
||||
assert!(item.required_acls.contains(&required), "{required}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,48 +171,3 @@ pub fn audit_logs_page(
|
||||
};
|
||||
admin_layout(config, auth, "Audit Logs", "audit-logs", None, content)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn target_type_filter_offers_bulk_jobs() {
|
||||
let params = AuditLogsParams {
|
||||
query: "",
|
||||
admin_user_id: "",
|
||||
target_id: "",
|
||||
target_type: "bulk_job",
|
||||
access: "",
|
||||
sort_by: "createdAt",
|
||||
sort_order: "desc",
|
||||
limit: 50,
|
||||
current_page: 0,
|
||||
};
|
||||
let markup = filters_section("/admin", ¶ms).into_string();
|
||||
assert!(markup.contains(r#"<option value="bulk_job" selected>Bulk job</option>"#));
|
||||
assert!(markup.contains(r#"<option value="" selected>All entries</option>"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_filter_survives_form_and_pagination() {
|
||||
let params = AuditLogsParams {
|
||||
query: "",
|
||||
admin_user_id: "",
|
||||
target_id: "1500000000000000001",
|
||||
target_type: "",
|
||||
access: "read",
|
||||
sort_by: "createdAt",
|
||||
sort_order: "desc",
|
||||
limit: 50,
|
||||
current_page: 0,
|
||||
};
|
||||
let markup = filters_section("/admin", ¶ms).into_string();
|
||||
assert!(markup.contains(r#"<select id="access" name="access""#));
|
||||
assert!(markup.contains(r#"<option value="read" selected>Reads only</option>"#));
|
||||
assert_eq!(
|
||||
build_pagination_url("/admin", 1, ¶ms),
|
||||
"/admin/audit-logs?page=1&target_id=1500000000000000001&access=read&sort_by=createdAt&sort_order=desc&limit=50"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -326,58 +326,3 @@ pub fn audit_log_table_body(base: &str, entries: &[AuditLogEntry]) -> Markup {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(target_type: &str, target_id: &str) -> AuditLogEntry {
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"log_id": "1900000000000000001",
|
||||
"admin_user_id": "1500000000000000001",
|
||||
"action": "bulk_schedule_deletion",
|
||||
"target_id": target_id,
|
||||
"target_type": target_type,
|
||||
"audit_log_reason": "Raid cleanup",
|
||||
"created_at": "2026-09-14T12:00:00.000Z"
|
||||
}))
|
||||
.expect("audit log fixture must deserialize")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bulk_job_targets_link_to_the_job_detail_page() {
|
||||
let markup = target_cell("/admin", &entry("bulk_job", "1900000000000000002")).into_string();
|
||||
assert!(markup.contains(r#"href="/admin/jobs/1900000000000000002""#));
|
||||
assert!(markup.contains("Bulk job"));
|
||||
assert!(!markup.contains("/admin/users/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_labels_drop_the_zero_tag_only_without_tags() {
|
||||
let admin = AuditLogUserSummary {
|
||||
id: "1500000000000000001".to_owned(),
|
||||
username: "lilith".to_owned(),
|
||||
discriminator: "0".to_owned(),
|
||||
global_name: Some("Lilith".to_owned()),
|
||||
};
|
||||
assert_eq!(user_label(&admin), "Lilith (lilith#0000)");
|
||||
crate::utils::user_tag::sync_with_unique_usernames(true, || {
|
||||
assert_eq!(user_label(&admin), "Lilith (lilith)");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_target_types_stay_unlinked() {
|
||||
let markup = target_cell("/admin", &entry("email_domain", "spam.example")).into_string();
|
||||
assert!(!markup.contains("<a "));
|
||||
assert!(markup.contains("Email domain"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retired_action_names_still_render() {
|
||||
let mut retired = entry("user", "1500000000000000002");
|
||||
retired.action = "update_retired_toggle".to_string();
|
||||
let markup = audit_log_table_body("/admin", &[retired]).into_string();
|
||||
assert!(markup.contains("Update retired toggle"));
|
||||
}
|
||||
}
|
||||
@@ -418,30 +418,6 @@ fn bulk_delete_user_messages_section(base: &str, csrf_token: &str) -> Markup {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_grid_offers_only_the_opt_in_clone_features() {
|
||||
let markup = guild_feature_checkbox_grid("add_features[]", false).into_string();
|
||||
assert!(markup.contains(r#"value="CLONE_EMOJI_ENABLED""#));
|
||||
assert!(markup.contains(r#"value="CLONE_STICKER_ENABLED""#));
|
||||
assert!(!markup.contains(r#"value="CLONE_EMOJI_DISABLED""#));
|
||||
assert!(!markup.contains(r#"value="CLONE_STICKER_DISABLED""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion_form_has_no_preselected_reason() {
|
||||
let markup = bulk_schedule_deletion_section("/admin", "csrf", false).into_string();
|
||||
assert!(markup.contains(r#"<option value="" selected>Select a reason</option>"#));
|
||||
for (value, _) in DELETION_REASONS {
|
||||
assert!(!markup.contains(&format!(r#"<option value="{value}" selected>"#)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion_form_defaults_to_the_moderation_retention_floor() {
|
||||
let markup = bulk_schedule_deletion_section("/admin", "csrf", false).into_string();
|
||||
assert!(markup.contains(r#"name="days_until_deletion" value="60" min="14" max="365""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion_form_emails_each_user_by_default() {
|
||||
let markup = bulk_schedule_deletion_section("/admin", "csrf", false).into_string();
|
||||
@@ -456,13 +432,4 @@ mod tests {
|
||||
assert!(!deletion.contains(r#"name="notify_user" value="true""#));
|
||||
assert!(deletion.contains(r#"name="notify_user_present" value="1""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_grid_can_clear_the_deprecated_clone_features() {
|
||||
let markup = guild_feature_checkbox_grid("remove_features[]", true).into_string();
|
||||
assert!(markup.contains(r#"value="CLONE_EMOJI_DISABLED""#));
|
||||
assert!(markup.contains(r#"value="CLONE_STICKER_DISABLED""#));
|
||||
assert!(markup.contains("CLONE_EMOJI_DISABLED (deprecated, removal only)"));
|
||||
assert!(markup.contains(r#"value="CLONE_EMOJI_ENABLED""#));
|
||||
}
|
||||
}
|
||||
@@ -139,39 +139,3 @@ pub fn gift_codes_page(
|
||||
};
|
||||
admin_layout(config, auth, "Gift Codes", "gift-codes", None, content)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn branding(name: &str, premium_enabled: bool) -> PremiumBranding {
|
||||
PremiumBranding {
|
||||
name: Some(name.to_owned()),
|
||||
premium_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn premium_name_comes_from_branding_with_per_deployment_fallbacks() {
|
||||
let hosted = GiftCodesPremium::from_branding(false, None);
|
||||
assert_eq!(hosted.name, "Plutonium");
|
||||
assert!(!hosted.needs_mirror_mode);
|
||||
let self_hosted = GiftCodesPremium::from_branding(true, None);
|
||||
assert_eq!(self_hosted.name, "Premium");
|
||||
assert!(!self_hosted.needs_mirror_mode);
|
||||
let gold = GiftCodesPremium::from_branding(true, Some(&branding("Gold", true)));
|
||||
assert_eq!(gold.name, "Gold");
|
||||
assert!(!gold.needs_mirror_mode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everyone_mode_is_only_flagged_on_self_hosted_instances() {
|
||||
assert!(
|
||||
GiftCodesPremium::from_branding(true, Some(&branding("Gold", false))).needs_mirror_mode
|
||||
);
|
||||
assert!(
|
||||
!GiftCodesPremium::from_branding(false, Some(&branding("Plutonium", false)))
|
||||
.needs_mirror_mode
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -182,56 +182,3 @@ fn filtered_features() -> Vec<&'static str> {
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn guild_with_features(features: &[&str]) -> GuildInfo {
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"id": "1600000000000000001",
|
||||
"name": "Test Guild",
|
||||
"icon": null,
|
||||
"banner": null,
|
||||
"owner_id": "1500000000000000001",
|
||||
"owner_username": null,
|
||||
"owner_global_name": null,
|
||||
"owner_discriminator": null,
|
||||
"features": features,
|
||||
"nsfw_level": null,
|
||||
"nsfw": null,
|
||||
"content_warning_level": null,
|
||||
"content_warning_text": null,
|
||||
"description": null,
|
||||
"vanity_url_code": null,
|
||||
}))
|
||||
.expect("guild fixture")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_offers_the_opt_in_clone_features() {
|
||||
assert!(GUILD_FEATURES.contains(&"CLONE_EMOJI_ENABLED"));
|
||||
assert!(GUILD_FEATURES.contains(&"CLONE_STICKER_ENABLED"));
|
||||
assert!(!DEPRECATED_FEATURES.contains(&"CLONE_EMOJI_ENABLED"));
|
||||
assert!(!DEPRECATED_FEATURES.contains(&"CLONE_STICKER_ENABLED"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_keeps_the_deprecated_clone_features_clearable() {
|
||||
assert!(GUILD_FEATURES.contains(&"CLONE_EMOJI_DISABLED"));
|
||||
assert!(GUILD_FEATURES.contains(&"CLONE_STICKER_DISABLED"));
|
||||
assert_eq!(
|
||||
feature_label("CLONE_EMOJI_DISABLED"),
|
||||
"CLONE_EMOJI_DISABLED (deprecated, no longer enforced)"
|
||||
);
|
||||
assert_eq!(feature_label("CLONE_EMOJI_ENABLED"), "CLONE_EMOJI_ENABLED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readonly_view_marks_a_stale_flag_as_deprecated() {
|
||||
let guild = guild_with_features(&["CLONE_EMOJI_DISABLED", "CLONE_STICKER_ENABLED"]);
|
||||
let markup = features_tab_readonly(&guild, GUILD_FEATURES).into_string();
|
||||
assert!(markup.contains("CLONE_EMOJI_DISABLED (deprecated, no longer enforced)"));
|
||||
assert!(markup.contains("CLONE_STICKER_ENABLED"));
|
||||
}
|
||||
}
|
||||
@@ -110,49 +110,3 @@ fn report_row(base: &str, report: &ReportEntry) -> Markup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn guild_report_rows_format_the_date_and_wrap_long_cells() {
|
||||
let report: ReportEntry = serde_json::from_value(json!({
|
||||
"report_id": "1800000000000000006",
|
||||
"reporter_id": "1500000000000000000",
|
||||
"reporter_username": "reporter_0423a7212d56",
|
||||
"reporter_discriminator": "8650",
|
||||
"reporter_global_name": "Avery Reporter",
|
||||
"reported_at": "2026-10-05T23:23:28.067Z",
|
||||
"status": 0,
|
||||
"report_type": 0,
|
||||
"category": "spam",
|
||||
"reason": "spam",
|
||||
"reason_label": "Spam",
|
||||
"reported_guild_id": "1700000000000000800"
|
||||
}))
|
||||
.expect("valid report");
|
||||
let markup = report_row("/admin", &report).into_string();
|
||||
assert!(markup.contains("Oct 5, 2026, 11:23 PM UTC"), "{markup}");
|
||||
assert!(!markup.contains("2026-10-05T23:23:28.067Z"), "{markup}");
|
||||
assert!(
|
||||
markup
|
||||
.contains(r#"<span class="whitespace-nowrap text-neutral-500 text-xs">(reporter_0423a7212d56#8650)</span>"#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains("Avery Reporter "), "{markup}");
|
||||
assert!(
|
||||
markup.contains(r#"<td class="hidden whitespace-nowrap px-4 py-3 text-sm text-neutral-900 xl:table-cell">Pending</td>"#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(
|
||||
markup.contains(r#"<div class="mb-1 text-neutral-900 xl:hidden" data-status-compact="1800000000000000006">Pending</div>"#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains(">Pending<"), "{markup}");
|
||||
assert!(markup.contains(">View<"), "{markup}");
|
||||
assert_eq!(markup.matches(">Spam<").count(), 2, "{markup}");
|
||||
assert!(markup.contains(r#"data-report-reason="spam""#), "{markup}");
|
||||
}
|
||||
}
|
||||
@@ -294,32 +294,3 @@ fn readonly_field(label: &str, value: &str) -> Markup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn guild() -> GuildDetailInfo {
|
||||
serde_json::from_value(json!({
|
||||
"id": "1500000000000000001",
|
||||
"owner_id": "1400000000000000001",
|
||||
"name": "Guild",
|
||||
"verification_level": 1
|
||||
}))
|
||||
.expect("valid guild detail")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_verification_names_a_claimed_account_in_username_mode() {
|
||||
let markup = settings_tab_readonly(&guild(), true).into_string();
|
||||
assert!(markup.contains("Low (claimed account)"));
|
||||
assert!(!markup.contains("verified email"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_verification_names_a_verified_email_in_email_mode() {
|
||||
let markup = settings_tab_readonly(&guild(), false).into_string();
|
||||
assert!(markup.contains("Low (verified email)"));
|
||||
}
|
||||
}
|
||||
@@ -464,189 +464,3 @@ pub fn premium_billing_section(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn operator_billing() -> InstanceBillingResponse {
|
||||
InstanceBillingResponse {
|
||||
enabled: Some(true),
|
||||
effective_enabled: true,
|
||||
stripe_secret_key_set: true,
|
||||
stripe_webhook_secret_set: true,
|
||||
stripe_secret_key_stored: true,
|
||||
stripe_webhook_secret_stored: true,
|
||||
default_currency: Some("GBP".to_owned()),
|
||||
prices: Some(BTreeMap::from([(
|
||||
"GBP".to_owned(),
|
||||
BillingPriceSet {
|
||||
monthly: Some("price_1GbpM".to_owned()),
|
||||
yearly: Some("price_1GbpY".to_owned()),
|
||||
gift_1_month: None,
|
||||
gift_1_year: Some("price_1GbpG".to_owned()),
|
||||
},
|
||||
)])),
|
||||
country_currencies: Some(BTreeMap::from([
|
||||
("GB".to_owned(), "GBP".to_owned()),
|
||||
("IE".to_owned(), "GBP".to_owned()),
|
||||
])),
|
||||
legacy_prices: Some(BTreeMap::from([(
|
||||
"monthly_GBP".to_owned(),
|
||||
vec!["price_1OldA".to_owned(), "price_1OldB".to_owned()],
|
||||
)])),
|
||||
billing_active: true,
|
||||
stripe_serviceable: true,
|
||||
catalog_mode: BillingCatalogMode::Operator,
|
||||
webhook_url: "https://api.example.com/stripe/webhook".to_owned(),
|
||||
automatic_tax: None,
|
||||
tax_id_collection: Some(true),
|
||||
terms_consent_required: Some(false),
|
||||
effective_automatic_tax: false,
|
||||
effective_tax_id_collection: true,
|
||||
effective_terms_consent_required: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn branding(name: &str) -> AppBrandingConfigResponse {
|
||||
AppBrandingConfigResponse {
|
||||
premium_product_name: name.to_owned(),
|
||||
premium_info_url: Some("https://example.com/gold".to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn section_renders_every_field_and_one_empty_price_row() {
|
||||
let markup = premium_billing_section(
|
||||
"/admin",
|
||||
"csrf",
|
||||
&branding("Gold"),
|
||||
&operator_billing(),
|
||||
PremiumMode::Mirror,
|
||||
)
|
||||
.into_string();
|
||||
assert!(markup.contains("action=\"/admin/instance-config?action=update_billing\""));
|
||||
assert!(markup.contains("data-admin-result-form=\"true\""));
|
||||
assert!(markup.contains("<option value=\"on\" selected>On</option>"));
|
||||
assert!(markup.contains("name=\"billing_automatic_tax\""));
|
||||
assert!(markup.contains("name=\"billing_tax_id_collection\""));
|
||||
assert!(markup.contains("name=\"billing_terms_consent_required\""));
|
||||
assert!(markup.contains("Customer portal"));
|
||||
assert!(markup.contains("name=\"billing_premium_product_name\""));
|
||||
assert!(markup.contains("value=\"Gold\""));
|
||||
assert!(markup.contains("value=\"https://example.com/gold\""));
|
||||
assert!(markup.contains("name=\"billing_enabled\""));
|
||||
assert!(markup.contains("type=\"password\" id=\"billing_stripe_secret_key\""));
|
||||
assert!(markup.contains("name=\"billing_clear_stripe_secret_key\""));
|
||||
assert!(markup.contains("name=\"billing_clear_stripe_webhook_secret\""));
|
||||
assert!(markup.contains("value=\"https://api.example.com/stripe/webhook\""));
|
||||
assert!(markup.contains("Billing active"));
|
||||
assert!(!markup.contains("Purchases are unavailable"));
|
||||
assert_eq!(markup.matches("name=\"billing_price_currency\"").count(), 2);
|
||||
assert_eq!(
|
||||
markup.matches("name=\"billing_price_gift_1_year\"").count(),
|
||||
2
|
||||
);
|
||||
assert!(markup.contains("value=\"price_1GbpG\""));
|
||||
assert!(markup.contains("GB=GBP\nIE=GBP"));
|
||||
assert!(markup.contains("monthly_GBP=price_1OldA\nmonthly_GBP=price_1OldB"));
|
||||
assert!(!markup.contains("Plutonium"));
|
||||
assert!(!markup.contains("sk_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unset_secrets_have_no_clear_checkbox() {
|
||||
let billing = InstanceBillingResponse::default();
|
||||
let markup = premium_billing_section(
|
||||
"/admin",
|
||||
"csrf",
|
||||
&branding("Premium"),
|
||||
&billing,
|
||||
PremiumMode::Everyone,
|
||||
)
|
||||
.into_string();
|
||||
assert!(!markup.contains("billing_clear_stripe_secret_key"));
|
||||
assert!(!markup.contains("billing_clear_stripe_webhook_secret"));
|
||||
assert_eq!(markup.matches("name=\"billing_price_currency\"").count(), 1);
|
||||
assert!(markup.contains("Switch the premium model to Mirror"));
|
||||
assert!(markup.contains("Catalog: environment"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_secrets_are_labelled_and_cannot_be_cleared() {
|
||||
let billing = InstanceBillingResponse {
|
||||
stripe_secret_key_set: true,
|
||||
stripe_webhook_secret_set: true,
|
||||
..Default::default()
|
||||
};
|
||||
let markup = premium_billing_section(
|
||||
"/admin",
|
||||
"csrf",
|
||||
&branding("Premium"),
|
||||
&billing,
|
||||
PremiumMode::Mirror,
|
||||
)
|
||||
.into_string();
|
||||
assert!(markup.contains("Stripe secret key from environment"));
|
||||
assert!(markup.contains("Webhook secret from environment"));
|
||||
assert!(markup.contains("Set from the environment"));
|
||||
assert!(!markup.contains("billing_clear_stripe_secret_key"));
|
||||
assert!(!markup.contains("billing_clear_stripe_webhook_secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tri_state_selects_reflect_the_stored_value() {
|
||||
let render = |stored| {
|
||||
tri_state_select(
|
||||
"billing_enabled",
|
||||
"Billing",
|
||||
"Use environment setting",
|
||||
stored,
|
||||
"",
|
||||
)
|
||||
.into_string()
|
||||
};
|
||||
assert!(
|
||||
render(None)
|
||||
.contains("<option value=\"default\" selected>Use environment setting</option>")
|
||||
);
|
||||
assert!(render(Some(true)).contains("<option value=\"on\" selected>On</option>"));
|
||||
assert!(render(Some(false)).contains("<option value=\"off\" selected>Off</option>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockers_explain_why_billing_is_inactive() {
|
||||
let mut billing = InstanceBillingResponse::default();
|
||||
assert_eq!(
|
||||
billing_blockers(&billing, PremiumMode::Everyone),
|
||||
vec![
|
||||
"the premium model is Everyone, so there is no paid tier to sell",
|
||||
"billing is not enabled",
|
||||
"no Stripe secret key is set",
|
||||
]
|
||||
);
|
||||
billing.effective_enabled = true;
|
||||
billing.stripe_secret_key_set = true;
|
||||
assert_eq!(
|
||||
billing_blockers(&billing, PremiumMode::Mirror),
|
||||
vec![
|
||||
"the environment price catalog has no currency with both a monthly and a yearly price ID"
|
||||
]
|
||||
);
|
||||
billing.catalog_mode = BillingCatalogMode::Operator;
|
||||
billing.prices = Some(BTreeMap::from([(
|
||||
"GBP".to_owned(),
|
||||
BillingPriceSet {
|
||||
monthly: Some("price_1A".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
)]));
|
||||
assert_eq!(
|
||||
billing_blockers(&billing, PremiumMode::Mirror),
|
||||
vec!["no currency has both a monthly and a yearly price ID"]
|
||||
);
|
||||
assert!(billing_blockers(&operator_billing(), PremiumMode::Mirror).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -2142,224 +2142,3 @@ fn limit_config_section(base: &str, limit_config: &LimitConfigResponse) -> Marku
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn username_instances_hide_email_delivery_and_the_smtp_test() {
|
||||
let integrations = InstanceIntegrationsResponse::default();
|
||||
let username = integrations_config_section(
|
||||
"/admin",
|
||||
"csrf",
|
||||
&integrations,
|
||||
AccountIdentityMode::Username,
|
||||
"Fluxer",
|
||||
)
|
||||
.into_string();
|
||||
assert!(!username.contains("Email delivery"));
|
||||
assert!(!username.contains("test_smtp"));
|
||||
assert!(!username.contains("integration_email_present"));
|
||||
assert!(username.contains("Bluesky OAuth"));
|
||||
|
||||
let email = integrations_config_section(
|
||||
"/admin",
|
||||
"csrf",
|
||||
&integrations,
|
||||
AccountIdentityMode::Email,
|
||||
"Fluxer",
|
||||
)
|
||||
.into_string();
|
||||
assert!(email.contains("Email delivery"));
|
||||
assert!(email.contains("test_smtp"));
|
||||
assert!(email.contains(r#"name="integration_email_present" value="1""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_identity_section_has_no_tag_choice_in_username_mode() {
|
||||
let markup = account_identity_section(&AccountIdentityConfigResponse {
|
||||
mode: AccountIdentityMode::Username,
|
||||
locked: Some(true),
|
||||
tag_style: TagStyle::None,
|
||||
})
|
||||
.into_string();
|
||||
assert!(markup.contains("Sign-in Method"));
|
||||
assert!(markup.contains("Username"));
|
||||
assert!(markup.contains("Fixed"));
|
||||
assert!(!markup.contains("No tags"));
|
||||
assert!(!markup.contains("Random tags"));
|
||||
assert!(!markup.contains("username tags"));
|
||||
assert!(!markup.contains("<form"));
|
||||
assert!(!markup.contains("<input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_identity_section_shows_random_tags_in_email_mode() {
|
||||
let markup = account_identity_section(&AccountIdentityConfigResponse {
|
||||
mode: AccountIdentityMode::Email,
|
||||
locked: Some(true),
|
||||
tag_style: TagStyle::Random,
|
||||
})
|
||||
.into_string();
|
||||
assert!(markup.contains("Random tags"));
|
||||
assert!(!markup.contains("No tags"));
|
||||
assert!(markup.contains("username tags"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_identity_section_shows_no_tags_in_email_mode() {
|
||||
let markup = account_identity_section(&AccountIdentityConfigResponse {
|
||||
mode: AccountIdentityMode::Email,
|
||||
locked: Some(true),
|
||||
tag_style: TagStyle::None,
|
||||
})
|
||||
.into_string();
|
||||
assert!(markup.contains("No tags"));
|
||||
assert!(!markup.contains("Random tags"));
|
||||
assert!(!markup.contains("<input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_identity_section_shows_the_lock_state_only_when_known() {
|
||||
let render = |locked| {
|
||||
account_identity_section(&AccountIdentityConfigResponse {
|
||||
mode: AccountIdentityMode::Email,
|
||||
locked,
|
||||
tag_style: TagStyle::Random,
|
||||
})
|
||||
.into_string()
|
||||
};
|
||||
let unlocked = render(Some(false));
|
||||
assert!(unlocked.contains("Not fixed yet"));
|
||||
let unknown = render(None);
|
||||
assert!(!unknown.contains("Fixed"));
|
||||
assert!(!unknown.contains("Not fixed yet"));
|
||||
assert!(unknown.contains("Random tags"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captcha_section_posts_the_switch_and_difficulty_fields() {
|
||||
let markup =
|
||||
captcha_section("/admin", "csrf", &CaptchaConfigResponse::default()).into_string();
|
||||
assert!(markup.contains("/admin/instance-config?action=update_captcha"));
|
||||
assert!(markup.contains(r#"name="captcha_enabled""#));
|
||||
assert!(markup.contains(r#"name="captcha_cost""#));
|
||||
assert!(markup.contains(r#"name="captcha_max_counter""#));
|
||||
assert!(markup.contains("about 3.6 s"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_end_solve_estimate_at_the_defaults_is_about_three_and_a_half_seconds() {
|
||||
let seconds = estimate_low_end_solve_seconds(5_000, 1_000);
|
||||
assert!((seconds - 3.57).abs() < 0.01, "{seconds}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_migration_section_shows_both_rollouts_and_list_counts() {
|
||||
let domain_migration = DomainMigrationConfigResponse {
|
||||
anonymous_rollout_basis_points: 250,
|
||||
included_user_ids: vec!["1500000000000000001".to_owned()],
|
||||
excluded_user_ids: vec![
|
||||
"1500000000000000002".to_owned(),
|
||||
"1500000000000000003".to_owned(),
|
||||
],
|
||||
..DomainMigrationConfigResponse::default()
|
||||
};
|
||||
let markup = domain_migration_section("/admin", "csrf", &domain_migration).into_string();
|
||||
assert!(markup.contains("action=update_domain_migration"));
|
||||
assert!(markup.contains("domain_migration_enabled"));
|
||||
assert!(markup.contains("name=\"domain_migration_anonymous_rollout_basis_points\""));
|
||||
assert!(markup.contains("value=\"250\""));
|
||||
assert!(markup.contains("name=\"domain_migration_standalone_forwarding\""));
|
||||
assert!(markup.contains("name=\"domain_migration_rollout_country_codes\""));
|
||||
assert!(markup.contains("1 of 1000 stored"));
|
||||
assert!(markup.contains("2 of 1000 stored"));
|
||||
assert!(!markup.contains("at the cap"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_p2p_section_shows_the_rollout_and_list_counts() {
|
||||
let voice_p2p = VoiceP2pConfigResponse {
|
||||
enabled: true,
|
||||
config_version: 3,
|
||||
rollout_basis_points: 250,
|
||||
rollout_country_codes: vec!["SE".to_owned(), "NO".to_owned()],
|
||||
max_participants: 3,
|
||||
included_user_ids: vec!["1500000000000000001".to_owned()],
|
||||
excluded_user_ids: vec![
|
||||
"1500000000000000002".to_owned(),
|
||||
"1500000000000000003".to_owned(),
|
||||
],
|
||||
..VoiceP2pConfigResponse::default()
|
||||
};
|
||||
let markup = voice_p2p_section("/admin", "csrf", &voice_p2p).into_string();
|
||||
assert!(markup.contains("Peer-to-peer voice"));
|
||||
assert!(markup.contains("action=update_voice_p2p"));
|
||||
assert!(markup.contains("name=\"voice_p2p_enabled\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_rollout_basis_points\""));
|
||||
assert!(markup.contains("value=\"250\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_max_participants\""));
|
||||
assert!(markup.contains("value=\"3\""));
|
||||
assert!(markup.contains("min=\"2\""));
|
||||
assert!(markup.contains("max=\"4\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_include_premium_users\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_included_guild_ids\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_rollout_country_codes\""));
|
||||
assert!(markup.contains("value=\"SE, NO\""));
|
||||
assert!(markup.contains("2 of 250 stored"));
|
||||
assert!(markup.contains("Config version 3"));
|
||||
assert!(markup.contains("1 of 1000 stored"));
|
||||
assert!(markup.contains("2 of 1000 stored"));
|
||||
assert!(!markup.contains("anonymous_rollout_basis_points"));
|
||||
assert!(!markup.contains("standalone_forwarding"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_relay_section_shows_the_consent_toggle() {
|
||||
let accepted = PushRelayConfigResponse {
|
||||
relay_consent_accepted: true,
|
||||
relay_consent_accepted_at: Some("2026-09-27T10:11:12.000Z".to_owned()),
|
||||
relay_consent_accepted_by: Some("1130650140672000000".to_owned()),
|
||||
};
|
||||
let markup = push_relay_section("/admin", "csrf", &accepted).into_string();
|
||||
assert!(markup.contains("action=update_push_relay"));
|
||||
assert!(markup.contains("name=\"push_relay_consent_accepted\""));
|
||||
assert!(markup.contains("https://fluxer.com/push-relay"));
|
||||
assert!(markup.contains("value=\"Sep 27, 2026, 10:11 AM UTC\""));
|
||||
assert!(markup.contains("value=\"1130650140672000000\""));
|
||||
assert!(!markup.contains("name=\"push_relay_consent_accepted_at\""));
|
||||
assert!(!markup.contains("name=\"push_relay_consent_accepted_by\""));
|
||||
assert!(!markup.to_lowercase().contains("rollout"));
|
||||
|
||||
let unaccepted =
|
||||
push_relay_section("/admin", "csrf", &PushRelayConfigResponse::default()).into_string();
|
||||
assert!(unaccepted.contains("name=\"push_relay_consent_accepted\""));
|
||||
assert!(unaccepted.contains("Not accepted"));
|
||||
assert!(unaccepted.contains("value=\"Never\""));
|
||||
assert!(unaccepted.contains("value=\"Nobody\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn premium_mode_options_use_the_configured_premium_name() {
|
||||
let markup =
|
||||
premium_mode_form("/admin", "csrf", &InstancePolicyResponse::default(), "Gold")
|
||||
.into_string();
|
||||
assert!(markup.contains("Mirror (Free and Gold tiers)"));
|
||||
assert!(markup.contains("Everyone (every member gets Gold limits)"));
|
||||
assert!(!markup.contains("Plutonium"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_migration_section_flags_a_list_at_its_cap() {
|
||||
let domain_migration = DomainMigrationConfigResponse {
|
||||
included_user_ids: (0..EXPERIMENT_MAX_TARGETED_USERS)
|
||||
.map(|index| index.to_string())
|
||||
.collect(),
|
||||
..DomainMigrationConfigResponse::default()
|
||||
};
|
||||
let markup = domain_migration_section("/admin", "csrf", &domain_migration).into_string();
|
||||
assert!(markup.contains("1000 of 1000 stored"));
|
||||
assert!(markup.contains("at the cap"));
|
||||
}
|
||||
}
|
||||
@@ -196,22 +196,3 @@ pub(crate) fn next_page_link(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::status_badge;
|
||||
|
||||
#[test]
|
||||
fn status_badges_use_us_spelling_and_readable_labels() {
|
||||
let canceled = status_badge("cancelled").into_string();
|
||||
assert!(canceled.contains(">Canceled<"));
|
||||
assert!(!canceled.contains("Cancelled"));
|
||||
assert!(
|
||||
status_badge("deadletter")
|
||||
.into_string()
|
||||
.contains(">Dead-letter<")
|
||||
);
|
||||
assert!(status_badge("running").into_string().contains(">Running<"));
|
||||
assert!(status_badge("mystery").into_string().contains(">mystery<"));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -640,126 +640,3 @@ fn reports_url(config: &AdminConfig, filters: &ReportFilters<'_>, page: u32, lim
|
||||
.join("&");
|
||||
format!("{}/reports?{}", config.base_path, query)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::{ProxyConfig, RuntimeEnv};
|
||||
use serde_json::json;
|
||||
|
||||
fn test_config() -> AdminConfig {
|
||||
AdminConfig {
|
||||
env: RuntimeEnv::Test,
|
||||
host: String::new(),
|
||||
port: 0,
|
||||
secret_key_base: "test-secret".to_owned(),
|
||||
base_path: "/admin".to_owned(),
|
||||
api_endpoint: String::new(),
|
||||
media_endpoint: "https://media.example.test".to_owned(),
|
||||
static_cdn_endpoint: String::new(),
|
||||
reports_bucket_origin: String::new(),
|
||||
admin_endpoint: String::new(),
|
||||
web_app_endpoint: String::new(),
|
||||
oauth_client_id: String::new(),
|
||||
oauth_client_secret: String::new(),
|
||||
oauth_redirect_uri: String::new(),
|
||||
build_version: "test".to_owned(),
|
||||
self_hosted: false,
|
||||
proxy: ProxyConfig {
|
||||
trust_client_ip_header: false,
|
||||
client_ip_header_name: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn report() -> ReportEntry {
|
||||
let mut value = json!({
|
||||
"report_id": "1556709309709027556",
|
||||
"reporter_id": "1556709306000000001",
|
||||
"reporter_username": "reporter_80f33d09e88a",
|
||||
"reporter_global_name": "Avery Reporter",
|
||||
"reporter_discriminator": "5193",
|
||||
"reported_at": "2026-10-06T04:33:00Z",
|
||||
"status": 0,
|
||||
"report_type": 1,
|
||||
"category": "harassment",
|
||||
"reported_user_id": "1556709306000000002",
|
||||
"reported_user_username": "target_bc57b33ca5c4",
|
||||
"reported_user_discriminator": "8316"
|
||||
});
|
||||
for key in [
|
||||
"reporter_tag",
|
||||
"reporter_email",
|
||||
"reporter_full_legal_name",
|
||||
"reporter_country_of_residence",
|
||||
"additional_info",
|
||||
"reported_user_tag",
|
||||
"reported_user_global_name",
|
||||
"reported_user_avatar_hash",
|
||||
"reported_guild_id",
|
||||
"reported_guild_name",
|
||||
"reported_guild_icon_hash",
|
||||
"reported_message_id",
|
||||
"reported_channel_id",
|
||||
"reported_channel_name",
|
||||
"reported_channel_nsfw",
|
||||
"reported_guild_invite_code",
|
||||
"reported_guild_nsfw_level",
|
||||
"reported_guild_nsfw",
|
||||
"reported_guild_content_warning_level",
|
||||
"reported_guild_content_warning_text",
|
||||
"reported_channel_nsfw_override",
|
||||
"reported_channel_content_warning_level",
|
||||
"reported_channel_content_warning_text",
|
||||
"reported_channel_effective_nsfw",
|
||||
"reported_channel_effective_content_warning_level",
|
||||
"reported_channel_effective_content_warning_text",
|
||||
"resolved_at",
|
||||
"resolved_by_admin_id",
|
||||
"public_comment",
|
||||
"mutual_dm_channel_id",
|
||||
"message_context",
|
||||
] {
|
||||
value[key] = serde_json::Value::Null;
|
||||
}
|
||||
serde_json::from_value(value).expect("report fixture")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn narrow_layout_moves_date_and_status_into_visible_columns() {
|
||||
let markup = render_reports_table(&test_config(), &[report()]).into_string();
|
||||
assert!(markup.contains(
|
||||
r#"<span class="text-neutral-600 text-xs xl:hidden" data-report-reported-at-compact="1556709309709027556">Oct 6, 2026, 4:33 AM UTC</span>"#
|
||||
));
|
||||
assert!(markup.contains(
|
||||
r#"<span class="xl:hidden" data-status-pill-compact="1556709309709027556">"#
|
||||
));
|
||||
assert!(markup.contains(r#"<span data-status-pill="1556709309709027556">"#));
|
||||
assert_eq!(
|
||||
markup
|
||||
.matches("hidden whitespace-nowrap xl:table-cell")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
markup
|
||||
.matches(
|
||||
"hidden whitespace-nowrap px-3 py-3 text-neutral-600 text-sm xl:table-cell"
|
||||
)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
markup
|
||||
.matches("hidden whitespace-nowrap px-3 py-3 text-sm xl:table-cell")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
markup
|
||||
.matches("[overflow-wrap:anywhere] [&_.whitespace-nowrap]:whitespace-normal")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -173,47 +173,3 @@ fn entry_row(base: &str, csrf_token: &str, entry: &BlocklistEntry) -> Markup {
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(value: &str, match_subdomains: bool) -> BlocklistEntry {
|
||||
BlocklistEntry {
|
||||
value: value.to_owned(),
|
||||
match_subdomains: Some(match_subdomains),
|
||||
category: Some("manual".to_owned()),
|
||||
created_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_table_lists_patterns_with_remove_forms() {
|
||||
let page = BlocklistEntryPage {
|
||||
items: vec![
|
||||
entry("*shop*.example.com", false),
|
||||
entry("store.example.com", true),
|
||||
],
|
||||
has_more: true,
|
||||
next_after: Some("store.example.com".to_owned()),
|
||||
};
|
||||
let markup = entries_table("/admin", "token", &page).into_string();
|
||||
assert!(markup.contains("*shop*.example.com"));
|
||||
assert!(markup.contains(">Pattern</span>"));
|
||||
assert!(markup.contains(">Domain</span>"));
|
||||
assert!(markup.contains(r#"name="domain" value="*shop*.example.com""#));
|
||||
assert!(markup.contains("/admin/url-domain-bans?action=unban&_csrf=token"));
|
||||
assert!(markup.contains("/admin/url-domain-bans?after=store.example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_table_reports_an_empty_list() {
|
||||
let page = BlocklistEntryPage {
|
||||
items: Vec::new(),
|
||||
has_more: false,
|
||||
next_after: None,
|
||||
};
|
||||
let markup = entries_table("/admin", "token", &page).into_string();
|
||||
assert!(markup.contains("No domains or patterns are blocked"));
|
||||
}
|
||||
}
|
||||
@@ -764,56 +764,6 @@ mod tests {
|
||||
serde_json::from_value(value).expect("valid admin user")
|
||||
}
|
||||
|
||||
fn entry(log_id: &str, action: &str, reason: &str, metadata: Value) -> AuditLogEntry {
|
||||
serde_json::from_value(json!({
|
||||
"log_id": log_id,
|
||||
"admin_user_id": "1400000000000000001",
|
||||
"admin_user": {"id": "1400000000000000001", "username": "lilith", "discriminator": "0001", "global_name": null},
|
||||
"action": action,
|
||||
"target_id": "1500000000000000001",
|
||||
"target_type": "user",
|
||||
"audit_log_reason": reason,
|
||||
"metadata": metadata,
|
||||
"created_at": "2026-09-01T10:00:00.000Z"
|
||||
}))
|
||||
.expect("valid audit log entry")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_deletion_card_names_the_scheduler_and_the_deletion_it_cancels() {
|
||||
let target = user(json!({
|
||||
"pending_deletion_at": "2026-10-30T17:40:29.690Z",
|
||||
"deletion_reason_code": 3,
|
||||
"deletion_public_reason": "Spam",
|
||||
"deletion_audit_log_reason": "Report batch 12",
|
||||
"deletion_scheduled_by": "1400000000000000001",
|
||||
"deletion_scheduled_at": "2026-08-31T17:40:29.690Z"
|
||||
}));
|
||||
let scheduler = user(json!({"id": "1400000000000000001", "username": "lilith"}));
|
||||
let markup =
|
||||
deletion_card("/admin", &target, "csrf", Some(&scheduler), false).into_string();
|
||||
assert!(markup.contains(r#"href="/admin/users/1400000000000000001""#));
|
||||
assert!(markup.contains("lilith"));
|
||||
assert!(markup.contains("Report batch 12"));
|
||||
assert!(
|
||||
markup.contains(
|
||||
r#"name="expected_pending_deletion_at" value="2026-10-30T17:40:29.690Z""#
|
||||
)
|
||||
);
|
||||
assert!(markup.contains(r#"name="notify_user" value="true""#));
|
||||
assert!(!markup.contains(r#"name="notify_user" value="true" checked"#));
|
||||
assert!(markup.contains("Cancel lilith's deletion (Spam, due"));
|
||||
assert!(markup.contains(r#"name="private_reason" required"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_form_makes_the_reason_an_explicit_choice() {
|
||||
let markup = deletion_card("/admin", &user(json!({})), "csrf", None, false).into_string();
|
||||
assert!(markup.contains(r#"<option value="" disabled selected>Choose a reason</option>"#));
|
||||
assert!(!markup.contains(r#"<option value="1" selected>"#));
|
||||
assert!(!markup.contains("replace_pending_deletion_at"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_form_emails_the_user_by_default() {
|
||||
let markup = deletion_card("/admin", &user(json!({})), "csrf", None, false).into_string();
|
||||
@@ -864,49 +814,4 @@ mod tests {
|
||||
));
|
||||
assert!(markup.contains(r#"name="private_reason""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_ban_is_the_entry_matching_the_ban_end_and_notes_attach_to_it() {
|
||||
let target = user(json!({"temp_banned_until": "2026-10-01T00:00:00.000Z"}));
|
||||
let logs = vec![
|
||||
entry(
|
||||
"3",
|
||||
"annotate_ban",
|
||||
"Also sent links",
|
||||
json!({"ban_audit_log_id": "2"}),
|
||||
),
|
||||
entry(
|
||||
"2",
|
||||
"temp_ban",
|
||||
"Regel § 3",
|
||||
json!({"banned_until": "2026-10-01T00:00:00.000Z"}),
|
||||
),
|
||||
entry(
|
||||
"1",
|
||||
"temp_ban",
|
||||
"Older ban",
|
||||
json!({"banned_until": "2026-01-01T00:00:00.000Z"}),
|
||||
),
|
||||
entry(
|
||||
"4",
|
||||
"annotate_ban",
|
||||
"Old note",
|
||||
json!({"ban_audit_log_id": "1"}),
|
||||
),
|
||||
];
|
||||
let ban = find_current_ban(&target, &logs).expect("current ban");
|
||||
assert_eq!(ban.entry.log_id, "2");
|
||||
assert_eq!(
|
||||
ban.notes
|
||||
.iter()
|
||||
.map(|note| note.log_id.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["3"]
|
||||
);
|
||||
let markup = ban_actions_card("/admin", &target, "csrf", Some(&ban), false).into_string();
|
||||
assert!(markup.contains("Regel § 3"));
|
||||
assert!(markup.contains("Also sent links"));
|
||||
assert!(markup.contains(r#"name="ban_audit_log_id" value="2""#));
|
||||
assert!(markup.contains("?action=annotate_ban&tab=moderation"));
|
||||
}
|
||||
}
|
||||
@@ -584,84 +584,3 @@ fn custom_traits<'a>(user: &'a AdminUser, trait_definitions: &[&str]) -> Vec<&'a
|
||||
.filter(|trait_name| !DERIVED_TRAITS.contains(trait_name))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_config() -> AdminConfig {
|
||||
AdminConfig {
|
||||
env: crate::config::RuntimeEnv::Test,
|
||||
host: String::new(),
|
||||
port: 3020,
|
||||
secret_key_base: "test-secret".to_owned(),
|
||||
base_path: "/admin".to_owned(),
|
||||
api_endpoint: String::new(),
|
||||
media_endpoint: String::new(),
|
||||
static_cdn_endpoint: String::new(),
|
||||
reports_bucket_origin: String::new(),
|
||||
admin_endpoint: String::new(),
|
||||
web_app_endpoint: String::new(),
|
||||
oauth_client_id: String::new(),
|
||||
oauth_client_secret: String::new(),
|
||||
oauth_redirect_uri: String::new(),
|
||||
build_version: "test".to_owned(),
|
||||
self_hosted: true,
|
||||
proxy: crate::config::ProxyConfig {
|
||||
trust_client_ip_header: false,
|
||||
client_ip_header_name: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn render_email_row(username_sign_in: bool) -> String {
|
||||
let user: AdminUser = serde_json::from_value(serde_json::json!({
|
||||
"id": "1500000000000000001",
|
||||
"username": "target",
|
||||
"discriminator": "0001",
|
||||
"email": "[email protected]"
|
||||
}))
|
||||
.expect("valid admin user");
|
||||
let acls = vec![acl::USER_VIEW_EMAIL.to_owned()];
|
||||
render_overview_tab(
|
||||
&test_config(),
|
||||
&user,
|
||||
&acls,
|
||||
"csrf",
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
username_sign_in,
|
||||
)
|
||||
.into_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_mode_hides_the_email_row() {
|
||||
assert!(!render_email_row(true).contains("[email protected]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_mode_shows_the_email_row() {
|
||||
assert!(render_email_row(false).contains("[email protected]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_active_and_grace_end_use_the_panel_date_format() {
|
||||
let user: AdminUser = serde_json::from_value(serde_json::json!({
|
||||
"id": "1500000000000000001",
|
||||
"username": "target",
|
||||
"discriminator": "0001",
|
||||
"last_active_at": "2026-10-06T20:23:38.591Z",
|
||||
"premium_grace_ends_at": "2026-11-01T08:00:00Z"
|
||||
}))
|
||||
.expect("valid admin user");
|
||||
let html =
|
||||
render_overview_tab(&test_config(), &user, &[], "csrf", None, None, false, false)
|
||||
.into_string();
|
||||
assert!(!html.contains("2026-10-06T20:23"));
|
||||
assert!(!html.contains("2026-11-01T08:00"));
|
||||
assert!(html.contains(&format_admin_timestamp("2026-10-06T20:23:38.591Z")));
|
||||
assert!(html.contains(&format_admin_timestamp("2026-11-01T08:00:00Z")));
|
||||
}
|
||||
}
|
||||
@@ -375,168 +375,3 @@ fn reported_entity_icon(config: &AdminConfig, report: &ReportEntry) -> Markup {
|
||||
}
|
||||
html! {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::{ProxyConfig, RuntimeEnv};
|
||||
use serde_json::json;
|
||||
|
||||
fn test_config() -> AdminConfig {
|
||||
AdminConfig {
|
||||
env: RuntimeEnv::Test,
|
||||
host: String::new(),
|
||||
port: 0,
|
||||
secret_key_base: "test-secret".to_owned(),
|
||||
base_path: "/admin".to_owned(),
|
||||
api_endpoint: String::new(),
|
||||
media_endpoint: "https://media.example.test".to_owned(),
|
||||
static_cdn_endpoint: String::new(),
|
||||
reports_bucket_origin: String::new(),
|
||||
admin_endpoint: String::new(),
|
||||
web_app_endpoint: String::new(),
|
||||
oauth_client_id: String::new(),
|
||||
oauth_client_secret: String::new(),
|
||||
oauth_redirect_uri: String::new(),
|
||||
build_version: "test".to_owned(),
|
||||
self_hosted: false,
|
||||
proxy: ProxyConfig {
|
||||
trust_client_ip_header: false,
|
||||
client_ip_header_name: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_webhook_reports_name_the_webhook_instead_of_the_guild() {
|
||||
let report: ReportEntry = serde_json::from_value(json!({
|
||||
"report_id": "1800000000000000004",
|
||||
"reporter_id": "1500000000000000000",
|
||||
"reported_at": "2026-10-04T10:00:00.000Z",
|
||||
"status": 0,
|
||||
"report_type": 0,
|
||||
"category": "spam",
|
||||
"reported_user_id": null,
|
||||
"reported_webhook_id": "1700000000000000500",
|
||||
"reported_webhook_name": "Harbor Bulletin",
|
||||
"reported_guild_id": "1700000000000000800",
|
||||
"reported_guild_name": "Harbor"
|
||||
}))
|
||||
.expect("valid report");
|
||||
let config = test_config();
|
||||
let sent = report_row(&config, "/admin", "sent", &report).into_string();
|
||||
assert!(
|
||||
sent.contains(r#"href="/admin/reports?reported_webhook_id=1700000000000000500""#),
|
||||
"{sent}"
|
||||
);
|
||||
assert!(
|
||||
sent.contains(r#"data-report-webhook="1700000000000000500""#),
|
||||
"{sent}"
|
||||
);
|
||||
assert!(sent.contains("Harbor Bulletin"), "{sent}");
|
||||
assert!(!sent.contains("/admin/guilds/"), "{sent}");
|
||||
let received = report_row(&config, "/admin", "received", &report).into_string();
|
||||
assert!(!received.contains("data-report-webhook"), "{received}");
|
||||
assert!(
|
||||
received.contains(r#"href="/admin/users/1500000000000000000""#),
|
||||
"{received}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_rows_label_the_category_and_keep_the_key() {
|
||||
let config = test_config();
|
||||
let report: ReportEntry = serde_json::from_value(json!({
|
||||
"report_id": "1800000000000000005",
|
||||
"reporter_id": "1500000000000000000",
|
||||
"reported_at": "2026-10-04T10:00:00.000Z",
|
||||
"status": 0,
|
||||
"report_type": 1,
|
||||
"category": "inappropriate_profile",
|
||||
"reported_user_id": "1500000000000000001",
|
||||
"reason": "harassment",
|
||||
"reason_label": "Harassment or bullying"
|
||||
}))
|
||||
.expect("valid report");
|
||||
for kind in ["sent", "received"] {
|
||||
let markup = report_row(&config, "/admin", kind, &report).into_string();
|
||||
assert!(
|
||||
markup.contains(r#"data-report-category="inappropriate_profile""#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains(">Inappropriate profile<"), "{markup}");
|
||||
assert!(!markup.contains(">inappropriate_profile<"), "{markup}");
|
||||
assert!(
|
||||
markup.contains(r#"data-report-reason="harassment""#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains("Oct 4, 2026, 10:00 AM UTC"), "{markup}");
|
||||
assert!(!markup.contains("2026-10-04T10:00:00.000Z"), "{markup}");
|
||||
assert_eq!(
|
||||
markup
|
||||
.matches(r#"href="/admin/reports/1800000000000000005""#)
|
||||
.count(),
|
||||
1,
|
||||
"{markup}"
|
||||
);
|
||||
assert!(!markup.contains(">View<"), "{markup}");
|
||||
}
|
||||
let mut unknown = report.clone();
|
||||
unknown.category = Some("future_value".to_owned());
|
||||
let markup = report_row(&config, "/admin", "sent", &unknown).into_string();
|
||||
assert!(markup.contains(">future_value<"), "{markup}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_rows_skip_a_reason_that_repeats_the_category() {
|
||||
let config = test_config();
|
||||
let report: ReportEntry = serde_json::from_value(json!({
|
||||
"report_id": "1800000000000000007",
|
||||
"reporter_id": "1500000000000000000",
|
||||
"reporter_username": "reporter_0423a7212d56",
|
||||
"reporter_discriminator": "8650",
|
||||
"reporter_global_name": "Avery Reporter",
|
||||
"reported_at": "2026-10-04T10:00:00.000Z",
|
||||
"status": 0,
|
||||
"report_type": 0,
|
||||
"category": "harassment",
|
||||
"reported_user_id": "1500000000000000001",
|
||||
"reason": "harassment",
|
||||
"reason_label": "Harassment or bullying"
|
||||
}))
|
||||
.expect("valid report");
|
||||
let markup = report_row(&config, "/admin", "received", &report).into_string();
|
||||
assert_eq!(
|
||||
markup.matches("Harassment or bullying").count(),
|
||||
2,
|
||||
"{markup}"
|
||||
);
|
||||
assert_eq!(
|
||||
markup.matches(r#"data-report-reason="harassment""#).count(),
|
||||
2,
|
||||
"{markup}"
|
||||
);
|
||||
assert!(
|
||||
markup.contains(
|
||||
r#"<div class="mt-1 xl:hidden" data-report-type-compact="1800000000000000007">"#
|
||||
),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(
|
||||
markup.contains(
|
||||
r#"<td class="hidden px-4 py-3 text-sm text-neutral-900 xl:table-cell">"#
|
||||
),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(
|
||||
markup.contains(r#"data-report-category="harassment""#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains(">Message<"), "{markup}");
|
||||
assert!(
|
||||
markup.contains(r#"Avery Reporter <span class="whitespace-nowrap text-neutral-500 text-xs">(reporter_0423a7212d56#8650)</span>"#),
|
||||
"{markup}"
|
||||
);
|
||||
assert!(markup.contains(">Pending<"), "{markup}");
|
||||
}
|
||||
}
|
||||
@@ -459,25 +459,3 @@ fn users_url(base: &str, params: &UserListParams, page: u32) -> String {
|
||||
pairs.push(format!("page={page}"));
|
||||
format!("{base}/users?{}", pairs.join("&"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn params() -> UserListParams {
|
||||
UserListParams::from_query(None, None, None, None, None, None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_mode_has_no_email_search() {
|
||||
let markup = search_form("/admin", ¶ms(), false).into_string();
|
||||
assert!(!markup.contains("search-email"));
|
||||
assert!(markup.contains("search-q"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_mode_keeps_the_email_search() {
|
||||
let markup = search_form("/admin", ¶ms(), true).into_string();
|
||||
assert!(markup.contains("search-email"));
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,3 @@ pub fn format_discriminator(discriminator: &str) -> String {
|
||||
let num: u16 = discriminator.parse().unwrap_or(0);
|
||||
format!("{num:04}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn format_discriminator_pads_short() {
|
||||
assert_eq!(format_discriminator("1"), "0001");
|
||||
assert_eq!(format_discriminator("42"), "0042");
|
||||
assert_eq!(format_discriminator("0"), "0000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_discriminator_four_digits() {
|
||||
assert_eq!(format_discriminator("1234"), "1234");
|
||||
assert_eq!(format_discriminator("9999"), "9999");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_discriminator_invalid_input() {
|
||||
assert_eq!(format_discriminator(""), "0000");
|
||||
assert_eq!(format_discriminator("abc"), "0000");
|
||||
}
|
||||
}
|
||||
@@ -128,73 +128,3 @@ impl MultiValueForm {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn clean_string_trims_whitespace() {
|
||||
assert_eq!(clean_string(" hello "), Some("hello".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_string_returns_none_for_empty() {
|
||||
assert_eq!(clean_string(""), None);
|
||||
assert_eq!(clean_string(" "), None);
|
||||
assert_eq!(clean_string("\t\n"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_string_preserves_inner_spaces() {
|
||||
assert_eq!(
|
||||
clean_string(" hello world "),
|
||||
Some("hello world".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_comma_separated_splits() {
|
||||
assert_eq!(parse_comma_separated("a, b, c"), vec!["a", "b", "c"]);
|
||||
assert_eq!(parse_comma_separated("a\nb\r\nc"), vec!["a", "b", "c"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_comma_separated_filters_empty() {
|
||||
assert_eq!(parse_comma_separated("a,,b, ,c"), vec!["a", "b", "c"]);
|
||||
assert!(parse_comma_separated("").is_empty());
|
||||
assert!(parse_comma_separated(", , ,").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_value_form_preserves_repeated_fields() {
|
||||
let form = MultiValueForm::parse(b"fields%5B%5D=avatar&fields%5B%5D=banner&name=Test");
|
||||
|
||||
assert_eq!(
|
||||
form.list_values("fields[]"),
|
||||
vec!["avatar".to_owned(), "banner".to_owned()]
|
||||
);
|
||||
assert_eq!(form.clean("name"), Some("Test".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_value_form_list_values_accepts_comma_and_repeated_values() {
|
||||
let form = MultiValueForm::parse(b"acls=user.read,user.write&acls=guild.read");
|
||||
|
||||
assert_eq!(
|
||||
form.list_values("acls"),
|
||||
vec![
|
||||
"user.read".to_owned(),
|
||||
"user.write".to_owned(),
|
||||
"guild.read".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_value_form_bool_value_accepts_html_checkbox_values() {
|
||||
assert!(MultiValueForm::parse(b"enabled=on").bool_value("enabled"));
|
||||
assert!(MultiValueForm::parse(b"enabled=true").bool_value("enabled"));
|
||||
assert!(!MultiValueForm::parse(b"enabled=false").bool_value("enabled"));
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,3 @@ pub fn count_noun(count: u64, singular: &str, plural: &str) -> String {
|
||||
pub fn noun_for(count: u64, singular: &'static str, plural: &'static str) -> &'static str {
|
||||
if count == 1 { singular } else { plural }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn count_noun_picks_the_singular_only_for_one() {
|
||||
assert_eq!(count_noun(0, "result", "results"), "0 results");
|
||||
assert_eq!(count_noun(1, "result", "results"), "1 result");
|
||||
assert_eq!(count_noun(2, "entry", "entries"), "2 entries");
|
||||
assert_eq!(noun_for(1, "item", "items"), "item");
|
||||
assert_eq!(noun_for(3, "item", "items"), "items");
|
||||
}
|
||||
}
|
||||
@@ -54,52 +54,3 @@ pub fn snowflake_creation_date(snowflake: &str) -> String {
|
||||
})
|
||||
.map_or_else(|| "Unknown".to_owned(), format_admin_datetime)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn snowflake_to_timestamp_ms_known_value() {
|
||||
assert_eq!(snowflake_to_timestamp_ms("0"), Some(FLUXER_EPOCH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snowflake_to_timestamp_ms_real_id() {
|
||||
let snowflake = (1u64 << 22).to_string();
|
||||
assert_eq!(
|
||||
snowflake_to_timestamp_ms(&snowflake),
|
||||
Some(FLUXER_EPOCH + 1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snowflake_to_timestamp_ms_invalid() {
|
||||
assert_eq!(snowflake_to_timestamp_ms(""), None);
|
||||
assert_eq!(snowflake_to_timestamp_ms("abc"), None);
|
||||
assert_eq!(snowflake_to_timestamp_ms("-1"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snowflake_creation_date_invalid() {
|
||||
assert_eq!(snowflake_creation_date("abc"), "Unknown");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snowflake_creation_date_valid() {
|
||||
assert_eq!(snowflake_creation_date("0"), "Jan 1, 2015, 12:00 AM UTC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_timestamps_render_in_the_panel_format() {
|
||||
assert_eq!(
|
||||
format_admin_timestamp("2026-10-06T14:05:09.123Z"),
|
||||
"Oct 6, 2026, 2:05 PM UTC"
|
||||
);
|
||||
assert_eq!(
|
||||
format_admin_timestamp("2026-10-06T00:30:00+02:00"),
|
||||
"Oct 5, 2026, 10:30 PM UTC"
|
||||
);
|
||||
assert_eq!(format_admin_timestamp("not a date"), "not a date");
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ pub async fn with_unique_usernames<F: Future>(unique_usernames: bool, future: F)
|
||||
UNIQUE_USERNAMES.scope(unique_usernames, future).await
|
||||
}
|
||||
|
||||
pub fn sync_with_unique_usernames<R>(unique_usernames: bool, f: impl FnOnce() -> R) -> R {
|
||||
UNIQUE_USERNAMES.sync_scope(unique_usernames, f)
|
||||
}
|
||||
|
||||
pub fn unique_usernames() -> bool {
|
||||
UNIQUE_USERNAMES.try_with(|value| *value).unwrap_or(false)
|
||||
}
|
||||
@@ -29,41 +25,3 @@ pub fn user_tag(username: &str, discriminator: &str, is_bot: bool) -> String {
|
||||
username.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn email_mode_keeps_every_tag() {
|
||||
assert_eq!(user_tag("alice", "0000", false), "alice#0000");
|
||||
assert_eq!(user_tag("alice", "0042", false), "alice#0042");
|
||||
sync_with_unique_usernames(false, || {
|
||||
assert_eq!(user_tag("alice", "0000", false), "alice#0000");
|
||||
assert_eq!(user_tag("bot", "0000", true), "bot#0000");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_mode_hides_zero_tag_for_humans() {
|
||||
sync_with_unique_usernames(true, || {
|
||||
assert_eq!(user_tag("alice", "0000", false), "alice");
|
||||
assert_eq!(user_tag("alice", "0", false), "alice");
|
||||
assert!(!shows_discriminator("0000", false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_mode_keeps_bot_and_non_zero_tags() {
|
||||
sync_with_unique_usernames(true, || {
|
||||
assert_eq!(user_tag("helper", "4363", true), "helper#4363");
|
||||
assert_eq!(user_tag("helper", "0000", true), "helper#0000");
|
||||
assert_eq!(user_tag("legacy", "0042", false), "legacy#0042");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_defaults_to_email_outside_a_request() {
|
||||
assert!(!unique_usernames());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user