@@ -14,6 +14,18 @@ const mockedKVs = [{
1414} , {
1515 key : "app.settings.fontSize" ,
1616 value : "40" ,
17+ } , {
18+ key : "app/settings/fontColor" ,
19+ value : "red" ,
20+ } , {
21+ key : "app/settings/fontSize" ,
22+ value : "40" ,
23+ } , {
24+ key : "app%settings%fontColor" ,
25+ value : "red" ,
26+ } , {
27+ key : "app%settings%fontSize" ,
28+ value : "40" ,
1729} , {
1830 key : "TestKey" ,
1931 label : "Test" ,
@@ -28,7 +40,30 @@ const mockedKVs = [{
2840} , {
2941 key : "KeyForEmptyValue" ,
3042 value : "" ,
31- } ] . map ( createMockedKeyValue ) ;
43+ } , {
44+ key : "app2.settings" ,
45+ value : JSON . stringify ( { fontColor : "blue" , fontSize : 20 } ) ,
46+ contentType : "application/json"
47+ } , {
48+ key : "app3.settings" ,
49+ value : "placeholder"
50+ } , {
51+ key : "app3.settings.fontColor" ,
52+ value : "yellow"
53+ } , {
54+ key : "app4.excludedFolders.0" ,
55+ value : "node_modules"
56+ } , {
57+ key : "app4.excludedFolders.1" ,
58+ value : "dist"
59+ } , {
60+ key : "app5.settings.fontColor" ,
61+ value : "yellow"
62+ } , {
63+ key : "app5.settings" ,
64+ value : "placeholder"
65+ }
66+ ] . map ( createMockedKeyValue ) ;
3267
3368describe ( "load" , function ( ) {
3469 this . timeout ( 10000 ) ;
@@ -85,11 +120,8 @@ describe("load", function () {
85120 trimKeyPrefixes : [ "app.settings." ]
86121 } ) ;
87122 expect ( settings ) . not . undefined ;
88- expect ( settings . has ( "fontColor" ) ) . eq ( true ) ;
89123 expect ( settings . get ( "fontColor" ) ) . eq ( "red" ) ;
90- expect ( settings . has ( "fontSize" ) ) . eq ( true ) ;
91124 expect ( settings . get ( "fontSize" ) ) . eq ( "40" ) ;
92- expect ( settings . has ( "TestKey" ) ) . eq ( false ) ;
93125 } ) ;
94126
95127 it ( "should trim longest key prefix first" , async ( ) => {
@@ -102,20 +134,15 @@ describe("load", function () {
102134 trimKeyPrefixes : [ "app." , "app.settings." , "Test" ]
103135 } ) ;
104136 expect ( settings ) . not . undefined ;
105- expect ( settings . has ( "fontColor" ) ) . eq ( true ) ;
106137 expect ( settings . get ( "fontColor" ) ) . eq ( "red" ) ;
107- expect ( settings . has ( "fontSize" ) ) . eq ( true ) ;
108138 expect ( settings . get ( "fontSize" ) ) . eq ( "40" ) ;
109- expect ( settings . has ( "TestKey" ) ) . eq ( false ) ;
110139 } ) ;
111140
112141 it ( "should support null/empty value" , async ( ) => {
113142 const connectionString = createMockedConnectionString ( ) ;
114143 const settings = await load ( connectionString ) ;
115144 expect ( settings ) . not . undefined ;
116- expect ( settings . has ( "KeyForNullValue" ) ) . eq ( true ) ;
117145 expect ( settings . get ( "KeyForNullValue" ) ) . eq ( null ) ;
118- expect ( settings . has ( "KeyForEmptyValue" ) ) . eq ( true ) ;
119146 expect ( settings . get ( "KeyForEmptyValue" ) ) . eq ( "" ) ;
120147 } ) ;
121148
@@ -153,7 +180,6 @@ describe("load", function () {
153180 } ]
154181 } ) ;
155182 expect ( settings ) . not . undefined ;
156- expect ( settings . has ( "TestKey" ) ) . eq ( true ) ;
157183 expect ( settings . get ( "TestKey" ) ) . eq ( "TestValueForProd" ) ;
158184 } ) ;
159185
@@ -172,8 +198,137 @@ describe("load", function () {
172198 } ]
173199 } ) ;
174200 expect ( settings ) . not . undefined ;
175- expect ( settings . has ( "TestKey" ) ) . eq ( true ) ;
176201 expect ( settings . get ( "TestKey" ) ) . eq ( "TestValueForProd" ) ;
177202 } ) ;
178203
204+ // access data property
205+ it ( "should directly access data property" , async ( ) => {
206+ const connectionString = createMockedConnectionString ( ) ;
207+ const settings = await load ( connectionString , {
208+ selectors : [ {
209+ keyFilter : "app.settings.*"
210+ } ]
211+ } ) ;
212+ expect ( settings ) . not . undefined ;
213+ const data = settings . constructConfigurationObject ( ) ;
214+ expect ( data ) . not . undefined ;
215+ expect ( data . app . settings . fontColor ) . eq ( "red" ) ;
216+ expect ( data . app . settings . fontSize ) . eq ( "40" ) ;
217+ } ) ;
218+
219+ it ( "should access property of JSON object content-type with data accessor" , async ( ) => {
220+ const connectionString = createMockedConnectionString ( ) ;
221+ const settings = await load ( connectionString , {
222+ selectors : [ {
223+ keyFilter : "app2.*"
224+ } ]
225+ } ) ;
226+ expect ( settings ) . not . undefined ;
227+ const data = settings . constructConfigurationObject ( ) ;
228+ expect ( data ) . not . undefined ;
229+ expect ( data . app2 . settings . fontColor ) . eq ( "blue" ) ;
230+ expect ( data . app2 . settings . fontSize ) . eq ( 20 ) ;
231+ } ) ;
232+
233+ it ( "should not access property of JSON content-type object with get()" , async ( ) => {
234+ const connectionString = createMockedConnectionString ( ) ;
235+ const settings = await load ( connectionString , {
236+ selectors : [ {
237+ keyFilter : "app2.*"
238+ } ]
239+ } ) ;
240+ expect ( settings ) . not . undefined ;
241+ expect ( settings . get ( "app2.settings" ) ) . not . undefined ; // JSON object accessed as a whole
242+ expect ( settings . get ( "app2.settings.fontColor" ) ) . undefined ;
243+ expect ( settings . get ( "app2.settings.fontSize" ) ) . undefined ;
244+ } ) ;
245+
246+ /**
247+ * Edge case: Hierarchical key-value pairs with overlapped key prefix.
248+ * key: "app3.settings" => value: "placeholder"
249+ * key: "app3.settings.fontColor" => value: "yellow"
250+ *
251+ * get() will return "placeholder" for "app3.settings" and "yellow" for "app3.settings.fontColor", as expected.
252+ * data.app3.settings will return "placeholder" as a whole JSON object, which is not guarenteed to be correct.
253+ */
254+ it ( "Edge case 1: Hierarchical key-value pairs with overlapped key prefix." , async ( ) => {
255+ const connectionString = createMockedConnectionString ( ) ;
256+ const settings = await load ( connectionString , {
257+ selectors : [ {
258+ keyFilter : "app3.settings*"
259+ } ]
260+ } ) ;
261+ expect ( settings ) . not . undefined ;
262+ expect ( ( ) => {
263+ settings . constructConfigurationObject ( ) ;
264+ } ) . to . throw ( "Ambiguity occurs when constructing configuration object from key 'app3.settings.fontColor', value 'yellow'. The path 'app3.settings' has been occupied." ) ;
265+ } ) ;
266+
267+ /**
268+ * Edge case: Hierarchical key-value pairs with overlapped key prefix.
269+ * key: "app5.settings.fontColor" => value: "yellow"
270+ * key: "app5.settings" => value: "placeholder"
271+ *
272+ * When ocnstructConfigurationObject() is called, it first constructs from key "app5.settings.fontColor" and then from key "app5.settings".
273+ * An error will be thrown when constructing from key "app5.settings" because there is ambiguity between the two keys.
274+ */
275+ it ( "Edge case 1: Hierarchical key-value pairs with overlapped key prefix." , async ( ) => {
276+ const connectionString = createMockedConnectionString ( ) ;
277+ const settings = await load ( connectionString , {
278+ selectors : [ {
279+ keyFilter : "app5.settings*"
280+ } ]
281+ } ) ;
282+ expect ( settings ) . not . undefined ;
283+ expect ( ( ) => {
284+ settings . constructConfigurationObject ( ) ;
285+ } ) . to . throw ( "Ambiguity occurs when constructing configuration object from key 'app5.settings', value 'placeholder'. The key should not be part of another key." ) ;
286+ } ) ;
287+
288+ it ( "should construct configuration object with array" , async ( ) => {
289+ const connectionString = createMockedConnectionString ( ) ;
290+ const settings = await load ( connectionString , {
291+ selectors : [ {
292+ keyFilter : "app4.*"
293+ } ]
294+ } ) ;
295+ expect ( settings ) . not . undefined ;
296+ const data = settings . constructConfigurationObject ( ) ;
297+ expect ( data ) . not . undefined ;
298+ // Both { '0': 'node_modules', '1': 'dist' } and ['node_modules', 'dist'] are valid.
299+ expect ( data . app4 . excludedFolders [ 0 ] ) . eq ( "node_modules" ) ;
300+ expect ( data . app4 . excludedFolders [ 1 ] ) . eq ( "dist" ) ;
301+ } ) ;
302+
303+ it ( "should construct configuration object with customized separator" , async ( ) => {
304+ const connectionString = createMockedConnectionString ( ) ;
305+ const settings = await load ( connectionString , {
306+ selectors : [ {
307+ keyFilter : "app/settings/*"
308+ } ]
309+ } ) ;
310+ expect ( settings ) . not . undefined ;
311+ const data = settings . constructConfigurationObject ( { separator : "/" } ) ;
312+ expect ( data ) . not . undefined ;
313+ expect ( data . app . settings . fontColor ) . eq ( "red" ) ;
314+ expect ( data . app . settings . fontSize ) . eq ( "40" ) ;
315+ } ) ;
316+
317+ it ( "should throw error when construct configuration object with invalid separator" , async ( ) => {
318+ const connectionString = createMockedConnectionString ( ) ;
319+ const settings = await load ( connectionString , {
320+ selectors : [ {
321+ keyFilter : "app%settings%*"
322+ } ]
323+ } ) ;
324+ expect ( settings ) . not . undefined ;
325+
326+ expect ( ( ) => {
327+ // Below line will throw error because of type checking, i.e. Type '"%"' is not assignable to type '"/" | "." | "," | ";" | "-" | "_" | "__" | ":" | undefined'.ts(2322)
328+ // Here force to turn if off for testing purpose, as JavaScript does not have type checking.
329+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
330+ // @ts -ignore
331+ settings . constructConfigurationObject ( { separator : "%" } ) ;
332+ } ) . to . throw ( "Invalid separator '%'. Supported values: '.', ',', ';', '-', '_', '__', '/', ':'." ) ;
333+ } ) ;
179334} ) ;
0 commit comments