search/
environment_matching.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 functions for testing if an environment from the
6//! configuration matches the user environment.
7
8use crate::{JSONVariantEnvironment, SearchUserEnvironment};
9use firefox_versioning::version::Version;
10
11/// Matches the user's environment against the given environment from the
12/// configuration.
13///
14/// This function expects the locale, region and app version in the environment
15/// to be lower case.
16pub(crate) fn matches_user_environment(
17    environment: &JSONVariantEnvironment,
18    user_environment: &SearchUserEnvironment,
19) -> bool {
20    if !environment.experiment.is_empty() && user_environment.experiment != environment.experiment {
21        return false;
22    }
23
24    if !environment.excluded_distributions.is_empty()
25        && environment
26            .excluded_distributions
27            .contains(&user_environment.distribution_id)
28    {
29        return false;
30    }
31
32    matches_region_and_locale(
33        &user_environment.region,
34        &user_environment.locale,
35        environment,
36    ) && is_empty_or_contains(
37        &environment.distributions,
38        &user_environment.distribution_id,
39    ) && matches_version(
40        &user_environment.version,
41        &environment.min_version,
42        &environment.max_version,
43    ) && is_empty_or_contains(&environment.channels, &user_environment.update_channel)
44        && is_empty_or_contains(&environment.applications, &user_environment.app_name)
45        && is_empty_or_contains(&environment.device_type, &user_environment.device_type)
46}
47
48/// Determines whether the region and locale constraints in the supplied
49/// environment apply to a user given the region and locale they are using.
50fn matches_region_and_locale(
51    user_region: &str,
52    user_locale: &str,
53    environment: &JSONVariantEnvironment,
54) -> bool {
55    if does_array_include(&environment.excluded_regions, user_region)
56        || does_array_include(&environment.excluded_locales, user_locale)
57    {
58        return false;
59    }
60
61    // This is a special case, if all_regions_and_locales is false (default value)
62    // and region and locales are not set, then we assume that for the purposes of
63    // matching region & locale, we do match everywhere. This allows us to specify
64    // none of these options but match against other items such as distribution or
65    // application name.
66    if !environment.all_regions_and_locales
67        && environment.regions.is_empty()
68        && environment.locales.is_empty()
69    {
70        return true;
71    }
72
73    if does_array_include(&environment.regions, user_region)
74        && does_array_include(&environment.locales, user_locale)
75    {
76        return true;
77    }
78
79    if environment.regions.is_empty() && does_array_include(&environment.locales, user_locale) {
80        return true;
81    }
82
83    if environment.locales.is_empty() && does_array_include(&environment.regions, user_region) {
84        return true;
85    }
86
87    if environment.all_regions_and_locales {
88        return true;
89    }
90
91    false
92}
93
94fn matches_version(
95    user_version: &str,
96    environment_min_version: &str,
97    environment_max_version: &str,
98) -> bool {
99    use std::ops::{Bound, RangeBounds};
100
101    let (min_version, max_version) = match (
102        environment_min_version.is_empty(),
103        environment_max_version.is_empty(),
104    ) {
105        (true, true) => return true,
106        (true, false) => (
107            Bound::Unbounded,
108            Version::try_from(environment_max_version).map_or(Bound::Unbounded, Bound::Included),
109        ),
110        (false, true) => (
111            Version::try_from(environment_min_version).map_or(Bound::Unbounded, Bound::Included),
112            Bound::Unbounded,
113        ),
114        (false, false) => (
115            Version::try_from(environment_min_version).map_or(Bound::Unbounded, Bound::Included),
116            Version::try_from(environment_max_version).map_or(Bound::Unbounded, Bound::Included),
117        ),
118    };
119
120    !user_version.is_empty()
121        && Version::try_from(user_version).map_or(true, |user_version| {
122            (min_version, max_version).contains(&user_version)
123        })
124}
125
126fn is_empty_or_contains<T: std::cmp::PartialEq>(env_value: &[T], user_value: &T) -> bool {
127    env_value.is_empty() || env_value.contains(user_value)
128}
129
130fn does_array_include(config_array: &[String], compare_item: &str) -> bool {
131    !config_array.is_empty()
132        && config_array
133            .iter()
134            .any(|x| x.to_lowercase() == compare_item)
135}
136
137#[cfg(test)]
138mod tests {
139    use std::vec;
140
141    use super::*;
142    use crate::*;
143
144    #[test]
145    fn test_matches_user_environment_all_locales() {
146        assert!(
147            matches_user_environment(
148                &crate::JSONVariantEnvironment {
149                    all_regions_and_locales: true,
150                    excluded_locales: vec![],
151                    excluded_regions: vec![],
152                    locales: vec![],
153                    regions: vec![],
154                    ..Default::default()
155                },
156                &SearchUserEnvironment {
157                    locale: "fi".into(),
158                    region: "FR".into(),
159                    ..Default::default()
160                }
161            ),
162            "Should return true when all_regions_and_locales is true"
163        );
164
165        assert!(
166            matches_user_environment(
167                &crate::JSONVariantEnvironment {
168                    all_regions_and_locales: false,
169                    excluded_locales: vec![],
170                    excluded_regions: vec![],
171                    locales: vec![],
172                    regions: vec![],
173                    ..Default::default()
174                },
175                &SearchUserEnvironment {
176                    locale: "fi".into(),
177                    region: "fr".into(),
178                    ..Default::default()
179                }
180            ),
181            "Should return true when all_regions_and_locales is false (default) and no regions/locales are specified"
182        );
183
184        assert!(
185            !matches_user_environment(
186                &crate::JSONVariantEnvironment {
187                    all_regions_and_locales: true,
188                    excluded_locales: vec!["fi".to_string()],
189                    excluded_regions: vec![],
190                    locales: vec![],
191                    regions: vec![],
192                    ..Default::default()
193                },
194                &SearchUserEnvironment {
195                    locale: "fi".into(),
196                    region: "fr".into(),
197                    ..Default::default()
198                }
199            ),
200            "Should return false when all_regions_and_locales is true and the locale is excluded"
201        );
202
203        assert!(
204            !matches_user_environment(
205                &crate::JSONVariantEnvironment {
206                    all_regions_and_locales: true,
207                    excluded_locales: vec!["FI".to_string()],
208                    excluded_regions: vec![],
209                    locales: vec![],
210                    regions: vec![],
211                    ..Default::default()
212                },
213                &SearchUserEnvironment {
214                    locale: "fi".into(),
215                    region: "fr".into(),
216                    ..Default::default()
217                }
218            ),
219            "Should return false when all_regions_and_locales is true and the excluded locale is a different case"
220        );
221
222        assert!(
223            matches_user_environment(
224                &crate::JSONVariantEnvironment {
225                    all_regions_and_locales: true,
226                    excluded_locales: vec!["en-US".to_string()],
227                    excluded_regions: vec![],
228                    locales: vec![],
229                    regions: vec![],
230                    ..Default::default()
231                },
232                &SearchUserEnvironment {
233                    locale: "fi".into(),
234                    region: "fr".into(),
235                    ..Default::default()
236                }
237            ),
238            "Should return true when all_regions_and_locales is true and the locale is not excluded"
239        );
240
241        assert!(
242            !matches_user_environment(
243                &crate::JSONVariantEnvironment {
244                    all_regions_and_locales: true,
245                    excluded_regions: vec!["us".to_string(), "fr".to_string()],
246                    locales: vec![],
247                    regions: vec![],
248                    ..Default::default()
249                },
250                &SearchUserEnvironment {
251                    locale: "fi".into(),
252                    region: "fr".into(),
253                    ..Default::default()
254                }
255            ),
256            "Should return false when all_regions_and_locales is true and the region is excluded"
257        );
258
259        assert!(
260            !matches_user_environment(
261                &crate::JSONVariantEnvironment {
262                    all_regions_and_locales: true,
263                    excluded_locales: vec![],
264                    excluded_regions: vec!["US".to_string(), "FR".to_string()],
265                    locales: vec![],
266                    regions: vec![],
267                    ..Default::default()
268                },
269                &SearchUserEnvironment {
270                    locale: "fi".into(),
271                    region: "fr".into(),
272                    ..Default::default()
273                }
274            ),
275            "Should return false when all_regions_and_locales is true and the excluded region is a different case"
276        );
277
278        assert!(
279            matches_user_environment(
280                &crate::JSONVariantEnvironment {
281                    all_regions_and_locales: true,
282                    excluded_locales: vec![],
283                    excluded_regions: vec!["us".to_string()],
284                    locales: vec![],
285                    regions: vec![],
286                    ..Default::default()
287                },
288                &SearchUserEnvironment {
289                    locale: "fi".into(),
290                    region: "fr".into(),
291                    ..Default::default()
292                }
293            ),
294            "Should return true when all_regions_and_locales is true and the region is not excluded"
295        );
296    }
297
298    #[test]
299    fn test_matches_user_environment_locales() {
300        assert!(
301            matches_user_environment(
302                &crate::JSONVariantEnvironment {
303                    all_regions_and_locales: false,
304                    excluded_locales: vec![],
305                    excluded_regions: vec![],
306                    locales: vec!["en-gb".to_string(), "fi".to_string()],
307                    regions: vec![],
308                    ..Default::default()
309                },
310                &SearchUserEnvironment {
311                    locale: "fi".into(),
312                    region: "fr".into(),
313                    ..Default::default()
314                }
315            ),
316            "Should return true when the user locale matches one from the config"
317        );
318
319        assert!(
320            matches_user_environment(
321                &crate::JSONVariantEnvironment {
322                    all_regions_and_locales: false,
323                    excluded_locales: vec![],
324                    excluded_regions: vec![],
325                    locales: vec!["en-GB".to_string(), "FI".to_string()],
326                    regions: vec![],
327                    ..Default::default()
328                },
329                &SearchUserEnvironment {
330                    locale: "fi".into(),
331                    region: "fr".into(),
332                    ..Default::default()
333                }
334            ),
335            "Should return true when the user locale matches one from the config and is a different case"
336        );
337
338        assert!(
339            !matches_user_environment(
340                &crate::JSONVariantEnvironment {
341                    all_regions_and_locales: false,
342                    excluded_locales: vec![],
343                    excluded_regions: vec![],
344                    locales: vec!["en-gb".to_string(), "en-ca".to_string()],
345                    regions: vec![],
346                    ..Default::default()
347                },
348                &SearchUserEnvironment {
349                    locale: "fi".into(),
350                    region: "fr".into(),
351                    ..Default::default()
352                }
353            ),
354            "Should return false when the user locale does not match one from the config"
355        );
356    }
357
358    #[test]
359    fn test_matches_user_environment_regions() {
360        assert!(
361            matches_user_environment(
362                &crate::JSONVariantEnvironment {
363                    all_regions_and_locales: false,
364                    excluded_locales: vec![],
365                    excluded_regions: vec![],
366                    locales: vec![],
367                    regions: vec!["gb".to_string(), "fr".to_string()],
368                    ..Default::default()
369                },
370                &SearchUserEnvironment {
371                    locale: "fi".into(),
372                    region: "fr".into(),
373                    ..Default::default()
374                }
375            ),
376            "Should return true when the user region matches one from the config"
377        );
378
379        assert!(
380            matches_user_environment(
381                &crate::JSONVariantEnvironment {
382                    all_regions_and_locales: false,
383                    excluded_locales: vec![],
384                    excluded_regions: vec![],
385                    locales: vec![],
386                    regions: vec!["GB".to_string(), "FR".to_string()],
387                    ..Default::default()
388                },
389                &SearchUserEnvironment {
390                    locale: "fi".into(),
391                    region: "fr".into(),
392                    ..Default::default()
393                }
394            ),
395            "Should return true when the user region matches one from the config and is a different case"
396        );
397
398        assert!(
399            !matches_user_environment(
400                &crate::JSONVariantEnvironment {
401                    all_regions_and_locales: false,
402                    excluded_locales: vec![],
403                    excluded_regions: vec![],
404                    locales: vec!["gb".to_string(), "ca".to_string()],
405                    regions: vec![],
406                    ..Default::default()
407                },
408                &SearchUserEnvironment {
409                    locale: "fi".into(),
410                    region: "fr".into(),
411                    ..Default::default()
412                }
413            ),
414            "Should return false when the user region does not match one from the config"
415        );
416    }
417
418    #[test]
419    fn test_matches_user_environment_locales_with_excluded_regions() {
420        assert!(
421            matches_user_environment(
422                &crate::JSONVariantEnvironment {
423                    all_regions_and_locales: false,
424                    excluded_locales: vec![],
425                    excluded_regions: vec!["gb".to_string(), "ca".to_string()],
426                    locales: vec!["en-gb".to_string(), "fi".to_string()],
427                    regions: vec![],
428                    ..Default::default()
429                },
430                &SearchUserEnvironment {
431                    locale: "fi".into(),
432                    region: "fr".into(),
433                    ..Default::default()
434                }
435            ),
436            "Should return true when the locale matches and the region is not excluded"
437        );
438
439        assert!(
440            !matches_user_environment(
441                &crate::JSONVariantEnvironment {
442                    all_regions_and_locales: false,
443                    excluded_locales: vec![],
444                    excluded_regions: vec!["gb".to_string(), "fr".to_string()],
445                    locales: vec!["en-gb".to_string(), "fi".to_string()],
446                    regions: vec![],
447                    ..Default::default()
448                },
449                &SearchUserEnvironment {
450                    locale: "fi".into(),
451                    region: "fr".into(),
452                    ..Default::default()
453                }
454            ),
455            "Should return false when the locale matches and the region is excluded"
456        );
457    }
458
459    #[test]
460    fn test_matches_user_environment_regions_with_excluded_locales() {
461        assert!(
462            matches_user_environment(
463                &crate::JSONVariantEnvironment {
464                    all_regions_and_locales: false,
465                    excluded_locales: vec!["en-gb".to_string(), "de".to_string()],
466                    excluded_regions: vec![],
467                    locales: vec![],
468                    regions: vec!["gb".to_string(), "fr".to_string()],
469                    ..Default::default()
470                },
471                &SearchUserEnvironment {
472                    locale: "fi".into(),
473                    region: "fr".into(),
474                    ..Default::default()
475                }
476            ),
477            "Should return true when the region matches and the locale is not excluded"
478        );
479
480        assert!(
481            !matches_user_environment(
482                &crate::JSONVariantEnvironment {
483                    all_regions_and_locales: false,
484                    excluded_locales: vec!["en-gb".to_string(), "fi".to_string()],
485                    excluded_regions: vec![],
486                    locales: vec![],
487                    regions: vec!["gb".to_string(), "fr".to_string()],
488                    ..Default::default()
489                },
490                &SearchUserEnvironment {
491                    locale: "fi".into(),
492                    region: "fr".into(),
493                    ..Default::default()
494                }
495            ),
496            "Should return false when the region matches and the locale is excluded"
497        );
498    }
499
500    #[test]
501    fn test_matches_user_environment_distributions() {
502        assert!(
503            matches_user_environment(
504                &crate::JSONVariantEnvironment {
505                    distributions: vec!["distro-1".to_string()],
506                    ..Default::default()
507                },
508                &SearchUserEnvironment {
509                    distribution_id: "distro-1".into(),
510                    ..Default::default()
511                }
512            ),
513            "Should return true when the distribution matches one in the environment"
514        );
515
516        assert!(
517            matches_user_environment(
518                &crate::JSONVariantEnvironment {
519                    distributions: vec!["distro-2".to_string(), "distro-3".to_string()],
520                    ..Default::default()
521                },
522                &SearchUserEnvironment {
523                    distribution_id: "distro-3".into(),
524                    ..Default::default()
525                }
526            ),
527            "Should return true when the distribution matches one in the environment when there are multiple"
528        );
529
530        assert!(
531            !matches_user_environment(
532                &crate::JSONVariantEnvironment {
533                    distributions: vec!["distro-2".to_string(), "distro-3".to_string()],
534                    ..Default::default()
535                },
536                &SearchUserEnvironment {
537                    distribution_id: "distro-4".into(),
538                    ..Default::default()
539                }
540            ),
541            "Should return false when the distribution does not match any in the environment"
542        );
543
544        assert!(
545            matches_user_environment(
546                &crate::JSONVariantEnvironment {
547                    regions: vec!["fr".to_string()],
548                    distributions: vec!["distro-1".to_string(), "distro-2".to_string()],
549                    ..Default::default()
550                },
551                &SearchUserEnvironment {
552                    locale: "fi".into(),
553                    region: "fr".into(),
554                    distribution_id: "distro-2".into(),
555                    ..Default::default()
556                }
557            ),
558            "Should return true when the distribution and region matches the environment"
559        );
560    }
561
562    #[test]
563    fn test_matches_user_environment_excluded_distributions() {
564        assert!(
565            matches_user_environment(
566                &crate::JSONVariantEnvironment {
567                    distributions: vec!["distro-1".to_string(), "distro-2".to_string()],
568                    excluded_distributions: vec!["
569                        distro-3".to_string(), "distro-4".to_string()
570                    ],
571                    ..Default::default()
572                },
573                &SearchUserEnvironment {
574                    distribution_id: "distro-2".into(),
575                    ..Default::default()
576                }
577            ),
578            "Should return true when the distribution matches the distribution list but not the excluded distributions"
579        );
580
581        assert!(
582            !matches_user_environment(
583                &crate::JSONVariantEnvironment {
584                    distributions: vec!["distro-1".to_string(), "distro-2".to_string()],
585                    excluded_distributions: vec!["distro-3".to_string(), "distro-4".to_string()],
586                    ..Default::default()
587                },
588                &SearchUserEnvironment {
589                    distribution_id: "distro-3".into(),
590                    ..Default::default()
591                }
592            ),
593            "Should return false when the distribution matches the the excluded distributions"
594        );
595    }
596
597    #[test]
598    fn test_matches_user_environment_application_name() {
599        assert!(
600            matches_user_environment(
601                &crate::JSONVariantEnvironment {
602                    applications: vec![SearchApplicationName::Firefox],
603                    ..Default::default()
604                },
605                &SearchUserEnvironment {
606                    app_name: SearchApplicationName::Firefox,
607                    ..Default::default()
608                }
609            ),
610            "Should return true when the application name matches the one in the environment"
611        );
612
613        assert!(
614            matches_user_environment(
615                &crate::JSONVariantEnvironment {
616                    applications: vec![
617                        SearchApplicationName::FirefoxAndroid,
618                        SearchApplicationName::Firefox
619                    ],
620                    ..Default::default()
621                },
622                &SearchUserEnvironment {
623                    app_name: SearchApplicationName::Firefox,
624                    ..Default::default()
625                }
626            ),
627            "Should return true when the application name matches one in the environment when there are multiple"
628        );
629
630        assert!(
631            !matches_user_environment(
632                &crate::JSONVariantEnvironment {
633                    applications: vec![
634                        SearchApplicationName::FirefoxAndroid,
635                        SearchApplicationName::Firefox
636                    ],
637                    ..Default::default()
638                },
639                &SearchUserEnvironment {
640                    app_name: SearchApplicationName::FirefoxIos,
641                    ..Default::default()
642                }
643            ),
644            "Should return false when the applications do not match the one in the environment"
645        );
646
647        assert!(
648            matches_user_environment(
649                &crate::JSONVariantEnvironment {
650                    regions: vec!["fr".to_string()],
651                    applications: vec![
652                        SearchApplicationName::FirefoxAndroid,
653                        SearchApplicationName::Firefox
654                    ],
655                    ..Default::default()
656                },
657                &SearchUserEnvironment {
658                    locale: "fi".into(),
659                    region: "fr".into(),
660                    app_name: SearchApplicationName::Firefox,
661                    ..Default::default()
662                }
663            ),
664            "Should return true when the application name matches the one in the environment"
665        );
666    }
667
668    #[test]
669    fn test_matches_user_environment_channel() {
670        assert!(
671            matches_user_environment(
672                &crate::JSONVariantEnvironment {
673                    channels: vec![SearchUpdateChannel::Nightly],
674                    ..Default::default()
675                },
676                &SearchUserEnvironment {
677                    update_channel: SearchUpdateChannel::Nightly,
678                    ..Default::default()
679                }
680            ),
681            "Should return true when the channel matches one in the environment"
682        );
683
684        assert!(
685            matches_user_environment(
686                &crate::JSONVariantEnvironment {
687                    channels: vec![SearchUpdateChannel::Nightly, SearchUpdateChannel::Release],
688                    ..Default::default()
689                },
690                &SearchUserEnvironment {
691                    update_channel: SearchUpdateChannel::Release,
692                    ..Default::default()
693                }
694            ),
695            "Should return true when the channel matches one in the environment when there are multiple"
696        );
697
698        assert!(
699            !matches_user_environment(
700                &crate::JSONVariantEnvironment {
701                    channels: vec![SearchUpdateChannel::Nightly],
702                    ..Default::default()
703                },
704                &SearchUserEnvironment {
705                    locale: "fi".into(),
706                    region: "fr".into(),
707                    update_channel: SearchUpdateChannel::Default,
708                    distribution_id: "distro-4".into(),
709                    experiment: String::new(),
710                    app_name: SearchApplicationName::Firefox,
711                    version: String::new(),
712                    device_type: SearchDeviceType::None,
713                }
714            ),
715            "Should return false when the channel does not match any in the environment"
716        );
717
718        assert!(
719            matches_user_environment(
720                &crate::JSONVariantEnvironment {
721                    regions: vec!["fr".to_string()],
722                    channels: vec![SearchUpdateChannel::Default],
723                    ..Default::default()
724                },
725                &SearchUserEnvironment {
726                    locale: "fi".into(),
727                    region: "fr".into(),
728                    update_channel: SearchUpdateChannel::Default,
729                    ..Default::default()
730                }
731            ),
732            "Should return true when the channel and region matches the environment"
733        );
734    }
735
736    #[test]
737    fn test_matches_user_environment_experiment() {
738        assert!(
739            matches_user_environment(
740                &crate::JSONVariantEnvironment {
741                    experiment: "warp-drive".to_string(),
742                    ..Default::default()
743                },
744                &SearchUserEnvironment {
745                    experiment: "warp-drive".to_string(),
746                    ..Default::default()
747                }
748            ),
749            "Should return true when the experiment matches the one in the environment"
750        );
751
752        assert!(
753            !matches_user_environment(
754                &crate::JSONVariantEnvironment {
755                    experiment: "warp-drive".to_string(),
756                    ..Default::default()
757                },
758                &SearchUserEnvironment {
759                    experiment: "cloak".to_string(),
760                    ..Default::default()
761                }
762            ),
763            "Should return false when the experiment does not match the one in the environment"
764        );
765
766        assert!(
767            matches_user_environment(
768                &crate::JSONVariantEnvironment {
769                    regions: vec!["fr".to_string()],
770                    experiment: "warp-drive".to_string(),
771                    ..Default::default()
772                },
773                &SearchUserEnvironment {
774                    locale: "fi".into(),
775                    region: "fr".into(),
776                    experiment: "warp-drive".to_string(),
777                    ..Default::default()
778                }
779            ),
780            "Should return true when the experiment and region matches the environment"
781        );
782    }
783
784    #[test]
785    fn test_matches_user_environment_versions() {
786        assert!(
787            !matches_user_environment(
788                &crate::JSONVariantEnvironment {
789                    min_version: "43.0.0".to_string(),
790                    max_version: "".to_string(),
791                    ..Default::default()
792                },
793                &SearchUserEnvironment {
794                    version: "42.0.0".to_string(),
795                    ..Default::default()
796                },
797            ),
798            "Should return false when the version is below the minimum"
799        );
800        assert!(
801            matches_user_environment(
802                &crate::JSONVariantEnvironment {
803                    min_version: "42.0.0".to_string(),
804                    max_version: "".to_string(),
805                    ..Default::default()
806                },
807                &SearchUserEnvironment {
808                    version: "42.0.0".to_string(),
809                    ..Default::default()
810                },
811            ),
812            "Should return true when the version is equal to the minimum"
813        );
814        assert!(
815            matches_user_environment(
816                &crate::JSONVariantEnvironment {
817                    min_version: "41.0.0".to_string(),
818                    max_version: "".to_string(),
819                    ..Default::default()
820                },
821                &SearchUserEnvironment {
822                    version: "42.0.0".to_string(),
823                    ..Default::default()
824                },
825            ),
826            "Should return true when the version is above the minimum"
827        );
828
829        assert!(
830            matches_user_environment(
831                &crate::JSONVariantEnvironment {
832                    min_version: "".to_string(),
833                    max_version: "43.0.0".to_string(),
834                    ..Default::default()
835                },
836                &SearchUserEnvironment {
837                    version: "42.0.0".to_string(),
838                    ..Default::default()
839                },
840            ),
841            "Should return true when the version is below the maximum"
842        );
843        assert!(
844            matches_user_environment(
845                &crate::JSONVariantEnvironment {
846                    min_version: "".to_string(),
847                    max_version: "42.0.0".to_string(),
848                    ..Default::default()
849                },
850                &SearchUserEnvironment {
851                    version: "42.0.0".to_string(),
852                    ..Default::default()
853                },
854            ),
855            "Should return true when the version is equal to the maximum"
856        );
857        assert!(
858            !matches_user_environment(
859                &crate::JSONVariantEnvironment {
860                    min_version: "".to_string(),
861                    max_version: "41.0.0".to_string(),
862                    ..Default::default()
863                },
864                &SearchUserEnvironment {
865                    version: "42.0.0".to_string(),
866                    ..Default::default()
867                },
868            ),
869            "Should return false when the version is above the maximum"
870        );
871
872        assert!(
873            !matches_user_environment(
874                &crate::JSONVariantEnvironment {
875                    min_version: "41.0.0".to_string(),
876                    max_version: "43.0.0".to_string(),
877                    ..Default::default()
878                },
879                &SearchUserEnvironment {
880                    version: "2.0.0".to_string(),
881                    ..Default::default()
882                },
883            ),
884            "Should return false when the version is below the minimum and both are specified"
885        );
886        assert!(
887            matches_user_environment(
888                &crate::JSONVariantEnvironment {
889                    min_version: "41.0.0".to_string(),
890                    max_version: "43.0.0".to_string(),
891                    ..Default::default()
892                },
893                &SearchUserEnvironment {
894                    version: "41.0.0".to_string(),
895                    ..Default::default()
896                },
897            ),
898            "Should return true when the version is equal to the minimum and both are specified"
899        );
900        assert!(
901            matches_user_environment(
902                &crate::JSONVariantEnvironment {
903                    min_version: "41.0.0".to_string(),
904                    max_version: "43.0.0".to_string(),
905                    ..Default::default()
906                },
907                &SearchUserEnvironment {
908                    version: "42.0.0".to_string(),
909                    ..Default::default()
910                },
911            ),
912            "Should return true when the version is between the minimum and maximum"
913        );
914        assert!(
915            matches_user_environment(
916                &crate::JSONVariantEnvironment {
917                    min_version: "41.0.0".to_string(),
918                    max_version: "43.0.0".to_string(),
919                    ..Default::default()
920                },
921                &SearchUserEnvironment {
922                    version: "43.0.0".to_string(),
923                    ..Default::default()
924                },
925            ),
926            "Should return true when the version is equal to the maximum and both are specified"
927        );
928        assert!(
929            !matches_user_environment(
930                &crate::JSONVariantEnvironment {
931                    min_version: "41.0.0".to_string(),
932                    max_version: "43.0.0".to_string(),
933                    ..Default::default()
934                },
935                &SearchUserEnvironment {
936                    version: "44.0.0".to_string(),
937                    ..Default::default()
938                },
939            ),
940            "Should return false when the version is above the maximum and both are specified"
941        );
942    }
943
944    #[test]
945    fn test_matches_user_environment_device_type() {
946        assert!(
947            matches_user_environment(
948                &crate::JSONVariantEnvironment {
949                    device_type: vec![SearchDeviceType::None],
950                    ..Default::default()
951                },
952                &SearchUserEnvironment {
953                    device_type: SearchDeviceType::None,
954                    ..Default::default()
955                }
956            ),
957            "Should return true when the device type matches one in the environment"
958        );
959
960        assert!(
961            matches_user_environment(
962                &crate::JSONVariantEnvironment {
963                    device_type: vec![SearchDeviceType::Smartphone, SearchDeviceType::Tablet],
964                    ..Default::default()
965                },
966                &SearchUserEnvironment {
967                    device_type: SearchDeviceType::Tablet,
968                    ..Default::default()
969                }
970            ),
971            "Should return true when the device type matches one in the environment when there are multiple"
972        );
973
974        assert!(
975            !matches_user_environment(
976                &crate::JSONVariantEnvironment {
977                    device_type: vec![SearchDeviceType::Smartphone],
978                    ..Default::default()
979                },
980                &SearchUserEnvironment {
981                    device_type: SearchDeviceType::None,
982                    ..Default::default()
983                }
984            ),
985            "Should return false when the device type does not match any in the environment"
986        );
987
988        assert!(
989            matches_user_environment(
990                &crate::JSONVariantEnvironment {
991                    regions: vec!["fr".to_string()],
992                    device_type: vec![SearchDeviceType::Tablet],
993                    ..Default::default()
994                },
995                &SearchUserEnvironment {
996                    locale: "fi".into(),
997                    region: "fr".into(),
998                    device_type: SearchDeviceType::Tablet,
999                    ..Default::default()
1000                }
1001            ),
1002            "Should return true when the device type and region matches the environment"
1003        );
1004    }
1005}