refactor: use fixtures to populate db
refactor: method for creating server channels
This commit is contained in:
89
crates/core/database/fixtures/server_with_roles.json
Normal file
89
crates/core/database/fixtures/server_with_roles.json
Normal file
@@ -0,0 +1,89 @@
|
||||
[
|
||||
{
|
||||
"_object_type": "User",
|
||||
"_id": "__ID:0__",
|
||||
"username": "Owner",
|
||||
"discriminator": "0001"
|
||||
},
|
||||
{
|
||||
"_object_type": "User",
|
||||
"_id": "__ID:1__",
|
||||
"username": "Moderator",
|
||||
"discriminator": "0001"
|
||||
},
|
||||
{
|
||||
"_object_type": "User",
|
||||
"_id": "__ID:2__",
|
||||
"username": "User",
|
||||
"discriminator": "0001"
|
||||
},
|
||||
{
|
||||
"_object_type": "Channel",
|
||||
"_id": "__ID:3__",
|
||||
"channel_type": "TextChannel",
|
||||
"name": "General",
|
||||
"server": "__ID:4__",
|
||||
"default_permissions": {
|
||||
"a": 0,
|
||||
"d": 1048576
|
||||
},
|
||||
"role_permissions": {
|
||||
"__ID:5__": {
|
||||
"a": 1048576,
|
||||
"d": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_object_type": "Server",
|
||||
"_id": "__ID:4__",
|
||||
"owner": "__ID:0__",
|
||||
"name": "Server",
|
||||
"channels": ["__ID:3__"],
|
||||
"roles": {
|
||||
"__ID:5__": {
|
||||
"name": "Moderator",
|
||||
"permissions": {
|
||||
"a": 545270208,
|
||||
"d": 0
|
||||
},
|
||||
"rank": 3
|
||||
},
|
||||
"__ID:6__": {
|
||||
"name": "Owner",
|
||||
"permissions": {
|
||||
"a": 0,
|
||||
"d": 0
|
||||
},
|
||||
"rank": 0
|
||||
}
|
||||
},
|
||||
"default_permissions": 4000322560
|
||||
},
|
||||
{
|
||||
"_object_type": "ServerMember",
|
||||
"_id": {
|
||||
"user": "__ID:0__",
|
||||
"server": "__ID:4__"
|
||||
},
|
||||
"roles": ["__ID:6__"],
|
||||
"joined_at": 1698318340195
|
||||
},
|
||||
{
|
||||
"_object_type": "ServerMember",
|
||||
"_id": {
|
||||
"user": "__ID:1__",
|
||||
"server": "__ID:4__"
|
||||
},
|
||||
"roles": ["__ID:5__"],
|
||||
"joined_at": 1698318340195
|
||||
},
|
||||
{
|
||||
"_object_type": "ServerMember",
|
||||
"_id": {
|
||||
"user": "__ID:2__",
|
||||
"server": "__ID:4__"
|
||||
},
|
||||
"joined_at": 1698318340195
|
||||
}
|
||||
]
|
||||
@@ -6,7 +6,10 @@ use revolt_permissions::OverrideField;
|
||||
use revolt_result::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{events::client::EventV1, Database, File, IntoDocumentPath, SystemMessage, User};
|
||||
use crate::{
|
||||
events::client::EventV1, Database, File, IntoDocumentPath, PartialServer, Server,
|
||||
SystemMessage, User,
|
||||
};
|
||||
|
||||
auto_derived!(
|
||||
#[serde(tag = "channel_type")]
|
||||
@@ -189,6 +192,67 @@ impl Channel {
|
||||
Ok(())
|
||||
}*/
|
||||
|
||||
/// Create a new server channel
|
||||
pub async fn create_server_channel(
|
||||
db: &Database,
|
||||
server: &mut Server,
|
||||
data: v0::DataCreateServerChannel,
|
||||
update_server: bool,
|
||||
) -> Result<Channel> {
|
||||
let config = config().await;
|
||||
if server.channels.len() > config.features.limits.default.server_channels {
|
||||
return Err(create_error!(TooManyChannels {
|
||||
max: config.features.limits.default.server_channels,
|
||||
}));
|
||||
};
|
||||
|
||||
let id = ulid::Ulid::new().to_string();
|
||||
let channel = match data.channel_type {
|
||||
v0::LegacyServerChannelType::Text => Channel::TextChannel {
|
||||
id: id.clone(),
|
||||
server: server.id.to_owned(),
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
icon: None,
|
||||
last_message_id: None,
|
||||
default_permissions: None,
|
||||
role_permissions: HashMap::new(),
|
||||
nsfw: data.nsfw.unwrap_or(false),
|
||||
},
|
||||
v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
|
||||
id: id.clone(),
|
||||
server: server.id.to_owned(),
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
icon: None,
|
||||
default_permissions: None,
|
||||
role_permissions: HashMap::new(),
|
||||
nsfw: data.nsfw.unwrap_or(false),
|
||||
},
|
||||
};
|
||||
|
||||
db.insert_channel(&channel).await?;
|
||||
|
||||
if update_server {
|
||||
server
|
||||
.update(
|
||||
db,
|
||||
PartialServer {
|
||||
channels: Some([server.channels.clone(), [id].into()].concat()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
EventV1::ChannelCreate(channel.clone().into())
|
||||
.p(server.id.clone())
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
/// Create a group
|
||||
pub async fn create_group(
|
||||
db: &Database,
|
||||
@@ -627,130 +691,18 @@ impl IntoDocumentPath for FieldsChannel {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, OverrideField};
|
||||
|
||||
use crate::{
|
||||
util::permissions::DatabasePermissionQuery, Channel, Member, MemberCompositeKey, Role,
|
||||
Server, User,
|
||||
};
|
||||
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||
|
||||
#[async_std::test]
|
||||
async fn permissions_text_channel() {
|
||||
database_test!(|db| async move {
|
||||
let owner = User::create(&db, "Owner".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let moderator = User::create(&db, "Moderator".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = User::create(&db, "User".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_id = ulid::Ulid::new().to_string();
|
||||
|
||||
let channel = Channel::TextChannel {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
server: server_id.clone(),
|
||||
name: "Channel".to_string(),
|
||||
description: None,
|
||||
icon: None,
|
||||
last_message_id: None,
|
||||
default_permissions: Some(OverrideField {
|
||||
d: 1048576, // TODO: bitfield
|
||||
..Default::default()
|
||||
}),
|
||||
role_permissions: HashMap::from([(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
OverrideField {
|
||||
a: 1048576, // TODO: bitfield
|
||||
..Default::default()
|
||||
},
|
||||
)]),
|
||||
nsfw: false,
|
||||
};
|
||||
|
||||
let server = Server {
|
||||
id: server_id,
|
||||
owner: owner.id.clone(),
|
||||
name: "My Server".to_string(),
|
||||
description: None,
|
||||
channels: vec![channel.id()],
|
||||
categories: None,
|
||||
system_messages: None,
|
||||
roles: HashMap::from([
|
||||
(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
Role {
|
||||
name: "Moderator".to_string(),
|
||||
permissions: OverrideField {
|
||||
a: 545270208, // TODO: explicit
|
||||
..Default::default()
|
||||
},
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 3,
|
||||
},
|
||||
),
|
||||
(
|
||||
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
|
||||
Role {
|
||||
name: "Owner".to_string(),
|
||||
permissions: Default::default(),
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
default_permissions: 4000322560, // TODO: use bitfield
|
||||
icon: None,
|
||||
banner: None,
|
||||
flags: None,
|
||||
nsfw: false,
|
||||
analytics: false,
|
||||
discoverable: false,
|
||||
};
|
||||
|
||||
// TODO: proper creation
|
||||
db.insert_channel(&channel).await.unwrap();
|
||||
server.create(&db).await.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: owner.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: moderator.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: user.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
fixture!(db, "server_with_roles",
|
||||
owner user 0
|
||||
moderator user 1
|
||||
user user 2
|
||||
channel channel 3);
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(&db, &owner).channel(&channel);
|
||||
assert!(calculate_channel_permissions(&mut query)
|
||||
|
||||
@@ -453,109 +453,18 @@ impl SystemMessageChannels {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission, OverrideField};
|
||||
|
||||
use crate::{
|
||||
util::permissions::DatabasePermissionQuery, Member, MemberCompositeKey, Role, Server, User,
|
||||
};
|
||||
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||
|
||||
#[async_std::test]
|
||||
async fn permissions() {
|
||||
database_test!(|db| async move {
|
||||
let owner = User::create(&db, "Owner".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let moderator = User::create(&db, "Moderator".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = User::create(&db, "User".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_id = ulid::Ulid::new().to_string();
|
||||
|
||||
// TODO: seeder functions
|
||||
// e.g. seed!("channel", "file.json")
|
||||
let server = Server {
|
||||
id: server_id,
|
||||
owner: owner.id.clone(),
|
||||
name: "My Server".to_string(),
|
||||
description: None,
|
||||
channels: vec![],
|
||||
categories: None,
|
||||
system_messages: None,
|
||||
roles: HashMap::from([
|
||||
(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
Role {
|
||||
name: "Moderator".to_string(),
|
||||
permissions: OverrideField {
|
||||
a: 545270208, // TODO: explicit
|
||||
..Default::default()
|
||||
},
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 3,
|
||||
},
|
||||
),
|
||||
(
|
||||
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
|
||||
Role {
|
||||
name: "Owner".to_string(),
|
||||
permissions: Default::default(),
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
default_permissions: 4000322560, // TODO: use bitfield
|
||||
icon: None,
|
||||
banner: None,
|
||||
flags: None,
|
||||
nsfw: false,
|
||||
analytics: false,
|
||||
discoverable: false,
|
||||
};
|
||||
|
||||
// TODO: proper creation
|
||||
server.create(&db).await.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: owner.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: moderator.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: user.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
fixture!(db, "server_with_roles",
|
||||
owner user 0
|
||||
moderator user 1
|
||||
user user 2
|
||||
server server 4);
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(&db, &owner).server(&server);
|
||||
assert!(calculate_server_permissions(&mut query)
|
||||
|
||||
@@ -2,3 +2,6 @@ pub mod bridge;
|
||||
pub mod idempotency;
|
||||
pub mod permissions;
|
||||
pub mod reference;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_fixtures;
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::{
|
||||
JsonSchema,
|
||||
};
|
||||
|
||||
use crate::{Bot, Channel, Database, Emoji, Message, User, Webhook};
|
||||
use crate::{Bot, Channel, Database, Emoji, Message, Server, User, Webhook};
|
||||
|
||||
/// Reference to some object in the database
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -42,6 +42,11 @@ impl Reference {
|
||||
db.fetch_message(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch server from Ref
|
||||
pub async fn as_server(&self, db: &Database) -> Result<Server> {
|
||||
db.fetch_server(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch user from Ref
|
||||
pub async fn as_user(&self, db: &Database) -> Result<User> {
|
||||
db.fetch_user(&self.id).await
|
||||
|
||||
103
crates/core/database/src/util/test_fixtures.rs
Normal file
103
crates/core/database/src/util/test_fixtures.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{Captures, Regex};
|
||||
use serde_json::from_str;
|
||||
|
||||
use crate::{Channel, Database, Member, Server, User};
|
||||
|
||||
static RE_ID: Lazy<Regex> = Lazy::new(|| Regex::new("__ID:(\\d+)__").unwrap());
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "_object_type")]
|
||||
enum LoadedFixture {
|
||||
User(User),
|
||||
Channel(Channel),
|
||||
Server(Server),
|
||||
ServerMember(Member),
|
||||
}
|
||||
|
||||
pub async fn load_fixture(db: &Database, input: &str) -> HashMap<String, String> {
|
||||
let mut ids = HashMap::<String, String>::new();
|
||||
let input = RE_ID.replace_all(input, |cap: &Captures| {
|
||||
let d = cap.get(1).unwrap().as_str();
|
||||
|
||||
if !ids.contains_key(d) {
|
||||
ids.insert(d.to_string(), ulid::Ulid::new().to_string());
|
||||
}
|
||||
|
||||
ids.get(d).unwrap().clone()
|
||||
});
|
||||
|
||||
// Deserialise the fixtures
|
||||
let items: Vec<LoadedFixture> = from_str(&input).expect("Failed to deserialise fixture");
|
||||
|
||||
// Load all of the items within
|
||||
for item in items {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
match item {
|
||||
LoadedFixture::User(user) => db.insert_user(&user).await.unwrap(),
|
||||
LoadedFixture::Channel(channel) => db.insert_channel(&channel).await.unwrap(),
|
||||
LoadedFixture::Server(server) => db.insert_server(&server).await.unwrap(),
|
||||
LoadedFixture::ServerMember(member) => db.insert_member(&member).await.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// Return IDs for ease of use
|
||||
ids
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait FetchFixture {
|
||||
async fn user(&self, db: &Database, d: usize) -> User;
|
||||
async fn channel(&self, db: &Database, d: usize) -> Channel;
|
||||
async fn server(&self, db: &Database, d: usize) -> Server;
|
||||
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FetchFixture for HashMap<String, String> {
|
||||
async fn user(&self, db: &Database, d: usize) -> User {
|
||||
db.fetch_user(self.get(&d.to_string()).unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn channel(&self, db: &Database, d: usize) -> Channel {
|
||||
db.fetch_channel(self.get(&d.to_string()).unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn server(&self, db: &Database, d: usize) -> Server {
|
||||
db.fetch_server(self.get(&d.to_string()).unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member {
|
||||
db.fetch_member(
|
||||
self.get(&d_server.to_string()).unwrap(),
|
||||
self.get(&d_user.to_string()).unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fixture {
|
||||
( $database:expr, $name:expr, $( $variable:ident $type:ident $id: expr )+ ) => {
|
||||
use $crate::util::test_fixtures::FetchFixture;
|
||||
|
||||
let fixtures = $crate::util::test_fixtures::load_fixture(
|
||||
&$database,
|
||||
include_str!(concat!("../../../fixtures/", $name, ".json")),
|
||||
)
|
||||
.await;
|
||||
|
||||
$(
|
||||
let $variable = fixtures.$type(&$database, $id).await;
|
||||
)+
|
||||
};
|
||||
}
|
||||
@@ -212,24 +212,52 @@ auto_derived!(
|
||||
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||
pub struct DataCreateGroup {
|
||||
/// Group name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||
pub name: String,
|
||||
/// Group description
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||
pub description: Option<String>,
|
||||
/// Group icon
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||
pub icon: Option<String>,
|
||||
/// Array of user IDs to add to the group
|
||||
///
|
||||
/// Must be friends with these users.
|
||||
#[validate(length(min = 0, max = 49))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 49)))]
|
||||
#[serde(default)]
|
||||
pub users: HashSet<String>,
|
||||
/// Whether this group is age-restricted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nsfw: Option<bool>,
|
||||
}
|
||||
|
||||
/// Server Channel Type
|
||||
#[derive(Default)]
|
||||
pub enum LegacyServerChannelType {
|
||||
/// Text Channel
|
||||
#[default]
|
||||
Text,
|
||||
/// Voice Channel
|
||||
Voice,
|
||||
}
|
||||
|
||||
/// Create new server channel
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||
pub struct DataCreateServerChannel {
|
||||
/// Channel type
|
||||
#[serde(rename = "type", default = "LegacyServerChannelType::default")]
|
||||
pub channel_type: LegacyServerChannelType,
|
||||
/// Channel name
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||
pub name: String,
|
||||
/// Channel description
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||
pub description: Option<String>,
|
||||
/// Whether this channel is age restricted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nsfw: Option<bool>,
|
||||
}
|
||||
);
|
||||
|
||||
impl Channel {
|
||||
|
||||
Reference in New Issue
Block a user