disma/api/
list_changes.rs

1use std::sync::Arc;
2
3use crate::{
4    api::params::guild::GuildParams,
5    channel::Channel,
6    core::changes::{
7        category::{CategoryChange, CategoryChangesService},
8        channel::{ChannelChange, ChannelChangesService},
9        role::{RoleChange, RoleChangesService},
10        Change, ChangeEntity,
11    },
12    guild::{AwaitingGuild, ExistingGuild, GuildQuerier},
13};
14
15pub struct ListChangesUseCase {
16    querier: Arc<dyn GuildQuerier>,
17    role_changes_service: Arc<RoleChangesService>,
18    category_changes_service: Arc<CategoryChangesService>,
19    channel_changes_service: Arc<ChannelChangesService>,
20}
21
22impl ListChangesUseCase {
23    pub fn new(
24        querier: Arc<dyn GuildQuerier>,
25        role_changes_service: Arc<RoleChangesService>,
26        category_changes_service: Arc<CategoryChangesService>,
27        channel_changes_service: Arc<ChannelChangesService>,
28    ) -> Self {
29        Self {
30            querier,
31            role_changes_service,
32            category_changes_service,
33            channel_changes_service,
34        }
35    }
36
37    pub fn execute(&self, guild_id: &str, params: GuildParams) -> Vec<Change> {
38        let awaiting_guild: AwaitingGuild = params.into();
39        let existing_guild = self.querier.get_guild(guild_id);
40
41        self.list_role_changes(&existing_guild, &awaiting_guild)
42            .chain(self.list_category_changes(&existing_guild, &awaiting_guild))
43            .chain(self.list_channel_changes(&existing_guild, &awaiting_guild))
44            .collect()
45    }
46
47    fn list_role_changes(
48        &self,
49        existing_guild: &ExistingGuild,
50        awaiting_guild: &AwaitingGuild,
51    ) -> impl Iterator<Item = Change> {
52        let role_changes = self
53            .role_changes_service
54            .list_changes(existing_guild, awaiting_guild);
55
56        role_changes.into_iter().map(|change| match change {
57            RoleChange::Create(awaiting) => Change::Create(ChangeEntity::Role, awaiting.name),
58            RoleChange::Update(existing, _, diffs) => {
59                Change::Update(ChangeEntity::Role, existing.name.clone(), diffs)
60            }
61            RoleChange::Delete(existing) => Change::Delete(ChangeEntity::Role, existing.name),
62        })
63    }
64
65    fn list_category_changes(
66        &self,
67        existing_guild: &ExistingGuild,
68        awaiting_guild: &AwaitingGuild,
69    ) -> impl Iterator<Item = Change> {
70        let category_changes = self
71            .category_changes_service
72            .list_changes(existing_guild, awaiting_guild);
73
74        category_changes.into_iter().map(|change| match change {
75            CategoryChange::Create(awaiting) => {
76                Change::Create(ChangeEntity::Category, awaiting.name)
77            }
78            CategoryChange::Update(existing, _, diffs) => {
79                Change::Update(ChangeEntity::Category, existing.name.clone(), diffs)
80            }
81            CategoryChange::Delete(existing) => {
82                Change::Delete(ChangeEntity::Category, existing.name)
83            }
84        })
85    }
86
87    fn list_channel_changes(
88        &self,
89        existing_guild: &ExistingGuild,
90        awaiting_guild: &AwaitingGuild,
91    ) -> impl Iterator<Item = Change> {
92        let channel_changes = self
93            .channel_changes_service
94            .list_changes(existing_guild, awaiting_guild);
95
96        channel_changes.into_iter().map(|change| match change {
97            ChannelChange::Create(awaiting) => {
98                Change::Create(ChangeEntity::Channel, awaiting.unique_name().to_string())
99            }
100            ChannelChange::Update(existing, _, diffs) => Change::Update(
101                ChangeEntity::Channel,
102                existing.unique_name().to_string(),
103                diffs,
104            ),
105            ChannelChange::Delete(existing) => {
106                Change::Delete(ChangeEntity::Channel, existing.unique_name().to_string())
107            }
108        })
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use std::sync::Arc;
115
116    use mock_it::eq;
117
118    use crate::{
119        api::params::permission::PermissionsOverwriteParams,
120        core::{
121            changes::{
122                category::CategoryChangesService, channel::ChannelChangesService,
123                role::RoleChangesService, Change, ChangeEntity,
124            },
125            diffs::Diff,
126        },
127        guild::GuildQuerierMock,
128        tests::{
129            fixtures::{
130                existing::{
131                    ExistingCategoryFixture, ExistingChannelFixture, ExistingGuildFixture,
132                    ExistingRoleFixture,
133                },
134                params::{
135                    CategoryParamsFixture, ChannelParamsFixture, GuildParamsFixture,
136                    RoleParamsFixture,
137                },
138            },
139            utils::vec::assert_contains_exactly_in_any_order,
140        },
141    };
142
143    use super::ListChangesUseCase;
144
145    static GUILD_ID: &str = "abc";
146    static A_ROLE_NAME: &str = "a_role";
147    static A_CATEGORY_NAME: &str = "a_category";
148
149    fn create_usecase(querier: GuildQuerierMock) -> ListChangesUseCase {
150        ListChangesUseCase::new(
151            Arc::from(querier),
152            Arc::from(RoleChangesService {}),
153            Arc::from(CategoryChangesService {}),
154            Arc::from(ChannelChangesService {}),
155        )
156    }
157
158    #[test]
159    fn when_no_changes_it_returns_empty_list() {
160        let querier = GuildQuerierMock::new();
161        let empty_guild = ExistingGuildFixture::new().build();
162        let params_with_no_changes = GuildParamsFixture::new().build();
163
164        querier
165            .when_get_guild(eq(GUILD_ID))
166            .will_return(empty_guild);
167
168        let usecase = create_usecase(querier);
169
170        let changes = usecase.execute(GUILD_ID, params_with_no_changes);
171
172        assert_eq!(changes, Vec::new());
173    }
174
175    #[test]
176    fn can_list_role_changes() {
177        let querier = GuildQuerierMock::new();
178
179        let role_to_remove = ExistingRoleFixture::new().with_name("to_remove").build();
180        let role_to_add_params = RoleParamsFixture::new().with_name("to_add").build();
181        let role_to_update = ExistingRoleFixture::new().with_name("to_update").build();
182        let role_to_update_params = RoleParamsFixture::new()
183            .with_name("to_update")
184            .with_color("124f5d")
185            .build();
186        let role_not_to_update = ExistingRoleFixture::new()
187            .with_name("not_to_update")
188            .build();
189        let role_not_to_update_params = RoleParamsFixture::new().with_name("not_to_update").build();
190
191        querier.when_get_guild(eq(GUILD_ID)).will_return(
192            ExistingGuildFixture::new()
193                .with_role(role_to_remove.clone())
194                .with_role(role_to_update.clone())
195                .with_role(role_not_to_update.clone())
196                .build(),
197        );
198
199        let usecase = create_usecase(querier);
200
201        let changes = usecase.execute(
202            GUILD_ID,
203            GuildParamsFixture::new()
204                .with_role(role_to_add_params.clone())
205                .with_role(role_to_update_params.clone())
206                .with_role(role_not_to_update_params.clone())
207                .remove_extra_roles()
208                .build(),
209        );
210
211        assert_eq!(
212            changes,
213            vec![
214                Change::Create(ChangeEntity::Role, role_to_add_params.name),
215                Change::Update(
216                    ChangeEntity::Role,
217                    role_to_update.name,
218                    vec![Diff::Update(
219                        "color".to_string(),
220                        vec![Diff::Add("124f5d".to_string())]
221                    )]
222                ),
223                Change::Delete(ChangeEntity::Role, role_to_remove.name)
224            ]
225        );
226    }
227
228    #[test]
229    fn can_list_category_changes() {
230        let querier = GuildQuerierMock::new();
231
232        let category_to_remove = ExistingCategoryFixture::new()
233            .with_name("to_remove")
234            .build();
235        let category_to_add_params = CategoryParamsFixture::new().with_name("to_add").build();
236        let category_to_update = ExistingCategoryFixture::new()
237            .with_name("to_update")
238            .build();
239        let category_not_to_update = ExistingCategoryFixture::new()
240            .with_name("not_to_update")
241            .build();
242        let category_to_update_params = CategoryParamsFixture::new()
243            .with_name("to_update")
244            .with_permissions_overwrite(PermissionsOverwriteParams {
245                role: A_ROLE_NAME.to_string(),
246                allow: Vec::new(),
247                deny: Vec::new(),
248            })
249            .build();
250        let category_not_to_update_params = CategoryParamsFixture::new()
251            .with_name("not_to_update")
252            .keep_extra_channels()
253            .build();
254
255        querier.when_get_guild(eq(GUILD_ID)).will_return(
256            ExistingGuildFixture::new()
257                .with_role(ExistingRoleFixture::new().with_name(A_ROLE_NAME).build())
258                .with_category(category_to_remove.clone())
259                .with_category(category_to_update.clone())
260                .with_category(category_not_to_update.clone())
261                .build(),
262        );
263
264        let usecase = create_usecase(querier);
265
266        let changes = usecase.execute(
267            GUILD_ID,
268            GuildParamsFixture::new()
269                .with_role(RoleParamsFixture::new().with_name(A_ROLE_NAME).build())
270                .with_category(category_to_add_params.clone())
271                .with_category(category_to_update_params.clone())
272                .with_category(category_not_to_update_params.clone())
273                .remove_extra_categories()
274                .build(),
275        );
276
277        assert_eq!(
278            changes,
279            vec![
280                Change::Create(ChangeEntity::Category, category_to_add_params.name),
281                Change::Update(
282                    ChangeEntity::Category,
283                    category_to_update.name,
284                    vec![Diff::Update(
285                        "overwrites".to_string(),
286                        vec![Diff::Add(A_ROLE_NAME.to_string())]
287                    )]
288                ),
289                Change::Delete(ChangeEntity::Category, category_to_remove.name)
290            ]
291        );
292    }
293
294    #[test]
295    fn can_list_channel_changes() {
296        let querier = GuildQuerierMock::new();
297
298        let channel_to_remove = ExistingChannelFixture::new().with_name("to_remove").build();
299        let channel_to_add_params = ChannelParamsFixture::new().with_name("to_add").build();
300        let channel_to_update = ExistingChannelFixture::new().with_name("to_update").build();
301        let channel_not_to_update = ExistingChannelFixture::new()
302            .with_name("not_to_update")
303            .build();
304        let channel_to_update_params = ChannelParamsFixture::new()
305            .with_name("to_update")
306            .with_topic("new_topic")
307            .build();
308        let channel_not_to_update_params = ChannelParamsFixture::new()
309            .with_name("not_to_update")
310            // TODO keep like that or change that should not trigger update
311            .build();
312        let channel_to_change_category = ExistingChannelFixture::new()
313            .with_name("category_change")
314            .build();
315        let channel_to_change_category_params = ChannelParamsFixture::new()
316            .with_name("category_change")
317            .with_category(A_CATEGORY_NAME)
318            .build();
319
320        querier.when_get_guild(eq(GUILD_ID)).will_return(
321            ExistingGuildFixture::new()
322                .with_category(
323                    ExistingCategoryFixture::new()
324                        .with_name(A_CATEGORY_NAME)
325                        .build(),
326                )
327                .with_channel(channel_to_remove.clone())
328                .with_channel(channel_to_update.clone())
329                .with_channel(channel_not_to_update.clone())
330                .with_channel(channel_to_change_category.clone())
331                .build(),
332        );
333
334        let usecase = create_usecase(querier);
335
336        let changes = usecase.execute(
337            GUILD_ID,
338            GuildParamsFixture::new()
339                .with_category(
340                    CategoryParamsFixture::new()
341                        .with_name(A_CATEGORY_NAME)
342                        .build(),
343                )
344                .with_channel(channel_to_add_params.clone())
345                .with_channel(channel_to_update_params.clone())
346                .with_channel(channel_not_to_update_params.clone())
347                .with_channel(channel_to_change_category_params.clone())
348                .remove_extra_channels()
349                .build(),
350        );
351
352        // TODO should contain the UniqueChannelName and not the pre-computed string
353        assert_contains_exactly_in_any_order(
354            &changes,
355            &vec![
356                Change::Create(ChangeEntity::Channel, ":to_add (TEXT)".to_string()),
357                Change::Create(
358                    ChangeEntity::Channel,
359                    "a_category:category_change (TEXT)".to_string(),
360                ),
361                Change::Update(
362                    ChangeEntity::Channel,
363                    ":to_update (TEXT)".to_string(),
364                    vec![Diff::Update(
365                        "topic".to_string(),
366                        vec![Diff::Add("new_topic".to_string())],
367                    )],
368                ),
369                Change::Delete(ChangeEntity::Channel, ":category_change (TEXT)".to_string()),
370                Change::Delete(ChangeEntity::Channel, ":to_remove (TEXT)".to_string()),
371            ],
372        );
373    }
374}