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 .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 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}