Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b07a63d
fix(test): bound CleanupGuard::drop join to 30s
Detair Apr 11, 2026
6efb39c
feat(test): add CleanupGuard::cleanup() explicit method
Detair Apr 11, 2026
d4f49f3
test(guild_limits): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
dee1055
test(custom_status): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
fa38d14
test(workspaces): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
5117911
test(filters_http): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
1ace052
test(governance): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
76126b5
test(webhooks): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
51ef704
test(channel_pins): migrate to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
28a2f3d
test(api): migrate connectivity_http to explicit CleanupGuard::cleanu…
Detair Apr 11, 2026
e1059ba
test(api): migrate setup_http to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
d97d137
test(chat): migrate messages_http to explicit CleanupGuard::cleanup()…
Detair Apr 11, 2026
98b8ddf
test(api): migrate media_processing to explicit CleanupGuard::cleanup…
Detair Apr 11, 2026
92bd00f
test(api): migrate bot_intents to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
adfdd9b
test(chat): migrate dm_http to explicit CleanupGuard::cleanup().await
Detair Apr 11, 2026
8d07344
test(api): migrate channels_http to explicit CleanupGuard::cleanup().…
Detair Apr 11, 2026
082218c
test(api): migrate uploads_http to explicit CleanupGuard::cleanup().a…
Detair Apr 11, 2026
df217ab
test(api): migrate setup_concurrent_http to explicit CleanupGuard::cl…
Detair Apr 11, 2026
81b993d
docs(test): clarify CleanupGuard Drop is a permanent safety net
Detair Apr 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions server/tests/integration/bot_intents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async fn update_intents_persists() {
assert!(intents.iter().any(|v| v == "messages"));
assert!(intents.iter().any(|v| v == "members"));
assert!(intents.iter().any(|v| v == "commands"));
guard.cleanup().await;
}

#[tokio::test]
Expand Down Expand Up @@ -73,6 +74,7 @@ async fn update_intents_reflects_in_get() {
assert_eq!(intents.len(), 2);
assert!(intents.iter().any(|v| v == "messages"));
assert!(intents.iter().any(|v| v == "members"));
guard.cleanup().await;
}

