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::{RemoteSettingsConfig2, 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 = RemoteSettingsConfig2 {
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            "/v1/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            "signature": {
918              "x5u": "fake",
919              "signature": "fake",
920            },
921          },
922          "timestamp": 1000,
923          "changes": [
924            EngineRecord::minimal("test", "Test")
925              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7")
926              .schema(1001)
927              .last_modified(1000)
928              .build(),
929            EngineRecord::minimal("distro-default", "Distribution Default")
930              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d8")
931              .schema(1002)
932              .last_modified(1000)
933              .build(),
934            EngineRecord::minimal("private-default-FR", "Private default FR")
935              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d9")
936              .schema(1003)
937              .last_modified(1000)
938              .build(),
939            {
940              "recordType": "defaultEngines",
941              "globalDefault": "test",
942              "specificDefaults": [{
943                "environment": {
944                  "distributions": ["test-distro"],
945                },
946                "default": "distro-default"
947              }, {
948                "environment": {
949                  "regions": ["fr"]
950                },
951                "defaultPrivate": "private-default-FR"
952              }],
953              "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0e0",
954              "schema": 1004,
955              "last_modified": 1000,
956            }
957          ]
958        })
959        .to_string()
960    }
961
962    fn response_body_changes() -> String {
963        json!({
964          "timestamp": 1000,
965          "changes": [
966            {
967              "collection": "search-config-v2",
968              "bucket": "main",
969              "last_modified": 1000,
970            }
971        ],
972        })
973        .to_string()
974    }
975
976    fn response_body_locales() -> String {
977        json!({
978          "metadata": {
979            "id": "search-config-v2",
980            "last_modified": 1000,
981            "bucket": "main",
982            "signature": {
983              "x5u": "fake",
984              "signature": "fake",
985            },
986          },
987          "timestamp": 1000,
988          "changes": [
989            EngineRecord::minimal("engine-de", "German Engine")
990              .override_variants(
991                Variant::new()
992                  .locales(&["de"])
993              )
994              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7")
995              .schema(1001)
996              .last_modified(1000)
997              .build(),
998            EngineRecord::minimal("engine-en-us", "English US Engine")
999              .override_variants(
1000                Variant::new()
1001                  .locales(&["en-US"])
1002              )
1003              .id("c5dcd1da-7126-4abb-846b-ec85b0d4d0d8")
1004              .schema(1002)
1005              .last_modified(1000)
1006              .build(),
1007            {
1008              "recordType": "availableLocales",
1009              "locales": ["de", "en-US"],
1010              "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0e0",
1011              "schema": 1004,
1012              "last_modified": 1000,
1013            }
1014          ]
1015        })
1016        .to_string()
1017    }
1018
1019    fn response_body_overrides() -> String {
1020        let mut engine = test_helpers::overrides_engine();
1021        engine["identifier"] = json!("test");
1022        engine["id"] = json!("c5dcd1da-7126-4abb-846b-ec85b0d4d0d7");
1023        engine["schema"] = json!(1001);
1024        engine["last_modified"] = json!(1000);
1025
1026        json!({
1027          "metadata": {
1028            "id": "search-config-overrides-v2",
1029            "last_modified": 1000,
1030            "bucket": "main",
1031            "signature": {
1032              "x5u": "fake",
1033              "signature": "fake",
1034            },
1035          },
1036          "timestamp": 1000,
1037          "changes": [ engine ]
1038        })
1039        .to_string()
1040    }
1041
1042    #[test]
1043    fn test_remote_settings_empty_search_config_records_throws_error() {
1044        let changes_mock = mock_changes_endpoint();
1045        let m = mock(
1046            "GET",
1047            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1048        )
1049        .with_body(
1050            json!({
1051              "metadata": {
1052                "id": "search-config-v2",
1053                "last_modified": 1000,
1054                "bucket": "main",
1055                "signature": {
1056                  "x5u": "fake",
1057                  "signature": "fake",
1058                },
1059              },
1060              "timestamp": 1000,
1061              "changes": [
1062            ]})
1063            .to_string(),
1064        )
1065        .with_status(200)
1066        .with_header("content-type", "application/json")
1067        .with_header("etag", "\"1000\"")
1068        .create();
1069
1070        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1071
1072        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1073            distribution_id: "test-distro".to_string(),
1074            ..Default::default()
1075        });
1076        assert!(
1077            result.is_err(),
1078            "Should throw an error when a configuration has not been specified before filtering"
1079        );
1080        assert!(result
1081            .unwrap_err()
1082            .to_string()
1083            .contains("No search config v2 records received from remote settings"));
1084        changes_mock.expect(1).assert();
1085        m.expect(1).assert();
1086    }
1087
1088    #[test]
1089    fn test_remote_settings_search_config_records_is_none_throws_error() {
1090        let changes_mock = mock_changes_endpoint();
1091        let m1 = mock(
1092            "GET",
1093            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1094        )
1095        .with_body(response_body())
1096        .with_status(501)
1097        .with_header("content-type", "application/json")
1098        .with_header("etag", "\"1000\"")
1099        .create();
1100
1101        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_MISSING);
1102
1103        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1104            distribution_id: "test-distro".to_string(),
1105            ..Default::default()
1106        });
1107        assert!(
1108            result.is_err(),
1109            "Should throw an error when a configuration has not been specified before filtering"
1110        );
1111        assert!(result
1112            .unwrap_err()
1113            .to_string()
1114            .contains("No search config v2 records received from remote settings"));
1115        changes_mock.expect(1).assert();
1116        m1.expect(1).assert();
1117    }
1118
1119    #[test]
1120    fn test_remote_settings_empty_search_config_overrides_filtered_without_error() {
1121        let changes_mock = mock_changes_endpoint();
1122        let m1 = mock(
1123            "GET",
1124            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1125        )
1126        .with_body(response_body())
1127        .with_status(200)
1128        .with_header("content-type", "application/json")
1129        .with_header("etag", "\"1000\"")
1130        .create();
1131
1132        let m2 = mock(
1133            "GET",
1134            "/v1/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1135        )
1136        .with_body(
1137            json!({
1138               "metadata": {
1139                 "id": "search-config-overrides-v2",
1140                 "last_modified": 1000,
1141                 "bucket": "main",
1142                 "signature": {
1143                   "x5u": "fake",
1144                   "signature": "fake",
1145                 },
1146               },
1147               "timestamp": 1000,
1148               "changes": [
1149            ]})
1150            .to_string(),
1151        )
1152        .with_status(200)
1153        .with_header("content-type", "application/json")
1154        .with_header("etag", "\"1000\"")
1155        .create();
1156
1157        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_PRESENT);
1158
1159        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1160            distribution_id: "test-distro".to_string(),
1161            ..Default::default()
1162        });
1163        assert!(
1164            result.is_ok(),
1165            "Should have filtered the configuration using an empty search config overrides without causing an error. {:?}",
1166            result
1167        );
1168        changes_mock.expect(1).assert();
1169        m1.expect(1).assert();
1170        m2.expect(1).assert();
1171    }
1172
1173    #[test]
1174    fn test_remote_settings_search_config_overrides_records_is_none_throws_error() {
1175        let changes_mock = mock_changes_endpoint();
1176        let m1 = mock(
1177            "GET",
1178            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1179        )
1180        .with_body(response_body())
1181        .with_status(200)
1182        .with_header("content-type", "application/json")
1183        .with_header("etag", "\"1000\"")
1184        .create();
1185
1186        let m2 = mock(
1187            "GET",
1188            "/v1/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1189        )
1190        .with_body(response_body_overrides())
1191        .with_status(501)
1192        .with_header("content-type", "application/json")
1193        .with_header("etag", "\"1000\"")
1194        .create();
1195
1196        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_MISSING);
1197
1198        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1199            distribution_id: "test-distro".to_string(),
1200            ..Default::default()
1201        });
1202        assert!(
1203            result.is_err(),
1204            "Should throw an error when a configuration overrides has not been specified before filtering"
1205        );
1206        assert!(result
1207            .unwrap_err()
1208            .to_string()
1209            .contains("No search config overrides v2 records received from remote settings"));
1210        changes_mock.expect(1).assert();
1211        m1.expect(1).assert();
1212        m2.expect(1).assert();
1213    }
1214
1215    #[test]
1216    fn test_filter_with_remote_settings_overrides() {
1217        let changes_mock = mock_changes_endpoint();
1218        let m1 = mock(
1219            "GET",
1220            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1221        )
1222        .with_body(response_body())
1223        .with_status(200)
1224        .with_header("content-type", "application/json")
1225        .with_header("etag", "\"1000\"")
1226        .create();
1227
1228        let m2 = mock(
1229            "GET",
1230            "/v1/buckets/main/collections/search-config-overrides-v2/changeset?_expected=0",
1231        )
1232        .with_body(response_body_overrides())
1233        .with_status(200)
1234        .with_header("content-type", "application/json")
1235        .with_header("etag", "\"1000\"")
1236        .create();
1237
1238        let selector = setup_remote_settings_test(APPLY_OVERRIDES, RECORDS_PRESENT);
1239
1240        let override_test_engine = ExpectedEngine::minimal("test", "Test")
1241            .partner_code("overrides-partner-code")
1242            .click_url("https://example.com/click-url")
1243            .telemetry_suffix("overrides-telemetry-suffix")
1244            .search_base("https://example.com/search-overrides")
1245            .search_term_param_name("search")
1246            .search_params(vec![SearchUrlParam {
1247                name: "overrides-name".to_string(),
1248                value: Some("overrides-value".to_string()),
1249                enterprise_value: None,
1250                experiment_config: None,
1251            }])
1252            .build();
1253
1254        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1255            ..Default::default()
1256        });
1257
1258        assert!(
1259            result.is_ok(),
1260            "Should have filtered the configuration without error. {:?}",
1261            result
1262        );
1263        assert_eq!(
1264            result.unwrap().engines[0],
1265            override_test_engine.clone(),
1266            "Should have applied the overrides to the matching engine"
1267        );
1268        changes_mock.expect(1).assert();
1269        m1.expect(1).assert();
1270        m2.expect(1).assert();
1271    }
1272
1273    #[test]
1274    fn test_filter_with_remote_settings() {
1275        let changes_mock = mock_changes_endpoint();
1276
1277        let m = mock(
1278            "GET",
1279            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1280        )
1281        .with_body(response_body())
1282        .with_status(200)
1283        .with_header("content-type", "application/json")
1284        .with_header("etag", "\"1000\"")
1285        .create();
1286
1287        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1288
1289        let test_engine = ExpectedEngine::minimal("test", "Test").build();
1290        let private_default_fr_engine =
1291            ExpectedEngine::minimal("private-default-FR", "Private default FR").build();
1292        let distro_default_engine =
1293            ExpectedEngine::minimal("distro-default", "Distribution Default").build();
1294
1295        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1296            distribution_id: "test-distro".to_string(),
1297            ..Default::default()
1298        });
1299        assert!(
1300            result.is_ok(),
1301            "Should have filtered the configuration without error. {:?}",
1302            result
1303        );
1304        assert_eq!(
1305            result.unwrap(),
1306            RefinedSearchConfig {
1307                engines: vec![
1308                    distro_default_engine.clone(),
1309                    private_default_fr_engine.clone(),
1310                    test_engine.clone(),
1311                ],
1312                app_default_engine_id: Some("distro-default".to_string()),
1313                app_private_default_engine_id: None
1314            },
1315            "Should have selected the default engine for the matching specific default"
1316        );
1317
1318        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1319            region: "fr".into(),
1320            distribution_id: String::new(),
1321            ..Default::default()
1322        });
1323        assert!(
1324            result.is_ok(),
1325            "Should have filtered the configuration without error. {:?}",
1326            result
1327        );
1328        assert_eq!(
1329            result.unwrap(),
1330            RefinedSearchConfig {
1331                engines: vec![
1332                    test_engine.clone(),
1333                    private_default_fr_engine.clone(),
1334                    distro_default_engine.clone(),
1335                ],
1336                app_default_engine_id: Some("test".to_string()),
1337                app_private_default_engine_id: Some("private-default-FR".to_string())
1338            },
1339            "Should have selected the private default engine for the matching specific default"
1340        );
1341        changes_mock.expect(1).assert();
1342        m.expect(1).assert();
1343    }
1344
1345    #[test]
1346    fn test_filter_with_remote_settings_negotiate_locales() {
1347        let changes_mock = mock_changes_endpoint();
1348        let m = mock(
1349            "GET",
1350            "/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
1351        )
1352        .with_body(response_body_locales())
1353        .with_status(200)
1354        .with_header("content-type", "application/json")
1355        .with_header("etag", "\"1000\"")
1356        .create();
1357
1358        let selector = setup_remote_settings_test(DO_NOT_APPLY_OVERRIDES, RECORDS_PRESENT);
1359
1360        let result_de = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1361            locale: "de-AT".into(),
1362            ..Default::default()
1363        });
1364        assert!(
1365            result_de.is_ok(),
1366            "Should have filtered the configuration without error. {:?}",
1367            result_de
1368        );
1369
1370        assert_eq!(
1371            result_de.unwrap(),
1372            RefinedSearchConfig {
1373                engines: vec![ExpectedEngine::minimal("engine-de", "German Engine").build()],
1374                app_default_engine_id: None,
1375                app_private_default_engine_id: None,
1376            },
1377            "Should have selected the de engine when given de-AT which is not an available locale"
1378        );
1379
1380        let result_en = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1381            locale: "en-AU".to_string(),
1382            ..Default::default()
1383        });
1384        assert_eq!(
1385            result_en.unwrap(),
1386            RefinedSearchConfig {
1387                engines: vec![ExpectedEngine::minimal("engine-en-us", "English US Engine").build(),],
1388                app_default_engine_id: None,
1389                app_private_default_engine_id: None,
1390            },
1391            "Should have selected the en-us engine when given another english locale we don't support"
1392        );
1393        changes_mock.expect(1).assert();
1394        m.expect(1).assert();
1395    }
1396
1397    #[test]
1398    fn test_configuration_overrides_applied() {
1399        let selector = Arc::new(SearchEngineSelector::new());
1400
1401        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
1402            json!({
1403              "data": [
1404                test_helpers::overrides_engine(),
1405                { // Test partial override with some missing fields
1406                  "identifier": "distro-default",
1407                  "partnerCode": "distro-overrides-partner-code",
1408                  "clickUrl": "https://example.com/click-url-distro",
1409                  "urls": {
1410                    "search": {
1411                      "base": "https://example.com/search-distro",
1412                    },
1413                  },
1414                }
1415              ]
1416            })
1417            .to_string(),
1418        );
1419        let config_result = Arc::clone(&selector).set_search_config(
1420            json!({
1421              "data": [
1422                EngineRecord::minimal("overrides-engine", "Overrides Engine")
1423                    .build(),
1424                EngineRecord::minimal("distro-default", "Distribution Default")
1425                    .override_variants(Variant::new()
1426                    .all_regions_and_locales()
1427                    .telemetry_suffix("distro-telemetry-suffix"))
1428                    .build(),
1429              ]
1430            })
1431            .to_string(),
1432        );
1433        config_result.expect("Should have set the configuration successfully");
1434        config_overrides_result.expect("Should have set the configuration overrides successfully");
1435
1436        let override_test_engine = ExpectedEngine::minimal("overrides-engine", "Overrides Engine")
1437            .partner_code("overrides-partner-code")
1438            .click_url("https://example.com/click-url")
1439            .telemetry_suffix("overrides-telemetry-suffix")
1440            .search_base("https://example.com/search-overrides")
1441            .search_params(vec![SearchUrlParam {
1442                name: "overrides-name".to_string(),
1443                value: Some("overrides-value".to_string()),
1444                enterprise_value: None,
1445                experiment_config: None,
1446            }])
1447            .build();
1448
1449        let override_distro_default_engine =
1450            ExpectedEngine::minimal("distro-default", "Distribution Default")
1451                .partner_code("distro-overrides-partner-code")
1452                .click_url("https://example.com/click-url-distro")
1453                .search_base("https://example.com/search-distro")
1454                .telemetry_suffix("distro-telemetry-suffix")
1455                .build();
1456
1457        let result = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1458            ..Default::default()
1459        });
1460        assert!(
1461            result.is_ok(),
1462            "Should have filtered the configuration without error. {:?}",
1463            result
1464        );
1465
1466        assert_eq!(
1467            result.unwrap(),
1468            RefinedSearchConfig {
1469                engines: vec![
1470                    override_distro_default_engine.clone(),
1471                    override_test_engine.clone(),
1472                ],
1473                app_default_engine_id: None,
1474                app_private_default_engine_id: None
1475            },
1476            "Should have applied the overrides to the matching engine."
1477        );
1478    }
1479
1480    #[test]
1481    fn test_filter_engine_configuration_negotiate_locales() {
1482        let selector = Arc::new(SearchEngineSelector::new());
1483        let config_overrides_result = Arc::clone(&selector).set_config_overrides(
1484            json!({ "data": [test_helpers::overrides_engine()] }).to_string(),
1485        );
1486
1487        let config_result = Arc::clone(&selector).set_search_config(
1488            json!({
1489              "data": [
1490                {
1491                    "recordType": "availableLocales",
1492                    "locales": ["de", "en-US"]
1493                },
1494                EngineRecord::minimal("engine-de", "German Engine")
1495                    .override_variants(Variant::new()
1496                    .locales(&["de"]))
1497                    .build(),
1498                EngineRecord::minimal("engine-en-us", "English US Engine")
1499                .override_variants(Variant::new()
1500                    .locales(&["en-US"]))
1501                    .build(),
1502              ]
1503            })
1504            .to_string(),
1505        );
1506        config_result.expect("Should have set the configuration successfully");
1507        config_overrides_result.expect("Should have set the configuration overrides successfully");
1508
1509        let result_de = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1510            locale: "de-AT".into(),
1511            ..Default::default()
1512        });
1513        assert!(
1514            result_de.is_ok(),
1515            "Should have filtered the configuration without error. {:?}",
1516            result_de
1517        );
1518
1519        assert_eq!(
1520            result_de.unwrap(),
1521            RefinedSearchConfig {
1522                engines: vec![ExpectedEngine::minimal("engine-de", "German Engine").build(),],
1523                app_default_engine_id: None,
1524                app_private_default_engine_id: None,
1525            },
1526            "Should have selected the de engine when given de-AT which is not an available locale"
1527        );
1528
1529        let result_en = Arc::clone(&selector).filter_engine_configuration(SearchUserEnvironment {
1530            locale: "en-AU".to_string(),
1531            ..Default::default()
1532        });
1533        assert_eq!(
1534            result_en.unwrap(),
1535            RefinedSearchConfig {
1536                engines: vec![ExpectedEngine::minimal("engine-en-us", "English US Engine").build(),],
1537                app_default_engine_id: None,
1538                app_private_default_engine_id: None,
1539            },
1540            "Should have selected the en-us engine when given another english locale we don't support"
1541        );
1542    }
1543}