From 91a698b04071981f05bba5fa8404e776463f506f Mon Sep 17 00:00:00 2001 From: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> Date: Sat, 1 Aug 2026 14:58:34 -0400 Subject: [PATCH] test(db): cover SQLite channel contracts Make ephemeral reaping atomic with UPDATE RETURNING and add focused lifecycle, bulk-read, canvas, tenant-scope, and idempotency coverage. Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> --- crates/buzz-db/src/sqlite.rs | 230 +++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 21 deletions(-) diff --git a/crates/buzz-db/src/sqlite.rs b/crates/buzz-db/src/sqlite.rs index 4d3c173f4..58172627f 100644 --- a/crates/buzz-db/src/sqlite.rs +++ b/crates/buzz-db/src/sqlite.rs @@ -3384,27 +3384,26 @@ pub(crate) async fn get_member_counts_bulk( pub(crate) async fn reap_expired_ephemeral_channels( pool: &SqlitePool, ) -> Result> { - let rows=sqlx::query("SELECT ch.community_id,c.host,ch.id FROM channels ch JOIN communities c ON c.id=ch.community_id WHERE ch.ttl_seconds IS NOT NULL AND ch.ttl_deadline Result> { @@ -4892,6 +4891,195 @@ mod tests { assert!(get_workflow_run(&pool, community, run_id).await.is_err()); } + #[tokio::test] + async fn channel_lifecycle_bulk_canvas_and_reaper_match_contracts() { + use crate::channel::{ChannelType, ChannelUpdate, ChannelVisibility, MemberRole}; + + let pool = connect(":memory:").await.unwrap(); + let community = ensure_configured_community(&pool, "channels.local") + .await + .unwrap() + .id; + let foreign = ensure_configured_community(&pool, "channels-foreign.local") + .await + .unwrap() + .id; + let owner = vec![61_u8; 32]; + let member = vec![62_u8; 32]; + ensure_user(&pool, community, &owner).await.unwrap(); + ensure_user(&pool, community, &member).await.unwrap(); + ensure_user(&pool, foreign, &owner).await.unwrap(); + + let channel = create_channel( + &pool, + community, + " Field Operations ", + ChannelType::Stream, + ChannelVisibility::Private, + Some("initial"), + &owner, + Some(60), + ) + .await + .unwrap(); + assert_eq!(channel.name, "Field Operations"); + assert_eq!( + get_member_count(&pool, community, channel.id) + .await + .unwrap(), + 1 + ); + assert!(get_channel(&pool, foreign, channel.id).await.is_err()); + + set_canvas(&pool, community, channel.id, Some("# Plan")) + .await + .unwrap(); + assert_eq!( + get_canvas(&pool, community, channel.id).await.unwrap(), + Some("# Plan".into()) + ); + assert_eq!(get_canvas(&pool, foreign, channel.id).await.unwrap(), None); + + add_member( + &pool, + community, + channel.id, + &member, + MemberRole::Member, + Some(&owner), + ) + .await + .unwrap(); + assert_eq!( + membership_pairs( + &pool, + community, + &[channel.id], + &[owner.clone(), member.clone()] + ) + .await + .unwrap() + .len(), + 2 + ); + assert_eq!( + get_members_bulk(&pool, community, &[channel.id]) + .await + .unwrap() + .len(), + 2 + ); + assert_eq!( + get_member_counts_bulk(&pool, community, &[channel.id]) + .await + .unwrap() + .get(&channel.id), + Some(&2) + ); + assert_eq!( + get_users_bulk(&pool, community, &[owner.clone(), member.clone()]) + .await + .unwrap() + .len(), + 2 + ); + assert_eq!( + get_accessible_channels(&pool, community, &member, None, Some(true)) + .await + .unwrap() + .len(), + 1 + ); + assert_eq!( + list_channels(&pool, community, Some("private")) + .await + .unwrap() + .len(), + 1 + ); + assert!(list_channels(&pool, foreign, None) + .await + .unwrap() + .is_empty()); + + let updated = update_channel( + &pool, + community, + channel.id, + ChannelUpdate { + name: Some("Renamed Channel".into()), + description: Some("updated".into()), + visibility: Some("open".into()), + ttl_seconds: Some(None), + }, + ) + .await + .unwrap(); + assert_eq!(updated.name, "Renamed Channel"); + assert_eq!(updated.description.as_deref(), Some("updated")); + assert_eq!(updated.visibility, "open"); + assert_eq!(updated.ttl_seconds, None); + assert_eq!(updated.ttl_deadline, None); + + archive_channel(&pool, community, channel.id).await.unwrap(); + assert!(archive_channel(&pool, community, channel.id).await.is_err()); + unarchive_channel(&pool, community, channel.id) + .await + .unwrap(); + assert!(unarchive_channel(&pool, community, channel.id) + .await + .is_err()); + remove_member(&pool, community, channel.id, &member, &owner) + .await + .unwrap(); + assert_eq!( + get_member_count(&pool, community, channel.id) + .await + .unwrap(), + 1 + ); + + let expiring = create_channel( + &pool, + community, + "temporary", + ChannelType::Stream, + ChannelVisibility::Open, + None, + &owner, + Some(1), + ) + .await + .unwrap(); + sqlx::query("UPDATE channels SET ttl_deadline=0 WHERE community_id=?1 AND id=?2") + .bind(community.as_uuid().to_string()) + .bind(expiring.id.to_string()) + .execute(&pool) + .await + .unwrap(); + let reaped = reap_expired_ephemeral_channels(&pool).await.unwrap(); + assert_eq!(reaped.len(), 1); + assert_eq!(reaped[0].community_id, community); + assert_eq!(reaped[0].host, "channels.local"); + assert_eq!(reaped[0].channel_id, expiring.id); + assert!(reap_expired_ephemeral_channels(&pool) + .await + .unwrap() + .is_empty()); + + assert!(soft_delete_channel(&pool, community, channel.id) + .await + .unwrap()); + assert!(!soft_delete_channel(&pool, community, channel.id) + .await + .unwrap()); + assert!(list_channels(&pool, community, None) + .await + .unwrap() + .iter() + .all(|row| row.id != channel.id)); + } + #[tokio::test] async fn relay_member_mutations_preserve_policy_owner_and_role_contracts() { use crate::relay_members::RemoveResult;