disma 0.16.4

Discord server management has never been easier!
Documentation
use core::fmt::Debug;

use crate::{
    category::AwaitingCategory,
    channel::{AwaitingChannel, Channel, ExistingChannel},
    core::{changes::channel::ChannelChange, diffs::Differ},
};

pub trait ExtraChannelsStrategy {
    fn _type(&self) -> ExtraChannelsStrategyType;
    fn handle_extra_channel(
        &self,
        extra_existing: &ExistingChannel,
        changes: &mut Vec<ChannelChange>,
        awaiting_category: Option<&AwaitingCategory>,
    );
}

#[derive(Debug, PartialEq)]
pub enum ExtraChannelsStrategyType {
    Keep,
    Remove,
    OverwritePermissionsWithCategory,
}

impl Debug for dyn ExtraChannelsStrategy {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self._type())
    }
}

pub struct RemoveExtraChannels {}

impl ExtraChannelsStrategy for RemoveExtraChannels {
    fn _type(&self) -> ExtraChannelsStrategyType {
        ExtraChannelsStrategyType::Remove
    }

    fn handle_extra_channel(
        &self,
        extra_existing: &ExistingChannel,
        changes: &mut Vec<ChannelChange>,
        _awaiting_category: Option<&AwaitingCategory>,
    ) {
        changes.push(ChannelChange::Delete(extra_existing.clone()));
    }
}

pub struct KeepExtraChannels {}

impl ExtraChannelsStrategy for KeepExtraChannels {
    fn _type(&self) -> ExtraChannelsStrategyType {
        ExtraChannelsStrategyType::Keep
    }

    fn handle_extra_channel(
        &self,
        _extra_existing: &ExistingChannel,
        _changes: &mut Vec<ChannelChange>,
        _awaiting_category: Option<&AwaitingCategory>,
    ) {
    }
}

pub struct SyncExtraChannelsPermissions {}

impl ExtraChannelsStrategy for SyncExtraChannelsPermissions {
    fn _type(&self) -> ExtraChannelsStrategyType {
        ExtraChannelsStrategyType::OverwritePermissionsWithCategory
    }

    fn handle_extra_channel(
        &self,
        extra_existing: &ExistingChannel,
        changes: &mut Vec<ChannelChange>,
        awaiting_category: Option<&AwaitingCategory>,
    ) {
        if let Some(category) = awaiting_category {
            let awaiting_channel = AwaitingChannel {
                name: extra_existing.name().to_string(),
                topic: extra_existing.topic.clone(),
                channel_type: extra_existing.channel_type.clone(),
                category: Some(category.clone()),
                overwrites: category.overwrites.clone(),
            };

            let diffs = extra_existing.diffs_with(&awaiting_channel);
            if diffs.is_empty() {
                return;
            };

            changes.push(ChannelChange::Update(
                extra_existing.clone(),
                awaiting_channel.clone(),
                diffs,
            ));
        } else {
            panic!("Category cannot be empty for overriding permissions overwrites of extra channel {}", extra_existing.name());
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        core::diffs::Diff,
        permission::{PermissionsList, PermissionsOverwrite},
        tests::fixtures::{
            awaiting::{AwaitingCategoryFixture, AwaitingChannelFixture, AwaitingRoleFixture},
            existing::{ExistingCategoryFixture, ExistingChannelFixture, ExistingRoleFixture},
        },
    };

    use super::*;

    const A_CATEGORY_NAME: &str = "category abc";
    const A_CHANNEL_NAME: &str = "channel abc";
    const A_ROLE_NAME: &str = "role abc";

    #[test]
    fn when_keeping_extra_channels_should_not_add_changes() {
        let mut changes: Vec<ChannelChange> = Vec::new();
        let extra_channel = ExistingChannelFixture::new().build();

        let strategy = KeepExtraChannels {};
        strategy.handle_extra_channel(&extra_channel, &mut changes, None);

        assert!(changes.is_empty());
    }

    #[test]
    fn when_removing_extra_channels_should_add_delete_change() {
        let mut changes: Vec<ChannelChange> = Vec::new();
        let extra_channel = ExistingChannelFixture::new().build();

        let strategy = RemoveExtraChannels {};
        strategy.handle_extra_channel(&extra_channel, &mut changes, None);

        assert!(!changes.is_empty());
        assert_eq!(changes, vec![ChannelChange::Delete(extra_channel)]);
    }

    #[test]
    #[should_panic]
    fn given_no_category_when_syncing_extra_channels_permissions_should_panic() {
        let mut changes: Vec<ChannelChange> = Vec::new();
        let extra_channel = ExistingChannelFixture::new().build();

        let strategy = SyncExtraChannelsPermissions {};
        strategy.handle_extra_channel(&extra_channel, &mut changes, None);
    }

    #[test]
    fn given_category_with_same_permissions_when_syncing_extra_channels_permissions_should_not_add_changes(
    ) {
        let mut changes: Vec<ChannelChange> = Vec::new();
        let awaiting_category = AwaitingCategoryFixture::new()
            .with_name(A_CATEGORY_NAME)
            .build();
        let existing_category = ExistingCategoryFixture::new()
            .with_name(A_CATEGORY_NAME)
            .build();
        let extra_channel = ExistingChannelFixture::new()
            .with_category(&existing_category)
            .build();

        let strategy = SyncExtraChannelsPermissions {};
        strategy.handle_extra_channel(&extra_channel, &mut changes, Some(&awaiting_category));

        assert_eq!(changes, vec![]);
    }

    #[test]
    fn given_category_with_different_permissions_when_syncing_extra_channels_permissions_should_add_update_changes_for_permissions(
    ) {
        let mut changes: Vec<ChannelChange> = Vec::new();

        let awaiting_overwrites = vec![PermissionsOverwrite {
            role: AwaitingRoleFixture::new().with_name(A_ROLE_NAME).build(),
            allow: PermissionsList::new(),
            deny: PermissionsList::new(),
        }];
        let existing_overwrites = vec![PermissionsOverwrite {
            role: ExistingRoleFixture::new().with_name(A_ROLE_NAME).build(),
            allow: PermissionsList::new(),
            deny: PermissionsList::new(),
        }];
        let awaiting_category = AwaitingCategoryFixture::new()
            .with_name(A_CATEGORY_NAME)
            .with_permissions_overwrites(awaiting_overwrites.clone())
            .build();
        let existing_category = ExistingCategoryFixture::new()
            .with_name(A_CATEGORY_NAME)
            .with_permissions_overwrites(existing_overwrites)
            .build();
        let existing_channel = ExistingChannelFixture::new()
            .with_category(&existing_category)
            .with_name(A_CHANNEL_NAME)
            .build();
        let expected_awaiting_channel = AwaitingChannelFixture::new()
            .with_category(&awaiting_category)
            .with_name(A_CHANNEL_NAME)
            .with_permissions_overwrites(awaiting_overwrites)
            .build();

        let strategy = SyncExtraChannelsPermissions {};
        strategy.handle_extra_channel(&existing_channel, &mut changes, Some(&awaiting_category));

        assert!(!changes.is_empty());
        assert_eq!(
            changes,
            vec![ChannelChange::Update(
                existing_channel,
                expected_awaiting_channel,
                vec![Diff::Update(
                    "overwrites".to_string(),
                    vec![Diff::Add(A_ROLE_NAME.to_string())]
                )]
            )]
        );
    }
}