mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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>
This commit is contained in:
parent
eefdd94eb0
commit
91a698b040
+209
-21
@@ -3384,27 +3384,26 @@ pub(crate) async fn get_member_counts_bulk(
|
||||
pub(crate) async fn reap_expired_ephemeral_channels(
|
||||
pool: &SqlitePool,
|
||||
) -> Result<Vec<crate::channel::ReapedEphemeralChannel>> {
|
||||
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<unixepoch() AND ch.archived_at IS NULL AND ch.deleted_at IS NULL AND c.archived_at IS NULL").fetch_all(pool).await?;
|
||||
let mut out = Vec::new();
|
||||
for r in rows {
|
||||
let cid: String = r.try_get("community_id")?;
|
||||
let id: String = r.try_get("id")?;
|
||||
sqlx::query(
|
||||
"UPDATE channels SET archived_at=unixepoch() WHERE id=? AND archived_at IS NULL",
|
||||
)
|
||||
.bind(&id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
out.push(crate::channel::ReapedEphemeralChannel {
|
||||
community_id: CommunityId::from_uuid(
|
||||
Uuid::parse_str(&cid).map_err(|e| crate::DbError::InvalidData(e.to_string()))?,
|
||||
),
|
||||
host: r.try_get("host")?,
|
||||
channel_id: Uuid::parse_str(&id)
|
||||
.map_err(|e| crate::DbError::InvalidData(e.to_string()))?,
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
let rows = sqlx::query(
|
||||
"UPDATE channels SET archived_at=unixepoch() WHERE ttl_seconds IS NOT NULL AND ttl_deadline<unixepoch() AND archived_at IS NULL AND deleted_at IS NULL AND EXISTS (SELECT 1 FROM communities c WHERE c.id=channels.community_id AND c.archived_at IS NULL) RETURNING community_id,id,(SELECT host FROM communities c WHERE c.id=channels.community_id) AS host",
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
rows.into_iter()
|
||||
.map(|row| {
|
||||
let community_id: String = row.try_get("community_id")?;
|
||||
let channel_id: String = row.try_get("id")?;
|
||||
Ok(crate::channel::ReapedEphemeralChannel {
|
||||
community_id: CommunityId::from_uuid(
|
||||
Uuid::parse_str(&community_id)
|
||||
.map_err(|e| crate::DbError::InvalidData(e.to_string()))?,
|
||||
),
|
||||
host: row.try_get("host")?,
|
||||
channel_id: Uuid::parse_str(&channel_id)
|
||||
.map_err(|e| crate::DbError::InvalidData(e.to_string()))?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn timestamp(value: i64) -> Result<chrono::DateTime<chrono::Utc>> {
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user