1+ /**
2+ * This Source Code Form is subject to the terms of the Mozilla Public License,
3+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+ * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+ * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+ *
7+ * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+ * graphic logo is a trademark of OpenMRS Inc.
9+ */
10+ package org .openmrs .web .filter .util ;
11+
12+ import org .junit .jupiter .api .BeforeEach ;
13+ import org .junit .jupiter .api .Test ;
14+
15+ import java .util .HashSet ;
16+ import java .util .Locale ;
17+ import java .util .Set ;
18+
19+ import static org .junit .jupiter .api .Assertions .assertEquals ;
20+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
21+
22+ /**
23+ * Unit tests for {@link CustomResourceLoader} locale matching functionality.
24+ * These tests verify RFC 4647 language range matching behavior.
25+ */
26+ public class CustomResourceLoaderTest {
27+
28+ private CustomResourceLoader resourceLoader ;
29+ private Set <Locale > availableLocales ;
30+
31+ @ BeforeEach
32+ public void setup () {
33+ // Create a set of available locales for testing
34+ availableLocales = new HashSet <>();
35+ availableLocales .add (Locale .ENGLISH );
36+ availableLocales .add (Locale .FRENCH );
37+ availableLocales .add (new Locale ("es" )); // Spanish
38+ availableLocales .add (new Locale ("pt" , "BR" )); // Portuguese (Brazil)
39+
40+ // Use the protected constructor to inject test locales
41+ resourceLoader = new CustomResourceLoader (availableLocales );
42+ }
43+
44+ @ Test
45+ public void testFindBestMatchLocale_ExactMatch () {
46+ // Test exact match for French
47+ Locale result = resourceLoader .findBestMatchLocale ("fr" );
48+ assertEquals (Locale .FRENCH , result );
49+ }
50+
51+ @ Test
52+ public void testFindBestMatchLocale_ExactMatchWithRegion () {
53+ // Test exact match for Portuguese (Brazil)
54+ Locale result = resourceLoader .findBestMatchLocale ("pt-BR" );
55+ assertEquals (new Locale ("pt" , "BR" ), result );
56+ }
57+
58+ @ Test
59+ public void testFindBestMatchLocale_RegionalFallback () {
60+ // Test that fr-BE (Belgian French) falls back to fr (French)
61+ // since Belgian French is not available but French is
62+ Locale result = resourceLoader .findBestMatchLocale ("fr-BE" );
63+ assertEquals (Locale .FRENCH , result );
64+ }
65+
66+ @ Test
67+ public void testFindBestMatchLocale_WithQualityWeights () {
68+ // Test with quality weights - should match the highest priority available
69+ // locale
70+ Locale result = resourceLoader .findBestMatchLocale ("fr-BE,fr;q=0.9,en;q=0.8" );
71+ // Should match French (fr) since fr-BE is not available but fr is
72+ assertEquals (Locale .FRENCH , result );
73+ }
74+
75+ @ Test
76+ public void testFindBestMatchLocale_QualityWeightsPreferEnglish () {
77+ // Test with English having higher priority than unavailable locales
78+ Locale result = resourceLoader .findBestMatchLocale ("de;q=0.9,en;q=0.8" );
79+ // German is not available, so should fall back to English
80+ assertEquals (Locale .ENGLISH , result );
81+ }
82+
83+ @ Test
84+ public void testFindBestMatchLocale_NullHeader () {
85+ // Test that null header returns default English locale
86+ Locale result = resourceLoader .findBestMatchLocale (null );
87+ assertEquals (Locale .ENGLISH , result );
88+ }
89+
90+ @ Test
91+ public void testFindBestMatchLocale_EmptyHeader () {
92+ // Test that empty header returns default English locale
93+ Locale result = resourceLoader .findBestMatchLocale ("" );
94+ assertEquals (Locale .ENGLISH , result );
95+ }
96+
97+ @ Test
98+ public void testFindBestMatchLocale_WhitespaceHeader () {
99+ // Test that whitespace-only header returns default English locale
100+ Locale result = resourceLoader .findBestMatchLocale (" " );
101+ assertEquals (Locale .ENGLISH , result );
102+ }
103+
104+ @ Test
105+ public void testFindBestMatchLocale_NoMatch () {
106+ // Test that when no locale matches, it returns English as default
107+ Locale result = resourceLoader .findBestMatchLocale ("de" );
108+ assertEquals (Locale .ENGLISH , result );
109+ }
110+
111+ @ Test
112+ public void testFindBestMatchLocale_NoMatchMultiple () {
113+ // Test with multiple non-matching locales
114+ Locale result = resourceLoader .findBestMatchLocale ("de,it,ja" );
115+ assertEquals (Locale .ENGLISH , result );
116+ }
117+
118+ @ Test
119+ public void testFindBestMatchLocale_ComplexHeader () {
120+ // Test with a complex Accept-Language header
121+ Locale result = resourceLoader .findBestMatchLocale ("fr-CH,fr;q=0.9,en;q=0.8,de;q=0.7,*;q=0.5" );
122+ // Should match French (fr) since fr-CH is not available but fr is
123+ assertEquals (Locale .FRENCH , result );
124+ }
125+
126+ @ Test
127+ public void testFindBestMatchLocale_CaseInsensitive () {
128+ // Test that locale matching is case-insensitive
129+ Locale result = resourceLoader .findBestMatchLocale ("FR" );
130+ assertEquals (Locale .FRENCH , result );
131+ }
132+
133+ @ Test
134+ public void testFindBestMatchLocale_MixedCase () {
135+ // Test with mixed case locale codes
136+ Locale result = resourceLoader .findBestMatchLocale ("Es" );
137+ assertEquals (new Locale ("es" ), result );
138+ }
139+
140+ @ Test
141+ public void testFindBestMatchLocale_MultipleRangesFirstMatch () {
142+ // Test that the first matching locale is returned when multiple match
143+ Locale result = resourceLoader .findBestMatchLocale ("en,fr,es" );
144+ // Should match English as it's first in the list
145+ assertEquals (Locale .ENGLISH , result );
146+ }
147+
148+ @ Test
149+ public void testFindBestMatchLocale_PortugueseBrazilFallback () {
150+ // Test that pt (Portuguese) does NOT match pt-BR
151+ // RFC 4647 lookup only matches more specific to less specific, not vice versa
152+ Locale result = resourceLoader .findBestMatchLocale ("pt" );
153+ // Since only pt-BR is available (not generic pt), it should return English as
154+ // default
155+ assertEquals (Locale .ENGLISH , result );
156+ }
157+
158+ @ Test
159+ public void testFindBestMatchLocale_MalformedHeader () {
160+ // Test with malformed header - should return English as default
161+ Locale result = resourceLoader .findBestMatchLocale ("this-is-not-valid;;;" );
162+ // Should handle gracefully and return default
163+ assertNotNull (result );
164+ assertEquals (Locale .ENGLISH , result );
165+ }
166+
167+ @ Test
168+ public void testFindBestMatchLocale_WithWildcard () {
169+ // Test with wildcard in Accept-Language header
170+ Locale result = resourceLoader .findBestMatchLocale ("de,*;q=0.5" );
171+ // German is not available, wildcard should match any available locale
172+ // In this case, it should still return English as the default
173+ assertNotNull (result );
174+ }
175+
176+ @ Test
177+ public void testConstructorWithNullLocales () {
178+ // Test that constructor handles null locale set gracefully
179+ CustomResourceLoader loader = new CustomResourceLoader ((Set <Locale >) null );
180+ assertNotNull (loader .getAvailablelocales ());
181+ assertEquals (0 , loader .getAvailablelocales ().size ());
182+ }
183+
184+ @ Test
185+ public void testConstructorWithEmptyLocales () {
186+ // Test constructor with empty locale set
187+ CustomResourceLoader loader = new CustomResourceLoader (new HashSet <>());
188+ assertNotNull (loader .getAvailablelocales ());
189+ assertEquals (0 , loader .getAvailablelocales ().size ());
190+
191+ // Should still return English as default when no locales are available
192+ Locale result = loader .findBestMatchLocale ("fr" );
193+ assertEquals (Locale .ENGLISH , result );
194+ }
195+ }
0 commit comments