adguard_flm/manager/
filter_list_manager_impl.rs

1//! Default implementation for [`FilterListManager`]
2
3use super::managers::configuration_update_manager::ConfigurationUpdateManager;
4use super::managers::db_manager::DbManager;
5use super::managers::filter_group_manager::FilterGroupManager;
6use super::managers::filter_manager::FilterManager;
7use super::managers::filter_metadata_grabber::FilterMetadataGrabber;
8use super::managers::filter_tag_manager::FilterTagManager;
9use super::managers::filter_update_manager::FilterUpdateManager;
10use super::managers::rules_list_manager::RulesListManager;
11use super::managers::streaming_rules_manager::StreamingRulesManager;
12use super::models::{
13    configuration::Configuration, FilterId, FilterListMetadata, FilterListMetadataWithBody,
14    FullFilterList, PullMetadataResult, UpdateResult,
15};
16use crate::manager::models::configuration::request_proxy_mode::RequestProxyMode;
17use crate::manager::models::configuration::Locale;
18use crate::manager::models::disabled_rules_raw::DisabledRulesRaw;
19use crate::manager::models::filter_group::FilterGroup;
20use crate::manager::models::filter_list_rules::FilterListRules;
21use crate::manager::models::filter_list_rules_raw::FilterListRulesRaw;
22use crate::manager::models::filter_tag::FilterTag;
23use crate::manager::models::rules_count_by_filter::RulesCountByFilter;
24use crate::storage::sql_generators::operator::SQLOperator;
25use crate::storage::DbConnectionManager;
26use crate::{
27    manager::FilterListManager, ActiveRulesInfo, ActiveRulesInfoRaw, FLMError, FLMResult,
28    StoredFilterMetadata,
29};
30use std::path::Path;
31
32/// Default implementation for [`FilterListManager`]
33pub struct FilterListManagerImpl {
34    configuration: Configuration,
35    pub(crate) connection_manager: DbConnectionManager,
36}
37
38impl FilterListManager for FilterListManagerImpl {
39    fn new(mut configuration: Configuration) -> FLMResult<Box<Self>> {
40        if configuration.app_name.is_empty() {
41            return Err(FLMError::InvalidConfiguration("app_name is empty"));
42        }
43        if configuration.version.is_empty() {
44            return Err(FLMError::InvalidConfiguration("version is empty"));
45        }
46
47        configuration.normalized();
48
49        let connection_manager = DbConnectionManager::from_configuration(&configuration)?;
50        if configuration.auto_lift_up_database {
51            unsafe { connection_manager.lift_up_database()? }
52        }
53
54        Ok(Box::new(Self {
55            configuration,
56            connection_manager,
57        }))
58    }
59
60    fn install_custom_filter_list(
61        &self,
62        download_url: String,
63        is_trusted: bool,
64        title: Option<String>,
65        description: Option<String>,
66    ) -> FLMResult<FullFilterList> {
67        FilterManager::new().install_custom_filter_list_from_url(
68            &self.connection_manager,
69            &self.configuration,
70            download_url,
71            is_trusted,
72            title,
73            description,
74        )
75    }
76
77    fn fetch_filter_list_metadata(&self, url: String) -> FLMResult<FilterListMetadata> {
78        FilterMetadataGrabber::new().fetch_filter_list_metadata(&self.configuration, url)
79    }
80
81    fn fetch_filter_list_metadata_with_body(
82        &self,
83        url: String,
84    ) -> FLMResult<FilterListMetadataWithBody> {
85        FilterMetadataGrabber::new().fetch_filter_list_metadata_with_body(&self.configuration, url)
86    }
87
88    fn enable_filter_lists(&self, ids: Vec<FilterId>, is_enabled: bool) -> FLMResult<usize> {
89        FilterManager::new().enable_filter_lists(&self.connection_manager, ids, is_enabled)
90    }
91
92    fn install_filter_lists(&self, ids: Vec<FilterId>, is_installed: bool) -> FLMResult<usize> {
93        FilterManager::new().install_filter_lists(&self.connection_manager, ids, is_installed)
94    }
95
96    fn delete_custom_filter_lists(&self, ids: Vec<FilterId>) -> FLMResult<usize> {
97        FilterManager::new().delete_custom_filter_lists(&self.connection_manager, ids)
98    }
99
100    fn get_all_tags(&self) -> FLMResult<Vec<FilterTag>> {
101        FilterTagManager::new().get_all_tags(&self.connection_manager)
102    }
103
104    fn get_all_groups(&self) -> FLMResult<Vec<FilterGroup>> {
105        FilterGroupManager::new().get_all_groups(&self.connection_manager, &self.configuration)
106    }
107
108    fn get_full_filter_list_by_id(&self, filter_id: FilterId) -> FLMResult<Option<FullFilterList>> {
109        FilterManager::new().get_full_filter_list_by_id(
110            &self.connection_manager,
111            &self.configuration,
112            Some(SQLOperator::FieldEqualValue("filter_id", filter_id.into())),
113        )
114    }
115
116    fn get_stored_filters_metadata(&self) -> FLMResult<Vec<StoredFilterMetadata>> {
117        FilterManager::new().get_stored_filters_metadata(
118            &self.connection_manager,
119            &self.configuration,
120            None,
121        )
122    }
123
124    fn get_stored_filter_metadata_by_id(
125        &self,
126        filter_id: FilterId,
127    ) -> FLMResult<Option<StoredFilterMetadata>> {
128        FilterManager::new().get_stored_filter_metadata_by_id(
129            &self.connection_manager,
130            &self.configuration,
131            Some(SQLOperator::FieldEqualValue("filter_id", filter_id.into())),
132        )
133    }
134
135    fn save_custom_filter_rules(&self, rules: FilterListRules) -> FLMResult<()> {
136        RulesListManager::new().save_custom_filter_rules(
137            &self.connection_manager,
138            &self.configuration,
139            rules,
140        )
141    }
142
143    fn save_disabled_rules(
144        &self,
145        filter_id: FilterId,
146        disabled_rules: Vec<String>,
147    ) -> FLMResult<()> {
148        RulesListManager::new().save_disabled_rules(
149            &self.connection_manager,
150            filter_id,
151            disabled_rules,
152        )
153    }
154
155    fn update_filters(
156        &self,
157        ignore_filters_expiration: bool,
158        loose_timeout: i32,
159        ignore_filters_status: bool,
160    ) -> FLMResult<Option<UpdateResult>> {
161        FilterUpdateManager::new().update_filters(
162            &self.connection_manager,
163            &self.configuration,
164            ignore_filters_expiration,
165            loose_timeout,
166            ignore_filters_status,
167        )
168    }
169
170    fn update_filters_by_ids(
171        &self,
172        ids: Vec<FilterId>,
173        ignore_filters_expiration: bool,
174        loose_timeout: i32,
175        ignore_filters_status: bool,
176    ) -> FLMResult<Option<UpdateResult>> {
177        FilterUpdateManager::new().update_filters_by_ids(
178            &self.connection_manager,
179            &self.configuration,
180            ids,
181            ignore_filters_expiration,
182            loose_timeout,
183            ignore_filters_status,
184        )
185    }
186
187    fn force_update_filters_by_ids(
188        &self,
189        ids: Vec<FilterId>,
190        loose_timeout: i32,
191    ) -> FLMResult<Option<UpdateResult>> {
192        FilterUpdateManager::new().force_update_filters_by_ids(
193            &self.connection_manager,
194            &self.configuration,
195            ids,
196            loose_timeout,
197        )
198    }
199
200    fn change_locale(&mut self, suggested_locale: Locale) -> FLMResult<bool> {
201        ConfigurationUpdateManager::new().change_locale(
202            &self.connection_manager,
203            &mut self.configuration,
204            suggested_locale,
205        )
206    }
207
208    fn pull_metadata(&self) -> FLMResult<PullMetadataResult> {
209        FilterUpdateManager::new().pull_metadata(&self.connection_manager, &self.configuration)
210    }
211
212    fn update_custom_filter_metadata(
213        &self,
214        filter_id: FilterId,
215        title: String,
216        is_trusted: bool,
217    ) -> FLMResult<bool> {
218        FilterManager::new().update_custom_filter_metadata(
219            &self.connection_manager,
220            filter_id,
221            title,
222            is_trusted,
223        )
224    }
225
226    fn get_database_path(&self) -> FLMResult<String> {
227        DbManager::new().get_database_path(&self.connection_manager)
228    }
229
230    fn lift_up_database(&self) -> FLMResult<()> {
231        // SAFETY: Safe, as long as the call to this function does not get inside the `execute_db` closure one way or another
232        // @see DbConnectionManager
233        unsafe { self.connection_manager.lift_up_database() }
234    }
235
236    fn get_database_version(&self) -> FLMResult<Option<i32>> {
237        DbManager::new().get_database_version(&self.connection_manager)
238    }
239
240    fn install_custom_filter_from_string(
241        &self,
242        download_url: String,
243        last_download_time: i64,
244        is_enabled: bool,
245        is_trusted: bool,
246        filter_body: String,
247        custom_title: Option<String>,
248        custom_description: Option<String>,
249    ) -> FLMResult<FullFilterList> {
250        FilterManager::new().install_custom_filter_from_string(
251            &self.connection_manager,
252            &self.configuration,
253            download_url,
254            last_download_time,
255            is_enabled,
256            is_trusted,
257            filter_body,
258            custom_title,
259            custom_description,
260        )
261    }
262
263    fn get_active_rules(&self) -> FLMResult<Vec<ActiveRulesInfo>> {
264        RulesListManager::new().get_active_rules(&self.connection_manager, &self.configuration)
265    }
266
267    fn get_active_rules_raw(&self, filter_by: Vec<FilterId>) -> FLMResult<Vec<ActiveRulesInfoRaw>> {
268        RulesListManager::new().get_active_rules_raw(
269            &self.connection_manager,
270            &self.configuration,
271            filter_by,
272        )
273    }
274
275    fn get_filter_rules_as_strings(
276        &self,
277        ids: Vec<FilterId>,
278    ) -> FLMResult<Vec<FilterListRulesRaw>> {
279        RulesListManager::new().get_filter_rules_as_strings(
280            &self.connection_manager,
281            &self.configuration,
282            ids,
283        )
284    }
285
286    fn save_rules_to_file_blob<P: AsRef<Path>>(
287        &self,
288        filter_id: FilterId,
289        file_path: P,
290    ) -> FLMResult<()> {
291        StreamingRulesManager::new().save_rules_to_file_blob(
292            &self.connection_manager,
293            filter_id,
294            file_path,
295        )
296    }
297
298    fn get_disabled_rules(&self, ids: Vec<FilterId>) -> FLMResult<Vec<DisabledRulesRaw>> {
299        RulesListManager::new().get_disabled_rules(&self.connection_manager, ids)
300    }
301
302    fn set_proxy_mode(&mut self, mode: RequestProxyMode) {
303        ConfigurationUpdateManager::new().set_proxy_mode(&mut self.configuration, mode)
304    }
305
306    fn get_rules_count(&self, ids: Vec<FilterId>) -> FLMResult<Vec<RulesCountByFilter>> {
307        RulesListManager::new().get_rules_count(&self.connection_manager, ids)
308    }
309}
310
311#[cfg(test)]
312impl FilterListManagerImpl {
313    pub(crate) fn get_configuration(&self) -> &Configuration {
314        &self.configuration
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use crate::manager::managers::filter_manager::FilterManager;
321    use crate::storage::entities::rules_list::rules_list_entity::RulesListEntity;
322    use crate::storage::repositories::filter_repository::FilterRepository;
323    use crate::storage::repositories::rules_list_repository::RulesListRepository;
324    use crate::storage::repositories::Repository;
325    use crate::storage::sql_generators::operator::SQLOperator;
326    use crate::storage::with_transaction;
327    use crate::storage::DbConnectionManager;
328    use crate::test_utils::spawn_test_db_with_metadata;
329    use crate::{
330        string, Configuration, FilterId, FilterListManager, FilterListManagerImpl, FilterListRules,
331        USER_RULES_FILTER_LIST_ID,
332    };
333    use chrono::{Duration, Utc};
334    use rand::prelude::SliceRandom;
335    use rand::thread_rng;
336    use rusqlite::Connection;
337    use std::fs::File;
338    use std::ops::Sub;
339    use std::time::{SystemTime, UNIX_EPOCH};
340    use std::{env, fs};
341    use url::Url;
342
343    #[test]
344    fn test_insert_custom_filter() {
345        let source = DbConnectionManager::factory_test().unwrap();
346        let _ = spawn_test_db_with_metadata(&source);
347
348        let mut conf = Configuration::default();
349        conf.app_name = "FlmApp".to_string();
350        conf.version = "1.2.3".to_string();
351        let flm = FilterListManagerImpl::new(conf).unwrap();
352
353        let path = fs::canonicalize("./tests/fixtures/1.txt").unwrap();
354
355        let first_filter_url = Url::from_file_path(path).unwrap();
356
357        let title = String::from("first title");
358        let description =
359            String::from("Filter that enables ad blocking on websites in Russian language.");
360
361        let current_time = Utc::now().timestamp();
362
363        let full_filter_list = flm
364            .install_custom_filter_list(
365                first_filter_url.to_string(),
366                true,
367                Some(title.clone()),
368                None,
369            )
370            .unwrap();
371
372        assert!(full_filter_list.is_custom);
373        assert!(full_filter_list.is_trusted);
374
375        assert_eq!(full_filter_list.title, title);
376        assert_eq!(full_filter_list.description, description);
377
378        assert!(full_filter_list.last_download_time >= current_time);
379
380        assert!(full_filter_list.is_enabled);
381    }
382
383    #[test]
384    fn delete_filter_lists() {
385        let source = DbConnectionManager::factory_test().unwrap();
386        let (_, inserted_filters) = spawn_test_db_with_metadata(&source);
387
388        let mut conf = Configuration::default();
389        conf.app_name = "FlmApp".to_string();
390        conf.version = "1.2.3".to_string();
391        let flm = FilterListManagerImpl::new(conf).unwrap();
392
393        let deleted = flm
394            .delete_custom_filter_lists(vec![inserted_filters.first().unwrap().filter_id.unwrap()])
395            .unwrap();
396
397        // Do not delete index filters
398        assert_eq!(deleted, 0);
399
400        let path = fs::canonicalize("./tests/fixtures/1.txt").unwrap();
401        let first_filter_url = Url::from_file_path(path).unwrap();
402
403        let title = String::from("first title");
404
405        let full_filter_list = flm
406            .install_custom_filter_list(
407                first_filter_url.to_string(),
408                true,
409                Some(title.clone()),
410                None,
411            )
412            .unwrap();
413
414        let custom_was_deleted = flm
415            .delete_custom_filter_lists(vec![full_filter_list.id])
416            .unwrap();
417
418        assert_eq!(custom_was_deleted, 1)
419    }
420
421    #[test]
422    fn test_install_local_custom_filter() {
423        let source = DbConnectionManager::factory_test().unwrap();
424        let _ = spawn_test_db_with_metadata(&source);
425
426        let mut conf = Configuration::default();
427        conf.app_name = "FlmApp".to_string();
428        conf.version = "1.2.3".to_string();
429        let flm = FilterListManagerImpl::new(conf).unwrap();
430
431        let title = String::from("titleeee");
432        let description = String::from("dessscrriptiiiioooonnn");
433
434        let full_filter_list = flm
435            .install_custom_filter_list(
436                String::new(),
437                true,
438                Some(title.clone()),
439                Some(description.clone()),
440            )
441            .unwrap();
442
443        assert!(full_filter_list.id.is_negative());
444        assert_eq!(full_filter_list.title, title);
445        assert_eq!(full_filter_list.description, description);
446        assert_eq!(full_filter_list.is_trusted, true);
447    }
448
449    #[test]
450    fn test_save_disabled_rules() {
451        let mut conf = Configuration::default();
452        conf.app_name = "FlmApp".to_string();
453        conf.version = "1.2.3".to_string();
454        let flm = FilterListManagerImpl::new(conf).unwrap();
455        let source = flm.connection_manager.as_ref();
456
457        let _ = spawn_test_db_with_metadata(source);
458
459        let title = String::from("titleeee");
460        let description = String::from("dessscrriptiiiioooonnn");
461
462        let full_filter_list = flm
463            .install_custom_filter_list(
464                String::new(),
465                true,
466                Some(title.clone()),
467                Some(description.clone()),
468            )
469            .unwrap();
470
471        let disabled_rules_vec: Vec<String> = vec!["first", "second", "third"]
472            .into_iter()
473            .map(|str| str.to_string())
474            .collect();
475        let disabled_rules_string = String::from("first\nsecond\nthird");
476
477        flm.save_disabled_rules(full_filter_list.id, disabled_rules_vec)
478            .unwrap();
479
480        let binding = source
481            .execute_db(|conn: Connection| {
482                let binding = RulesListRepository::new()
483                    .select(
484                        &conn,
485                        Some(SQLOperator::FieldEqualValue(
486                            "filter_id",
487                            full_filter_list.id.into(),
488                        )),
489                    )
490                    .unwrap()
491                    .unwrap();
492
493                Ok(binding)
494            })
495            .unwrap();
496
497        let rules_entity = binding.first().unwrap();
498
499        assert_eq!(rules_entity.disabled_text, disabled_rules_string);
500    }
501
502    #[test]
503    fn test_install_custom_filter_from_string() {
504        let source = DbConnectionManager::factory_test().unwrap();
505        let _ = spawn_test_db_with_metadata(&source);
506
507        let mut conf = Configuration::default();
508        conf.app_name = "FlmApp".to_string();
509        conf.version = "1.2.3".to_string();
510        let flm = FilterListManagerImpl::new(conf).unwrap();
511
512        let download_url = String::from("http://install.custom.filter.list.from.string");
513        let last_download_time = Utc::now().sub(Duration::days(5));
514        let filter_body = include_str!("../../tests/fixtures/small_pseudo_custom_filter.txt");
515
516        let filter_list = flm
517            .install_custom_filter_from_string(
518                download_url.clone(),
519                last_download_time.timestamp(),
520                true,
521                false,
522                String::from(filter_body),
523                None,
524                None,
525            )
526            .unwrap();
527
528        assert_eq!(filter_list.is_enabled, true);
529        assert_eq!(filter_list.is_trusted, false);
530        assert_eq!(filter_list.title.as_str(), "Pseudo Custom Filter Title");
531        assert_eq!(
532            filter_list.description.as_str(),
533            "Pseudo Custom Filter Description"
534        );
535        assert_eq!(filter_list.version.as_str(), "2.0.91.12");
536        assert_eq!(filter_list.expires, 5 * 86400);
537        assert_eq!(filter_list.is_custom, true);
538        assert_eq!(
539            filter_list.homepage.as_str(),
540            "https://github.com/AdguardTeam/AdGuardFilters"
541        );
542        assert_eq!(
543            filter_list.last_download_time,
544            last_download_time.timestamp()
545        );
546        assert_eq!(filter_list.time_updated, 1716903061);
547        assert_eq!(filter_list.checksum.as_str(), "GQRYLu/9jKZYam7zBiCudg");
548        assert_eq!(
549            filter_list.license.as_str(),
550            "https://github.com/AdguardTeam/AdguardFilters/blob/master/LICENSE"
551        );
552        assert!(filter_list.rules.unwrap().rules.len() > 0);
553    }
554
555    #[test]
556    fn test_we_can_understand_aliases_fields() {
557        let source = DbConnectionManager::factory_test().unwrap();
558        let _ = spawn_test_db_with_metadata(&source);
559
560        let mut conf = Configuration::default();
561        conf.app_name = "FlmApp".to_string();
562        conf.version = "1.2.3".to_string();
563        let flm = FilterListManagerImpl::new(conf).unwrap();
564
565        let download_url = String::from("http://install.custom.filter.list.from.string");
566        let last_download_time = Utc::now().sub(Duration::days(5));
567        let filter_body =
568            include_str!("../../tests/fixtures/small_pseudo_custom_filter_with_aliases.txt");
569
570        let filter_list = flm
571            .install_custom_filter_from_string(
572                download_url.clone(),
573                last_download_time.timestamp(),
574                true,
575                false,
576                String::from(filter_body),
577                None,
578                None,
579            )
580            .unwrap();
581
582        assert_eq!(filter_list.time_updated, 1719230481);
583        assert_eq!(
584            filter_list.last_download_time,
585            last_download_time.timestamp()
586        );
587    }
588
589    #[test]
590    fn test_we_can_select_localised_filters() {
591        {
592            let mut conf = Configuration::default();
593            conf.locale = String::from("el");
594            conf.app_name = "FlmApp".to_string();
595            conf.version = "1.2.3".to_string();
596
597            let flm = FilterListManagerImpl::new(conf).unwrap();
598            let source = flm.connection_manager.as_ref();
599            let _ = spawn_test_db_with_metadata(&source);
600
601            let filter = flm.get_full_filter_list_by_id(1).unwrap().unwrap();
602
603            assert_eq!(filter.title.as_str(), "AdGuard Ρωσικό φίλτρο");
604            assert_eq!(
605                filter.description.as_str(),
606                "Φίλτρο που επιτρέπει τον αποκλεισμό διαφημίσεων σε ιστότοπους στη ρωσική γλώσσα."
607            );
608        }
609
610        {
611            let mut conf = Configuration::default();
612            // Nonexistent
613            conf.locale = String::from("31");
614            conf.app_name = "FlmApp".to_string();
615            conf.version = "1.2.3".to_string();
616
617            let flm = FilterListManagerImpl::new(conf).unwrap();
618            let source = flm.connection_manager.as_ref();
619            let _ = spawn_test_db_with_metadata(&source);
620
621            let filter = flm.get_full_filter_list_by_id(1).unwrap().unwrap();
622
623            assert_eq!(filter.title.as_str(), "AdGuard Russian filter");
624            assert_eq!(
625                filter.description.as_str(),
626                "Filter that enables ad blocking on websites in Russian language."
627            );
628        }
629    }
630
631    #[test]
632    fn test_select_index_filter() {
633        let mut conf = Configuration::default();
634        conf.app_name = "FlmApp".to_string();
635        conf.version = "1.2.3".to_string();
636        let flm = FilterListManagerImpl::new(conf).unwrap();
637        let source = flm.connection_manager.as_ref();
638
639        let _ = spawn_test_db_with_metadata(&source);
640
641        let filter = flm.get_full_filter_list_by_id(257).unwrap().unwrap();
642
643        assert_eq!(
644            filter.subscription_url.as_str(),
645            "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/badware.txt"
646        );
647        assert_eq!(
648            filter.download_url.as_str(),
649            "https://example.org/extension/safari/filters/257_optimized.txt"
650        );
651        assert!(filter.subscription_url.len() > 0);
652        assert!(filter.download_url.len() > 0);
653    }
654
655    #[test]
656    fn test_save_custom_filter_rules_must_update_time() {
657        let source = DbConnectionManager::factory_test().unwrap();
658        let _ = spawn_test_db_with_metadata(&source);
659
660        let mut conf = Configuration::default();
661        conf.app_name = "FlmApp".to_string();
662        conf.version = "1.2.3".to_string();
663        let flm = FilterListManagerImpl::new(conf).unwrap();
664
665        let rules = FilterListRules {
666            filter_id: USER_RULES_FILTER_LIST_ID,
667            rules: vec![String::from("example.com")],
668            disabled_rules: vec![],
669            rules_count: 0,
670        };
671
672        // Set a new time here
673        flm.save_custom_filter_rules(rules.clone()).unwrap();
674
675        let original_time_updated = flm
676            .get_full_filter_list_by_id(USER_RULES_FILTER_LIST_ID)
677            .unwrap()
678            .unwrap()
679            .time_updated;
680
681        // Sleep a sec, then update once again
682        std::thread::sleep(core::time::Duration::from_secs(1));
683
684        // Set another time after sleeping a sec
685        flm.save_custom_filter_rules(rules).unwrap();
686
687        let user_rules = flm
688            .get_full_filter_list_by_id(USER_RULES_FILTER_LIST_ID)
689            .unwrap()
690            .unwrap();
691
692        assert_ne!(user_rules.time_updated, original_time_updated);
693    }
694
695    #[test]
696    fn test_guard_rewrite_user_rules_filter_by_another_filter() {
697        let source = DbConnectionManager::factory_test().unwrap();
698        let _ = spawn_test_db_with_metadata(&source);
699
700        let mut conf = Configuration::default();
701        conf.app_name = "FlmApp".to_string();
702        conf.version = "1.2.3".to_string();
703        let flm = FilterListManagerImpl::new(conf).unwrap();
704
705        let _ = flm
706            .install_custom_filter_from_string(
707                String::new(),
708                SystemTime::now()
709                    .duration_since(UNIX_EPOCH)
710                    .unwrap()
711                    .as_secs() as i64,
712                true,
713                true,
714                String::from("JJ"),
715                Some("FILTER".to_string()),
716                Some("DESC".to_string()),
717            )
718            .unwrap();
719
720        let list = source
721            .execute_db(|connection: Connection| {
722                let list = FilterRepository::new()
723                    .select(
724                        &connection,
725                        Some(SQLOperator::FieldEqualValue(
726                            "filter_id",
727                            USER_RULES_FILTER_LIST_ID.into(),
728                        )),
729                    )
730                    .unwrap()
731                    .unwrap();
732                Ok(list)
733            })
734            .unwrap();
735
736        assert!(!list.is_empty());
737    }
738
739    #[test]
740    fn test_database_is_automatically_lifted_in_constructor() {
741        let mut conf = Configuration::default();
742        conf.app_name = "FlmApp".to_string();
743        conf.version = "1.2.3".to_string();
744        let flm = FilterListManagerImpl::new(conf).unwrap();
745
746        let lists = FilterManager::new()
747            .get_full_filter_lists(&flm.connection_manager, &flm.configuration, None)
748            .unwrap();
749
750        assert!(lists.len() > 0);
751    }
752
753    #[test]
754    fn test_get_filter_rules_as_strings() {
755        const TEST_FILTERS_AMOUNT: usize = 3;
756        const NONEXISTENT_ID: FilterId = 450_123_456;
757
758        let mut conf = Configuration::default();
759        conf.app_name = "FlmApp".to_string();
760        conf.version = "1.2.3".to_string();
761        let flm = FilterListManagerImpl::new(conf).unwrap();
762        let source = &flm.connection_manager;
763        let (_, index_filters) = spawn_test_db_with_metadata(source);
764
765        let filter_repo = FilterRepository::new();
766        let rules_repo = RulesListRepository::new();
767
768        let guard_id = source
769            .execute_db(|connection: Connection| {
770                let guard_id = filter_repo
771                    .count(
772                        &connection,
773                        Some(SQLOperator::FieldIn(
774                            "filter_id",
775                            vec![NONEXISTENT_ID.into()],
776                        )),
777                    )
778                    .unwrap();
779
780                Ok(guard_id)
781            })
782            .unwrap();
783
784        assert_eq!(guard_id, 0);
785
786        let mut rng = thread_rng();
787        let mut ids = index_filters
788            .choose_multiple(&mut rng, TEST_FILTERS_AMOUNT)
789            .filter_map(|filter| filter.filter_id)
790            .collect::<Vec<FilterId>>();
791
792        source
793            .execute_db(|mut connection: Connection| {
794                // Add rules by ids
795                with_transaction(&mut connection, |transaction| {
796                    let entities = ids
797                        .clone()
798                        .into_iter()
799                        .map(|id| {
800                            RulesListEntity::with_disabled_text(
801                                id,
802                                string!("example.com\nexample.org"),
803                                string!("example.com"),
804                                0,
805                            )
806                        })
807                        .collect::<Vec<RulesListEntity>>();
808
809                    rules_repo.insert(&transaction, &entities).unwrap();
810
811                    Ok(())
812                })
813            })
814            .unwrap();
815
816        ids.push(NONEXISTENT_ID);
817
818        let rules = flm.get_filter_rules_as_strings(ids).unwrap();
819
820        assert_eq!(rules.len(), TEST_FILTERS_AMOUNT);
821        assert!(rules
822            .iter()
823            .find(|rules| rules.filter_id == NONEXISTENT_ID)
824            .is_none())
825    }
826
827    #[test]
828    fn test_save_rules_to_file_blob() {
829        let mut path = env::current_dir().unwrap();
830        path.push("fixtures");
831        path.push(format!(
832            "test_filter_rules_{}.txt",
833            Utc::now().timestamp_micros()
834        ));
835
836        let mut conf = Configuration::default();
837        conf.app_name = "FlmApp".to_string();
838        conf.version = "1.2.3".to_string();
839        let flm = FilterListManagerImpl::new(conf).unwrap();
840
841        {
842            File::create(&path).unwrap();
843        }
844
845        let rules = FilterListRules {
846            filter_id: USER_RULES_FILTER_LIST_ID,
847            rules: vec![
848                String::from("first"),
849                String::from("second"),
850                String::from("third"),
851                String::from("fourth"),
852                String::from("fifth"),
853            ],
854            disabled_rules: vec![
855                String::from("second"),
856                String::from("fourth"),
857                String::from("second"),
858            ],
859            rules_count: 0,
860        };
861
862        flm.save_custom_filter_rules(rules).unwrap();
863
864        flm.save_rules_to_file_blob(USER_RULES_FILTER_LIST_ID, &path)
865            .unwrap();
866
867        let test_string = fs::read_to_string(&path).unwrap();
868        fs::remove_file(&path).unwrap();
869
870        assert_eq!(test_string.as_str(), "first\nthird\nfifth");
871    }
872
873    #[test]
874    fn test_get_disabled_rules() {
875        let mut conf = Configuration::default();
876        conf.app_name = "FlmApp".to_string();
877        conf.version = "1.2.3".to_string();
878        let flm = FilterListManagerImpl::new(conf).unwrap();
879
880        let source = &flm.connection_manager;
881        let (_, index_filters) = spawn_test_db_with_metadata(source);
882
883        let last_filter_id = index_filters.last().unwrap().filter_id.unwrap();
884        let first_filter_id = index_filters.first().unwrap().filter_id.unwrap();
885
886        source
887            .execute_db(|mut connection: Connection| {
888                let rules1 = RulesListEntity::with_disabled_text(
889                    last_filter_id,
890                    string!("Text\nDisabled Text\n123"),
891                    string!("Disabled Text\n123"),
892                    0,
893                );
894
895                let rules2 = RulesListEntity::with_disabled_text(
896                    first_filter_id,
897                    string!("Text2\nDisabled Text2"),
898                    string!("Disabled Text2"),
899                    0,
900                );
901
902                let tx = connection.transaction().unwrap();
903                let repo = RulesListRepository::new();
904
905                repo.insert(&tx, vec![rules1, rules2].as_slice()).unwrap();
906
907                tx.commit().unwrap();
908
909                Ok(())
910            })
911            .unwrap();
912
913        let actual = flm
914            .get_disabled_rules(vec![first_filter_id, last_filter_id])
915            .unwrap();
916
917        assert_eq!(actual[0].text.as_str(), "Disabled Text2");
918        assert_eq!(actual[1].text.as_str(), "Disabled Text\n123");
919    }
920
921    #[test]
922    fn test_change_locale() {
923        let mut conf = Configuration::default();
924        conf.app_name = "FlmApp".to_string();
925        conf.version = "1.2.3".to_string();
926        let mut flm = FilterListManagerImpl::new(conf).unwrap();
927
928        let source = &flm.connection_manager;
929        spawn_test_db_with_metadata(source);
930
931        let mut res = flm.change_locale("ru".to_string()).unwrap();
932        assert!(res);
933
934        res = flm.change_locale("ru-RU".to_string()).unwrap();
935        assert!(res);
936
937        res = flm.change_locale("ru_RU".to_string()).unwrap();
938        assert!(res);
939
940        res = flm.change_locale("ruRU".to_string()).unwrap();
941        assert!(!res);
942    }
943
944    #[test]
945    fn test_get_rules_count() {
946        let mut conf = Configuration::default();
947        conf.app_name = "FlmApp".to_string();
948        conf.version = "1.2.3".to_string();
949        let flm = FilterListManagerImpl::new(conf).unwrap();
950
951        let source = &flm.connection_manager;
952        spawn_test_db_with_metadata(source);
953
954        let user_rules_count_result = 5;
955
956        source
957            .execute_db(|mut connection: Connection| {
958                let rules = RulesListEntity::make(
959                    USER_RULES_FILTER_LIST_ID,
960                    string!(),
961                    user_rules_count_result,
962                );
963
964                let tx = connection.transaction().unwrap();
965                let repo = RulesListRepository::new();
966
967                repo.insert(&tx, vec![rules].as_slice()).unwrap();
968
969                tx.commit().unwrap();
970
971                Ok(())
972            })
973            .unwrap();
974
975        let rules_count_by_filter = flm
976            .get_rules_count(vec![USER_RULES_FILTER_LIST_ID])
977            .unwrap();
978
979        assert_eq!(
980            rules_count_by_filter[0].filter_id,
981            USER_RULES_FILTER_LIST_ID
982        );
983        assert_eq!(
984            rules_count_by_filter[0].rules_count,
985            user_rules_count_result
986        );
987    }
988
989    #[test]
990    fn test_save_custom_filter_rules_must_update_rules_count() {
991        let source = DbConnectionManager::factory_test().unwrap();
992        spawn_test_db_with_metadata(&source);
993
994        let mut conf = Configuration::default();
995        conf.app_name = "FlmApp".to_string();
996        conf.version = "1.2.3".to_string();
997        let flm = FilterListManagerImpl::new(conf).unwrap();
998
999        let rules = FilterListRules {
1000            filter_id: USER_RULES_FILTER_LIST_ID,
1001            rules: "Text\n!Text\n# Text\n\n\nText"
1002                .split('\n')
1003                .map(str::to_string)
1004                .collect(),
1005            disabled_rules: "Disabled Text".split('\n').map(str::to_string).collect(),
1006            rules_count: 0,
1007        };
1008
1009        let user_rules_count_result = 2;
1010
1011        flm.save_custom_filter_rules(rules).unwrap();
1012
1013        let rules_count_by_filter = flm
1014            .get_rules_count(vec![USER_RULES_FILTER_LIST_ID])
1015            .unwrap();
1016
1017        assert_eq!(
1018            rules_count_by_filter[0].filter_id,
1019            USER_RULES_FILTER_LIST_ID
1020        );
1021        assert_eq!(
1022            rules_count_by_filter[0].rules_count,
1023            user_rules_count_result
1024        );
1025    }
1026
1027    #[test]
1028    fn test_install_custom_filter_sets_is_user_title_and_description_flags() {
1029        let mut conf = Configuration::default();
1030        conf.app_name = "FlmApp".to_string();
1031        conf.version = "1.2.3".to_string();
1032
1033        let flm = FilterListManagerImpl::new(conf).unwrap();
1034
1035        let source = &flm.connection_manager;
1036        spawn_test_db_with_metadata(source);
1037
1038        // sets is_user_title flag
1039        let installed_filter_list = flm
1040            .install_custom_filter_list(
1041                "https://filters.adtidy.org/extension/safari/filters/101_optimized.txt".to_string(),
1042                true,
1043                Some("title".to_string()),
1044                None,
1045            )
1046            .unwrap();
1047
1048        source
1049            .execute_db(|conn: Connection| {
1050                let filters = FilterRepository::new()
1051                    .select(
1052                        &conn,
1053                        Some(SQLOperator::FieldEqualValue(
1054                            "filter_id",
1055                            installed_filter_list.id.into(),
1056                        )),
1057                    )
1058                    .unwrap()
1059                    .unwrap();
1060
1061                assert!(filters[0].is_user_title());
1062                assert!(!filters[0].is_user_description());
1063
1064                Ok(())
1065            })
1066            .unwrap();
1067
1068        // sets is_user_description flag
1069        let installed_filter_list = flm
1070            .install_custom_filter_list(
1071                "https://filters.adtidy.org/extension/safari/filters/101_optimized.txt".to_string(),
1072                true,
1073                None,
1074                Some("description".to_string()),
1075            )
1076            .unwrap();
1077
1078        source
1079            .execute_db(|conn: Connection| {
1080                let filters = FilterRepository::new()
1081                    .select(
1082                        &conn,
1083                        Some(SQLOperator::FieldEqualValue(
1084                            "filter_id",
1085                            installed_filter_list.id.into(),
1086                        )),
1087                    )
1088                    .unwrap()
1089                    .unwrap();
1090
1091                assert!(!filters[0].is_user_title());
1092                assert!(filters[0].is_user_description());
1093
1094                Ok(())
1095            })
1096            .unwrap();
1097    }
1098
1099    #[test]
1100    fn test_update_custom_filter_metadata_sets_is_user_title_flag() {
1101        let mut conf = Configuration::default();
1102        conf.app_name = "FlmApp".to_string();
1103        conf.version = "1.2.3".to_string();
1104
1105        let flm = FilterListManagerImpl::new(conf).unwrap();
1106
1107        let source = &flm.connection_manager;
1108        spawn_test_db_with_metadata(source);
1109
1110        // sets is_user_title flag
1111        let installed_filter_list = flm
1112            .install_custom_filter_list(
1113                "https://filters.adtidy.org/extension/safari/filters/101_optimized.txt".to_string(),
1114                true,
1115                None,
1116                None,
1117            )
1118            .unwrap();
1119
1120        flm.update_custom_filter_metadata(installed_filter_list.id, "title".to_string(), true)
1121            .unwrap();
1122
1123        source
1124            .execute_db(|conn: Connection| {
1125                let filters = FilterRepository::new()
1126                    .select(
1127                        &conn,
1128                        Some(SQLOperator::FieldEqualValue(
1129                            "filter_id",
1130                            installed_filter_list.id.into(),
1131                        )),
1132                    )
1133                    .unwrap()
1134                    .unwrap();
1135
1136                assert!(filters[0].is_user_title());
1137
1138                Ok(())
1139            })
1140            .unwrap();
1141    }
1142
1143    #[test]
1144    fn test_update_filters_must_not_update_title_and_description() {
1145        let mut conf = Configuration::default();
1146        conf.metadata_url = "https://filters.adtidy.org/extension/safari/filters.json".to_string();
1147        conf.metadata_locales_url =
1148            "https://filters.adtidy.org/windows/filters_i18n.json".to_string();
1149        conf.app_name = "FlmApp".to_string();
1150        conf.version = "1.2.3".to_string();
1151
1152        let flm = FilterListManagerImpl::new(conf).unwrap();
1153
1154        let source = &flm.connection_manager;
1155        spawn_test_db_with_metadata(source);
1156
1157        // must not update title
1158        let installed_filter_list = flm
1159            .install_custom_filter_list(
1160                "https://filters.adtidy.org/extension/safari/filters/101_optimized.txt".to_string(),
1161                true,
1162                Some("title".to_string()),
1163                None,
1164            )
1165            .unwrap();
1166
1167        flm.update_filters(false, 0, false).unwrap();
1168
1169        source
1170            .execute_db(|conn: Connection| {
1171                let filters = FilterRepository::new()
1172                    .select(
1173                        &conn,
1174                        Some(SQLOperator::FieldEqualValue(
1175                            "filter_id",
1176                            installed_filter_list.id.into(),
1177                        )),
1178                    )
1179                    .unwrap()
1180                    .unwrap();
1181
1182                assert_eq!(filters[0].title, "title");
1183                assert_ne!(filters[0].description, "description");
1184
1185                Ok(filters)
1186            })
1187            .unwrap();
1188
1189        // must not update description
1190        let installed_filter_list = flm
1191            .install_custom_filter_list(
1192                "https://filters.adtidy.org/extension/safari/filters/101_optimized.txt".to_string(),
1193                true,
1194                None,
1195                Some("description".to_string()),
1196            )
1197            .unwrap();
1198
1199        flm.update_filters(false, 0, false).unwrap();
1200
1201        source
1202            .execute_db(|conn: Connection| {
1203                let filters = FilterRepository::new()
1204                    .select(
1205                        &conn,
1206                        Some(SQLOperator::FieldEqualValue(
1207                            "filter_id",
1208                            installed_filter_list.id.into(),
1209                        )),
1210                    )
1211                    .unwrap()
1212                    .unwrap();
1213
1214                assert_ne!(filters[0].title, "title");
1215                assert_eq!(filters[0].description, "description");
1216
1217                Ok(filters)
1218            })
1219            .unwrap();
1220    }
1221}