test(webapi): test room item posting and listing

This commit is contained in:
oxalica 2024-09-10 08:15:24 -04:00
parent 1a0347337c
commit c0ec429c24
3 changed files with 158 additions and 5 deletions

View file

@ -38,6 +38,12 @@ pub struct WithItemId<T> {
pub item: T, pub item: T,
} }
impl<T> WithItemId<T> {
pub fn new(cid: Id, item: T) -> Self {
Self { cid, item }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct UserKey(#[serde(with = "hex::serde")] pub [u8; PUBLIC_KEY_LENGTH]); pub struct UserKey(#[serde(with = "hex::serde")] pub [u8; PUBLIC_KEY_LENGTH]);

View file

@ -413,7 +413,7 @@ impl Pagination {
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoomItems { pub struct RoomItems {
pub items: Vec<WithItemId<ChatItem>>, pub items: Vec<WithItemId<ChatItem>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -790,7 +790,7 @@ async fn room_admin(
"only self-adding is implemented yet", "only self-adding is implemented yet",
)); ));
} }
if permission.is_empty() || !MemberPermission::MAX_SELF_ADD.contains(permission) { if !MemberPermission::MAX_SELF_ADD.contains(permission) {
return Err(error_response!( return Err(error_response!(
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"deserialization", "deserialization",

View file

@ -7,10 +7,11 @@ use std::sync::{Arc, LazyLock};
use anyhow::Result; use anyhow::Result;
use blah_types::{ use blah_types::{
get_timestamp, AuthPayload, CreateRoomPayload, Id, MemberPermission, RoomAdminOp, get_timestamp, AuthPayload, ChatItem, ChatPayload, CreateRoomPayload, Id, MemberPermission,
RoomAdminPayload, RoomAttrs, RoomMember, RoomMemberList, ServerPermission, UserKey, WithSig, RichText, RoomAdminOp, RoomAdminPayload, RoomAttrs, RoomMember, RoomMemberList,
ServerPermission, UserKey, WithItemId, WithSig,
}; };
use blahd::{ApiError, AppState, Database, RoomList, RoomMetadata}; use blahd::{ApiError, AppState, Database, RoomItems, RoomList, RoomMetadata};
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
use futures_util::TryFutureExt; use futures_util::TryFutureExt;
use rand::rngs::mock::StepRng; use rand::rngs::mock::StepRng;
@ -363,3 +364,149 @@ async fn room_join_leave(server: Server, ref mut rng: impl RngCore) {
.await .await
.expect_api_err(StatusCode::NOT_FOUND, "not_found"); .expect_api_err(StatusCode::NOT_FOUND, "not_found");
} }
#[rstest]
#[tokio::test]
async fn room_item_post_read(server: Server, ref mut rng: impl RngCore) {
let rid_pub = server
.create_room(
&ALICE_PRIV,
RoomAttrs::PUBLIC_READABLE | RoomAttrs::PUBLIC_JOINABLE,
"public room",
)
.await
.unwrap();
let rid_priv = server
.create_room(&ALICE_PRIV, RoomAttrs::empty(), "private room")
.await
.unwrap();
let mut chat = |rid: Id, key: &SigningKey, msg: &str| {
sign(
key,
rng,
ChatPayload {
room: rid,
rich_text: RichText::from(msg),
},
)
};
let post = |rid: Id, chat: ChatItem| {
server
.request::<_, Id>(Method::POST, format!("/room/{rid}/item"), None, Some(chat))
.map_ok(|opt| opt.unwrap())
};
// Ok.
let chat1 = chat(rid_pub, &ALICE_PRIV, "one");
let chat2 = chat(rid_pub, &ALICE_PRIV, "two");
let cid1 = post(rid_pub, chat1.clone()).await.unwrap();
let cid2 = post(rid_pub, chat2.clone()).await.unwrap();
// Duplicated chat.
post(rid_pub, chat2.clone())
.await
.expect_api_err(StatusCode::BAD_REQUEST, "duplicated_nonce");
// Wrong room.
post(rid_pub, chat(rid_priv, &ALICE_PRIV, "wrong room"))
.await
.expect_api_err(StatusCode::BAD_REQUEST, "invalid_request");
// Not a member.
post(rid_pub, chat(rid_pub, &BOB_PRIV, "not a member"))
.await
.expect_api_err(StatusCode::NOT_FOUND, "not_found");
// Is a member but without permission.
server
.join_room(rid_pub, &BOB_PRIV, MemberPermission::empty())
.await
.unwrap();
post(rid_pub, chat(rid_pub, &BOB_PRIV, "no permission"))
.await
.expect_api_err(StatusCode::FORBIDDEN, "permission_denied");
// Room not exists.
post(Id::INVALID, chat(Id::INVALID, &ALICE_PRIV, "not permitted"))
.await
.expect_api_err(StatusCode::NOT_FOUND, "not_found");
//// Item listing ////
let chat1 = WithItemId::new(cid1, chat1);
let chat2 = WithItemId::new(cid2, chat2);
// List with default page size.
let items = server
.get::<RoomItems>(format!("/room/{rid_pub}/item"), None)
.await
.unwrap();
assert_eq!(
items,
RoomItems {
items: vec![chat2.clone(), chat1.clone()],
skip_token: None,
},
);
// List with small page size.
let items = server
.get::<RoomItems>(format!("/room/{rid_pub}/item?top=1"), None)
.await
.unwrap();
assert_eq!(
items,
RoomItems {
items: vec![chat2.clone()],
skip_token: Some(cid2),
},
);
// Second page.
let items = server
.get::<RoomItems>(format!("/room/{rid_pub}/item?skipToken={cid2}&top=1"), None)
.await
.unwrap();
assert_eq!(
items,
RoomItems {
items: vec![chat1.clone()],
skip_token: Some(cid1),
},
);
// No more.
let items = server
.get::<RoomItems>(format!("/room/{rid_pub}/item?skipToken={cid1}&top=1"), None)
.await
.unwrap();
assert_eq!(items, RoomItems::default());
//// Private room ////
// Access without token.
server
.get::<RoomItems>(format!("/room/{rid_priv}/item"), None)
.await
.expect_api_err(StatusCode::NOT_FOUND, "not_found");
// Not a member.
server
.get::<RoomItems>(
format!("/room/{rid_priv}/item"),
Some(&auth(&BOB_PRIV, rng)),
)
.await
.expect_api_err(StatusCode::NOT_FOUND, "not_found");
// Ok.
let items = server
.get::<RoomItems>(
format!("/room/{rid_priv}/item"),
Some(&auth(&ALICE_PRIV, rng)),
)
.await
.unwrap();
assert_eq!(items, RoomItems::default());
}