// ============================================================================
Expand Down Expand Up @@ -100,6 +102,7 @@ async fn invalid_intent_name_rejected() {

let resp = app.oneshot(req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
guard.cleanup().await;
}

#[tokio::test]
Expand All @@ -125,6 +128,7 @@ async fn empty_intents_allowed() {
let json = body_to_json(resp).await;
let intents = json["gateway_intents"].as_array().unwrap();
assert!(intents.is_empty());
guard.cleanup().await;
}

// ============================================================================
Expand Down Expand Up @@ -152,6 +156,7 @@ async fn non_owner_cannot_update_intents() {

let resp = app.oneshot(req).await;
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
guard.cleanup().await;
}

// ============================================================================
Expand Down Expand Up @@ -185,6 +190,7 @@ async fn new_application_has_default_intents() {
// New applications should have empty gateway_intents by default (from DB default)
let intents = json["gateway_intents"].as_array().unwrap();
assert!(intents.is_empty());
guard.cleanup().await;
}

// ============================================================================
Expand Down
10 changes: 10 additions & 0 deletions server/tests/integration/channel_pins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ async fn test_pin_message_success() {
pins_arr[0]["pinned_at"].is_string(),
"Should include pinned_at"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -147,6 +148,7 @@ async fn test_unpin_message_success() {
let pins = list_pins(&app, channel_id, &token).await;
let pins_arr = pins.as_array().expect("pins should be an array");
assert!(pins_arr.is_empty(), "Pins list should be empty after unpin");
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -178,6 +180,7 @@ async fn test_pin_idempotent() {
1,
"Should have exactly one pin despite pinning twice"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -214,6 +217,7 @@ async fn test_pin_limit_50() {
"PIN_LIMIT_REACHED",
"Error code should be PIN_LIMIT_REACHED"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -252,6 +256,7 @@ async fn test_pin_forbidden_without_permission() {
200,
"Owner should succeed regardless of @everyone role permissions"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand All @@ -277,6 +282,7 @@ async fn test_pin_message_not_in_channel() {
404,
"Pinning a message in the wrong channel should return 404"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand All @@ -301,6 +307,7 @@ async fn test_pin_deleted_message() {
404,
"Pinning a deleted message should return 404"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -339,6 +346,7 @@ async fn test_system_message_on_pin() {
content.contains("pinned a message"),
"System message should contain 'pinned a message', got: {content}"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -381,6 +389,7 @@ async fn test_pinned_field_in_message_list() {
assert!(!pinned, "Non-pinned message should have pinned=false");
}
}
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -421,4 +430,5 @@ async fn test_cascade_on_message_delete() {
pins_arr.is_empty(),
"Pins list should be empty after message deletion"
);
guard.cleanup().await;
}
5 changes: 5 additions & 0 deletions server/tests/integration/channels_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async fn test_create_channel_success() {
assert_eq!(json["name"], "new-channel");
assert_eq!(json["channel_type"], "text");
assert_eq!(json["guild_id"], guild_id.to_string());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -107,6 +108,7 @@ async fn test_create_channel_validation_errors() {
400,
"Voice channel with user_limit=100 should return 400"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -155,6 +157,7 @@ async fn test_update_channel_requires_manage_channels() {

let json = body_to_json(resp).await;
assert_eq!(json["name"], "renamed-by-owner");
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -195,6 +198,7 @@ async fn test_delete_channel_requires_manage_channels() {
.unwrap();
let resp = app.oneshot(req).await;
assert_eq!(resp.status(), 204, "Owner should be able to delete");
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand All @@ -220,4 +224,5 @@ async fn test_get_channel_not_found() {
"Non-existent channel should return 403 or 404, got {}",
resp.status()
);
guard.cleanup().await;
}
8 changes: 8 additions & 0 deletions server/tests/integration/connectivity_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ async fn test_summary_empty() {
assert_eq!(json["total_sessions"], 0);
assert_eq!(json["total_duration_secs"], 0);
assert!(json["daily_stats"].as_array().unwrap().is_empty());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -144,6 +145,7 @@ async fn test_summary_with_data() {
assert!(json["total_duration_secs"].as_i64().unwrap() > 0);
assert!(json["avg_latency"].is_number());
assert!(!json["daily_stats"].as_array().unwrap().is_empty());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -182,6 +184,7 @@ async fn test_sessions_empty() {
let json = body_to_json(resp).await;
assert_eq!(json["total"], 0);
assert!(json["sessions"].as_array().unwrap().is_empty());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -219,6 +222,7 @@ async fn test_sessions_with_data() {
assert_eq!(sessions[0]["id"], session_id.to_string());
assert_eq!(sessions[0]["channel_name"], "voice-sess");
assert!(sessions[0]["guild_name"].is_string());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -262,6 +266,7 @@ async fn test_sessions_pagination() {
1,
"Should return exactly 1 session"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -322,6 +327,7 @@ async fn test_session_detail() {
assert!(metrics[0]["packet_loss"].is_number());
assert!(metrics[0]["jitter_ms"].is_number());
assert!(metrics[0]["quality"].is_number());
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand All @@ -342,6 +348,7 @@ async fn test_session_detail_not_found() {

let resp = app.oneshot(req).await;
assert_eq!(resp.status(), 404, "Non-existent session should return 404");
guard.cleanup().await;
}

// ============================================================================
Expand Down Expand Up @@ -386,4 +393,5 @@ async fn test_session_rls_isolation() {
let json = body_to_json(resp).await;
assert_eq!(json["total"], 0, "User B should not see User A's sessions");
assert!(json["sessions"].as_array().unwrap().is_empty());
guard.cleanup().await;
}
16 changes: 6 additions & 10 deletions server/tests/integration/custom_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ async fn test_custom_status_set_in_database() {
assert_eq!(deserialized.text, "Testing custom status");
assert_eq!(deserialized.emoji, Some("\u{1F9EA}".to_string()));
assert!(deserialized.expires_at.is_none());
guard.cleanup().await;
}

#[tokio::test]
Expand Down Expand Up @@ -273,8 +274,7 @@ async fn test_custom_status_clear_in_database() {
row.0.is_none(),
"custom_status should be NULL after clearing"
);

// guard drops here, runs cleanup even on panic
guard.cleanup().await;
}

#[tokio::test]
Expand Down Expand Up @@ -320,8 +320,7 @@ async fn test_custom_status_with_expiry_persists() {
diff <= 1,
"Expiry time should be within 1 second of set value, diff was {diff}s"
);

// guard drops here, runs cleanup even on panic
guard.cleanup().await;
}

// ============================================================================
Expand Down Expand Up @@ -389,8 +388,7 @@ async fn test_expiry_sweep_finds_expired_status() {
row.0.is_none(),
"custom_status should be NULL after sweep clears expired status"
);

// guard drops here, runs cleanup even on panic
guard.cleanup().await;
}

#[tokio::test]
Expand Down Expand Up @@ -446,8 +444,7 @@ async fn test_expiry_sweep_ignores_non_expired_status() {
row.0.is_some(),
"Non-expired custom_status should still be present"
);

// guard drops here, runs cleanup even on panic
guard.cleanup().await;
}

#[tokio::test]
Expand Down Expand Up @@ -490,6 +487,5 @@ async fn test_expiry_sweep_ignores_status_without_expiry() {
!expired_ids.contains(&user_id),
"Status without expiry should NOT be found by sweep query"
);

// guard drops here, runs cleanup even on panic
guard.cleanup().await;
}
5 changes: 5 additions & 0 deletions server/tests/integration/dm_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ async fn test_create_and_get_dm() {
let fetched = body_to_json(resp).await;
assert_eq!(fetched["id"].as_str().unwrap(), dm_id);
assert_eq!(fetched["channel_type"], "dm");
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -114,6 +115,7 @@ async fn test_create_dm_returns_existing() {
dm_id1, dm_id2,
"Creating DM twice with same participants should return same channel"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -151,6 +153,7 @@ async fn test_list_dms() {
let dms_b = list_dms(&app, &token_b).await;
let arr_b = dms_b.as_array().expect("DM list should be an array");
assert_eq!(arr_b.len(), 1, "User B should see 1 DM");
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -184,6 +187,7 @@ async fn test_dm_non_participant_forbidden() {
403,
"Non-participant should get 403 when accessing DM"
);
guard.cleanup().await;
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
Expand Down Expand Up @@ -222,4 +226,5 @@ async fn test_leave_dm() {
"After leaving, GET DM should return 403 or 404, got {}",
resp.status()
);
guard.cleanup().await;
}
Loading
Loading