commit 04811cc6530c75ef64f36f11ddf18453c5d95ad9
parent edb3ef68cc3349d12752da9f751337dddcb7359d
Author: quantumish <freifeld.david@gmail.com>
Date: Mon, 3 Oct 2022 09:39:08 -0700
Add tests (whoops) and make them pass
Diffstat:
2 files changed, 413 insertions(+), 6 deletions(-)
diff --git a/scuttlebutt/src/main.rs b/scuttlebutt/src/main.rs
@@ -119,7 +119,7 @@ impl Api {
let row = res.first_row().unwrap();
let channels: SetIterator = row.get(0).unwrap();
for channel in channels {
- self.__remove_channel_member(channel.get_i64().unwrap(), uid);
+ self.__remove_channel_member(channel.get_i64().unwrap(), uid).await;
}
self.sess.execute(&stmt!(&format!(
"UPDATE {}.user_groups SET groups = groups - {{{}}} WHERE id={};", self.kspc, gid, uid
@@ -259,7 +259,7 @@ impl Api {
))).wait().unwrap();
let res = self.sess.execute(&stmt!(&format!(
- "SELECT id, name, members, channels FROM {}.user_groups WHERE id={};",
+ "SELECT groups FROM {}.user_groups WHERE id={};",
self.kspc, auth.0.id
))).wait().unwrap();
let groups: SetIterator = match res.row_count() {
@@ -267,7 +267,7 @@ impl Api {
_ => res.first_row().unwrap().get(0).unwrap(),
};
for group in groups {
- self.__remove_group_member(group.get_i64().unwrap(), id);
+ self.__remove_group_member(group.get_i64().unwrap(), id).await;
}
self.sess.execute(&stmt!(&format!(
"DELETE FROM {}.user_groups WHERE id={};", self.kspc, id
@@ -312,7 +312,7 @@ impl Api {
if let Err(e) = self.validate_id("groups", gid.0) {
return NotFound(PlainText("Didn't find group or experienced database error.".to_string()));
}
- self.__remove_group_member(gid.0, auth.0.id);
+ self.__remove_group_member(gid.0, auth.0.id).await;
Success
}
@@ -486,7 +486,7 @@ impl Api {
} else if let Err(_) = self.validate_id("users", uid.0) {
return NotFound(PlainText("User not found".to_string()))
}
- self.__remove_group_member(gid.0, uid.0);
+ self.__remove_group_member(gid.0, uid.0).await;
Success
}
@@ -639,7 +639,7 @@ impl Api {
} else if let Err(_) = self.validate_id("users", uid.0) {
return NotFound(PlainText("User not found".to_string()))
}
- self.__remove_channel_member(cid.0, uid.0);
+ self.__remove_channel_member(cid.0, uid.0).await;
Success
}
diff --git a/scuttlebutt/src/tests.rs b/scuttlebutt/src/tests.rs
@@ -0,0 +1,407 @@
+use super::*;
+
+use hmac::Mac;
+use more_asserts::*;
+use poem::{
+ http::StatusCode,
+ middleware::AddDataEndpoint,
+ test::{TestClient, TestRequestBuilder, TestResponse},
+ Route,
+};
+use pretty_assertions::assert_eq;
+use sha2::Digest;
+
+type FakeClient = TestClient<AddDataEndpoint<Route, ServerKey>>;
+
+fn setup() -> FakeClient {
+ let key: String = rand::thread_rng()
+ .sample_iter(&Alphanumeric)
+ .take(7)
+ .map(char::from)
+ .collect();
+ let api_service = OpenApiService::new(Api::new("test"), "Scuttlebutt", "1.0").server("http://localhost:3000/api");
+ let app = Route::new()
+ .nest("/api", api_service)
+ .data(ServerKey::new_from_slice(&key.as_bytes()).unwrap());
+ TestClient::new(app)
+}
+
+fn hash_pass(pass: &str) -> String {
+ let mut hasher = Sha256::new();
+ hasher.update(pass);
+ hex::encode(hasher.finalize())
+}
+
+enum HttpMethod {
+ Post(String),
+ Get(String),
+ Delete(String),
+ Put(String),
+}
+use crate::tests::HttpMethod::*;
+
+async fn make_user(cli: &FakeClient, name: &str, email: &str, pass: &str) -> User {
+ let hash = hash_pass(pass);
+ let resp = cli
+ .post(format!("/api/user?name={}&email={}&hash={}", name, email, hash))
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ resp.json().await.value().deserialize::<User>()
+}
+
+async fn login(cli: &FakeClient, id: i64, pass: &str) -> String {
+ let hash = hash_pass(pass);
+ let mut resp = cli.post(format!("/api/login?id={}", id))
+ .content_type("text/plain")
+ .body(hash).send().await;
+ resp.assert_status_is_ok();
+ resp.0.take_body().into_string().await.unwrap()
+}
+
+async fn setup_user_auth() -> (FakeClient, User, String) {
+ let cli = setup();
+ let user = make_user(&cli, "test", "test@example.com", "12345").await;
+ let auth = login(&cli, user.id, "12345").await;
+ (cli, user, auth)
+}
+
+#[tokio::test]
+/// FIXME non exhaustive
+async fn post_user() {
+ let cli = setup();
+ let resp = make_user(&cli, "test", "test@example.com", "12345").await;
+
+ // TODO questionable
+ let mut id_gen = Snowflake::default();
+ assert_ge!(id_gen.generate(), resp.id);
+
+ assert_eq!(resp.email, "test@example.com");
+ assert_eq!(resp.username, "test");
+}
+
+#[tokio::test]
+async fn post_login() {
+ let cli = setup();
+ let user = make_user(&cli, "test", "test@example.com", "12345").await;
+ let hash = hash_pass("12345");
+
+ let resp = cli.post(format!("/api/login?id={}", user.id))
+ .content_type("text/plain").body("abc").send().await;
+ resp.assert_status(StatusCode::BAD_REQUEST);
+
+ let resp = cli.post("/api/login?id=12")
+ .content_type("text/plain").body(hash.clone()).send().await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+
+ let resp = cli.post(format!("/api/login?id={}", user.id))
+ .content_type("text/plain").body(hash_pass("123")).send().await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let mut resp = cli.post(format!("/api/login?id={}", user.id))
+ .content_type("text/plain").body(hash.clone()).send().await;
+ resp.assert_status_is_ok();
+ let raw_str = resp.0.take_body().into_string().await.unwrap();
+ let claims: Claims = serde_json::from_str(&String::from_utf8(base64::decode(
+ raw_str.split(".").nth(1).unwrap()
+ ).unwrap()).unwrap()).unwrap();
+ assert_eq!(claims.id, user.id);
+ assert_ge!(claims.exp, Local::now())
+}
+
+#[tokio::test]
+async fn get_user() {
+ let cli = setup();
+ let user = make_user(&cli, "test", "test@example.com", "12345").await;
+ let resp = cli.get(format!("/api/user?id={}", user.id)).send().await;
+ resp.assert_status_is_ok();
+ let ret_user = resp.json().await.value().deserialize::<User>();
+ assert_eq!(user, ret_user);
+}
+
+#[tokio::test]
+async fn put_user() {
+ let (cli, user, auth) = setup_user_auth().await;
+
+ let resp = cli.put("/api/user?name=fred&email=whoo@whee.com").send().await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+ let resp = cli
+ .put("/api/user?name=fred&email=whoo@whee.com")
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let ret_user = resp.json().await.value().deserialize::<User>();
+ assert_eq!(ret_user.email, "whoo@whee.com");
+ assert_eq!(ret_user.username, "fred");
+ // User should retain their underlying ID
+ assert_eq!(user.id, ret_user.id);
+}
+
+#[tokio::test]
+async fn del_user() {
+ let (cli, user, auth) = setup_user_auth().await;
+
+ let resp = cli.delete(format!("/api/user?id={}", user.id)).send().await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let resp = cli
+ .delete(format!("/api/user?id={}", user.id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let resp = cli.get(format!("/api/user?id={}", user.id)).send().await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+}
+
+async fn make_group(cli: &FakeClient, auth: String, name: &str) -> Group {
+ let resp = cli
+ .post(format!("/api/group?name={}", name))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ resp.json().await.value().deserialize::<Group>()
+}
+
+async fn make_channel(cli: &FakeClient, auth: String, gid: i64, name: &str) -> Channel {
+ let resp = cli
+ .post(format!("/api/group/channels?gid={}&name={}", gid, name))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ resp.json().await.value().deserialize::<Channel>()
+}
+
+async fn find_channel(cli: &FakeClient, auth: String, id: i64) -> Channel {
+ let resp = cli
+ .get(format!("/api/channel?id={}", id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ resp.json().await.value().deserialize::<Channel>()
+}
+
+async fn find_group(cli: &FakeClient, auth: String, id: i64) -> Group {
+ let resp = cli
+ .get(format!("/api/group?id={}", id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ resp.json().await.value().deserialize::<Group>()
+}
+
+#[tokio::test]
+async fn post_group() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let resp = cli.post("/api/group?name=")
+ .header::<&str, String>("ScuttleKey", auth.clone()).send().await;
+ resp.assert_status(StatusCode::BAD_REQUEST);
+
+ let resp = cli
+ .post("/api/group?name=test")
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let group = resp.json().await.value().deserialize::<Group>();
+ assert_eq!(group.name, "test");
+ assert_eq!(group.members, vec![user.id]);
+ assert_eq!(group.channels.len(), 1);
+ assert_eq!(
+ find_channel(&cli, auth, group.channels[0]).await.name,
+ String::from("main")
+ );
+}
+
+#[tokio::test]
+async fn put_group() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+
+ let resp = cli.put(format!("/api/group?id={}&name=test2", group.id)).send().await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let resp = cli
+ .put(format!("/api/group?id={}&name=", group.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::BAD_REQUEST);
+
+ let resp = cli
+ .put("/api/group?id=12&name=test2")
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+
+ let resp = cli
+ .put(format!("/api/group?id={}&name=test2", group.id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+}
+
+#[tokio::test]
+async fn del_group() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+
+ let resp = cli.delete(format!("/api/group?id={}", group.id)).send().await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let resp = cli
+ .delete("/api/group?id=12")
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+
+ let resp = cli
+ .delete(format!("/api/group?id={}", group.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+
+ let resp = cli
+ .get(format!("/api/group?id={}", group.id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+}
+
+#[tokio::test]
+async fn put_group_members() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+ let user2 = make_user(&cli, "testeroo", "test2@example.com", "123456").await;
+ let user3 = make_user(&cli, "testeroo", "test2@example.com", "123456").await;
+
+ let resp = cli
+ .put(format!("/api/group/members?gid={}&uid={}", group.id, user2.id))
+ .send()
+ .await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let resp = cli
+ .put("/api/group/members?gid=12&uid=32")
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+
+ let resp = cli
+ .put(format!("/api/group/members?gid={}&uid={}", group.id, user2.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let resp = cli
+ .put(format!("/api/group/members?gid={}&uid={}", group.id, user3.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+
+ let new_group = find_group(&cli, auth, group.id).await;
+ assert_eq!(new_group.members, vec![user.id, user2.id, user3.id]);
+}
+
+#[tokio::test]
+async fn post_group_channels() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+
+ let resp = cli
+ .post(format!("/api/group/channels?gid={}&name=test", group.id))
+ .send()
+ .await;
+ resp.assert_status(StatusCode::UNAUTHORIZED);
+
+ let resp = cli
+ .post(format!("/api/group/channels?gid={}&name=", group.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::BAD_REQUEST);
+
+ let resp = cli
+ .post("/api/group/channels?gid=12&name=test")
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status(StatusCode::NOT_FOUND);
+
+ let resp = cli
+ .post(format!("/api/group/channels?gid={}&name=test", group.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let channel = resp.json().await.value().deserialize::<Channel>();
+ assert_eq!(channel.name, "test");
+ assert_eq!(channel.members, vec![user.id]);
+ assert!(find_group(&cli, auth, group.id).await.channels.contains(&channel.id));
+}
+#[test]
+/// Test if gen_id() gives unique IDs on successive calls
+/// and if it can be called from multiple threads without error
+fn test_id_gen() {
+ let a = gen_id();
+ let b = gen_id();
+ assert_ge!(b, a);
+ let threads: Vec<_> = (0..100).map(|i| std::thread::spawn(move || gen_id())).collect();
+ for handle in threads {
+ handle.join().unwrap();
+ }
+}
+
+#[tokio::test]
+async fn get_channel() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+ let chan = make_channel(&cli, auth.clone(), group.id, "random").await;
+ let resp = cli
+ .get(format!("/api/channel?id={}", chan.id))
+ .header::<&str, String>("ScuttleKey", auth)
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let recv_chan = resp.json().await.value().deserialize::<Channel>();
+ assert_eq!(chan, recv_chan);
+}
+
+// FIXME non exhaustive
+#[tokio::test]
+async fn get_channels() {
+ let (cli, user, auth) = setup_user_auth().await;
+ let group = make_group(&cli, auth.clone(), "test").await;
+ let chan1 = make_channel(&cli, auth.clone(), group.id, "random").await;
+ let chan2 = make_channel(&cli, auth.clone(), group.id, "random").await;
+ let chan3 = make_channel(&cli, auth.clone(), group.id, "random").await;
+ let resp = cli
+ .get(format!("/api/group/channels?gid={}", group.id))
+ .header::<&str, String>("ScuttleKey", auth.clone())
+ .send()
+ .await;
+ resp.assert_status_is_ok();
+ let channels = resp.json().await.value().deserialize::<Vec<Channel>>();
+ assert_eq!(
+ channels,
+ vec![find_channel(&cli, auth, group.channels[0]).await, chan1, chan2, chan3]
+ );
+}
+
+// #[tokio::test]
+// async fn get_group_members() {
+// let (cli, user, auth) = setup_user_auth().await;
+// let group = make_group(&cli, auth.clone(), "test").await;
+// }