search/
selector.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! This module defines the main `SearchEngineSelector`.
6
7use crate::configuration_overrides_types::JSONOverridesRecord;
8use crate::configuration_overrides_types::JSONSearchConfigurationOverrides;
9use crate::filter::filter_engine_configuration_impl;
10use crate::{
11    error::Error, JSONSearchConfiguration, RefinedSearchConfig, SearchApiResult,
12    SearchUserEnvironment,
13};
14use error_support::handle_error;
15use parking_lot::Mutex;
16use remote_settings::{RemoteSettingsClient, RemoteSettingsService};
17use std::sync::Arc;
18
19#[derive(Default)]
20pub(crate) struct SearchEngineSelectorInner {
21    configuration: Option<JSONSearchConfiguration>,
22    configuration_overrides: Option<JSONSearchConfigurationOverrides>,
23    search_config_client: Option<Arc<RemoteSettingsClient>>,
24    search_config_overrides_client: Option<Arc<RemoteSettingsClient>>,
25}
26
27/// SearchEngineSelector parses the JSON configuration for
28/// search engines and returns the applicable engines depending
29/// on their region + locale.
30#[derive(Default, uniffi::Object)]
31pub struct SearchEngineSelector(Mutex<SearchEngineSelectorInner>);
32
33#[uniffi::export]
34impl SearchEngineSelector {
35    #[uniffi::constructor]
36    pub fn new() -> Self {
37        Self(Mutex::default())
38    }
39
40    /// Sets the RemoteSettingsService to use. The selector will create the
41    /// relevant remote settings client(s) from the service.
42    ///
43    /// # Params:
44    ///   - `service`: The remote settings service instance for the application.
45    ///   - `options`: The remote settings options to be passed to the client(s).
46    ///   - `apply_engine_overrides`: Whether or not to apply overrides from
47    ///     `search-config-v2-overrides` to the selected engines. Should be false unless the
48    ///     application supports the click URL feature.
49    pub fn use_remote_settings_server(
50        self: Arc<Self>,
51        service: &Arc<RemoteSettingsService>,
52        apply_engine_overrides: bool,
53    ) {
54        let mut inner = self.0.lock();
55        inner.search_config_client = Some(service.make_client("search-config-v2".to_string()));
56
57        if apply_engine_overrides {
58            inner.search_config_overrides_client =
59                Some(service.make_client("search-config-overrides-v2".to_string()));
60        }
61    }
62
63    /// Sets the search configuration from the given string. If the configuration
64    /// string is unchanged since the last update, the cached configuration is
65    /// reused to avoid unnecessary reprocessing. This helps optimize performance,
66    /// particularly during test runs where the same configuration may be used
67    /// repeatedly.
68    #[handle_error(Error)]
69    pub fn set_search_config(self: Arc<Self>, configuration: String) -> SearchApiResult<()> {
70        if configuration.is_empty() {
71            return Err(Error::SearchConfigNotSpecified);
72        }
73        self.0.lock().configuration = serde_json::from_str(&configuration)?;
74        Ok(())
75    }
76
77    #[handle_error(Error)]
78    pub fn set_config_overrides(self: Arc<Self>, overrides: String) -> SearchApiResult<()> {
79        if overrides.is_empty() {
80            return Err(Error::SearchConfigOverridesNotSpecified);
81        }
82        self.0.lock().configuration_overrides = serde_json::from_str(&overrides)?;
83        Ok(())
84    }
85
86    /// Clears the search configuration from memory if it is known that it is
87    /// not required for a time, e.g. if the configuration will only be re-filtered
88    /// after an app/environment update.
89    pub fn clear_search_config(self: Arc<Self>) {}
90
91    /// Filters the search configuration with the user's given environment,
92    /// and returns the set of engines and parameters that should be presented
93    /// to the user.
94    #[handle_error(Error)]
95    pub fn filter_engine_configuration(
96        self: Arc<Self>,
97        user_environment: SearchUserEnvironment,
98    ) -> SearchApiResult<RefinedSearchConfig> {
99        let inner = self.0.lock();
100        if let Some(client) = &inner.search_config_client {
101            // Remote settings ships dumps of the collections, so it is highly
102            // unlikely that we'll ever hit the case where we have no records.
103            // However, just in case of an issue that does causes us to receive
104            // no records, we will raise an error so that the application can
105            // handle or record it appropriately.
106            let records = client.get_records(false);
107
108            if let Some(records) = records {
109                if records.is_empty() {
110                    return Err(Error::SearchConfigNoRecords);
111                }
112
113                if let Some(overrides_client) = &inner.search_config_overrides_client {
114                    let overrides_records = overrides_client.get_records(false);
115
116                    if let Some(overrides_records) = overrides_records {
117                        if overrides_records.is_empty() {
118                            return filter_engine_configuration_impl(
119                                user_environment,
120                                &records,
121                                None,
122                            );
123                        }
124                        // TODO: Bug 1947241 - Find a way to avoid having to serialise the records
125                        // back to strings and then deserialise them into the records that we want.
126                        let stringified = serde_json::to_string(&overrides_records)?;
127                        let json_overrides: Vec<JSONOverridesRecord> =
128                            serde_json::from_str(&stringified)?;
129
130                        return filter_engine_configuration_impl(
131                            user_environment,
132                            &records,
133                            Some(json_overrides),
134                        );
135                    } else {
136                        return Err(Error::SearchConfigOverridesNoRecords);
137                    }
138                }
139
140                return filter_engine_configuration_impl(user_environment, &records, None);
141            } else {
142                return Err(Error::SearchConfigNoRecords);
143            }
144        }
145        let config = match &inner.configuration {
146            None => return Err(Error::SearchConfigNotSpecified),
147            Some(configuration) => configuration.data.clone(),
148        };
149
150        let config_overrides = match &inner.configuration_overrides {
151            None => return Err(Error::SearchConfigOverridesNotSpecified),
152            Some(overrides) => overrides.data.clone(),
153        };
154        return filter_engine_configuration_impl(user_environment, &config, Some(config_overrides));
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::test_helpers::{EngineRecord, ExpectedEngine, SubVariant, Variant};
162    use crate::{test_helpers, types::*, SearchApiError};
163    use mockito::mock;
164    use remote_settings::{RemoteSettingsConfig, RemoteSettingsContext, RemoteSettingsServer};
165    use serde_json::json;
166
167    #[test]
168    fn test_set_config_should_allow_basic_config() {
169        let selector = Arc::new(SearchEngineSelector::new());
170
171        let config = json!({
172            "data": [
173                EngineRecord::full("test1", "Test 1").build(),
174                {
175                    "recordType": "defaultEngines",
176                    "globalDefault": "test"
177                }
178            ]
179        });
180
181        let config_result = Arc::clone(&selector).set_search_config(config.to_string());
182        config_result.expect("Should have set the configuration successfully");
183    }
184
185    #[test]
186    fn test_set_config_should_allow_extra_fields() {
187        let selector = Arc::new(SearchEngineSelector::new());
188
189        let mut engine = EngineRecord::minimal("test", "Test").build();
190        engine["base"]["urls"]["search"]["extraField1"] = json!(true);
191        engine["base"]["extraField2"] = json!("123");
192        engine["extraField3"] = json!(["foo"]);
193
194        let config_result = Arc::clone(&selector).set_search_config(
195            json!({
196              "data": [
197                engine,
198                {
199                  "recordType": "defaultEngines",
200                  "globalDefault": "test",
201                  "extraField4": {
202                    "subField1": true
203                  }
204                }
205              ]
206            })
207            .to_string(),
208        );
209        config_result.expect("Should have set the configuration successfully with extra fields");
210    }
211
212    #[test]
213    fn test_set_config_should_ignore_unknown_record_types() {
214        let selector = Arc::new(SearchEngineSelector::new());
215        let config = json!({
216            "data": [
217                EngineRecord::full("test1", "Test 1").build(),
218                {
219                    "recordType": "defaultEngines",
220                    "globalDefault": "test"
221                },
222                {
223                  "recordType": "unknown"
224                }
225            ]
226        });
227        let config_result = Arc::clone(&selector).set_search_config(config.to_string());
228
229        config_result
230            .expect("Should have set the configuration successfully with unknown record types.");
231    }
232
233    #[test]
234    fn test_filter_engine_configuration_throws_without_config() {
235        let selector = Arc::new(SearchEngineSelector::new());
236
237        let result = selector.filter_engine_configuration(SearchUserEnvironment {
238            ..Default::default()
239        });
240
241        assert!(
242            result.is_err(),
243            "Should throw an error when a configuration has not been specified before filtering"
244        );
245        assert!(result
246            .unwrap_err()
247            .to_string()
248            .contains("Search configuration not specified"))
249    }
250
251    #[test]
252    fn test_filter_engine_configuration_throws_without_config_overrides() {
253        let selector = Arc::new(SearchEngineSelector::new());
254        let _ = Arc::clone(&selector).set_search_config(
255            json!({
256              "data": [
257                EngineRecord::full("test1", "Test 1").build(),
258            ]
259            })
260            .to_string(),
261        );
262
263        let result = selector.filter_engine_configuration(SearchUserEnvironment {
264            ..Default::default()
265        });
266
267        assert!(
268            result.is_err(),
269            "Should throw an error when a configuration overrides has not been specified before filtering"
270        );
271
272        assert!(result
273            .unwrap_err()
274            .to_string()
275            .contains("Search configuration overrides not specified"))
276    }
277
278    #[test]
279    fn test_filter_engine_configuration_returns_basic_engines() {
280        let selector = Arc::new(SearchEngineSelector::new());
281        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
282            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
283        );
284
285        let config_result = Arc::clone(&selector).set_search_config(
286            json!({
287              "data": [
288                EngineRecord::full("test1", "Test 1").build(),
289                EngineRecord::minimal("test2", "Test 2").build(),
290                {
291                  "recordType": "defaultEngines",
292                  "globalDefault": "test1",
293                  "globalDefaultPrivate": "test2"
294                }
295              ]
296            })
297            .to_string(),
298        );
299        config_result.expect("Should have set the configuration successfully");
300        config_overrides_result.expect("Should have set the configuration overrides successfully");
301
302        let result = selector.filter_engine_configuration(SearchUserEnvironment {
303            ..Default::default()
304        });
305
306        assert!(
307            result.is_ok(),
308            "Should have filtered the configuration without error. {:?}",
309            result
310        );
311        assert_eq!(
312            result.unwrap(),
313            RefinedSearchConfig {
314                engines: vec!(
315                    ExpectedEngine::full("test1", "Test 1").build(),
316                    ExpectedEngine::minimal("test2", "Test 2").build(),
317                ),
318                app_default_engine_id: Some("test1".to_string()),
319                app_private_default_engine_id: Some("test2".to_string())
320            }
321        )
322    }
323
324    #[test]
325    fn test_filter_engine_configuration_handles_basic_variants() {
326        let selector = Arc::new(SearchEngineSelector::new());
327        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
328            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
329        );
330
331        let config_result = Arc::clone(&selector).set_search_config(
332            json!({
333              "data": [
334                EngineRecord::full("test1", "Test 1")
335                .add_variant(
336                    Variant::new()
337                        .regions(&["FR"])
338                        .urls(json!({
339                            "search": {
340                                "method": "POST",
341                                "params": [{
342                                    "name": "mission",
343                                    "value": "ongoing"
344                                }]
345                            }
346                        }))
347                )
348                .build(),
349                EngineRecord::minimal("test2", "Test 2")
350                .add_variant(
351                    Variant::new()
352                        .optional(true)
353                        .partner_code("ship")
354                        .telemetry_suffix("E")
355                )
356                .build(),
357                {
358                  "recordType": "defaultEngines",
359                  "globalDefault": "test1",
360                  "globalDefaultPrivate": "test2"
361                }
362              ]
363            })
364            .to_string(),
365        );
366        config_result.expect("Should have set the configuration successfully");
367        config_overrides_result.expect("Should have set the configuration overrides successfully");
368
369        let result = selector.filter_engine_configuration(SearchUserEnvironment {
370            region: "FR".into(),
371            ..Default::default()
372        });
373
374        assert!(
375            result.is_ok(),
376            "Should have filtered the configuration without error. {:?}",
377            result
378        );
379
380        let expected_1 = ExpectedEngine::full("test1", "Test 1")
381            .search_method("POST")
382            .search_params(vec![SearchUrlParam {
383                name: "mission".to_string(),
384                value: Some("ongoing".to_string()),
385                enterprise_value: None,
386                experiment_config: None,
387            }])
388            .build();
389
390        let expected_2 = ExpectedEngine::minimal("test2", "Test 2")
391            .optional(true)
392            .partner_code("ship")
393            .telemetry_suffix("E")
394            .build();
395
396        assert_eq!(
397            result.unwrap(),
398            RefinedSearchConfig {
399                engines: vec!(expected_1, expected_2),
400                app_default_engine_id: Some("test1".to_string()),
401                app_private_default_engine_id: Some("test2".to_string())
402            }
403        );
404    }
405
406    #[test]
407    fn test_filter_engine_configuration_handles_basic_subvariants() {
408        let selector = Arc::new(SearchEngineSelector::new());
409        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
410            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
411        );
412
413        let config_result = Arc::clone(&selector).set_search_config(
414            json!({
415              "data": [
416                EngineRecord::full("test1", "Test 1")
417                  .add_variant(
418                    Variant::new()
419                      .regions(&["FR"])
420                      .add_subvariant(
421                        SubVariant::new()
422                          .locales(&["fr"])
423                          .partner_code("fr-partner-code")
424                          .telemetry_suffix("fr-telemetry-suffix"),
425                      )
426                      .add_subvariant(
427                        SubVariant::new()
428                          .locales(&["en-CA"])
429                          .urls(json!({
430                            "search": {
431                              "method": "GET",
432                              "params": [{
433                                "name": "en-ca-param-name",
434                                "enterpriseValue": "en-ca-param-value"
435                              }]
436                            }
437                          })),
438                      )
439                  )
440                  .build(),
441                {
442                  "recordType": "defaultEngines",
443                  "globalDefault": "test1"
444                },
445                {
446                  "recordType": "availableLocales",
447                  "locales": ["en-CA", "fr"]
448                }
449              ]
450            })
451            .to_string(),
452        );
453        config_result.expect("Should have set the configuration successfully");
454        config_overrides_result.expect("Should have set the configuration overrides successfully");
455
456        let mut result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
457            region: "FR".into(),
458            locale: "fr".into(),
459            ..Default::default()
460        });
461
462        assert!(
463            result.is_ok(),
464            "Should have filtered the configuration without error. {:?}",
465            result
466        );
467
468        let expected_1 = ExpectedEngine::full("test1", "Test 1")
469            .partner_code("fr-partner-code")
470            .telemetry_suffix("fr-telemetry-suffix")
471            .build();
472
473        assert_eq!(
474            result.unwrap(),
475            RefinedSearchConfig {
476                engines: vec!(expected_1),
477                app_default_engine_id: Some("test1".to_string()),
478                app_private_default_engine_id: None
479            },
480            "Should have correctly matched and merged the fr locale sub-variant."
481        );
482
483        result = selector.filter_engine_configuration(SearchUserEnvironment {
484            region: "FR".into(),
485            locale: "en-CA".into(),
486            ..Default::default()
487        });
488
489        assert!(
490            result.is_ok(),
491            "Should have filtered the configuration without error. {:?}",
492            result
493        );
494
495        let expected_2 = ExpectedEngine::full("test1", "Test 1")
496            .search_params(vec![SearchUrlParam {
497                name: "en-ca-param-name".to_string(),
498                value: None,
499                enterprise_value: Some("en-ca-param-value".to_string()),
500                experiment_config: None,
501            }])
502            .build();
503
504        assert_eq!(
505            result.unwrap(),
506            RefinedSearchConfig {
507                engines: vec!(expected_2),
508                app_default_engine_id: Some("test1".to_string()),
509                app_private_default_engine_id: None
510            },
511            "Should have correctly matched and merged the en-CA locale sub-variant."
512        );
513    }
514
515    #[test]
516    fn test_filter_engine_configuration_handles_environments() {
517        let selector = Arc::new(SearchEngineSelector::new());
518        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
519            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
520        );
521
522        let config_result = Arc::clone(&selector).set_search_config(
523            json!({
524              "data": [
525                EngineRecord::full("test1", "Test 1").build(),
526                EngineRecord::full("test2", "Test 2")
527                .override_variants(
528                    Variant::new()
529                      .applications(&["firefox-android", "focus-ios"])
530                )
531                .build(),
532                EngineRecord::full("test3", "Test 3")
533                .override_variants(
534                    Variant::new()
535                      .distributions(&["starship"])
536                )
537                .build(),
538                {
539                  "recordType": "defaultEngines",
540                  "globalDefault": "test1",
541                }
542              ]
543            })
544            .to_string(),
545        );
546        config_result.expect("Should have set the configuration successfully");
547        config_overrides_result.expect("Should have set the configuration overrides successfully");
548
549        let mut result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
550            distribution_id: String::new(),
551            app_name: SearchApplicationName::Firefox,
552            ..Default::default()
553        });
554
555        assert!(
556            result.is_ok(),
557            "Should have filtered the configuration without error. {:?}",
558            result
559        );
560
561        assert_eq!(
562            result.unwrap(),
563            RefinedSearchConfig {
564                engines: vec!(ExpectedEngine::full("test1", "Test 1").build()),
565                app_default_engine_id: Some("test1".to_string()),
566                app_private_default_engine_id: None
567            }, "Should have selected test1 for all matching locales, as the environments do not match for the other two"
568        );
569
570        result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
571            distribution_id: String::new(),
572            app_name: SearchApplicationName::FocusIos,
573            ..Default::default()
574        });
575
576        assert!(
577            result.is_ok(),
578            "Should have filtered the configuration without error. {:?}",
579            result
580        );
581
582        let expected_1 = ExpectedEngine::full("test1", "Test 1").build();
583        let expected_2 = ExpectedEngine::full("test2", "Test 2").build();
584        assert_eq!(
585            result.unwrap(),
586            RefinedSearchConfig {
587                engines: vec!(expected_1, expected_2),
588                app_default_engine_id: Some("test1".to_string()),
589                app_private_default_engine_id: None
590            },
591            "Should have selected test1 for all matching locales and test2 for matching Focus IOS"
592        );
593
594        result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
595            distribution_id: "starship".to_string(),
596            app_name: SearchApplicationName::Firefox,
597            ..Default::default()
598        });
599
600        assert!(
601            result.is_ok(),
602            "Should have filtered the configuration without error. {:?}",
603            result
604        );
605
606        let expected_1 = ExpectedEngine::full("test1", "Test 1").build();
607        let expected_3 = ExpectedEngine::full("test3", "Test 3").build();
608        assert_eq!(
609            result.unwrap(),
610            RefinedSearchConfig {
611                engines: vec!(expected_1, expected_3),
612                app_default_engine_id: Some("test1".to_string()),
613                app_private_default_engine_id: None
614            }, "Should have selected test1 for all matching locales and test3 for matching the distribution id"
615        );
616    }
617
618    #[test]
619    fn test_set_config_should_handle_default_engines() {
620        let selector = Arc::new(SearchEngineSelector::new());
621        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
622            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
623        );
624
625        let config_result = Arc::clone(&selector).set_search_config(
626            json!({
627              "data": [
628                EngineRecord::minimal("test", "Test").build(),
629                EngineRecord::minimal("distro-default", "Distribution Default").build(),
630                EngineRecord::minimal("private-default-FR", "Private default FR").build(),
631                {
632                  "recordType": "defaultEngines",
633                  "globalDefault": "test",
634                  "specificDefaults": [{
635                    "environment": {
636                      "distributions": ["test-distro"],
637                    },
638                    "default": "distro-default"
639                  }, {
640                    "environment": {
641                      "regions": ["fr"]
642                    },
643                    "defaultPrivate": "private-default-FR"
644                  }]
645                }
646              ]
647            })
648            .to_string(),
649        );
650        config_result.expect("Should have set the configuration successfully");
651        config_overrides_result.expect("Should have set the configuration overrides successfully");
652
653        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
654            distribution_id: "test-distro".to_string(),
655            ..Default::default()
656        });
657        assert!(
658            result.is_ok(),
659            "Should have filtered the configuration without error. {:?}",
660            result
661        );
662
663        assert_eq!(
664            result.unwrap(),
665            RefinedSearchConfig {
666                engines: vec![
667                    ExpectedEngine::minimal("distro-default", "Distribution Default").build(),
668                    ExpectedEngine::minimal("private-default-FR", "Private default FR").build(),
669                    ExpectedEngine::minimal("test", "Test").build(),
670                ],
671                app_default_engine_id: Some("distro-default".to_string()),
672                app_private_default_engine_id: None
673            },
674            "Should have selected the distro-default engine for the matching specific default"
675        );
676
677        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
678            region: "fr".into(),
679            distribution_id: String::new(),
680            ..Default::default()
681        });
682        assert!(
683            result.is_ok(),
684            "Should have filtered the configuration without error. {:?}",
685            result
686        );
687
688        assert_eq!(
689            result.unwrap(),
690            RefinedSearchConfig {
691                engines: vec![
692                    ExpectedEngine::minimal("test", "Test").build(),
693                    ExpectedEngine::minimal("private-default-FR", "Private default FR").build(),
694                    ExpectedEngine::minimal("distro-default", "Distribution Default").build(),
695                ],
696                app_default_engine_id: Some("test".to_string()),
697                app_private_default_engine_id: Some("private-default-FR".to_string())
698            },
699            "Should have selected the private default engine for the matching specific default"
700        );
701    }
702
703    #[test]
704    fn test_filter_engine_orders() {
705        let selector = Arc::new(SearchEngineSelector::new());
706        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
707            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
708        );
709
710        let engine_order_config = Arc::clone(&selector).set_search_config(
711            json!({
712              "data": [
713                EngineRecord::minimal("after-defaults", "after-defaults").build(),
714                EngineRecord::minimal("b-engine", "first alphabetical").build(),
715                EngineRecord::minimal("a-engine", "last alphabetical").build(),
716                EngineRecord::minimal("default-engine", "default-engine").build(),
717                EngineRecord::minimal("default-private-engine", "default-privite-engine").build(),
718                {
719                  "recordType": "defaultEngines",
720                  "globalDefault": "default-engine",
721                  "globalDefaultPrivate": "default-private-engine",
722                },
723                {
724                  "recordType": "engineOrders",
725                  "orders": [
726                    {
727                      "environment": {
728                        "locales": ["en-CA"],
729                        "regions": ["CA"],
730                      },
731                      "order": ["after-defaults"],
732                    },
733                  ],
734                },
735                {
736                  "recordType": "availableLocales",
737                  "locales": ["en-CA", "fr"]
738                }
739              ]
740            })
741            .to_string(),
742        );
743        engine_order_config.expect("Should have set the configuration successfully");
744        config_overrides_result.expect("Should have set the configuration overrides successfully");
745
746        fn assert_actual_engines_equals_expected(
747            result: Result<RefinedSearchConfig, SearchApiError>,
748            expected_engine_orders: Vec<String>,
749            message: &str,
750        ) {
751            assert!(
752                result.is_ok(),
753                "Should have filtered the configuration without error. {:?}",
754                result
755            );
756
757            let refined_config = result.unwrap();
758            let actual_engine_orders: Vec<String> = refined_config
759                .engines
760                .into_iter()
761                .map(|e| e.identifier)
762                .collect();
763
764            assert_eq!(actual_engine_orders, expected_engine_orders, "{}", message);
765        }
766
767        assert_actual_engines_equals_expected(
768            Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
769                locale: "en-CA".into(),
770                region: "CA".into(),
771                ..Default::default()
772            }),
773            vec![
774                "default-engine".to_string(),
775                "default-private-engine".to_string(),
776                "after-defaults".to_string(),
777                "b-engine".to_string(),
778                "a-engine".to_string(),
779            ],
780            "Should order the default engine first, default private engine second, and the rest of the engines based on order hint then alphabetically by name."
781        );
782
783        let starts_with_wiki_config = Arc::clone(&selector).set_search_config(
784            json!({
785              "data": [
786                EngineRecord::minimal("wiki-ca", "wiki-ca")
787                .override_variants(
788                    Variant::new()
789                      .locales(&["en-CA"])
790                      .regions(&["CA"])
791                )
792                .build(),
793                EngineRecord::minimal("wiki-uk", "wiki-uk")
794                .override_variants(
795                    Variant::new()
796                      .locales(&["en-GB"])
797                      .regions(&["GB"])
798                )
799                .build(),
800                EngineRecord::minimal("engine-1", "engine-1").build(),
801                EngineRecord::minimal("engine-2", "engine-2").build(),
802                {
803                  "recordType": "engineOrders",
804                  "orders": [
805                    {
806                      "environment": {
807                        "locales": ["en-CA"],
808                        "regions": ["CA"],
809                      },
810                      "order": ["wiki*", "engine-1", "engine-2"],
811                    },
812                    {
813                      "environment": {
814                        "locales": ["en-GB"],
815                        "regions": ["GB"],
816                      },
817                      "order": ["wiki*", "engine-1", "engine-2"],
818                    },
819                  ],
820                },
821                {
822                  "recordType": "availableLocales",
823                  "locales": ["en-CA", "en-GB", "fr"]
824                }
825
826              ]
827            })
828            .to_string(),
829        );
830        starts_with_wiki_config.expect("Should have set the configuration successfully");
831
832        assert_actual_engines_equals_expected(
833            Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
834                locale: "en-CA".into(),
835                region: "CA".into(),
836                ..Default::default()
837            }),
838            vec![
839                "wiki-ca".to_string(),
840                "engine-1".to_string(),
841                "engine-2".to_string(),
842            ],
843            "Should list the wiki-ca engine and other engines in correct orders with the en-CA and CA locale region environment."
844        );
845
846        assert_actual_engines_equals_expected(
847            Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
848                locale: "en-GB".into(),
849                region: "GB".into(),
850                ..Default::default()
851            }),
852            vec![
853                "wiki-uk".to_string(),
854                "engine-1".to_string(),
855                "engine-2".to_string(),
856            ],
857            "Should list the wiki-uk engine and other engines in correct orders with the en-GB and GB locale region environment."
858        );
859    }
860
861    const APPLY_OVERRIDES: bool = true;
862    const DO_NOT_APPLY_OVERRIDES: bool = false;
863    const RECORDS_MISSING: bool = false;
864    const RECORDS_PRESENT: bool = true;
865
866    fn setup_remote_settings_test(
867        should_apply_overrides: bool,
868        expect_sync_successful: bool,
869    ) -> Arc<SearchEngineSelector> {
870        error_support::init_for_tests();
871        viaduct_dev::init_backend_dev();
872
873        let config = RemoteSettingsConfig {
874            server: Some(RemoteSettingsServer::Custom {
875                url: mockito::server_url(),
876            }),
877            bucket_name: Some(String::from("main")),
878            app_context: Some(RemoteSettingsContext::default()),
879        };
880        let service = Arc::new(RemoteSettingsService::new(String::from(":memory:"), config));
881
882        let selector = Arc::new(SearchEngineSelector::new());
883
884        Arc::clone(&selector).use_remote_settings_server(&service, should_apply_overrides);
885        let sync_result = Arc::clone(&service).sync();
886        assert!(
887            if expect_sync_successful {
888                sync_result.is_ok()
889            } else {
890                sync_result.is_err()
891            },
892            "Should have completed the sync successfully. {:?}",
893            sync_result
894        );
895
896        selector
897    }
898
899    fn mock_changes_endpoint() -> mockito::Mock {
900        mock(
901            "GET",
902            "/v2/buckets/monitor/collections/changes/changeset?_expected=0",
903        )
904        .with_body(response_body_changes())
905        .with_status(200)
906        .with_header("content-type", "application/json")
907        .with_header("etag", "\"1000\"")
908        .create()
909    }
910
911    fn response_body() -> String {
912        json!({
913          "metadata": {
914            "id": "search-config-v2",
915            "last_modified": 1000,
916            "bucket": "main",
917            "signatures": [{
918              "x5u": "fake",
919              "signature": "fake",
920              "mode": "fake",
921            }],
922          },
923          "timestamp": 1000,
924          "changes": [
925            EngineRecord::minimal("test", "Test")
926              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7")
927              .schema(1001)
928              .last_modified(1000)
929              .build(),
930            EngineRecord::minimal("distro-default", "Distribution Default")
931              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d8")
932              .schema(1002)
933              .last_modified(1000)
934              .build(),
935            EngineRecord::minimal("private-default-FR", "Private default FR")
936              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d9")
937              .schema(1003)
938              .last_modified(1000)
939              .build(),
940            {
941              "recordType": "defaultEngines",
942              "globalDefault": "test",
943              "specificDefaults": [{
944                "environment": {
945                  "distributions": ["test-distro"],
946                },
947                "default": "distro-default"
948              }, {
949                "environment": {
950                  "regions": ["fr"]
951                },
952                "defaultPrivate": "private-default-FR"
953              }],
954              "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0e0",
955              "schema": 1004,
956              "last_modified": 1000,
957            }
958          ]
959        })
960        .to_string()
961    }
962
963    fn response_body_changes() -> String {
964        json!({
965          "timestamp": 1000,
966          "changes": [
967            {
968              "collection": "search-config-v2",
969              "bucket": "main",
970              "last_modified": 1000,
971            }
972        ],
973        })
974        .to_string()
975    }
976
977    fn response_body_locales() -> String {
978        json!({
979          "metadata": {
980            "id": "search-config-v2",
981            "last_modified": 1000,
982            "bucket": "main",
983            "signatures": [{
984              "x5u": "fake",
985              "signature": "fake",
986              "mode": "fake",
987            }],
988          },
989          "timestamp": 1000,
990          "changes": [
991            EngineRecord::minimal("engine-de", "German Engine")
992              .override_variants(
993                Variant::new()
994                  .locales(&["de"])
995              )
996              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7")
997              .schema(1001)
998              .last_modified(1000)
999              .build(),
1000            EngineRecord::minimal("engine-en-us", "English US Engine")
1001              .override_variants(
1002                Variant::new()
1003                  .locales(&["en-US"])
1004              )
1005              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d8")
1006              .schema(1002)
1007              .last_modified(1000)
1008              .build(),
1009            {
1010              "recordType": "availableLocales",
1011              "locales": ["de", "en-US"],
1012              "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0e0",
1013              "schema": 1004,
1014              "last_modified": 1000,
1015            }
1016          ]
1017        })
1018        .to_string()
1019    }
1020
1021    fn response_body_overrides() -> String {
1022        let mut engine = test_helpers::overrides_engine();
1023        engine["identifier"] = json!("test");
1024        engine["id"] = json!("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7");
1025        engine["schema"] = json!(1001);
1026        engine["last_modified"] = json!(1000);
1027
1028        json!({
1029          "metadata": {
1030            "id": "search-config-overrides-v2",
1031            "last_modified": 1000,
1032            "bucket": "main",
1033            "signatures": [{
1034              "x5u": "fake",
1035              "signature": "fake",
1036              "mode": "fake",
1037            }],
1038          },
1039          "timestamp": 1000,
1040          "changes": [ engine ]
1041        })
1042        .to_string()
1043    }
1044
1045    #[test]
1046    fn test_remote_settings_empty_search_config_records_throws_error() {
1047        let changes_mock = mock_changes_endpoint();
1048        let m = mock(
1049            "GET",
1050            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1051        )
1052        .with_body(
1053            json!({
1054              "metadata": {
1055                "id": "search-config-v2",
1056                "last_modified": 1000,
1057                "bucket": "main",
1058                "signatures": [{
1059                  "x5u": "fake",
1060                  "signature": "fake",
1061                  "mode": "fake",
1062                }],
1063              },
1064              "timestamp": 1000,
1065              "changes": [
1066            ]})
1067            .to_string(),
1068        )
1069        .with_status(200)
1070        .with_header("content-type", "application/json")
1071        .with_header("etag", "\"1000\"")
1072        .create();
1073
1074        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1075
1076        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1077            distribution_id: "test-distro".to_string(),
1078            ..Default::default()
1079        });
1080        assert!(
1081            result.is_err(),
1082            "Should throw an error when a configuration has not been specified before filtering"
1083        );
1084        assert!(result
1085            .unwrap_err()
1086            .to_string()
1087            .contains("No search config v2 records received from remote settings"));
1088        changes_mock.expect(1).assert();
1089        m.expect(1).assert();
1090    }
1091
1092    #[test]
1093    fn test_remote_settings_search_config_records_is_none_throws_error() {
1094        let changes_mock = mock_changes_endpoint();
1095        let m1 = mock(
1096            "GET",
1097            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1098        )
1099        .with_body(response_body())
1100        .with_status(501)
1101        .with_header("content-type", "application/json")
1102        .with_header("etag", "\"1000\"")
1103        .create();
1104
1105        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_MISSING);
1106
1107        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1108            distribution_id: "test-distro".to_string(),
1109            ..Default::default()
1110        });
1111        assert!(
1112            result.is_err(),
1113            "Should throw an error when a configuration has not been specified before filtering"
1114        );
1115        assert!(result
1116            .unwrap_err()
1117            .to_string()
1118            .contains("No search config v2 records received from remote settings"));
1119        changes_mock.expect(1).assert();
1120        m1.expect(1).assert();
1121    }
1122
1123    #[test]
1124    fn test_remote_settings_empty_search_config_overrides_filtered_without_error() {
1125        let changes_mock = mock_changes_endpoint();
1126        let m1 = mock(
1127            "GET",
1128            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1129        )
1130        .with_body(response_body())
1131        .with_status(200)
1132        .with_header("content-type", "application/json")
1133        .with_header("etag", "\"1000\"")
1134        .create();
1135
1136        let m2 = mock(
1137            "GET",
1138            "/v2/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1139        )
1140        .with_body(
1141            json!({
1142               "metadata": {
1143                 "id": "search-config-overrides-v2",
1144                 "last_modified": 1000,
1145                 "bucket": "main",
1146                 "signatures": [{
1147                   "x5u": "fake",
1148                   "signature": "fake",
1149                   "mode": "fake",
1150                 }],
1151               },
1152               "timestamp": 1000,
1153               "changes": [
1154            ]})
1155            .to_string(),
1156        )
1157        .with_status(200)
1158        .with_header("content-type", "application/json")
1159        .with_header("etag", "\"1000\"")
1160        .create();
1161
1162        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_PRESENT);
1163
1164        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1165            distribution_id: "test-distro".to_string(),
1166            ..Default::default()
1167        });
1168        assert!(
1169            result.is_ok(),
1170            "Should have filtered the configuration using an empty search config overrides without causing an error. {:?}",
1171            result
1172        );
1173        changes_mock.expect(1).assert();
1174        m1.expect(1).assert();
1175        m2.expect(1).assert();
1176    }
1177
1178    #[test]
1179    fn test_remote_settings_search_config_overrides_records_is_none_throws_error() {
1180        let changes_mock = mock_changes_endpoint();
1181        let m1 = mock(
1182            "GET",
1183            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1184        )
1185        .with_body(response_body())
1186        .with_status(200)
1187        .with_header("content-type", "application/json")
1188        .with_header("etag", "\"1000\"")
1189        .create();
1190
1191        let m2 = mock(
1192            "GET",
1193            "/v2/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1194        )
1195        .with_body(response_body_overrides())
1196        .with_status(501)
1197        .with_header("content-type", "application/json")
1198        .with_header("etag", "\"1000\"")
1199        .create();
1200
1201        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_MISSING);
1202
1203        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1204            distribution_id: "test-distro".to_string(),
1205            ..Default::default()
1206        });
1207        assert!(
1208            result.is_err(),
1209            "Should throw an error when a configuration overrides has not been specified before filtering"
1210        );
1211        assert!(result
1212            .unwrap_err()
1213            .to_string()
1214            .contains("No search config overrides v2 records received from remote settings"));
1215        changes_mock.expect(1).assert();
1216        m1.expect(1).assert();
1217        m2.expect(1).assert();
1218    }
1219
1220    #[test]
1221    fn test_filter_with_remote_settings_overrides() {
1222        let changes_mock = mock_changes_endpoint();
1223        let m1 = mock(
1224            "GET",
1225            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1226        )
1227        .with_body(response_body())
1228        .with_status(200)
1229        .with_header("content-type", "application/json")
1230        .with_header("etag", "\"1000\"")
1231        .create();
1232
1233        let m2 = mock(
1234            "GET",
1235            "/v2/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1236        )
1237        .with_body(response_body_overrides())
1238        .with_status(200)
1239        .with_header("content-type", "application/json")
1240        .with_header("etag", "\"1000\"")
1241        .create();
1242
1243        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_PRESENT);
1244
1245        let override_test_engine = ExpectedEngine::minimal("test", "Test")
1246            .partner_code("overrides-partner-code")
1247            .click_url("https://example.com/click-url")
1248            .telemetry_suffix("overrides-telemetry-suffix")
1249            .search_base("https://example.com/search-overrides")
1250            .search_term_param_name("search")
1251            .search_params(vec![SearchUrlParam {
1252                name: "overrides-name".to_string(),
1253                value: Some("overrides-value".to_string()),
1254                enterprise_value: None,
1255                experiment_config: None,
1256            }])
1257            .build();
1258
1259        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1260            ..Default::default()
1261        });
1262
1263        assert!(
1264            result.is_ok(),
1265            "Should have filtered the configuration without error. {:?}",
1266            result
1267        );
1268        assert_eq!(
1269            result.unwrap().engines[0],
1270            override_test_engine.clone(),
1271            "Should have applied the overrides to the matching engine"
1272        );
1273        changes_mock.expect(1).assert();
1274        m1.expect(1).assert();
1275        m2.expect(1).assert();
1276    }
1277
1278    #[test]
1279    fn test_filter_with_remote_settings() {
1280        let changes_mock = mock_changes_endpoint();
1281
1282        let m = mock(
1283            "GET",
1284            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1285        )
1286        .with_body(response_body())
1287        .with_status(200)
1288        .with_header("content-type", "application/json")
1289        .with_header("etag", "\"1000\"")
1290        .create();
1291
1292        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1293
1294        let test_engine = ExpectedEngine::minimal("test", "Test").build();
1295        let private_default_fr_engine =
1296            ExpectedEngine::minimal("private-default-FR", "Private default FR").build();
1297        let distro_default_engine =
1298            ExpectedEngine::minimal("distro-default", "Distribution Default").build();
1299
1300        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1301            distribution_id: "test-distro".to_string(),
1302            ..Default::default()
1303        });
1304        assert!(
1305            result.is_ok(),
1306            "Should have filtered the configuration without error. {:?}",
1307            result
1308        );
1309        assert_eq!(
1310            result.unwrap(),
1311            RefinedSearchConfig {
1312                engines: vec![
1313                    distro_default_engine.clone(),
1314                    private_default_fr_engine.clone(),
1315                    test_engine.clone(),
1316                ],
1317                app_default_engine_id: Some("distro-default".to_string()),
1318                app_private_default_engine_id: None
1319            },
1320            "Should have selected the default engine for the matching specific default"
1321        );
1322
1323        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1324            region: "fr".into(),
1325            distribution_id: String::new(),
1326            ..Default::default()
1327        });
1328        assert!(
1329            result.is_ok(),
1330            "Should have filtered the configuration without error. {:?}",
1331            result
1332        );
1333        assert_eq!(
1334            result.unwrap(),
1335            RefinedSearchConfig {
1336                engines: vec![
1337                    test_engine.clone(),
1338                    private_default_fr_engine.clone(),
1339                    distro_default_engine.clone(),
1340                ],
1341                app_default_engine_id: Some("test".to_string()),
1342                app_private_default_engine_id: Some("private-default-FR".to_string())
1343            },
1344            "Should have selected the private default engine for the matching specific default"
1345        );
1346        changes_mock.expect(1).assert();
1347        m.expect(1).assert();
1348    }
1349
1350    #[test]
1351    fn test_filter_with_remote_settings_negotiate_locales() {
1352        let changes_mock = mock_changes_endpoint();
1353        let m = mock(
1354            "GET",
1355            "/v2/buckets/main/collections/search-config-v2/changeset?_expected=0",
1356        )
1357        .with_body(response_body_locales())
1358        .with_status(200)
1359        .with_header("content-type", "application/json")
1360        .with_header("etag", "\"1000\"")
1361        .create();
1362
1363        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1364
1365        let result_de = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1366            locale: "de-AT".into(),
1367            ..Default::default()
1368        });
1369        assert!(
1370            result_de.is_ok(),
1371            "Should have filtered the configuration without error. {:?}",
1372            result_de
1373        );
1374
1375        assert_eq!(
1376            result_de.unwrap(),
1377            RefinedSearchConfig {
1378                engines: vec![ExpectedEngine::minimal("engine-de", "German Engine").build()],
1379                app_default_engine_id: None,
1380                app_private_default_engine_id: None,
1381            },
1382            "Should have selected the de engine when given de-AT which is not an available locale"
1383        );
1384
1385        let result_en = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1386            locale: "en-AU".to_string(),
1387            ..Default::default()
1388        });
1389        assert_eq!(
1390            result_en.unwrap(),
1391            RefinedSearchConfig {
1392                engines: vec![ExpectedEngine::minimal("engine-en-us", "English US Engine").build(),],
1393                app_default_engine_id: None,
1394                app_private_default_engine_id: None,
1395            },
1396            "Should have selected the en-us engine when given another english locale we don't support"
1397        );
1398        changes_mock.expect(1).assert();
1399        m.expect(1).assert();
1400    }
1401
1402    #[test]
1403    fn test_configuration_overrides_applied() {
1404        let selector = Arc::new(SearchEngineSelector::new());
1405
1406        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
1407            json!({
1408              "data": [
1409                test_helpers::overrides_engine(),
1410                { // Test partial override with some missing fields
1411                  "identifier": "distro-default",
1412                  "partnerCode": "distro-overrides-partner-code",
1413                  "clickUrl": "https://example.com/click-url-distro",
1414                  "urls": {
1415                    "search": {
1416                      "base": "https://example.com/search-distro",
1417                    },
1418                  },
1419                }
1420              ]
1421            })
1422            .to_string(),
1423        );
1424        let config_result = Arc::clone(&selector).set_search_config(
1425            json!({
1426              "data": [
1427                EngineRecord::minimal("overrides-engine", "Overrides Engine")
1428                    .build(),
1429                EngineRecord::minimal("distro-default", "Distribution Default")
1430                    .override_variants(Variant::new()
1431                    .all_regions_and_locales()
1432                    .telemetry_suffix("distro-telemetry-suffix"))
1433                    .build(),
1434              ]
1435            })
1436            .to_string(),
1437        );
1438        config_result.expect("Should have set the configuration successfully");
1439        config_overrides_result.expect("Should have set the configuration overrides successfully");
1440
1441        let override_test_engine = ExpectedEngine::minimal("overrides-engine", "Overrides Engine")
1442            .partner_code("overrides-partner-code")
1443            .click_url("https://example.com/click-url")
1444            .telemetry_suffix("overrides-telemetry-suffix")
1445            .search_base("https://example.com/search-overrides")
1446            .search_params(vec![SearchUrlParam {
1447                name: "overrides-name".to_string(),
1448                value: Some("overrides-value".to_string()),
1449                enterprise_value: None,
1450                experiment_config: None,
1451            }])
1452            .build();
1453
1454        let override_distro_default_engine =
1455            ExpectedEngine::minimal("distro-default", "Distribution Default")
1456                .partner_code("distro-overrides-partner-code")
1457                .click_url("https://example.com/click-url-distro")
1458                .search_base("https://example.com/search-distro")
1459                .telemetry_suffix("distro-telemetry-suffix")
1460                .build();
1461
1462        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1463            ..Default::default()
1464        });
1465        assert!(
1466            result.is_ok(),
1467            "Should have filtered the configuration without error. {:?}",
1468            result
1469        );
1470
1471        assert_eq!(
1472            result.unwrap(),
1473            RefinedSearchConfig {
1474                engines: vec![
1475                    override_distro_default_engine.clone(),
1476                    override_test_engine.clone(),
1477                ],
1478                app_default_engine_id: None,
1479                app_private_default_engine_id: None
1480            },
1481            "Should have applied the overrides to the matching engine."
1482        );
1483    }
1484
1485    #[test]
1486    fn test_filter_engine_configuration_negotiate_locales() {
1487        let selector = Arc::new(SearchEngineSelector::new());
1488        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
1489            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
1490        );
1491
1492        let config_result = Arc::clone(&selector).set_search_config(
1493            json!({
1494              "data": [
1495                {
1496                    "recordType": "availableLocales",
1497                    "locales": ["de", "en-US"]
1498                },
1499                EngineRecord::minimal("engine-de", "German Engine")
1500                    .override_variants(Variant::new()
1501                    .locales(&["de"]))
1502                    .build(),
1503                EngineRecord::minimal("engine-en-us", "English US Engine")
1504                .override_variants(Variant::new()
1505                    .locales(&["en-US"]))
1506                    .build(),
1507              ]
1508            })
1509            .to_string(),
1510        );
1511        config_result.expect("Should have set the configuration successfully");
1512        config_overrides_result.expect("Should have set the configuration overrides successfully");
1513
1514        let result_de = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1515            locale: "de-AT".into(),
1516            ..Default::default()
1517        });
1518        assert!(
1519            result_de.is_ok(),
1520            "Should have filtered the configuration without error. {:?}",
1521            result_de
1522        );
1523
1524        assert_eq!(
1525            result_de.unwrap(),
1526            RefinedSearchConfig {
1527                engines: vec![ExpectedEngine::minimal("engine-de", "German Engine").build(),],
1528                app_default_engine_id: None,
1529                app_private_default_engine_id: None,
1530            },
1531            "Should have selected the de engine when given de-AT which is not an available locale"
1532        );
1533
1534        let result_en = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1535            locale: "en-AU".to_string(),
1536            ..Default::default()
1537        });
1538        assert_eq!(
1539            result_en.unwrap(),
1540            RefinedSearchConfig {
1541                engines: vec![ExpectedEngine::minimal("engine-en-us", "English US Engine").build(),],
1542                app_default_engine_id: None,
1543                app_private_default_engine_id: None,
1544            },
1545            "Should have selected the en-us engine when given another english locale we don't support"
1546        );
1547    }
1548}