1- import { DefaultPrivacyLevel , INTAKE_SITE_US1 , display } from '@datadog/browser-core'
2- import { interceptRequests } from '@datadog/browser-core/test'
1+ import {
2+ DefaultPrivacyLevel ,
3+ INTAKE_SITE_US1 ,
4+ display ,
5+ setCookie ,
6+ deleteCookie ,
7+ ONE_MINUTE ,
8+ createContextManager ,
9+ } from '@datadog/browser-core'
10+ import { interceptRequests , registerCleanupTask } from '@datadog/browser-core/test'
11+ import { appendElement } from '../../../test'
312import type { RumInitConfiguration } from './configuration'
413import type { RumRemoteConfiguration } from './remoteConfiguration'
514import { applyRemoteConfiguration , buildEndpoint , fetchRemoteConfiguration } from './remoteConfiguration'
@@ -13,14 +22,13 @@ const DEFAULT_INIT_CONFIGURATION: RumInitConfiguration = {
1322}
1423
1524describe ( 'remoteConfiguration' , ( ) => {
16- let interceptor : ReturnType < typeof interceptRequests >
17-
18- beforeEach ( ( ) => {
19- interceptor = interceptRequests ( )
20- } )
21-
2225 describe ( 'fetchRemoteConfiguration' , ( ) => {
2326 const configuration = { remoteConfigurationId : 'xxx' } as RumInitConfiguration
27+ let interceptor : ReturnType < typeof interceptRequests >
28+
29+ beforeEach ( ( ) => {
30+ interceptor = interceptRequests ( )
31+ } )
2432
2533 it ( 'should fetch the remote configuration' , async ( ) => {
2634 interceptor . withFetch ( ( ) =>
@@ -92,9 +100,31 @@ describe('remoteConfiguration', () => {
92100
93101 describe ( 'applyRemoteConfiguration' , ( ) => {
94102 let displaySpy : jasmine . Spy
103+ let supportedContextManagers : {
104+ user : ReturnType < typeof createContextManager >
105+ context : ReturnType < typeof createContextManager >
106+ }
107+
108+ function expectAppliedRemoteConfigurationToBe (
109+ actual : Partial < RumRemoteConfiguration > ,
110+ expected : Partial < RumInitConfiguration >
111+ ) {
112+ const rumRemoteConfiguration : RumRemoteConfiguration = {
113+ applicationId : 'yyy' ,
114+ ...actual ,
115+ }
116+ expect (
117+ applyRemoteConfiguration ( DEFAULT_INIT_CONFIGURATION , rumRemoteConfiguration , supportedContextManagers )
118+ ) . toEqual ( {
119+ ...DEFAULT_INIT_CONFIGURATION ,
120+ applicationId : 'yyy' ,
121+ ...expected ,
122+ } )
123+ }
95124
96125 beforeEach ( ( ) => {
97126 displaySpy = spyOn ( display , 'error' )
127+ supportedContextManagers = { user : createContextManager ( ) , context : createContextManager ( ) }
98128 } )
99129
100130 it ( 'should override the initConfiguration options with the ones from the remote configuration' , ( ) => {
@@ -120,7 +150,9 @@ describe('remoteConfiguration', () => {
120150 ] ,
121151 defaultPrivacyLevel : DefaultPrivacyLevel . ALLOW ,
122152 }
123- expect ( applyRemoteConfiguration ( DEFAULT_INIT_CONFIGURATION , rumRemoteConfiguration ) ) . toEqual ( {
153+ expect (
154+ applyRemoteConfiguration ( DEFAULT_INIT_CONFIGURATION , rumRemoteConfiguration , supportedContextManagers )
155+ ) . toEqual ( {
124156 applicationId : 'yyy' ,
125157 clientToken : 'xxx' ,
126158 service : 'xxx' ,
@@ -138,30 +170,258 @@ describe('remoteConfiguration', () => {
138170 } )
139171
140172 it ( 'should display an error if the remote config contains invalid regex' , ( ) => {
141- const rumRemoteConfiguration : RumRemoteConfiguration = {
142- applicationId : 'yyy' ,
143- allowedTrackingOrigins : [ { rcSerializedType : 'regex' , value : 'Hello(?|!)' } ] ,
144- }
145- expect ( applyRemoteConfiguration ( DEFAULT_INIT_CONFIGURATION , rumRemoteConfiguration ) ) . toEqual ( {
146- ...DEFAULT_INIT_CONFIGURATION ,
147- applicationId : 'yyy' ,
148- allowedTrackingOrigins : [ undefined as any ] ,
149- } )
173+ expectAppliedRemoteConfigurationToBe (
174+ { allowedTrackingOrigins : [ { rcSerializedType : 'regex' , value : 'Hello(?|!)' } ] } ,
175+ { allowedTrackingOrigins : [ undefined as any ] }
176+ )
150177 expect ( displaySpy ) . toHaveBeenCalledWith ( "Invalid regex in the remote configuration: 'Hello(?|!)'" )
151178 } )
152179
153180 it ( 'should display an error if an unsupported `rcSerializedType` is provided' , ( ) => {
154- const rumRemoteConfiguration : RumRemoteConfiguration = {
155- applicationId : 'yyy' ,
156- allowedTrackingOrigins : [ { rcSerializedType : 'foo' as any , value : 'bar' } ] ,
157- }
158- expect ( applyRemoteConfiguration ( DEFAULT_INIT_CONFIGURATION , rumRemoteConfiguration ) ) . toEqual ( {
159- ...DEFAULT_INIT_CONFIGURATION ,
160- applicationId : 'yyy' ,
161- allowedTrackingOrigins : [ undefined as any ] ,
162- } )
181+ expectAppliedRemoteConfigurationToBe (
182+ { allowedTrackingOrigins : [ { rcSerializedType : 'foo' as any , value : 'bar' } ] } ,
183+ { allowedTrackingOrigins : [ undefined as any ] }
184+ )
163185 expect ( displaySpy ) . toHaveBeenCalledWith ( 'Unsupported remote configuration: "rcSerializedType": "foo"' )
164186 } )
187+
188+ it ( 'should display an error if an unsupported `strategy` is provided' , ( ) => {
189+ expectAppliedRemoteConfigurationToBe (
190+ { version : { rcSerializedType : 'dynamic' , strategy : 'foo' as any } as any } ,
191+ { version : undefined }
192+ )
193+ expect ( displaySpy ) . toHaveBeenCalledWith ( 'Unsupported remote configuration: "strategy": "foo"' )
194+ } )
195+
196+ describe ( 'cookie strategy' , ( ) => {
197+ const COOKIE_NAME = 'unit_rc'
198+
199+ it ( 'should resolve a configuration value from a cookie' , ( ) => {
200+ setCookie ( COOKIE_NAME , 'my-version' , ONE_MINUTE )
201+ registerCleanupTask ( ( ) => deleteCookie ( COOKIE_NAME ) )
202+ expectAppliedRemoteConfigurationToBe (
203+ { version : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : COOKIE_NAME } } ,
204+ { version : 'my-version' }
205+ )
206+ } )
207+
208+ it ( 'should resolve to undefined if the cookie is missing' , ( ) => {
209+ expectAppliedRemoteConfigurationToBe (
210+ { version : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : COOKIE_NAME } } ,
211+ { version : undefined }
212+ )
213+ } )
214+ } )
215+
216+ describe ( 'dom strategy' , ( ) => {
217+ beforeEach ( ( ) => {
218+ appendElement ( `<div>
219+ <span id="version1" class="version">version-123</span>
220+ <span id="version2" class="version" data-version="version-456"></span>
221+ </div>` )
222+ } )
223+
224+ it ( 'should resolve a configuration value from an element text content' , ( ) => {
225+ expectAppliedRemoteConfigurationToBe (
226+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version1' } } ,
227+ { version : 'version-123' }
228+ )
229+ } )
230+
231+ it ( 'should resolve a configuration value from an element text content and an extractor' , ( ) => {
232+ expectAppliedRemoteConfigurationToBe (
233+ {
234+ version : {
235+ rcSerializedType : 'dynamic' ,
236+ strategy : 'dom' ,
237+ selector : '#version1' ,
238+ extractor : { rcSerializedType : 'regex' , value : '\\d+' } ,
239+ } ,
240+ } ,
241+ { version : '123' }
242+ )
243+ } )
244+
245+ it ( 'should resolve a configuration value from the first element matching the selector' , ( ) => {
246+ expectAppliedRemoteConfigurationToBe (
247+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '.version' } } ,
248+ { version : 'version-123' }
249+ )
250+ } )
251+
252+ it ( 'should resolve to undefined and display an error if the selector is invalid' , ( ) => {
253+ expectAppliedRemoteConfigurationToBe (
254+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '' } } ,
255+ { version : undefined }
256+ )
257+ expect ( displaySpy ) . toHaveBeenCalledWith ( "Invalid selector in the remote configuration: ''" )
258+ } )
259+
260+ it ( 'should resolve to undefined if the element is missing' , ( ) => {
261+ expectAppliedRemoteConfigurationToBe (
262+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#missing' } } ,
263+ { version : undefined }
264+ )
265+ } )
266+
267+ it ( 'should resolve a configuration value from an element attribute' , ( ) => {
268+ expectAppliedRemoteConfigurationToBe (
269+ {
270+ version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version2' , attribute : 'data-version' } ,
271+ } ,
272+ { version : 'version-456' }
273+ )
274+ } )
275+
276+ it ( 'should resolve to undefined if the element attribute is missing' , ( ) => {
277+ expectAppliedRemoteConfigurationToBe (
278+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version2' , attribute : 'missing' } } ,
279+ { version : undefined }
280+ )
281+ } )
282+
283+ it ( 'should resolve to undefined if trying to access a password input value attribute' , ( ) => {
284+ appendElement ( '<input id="pwd" type="password" value="foo" />' )
285+ expectAppliedRemoteConfigurationToBe (
286+ { version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#pwd' , attribute : 'value' } } ,
287+ { version : undefined }
288+ )
289+ } )
290+ } )
291+
292+ describe ( 'with extractor' , ( ) => {
293+ const COOKIE_NAME = 'unit_rc'
294+
295+ beforeEach ( ( ) => {
296+ setCookie ( COOKIE_NAME , 'my-version-123' , ONE_MINUTE )
297+ } )
298+
299+ afterEach ( ( ) => {
300+ deleteCookie ( COOKIE_NAME )
301+ } )
302+
303+ it ( 'should resolve to the match on the value' , ( ) => {
304+ expectAppliedRemoteConfigurationToBe (
305+ {
306+ version : {
307+ rcSerializedType : 'dynamic' ,
308+ strategy : 'cookie' ,
309+ name : COOKIE_NAME ,
310+ extractor : { rcSerializedType : 'regex' , value : '\\d+' } ,
311+ } ,
312+ } ,
313+ { version : '123' }
314+ )
315+ } )
316+
317+ it ( 'should resolve to the capture group on the value' , ( ) => {
318+ expectAppliedRemoteConfigurationToBe (
319+ {
320+ version : {
321+ rcSerializedType : 'dynamic' ,
322+ strategy : 'cookie' ,
323+ name : COOKIE_NAME ,
324+ extractor : { rcSerializedType : 'regex' , value : 'my-version-(\\d+)' } ,
325+ } ,
326+ } ,
327+ { version : '123' }
328+ )
329+ } )
330+
331+ it ( "should resolve to undefined if the value don't match" , ( ) => {
332+ expectAppliedRemoteConfigurationToBe (
333+ {
334+ version : {
335+ rcSerializedType : 'dynamic' ,
336+ strategy : 'cookie' ,
337+ name : COOKIE_NAME ,
338+ extractor : { rcSerializedType : 'regex' , value : 'foo' } ,
339+ } ,
340+ } ,
341+ { version : undefined }
342+ )
343+ } )
344+
345+ it ( 'should display an error if the extractor is not a valid regex' , ( ) => {
346+ expectAppliedRemoteConfigurationToBe (
347+ {
348+ version : {
349+ rcSerializedType : 'dynamic' ,
350+ strategy : 'cookie' ,
351+ name : COOKIE_NAME ,
352+ extractor : { rcSerializedType : 'regex' , value : 'Hello(?|!)' } ,
353+ } ,
354+ } ,
355+ { version : undefined }
356+ )
357+ expect ( displaySpy ) . toHaveBeenCalledWith ( "Invalid regex in the remote configuration: 'Hello(?|!)'" )
358+ } )
359+ } )
360+
361+ describe ( 'supported contexts' , ( ) => {
362+ const COOKIE_NAME = 'unit_rc'
363+
364+ beforeEach ( ( ) => {
365+ setCookie ( COOKIE_NAME , 'first.second' , ONE_MINUTE )
366+ } )
367+
368+ afterEach ( ( ) => {
369+ deleteCookie ( COOKIE_NAME )
370+ } )
371+
372+ it ( 'should be resolved from the provided configuration' , ( ) => {
373+ expectAppliedRemoteConfigurationToBe (
374+ {
375+ user : [
376+ {
377+ key : 'id' ,
378+ value : {
379+ rcSerializedType : 'dynamic' ,
380+ strategy : 'cookie' ,
381+ name : COOKIE_NAME ,
382+ extractor : { rcSerializedType : 'regex' , value : '(\\w+)\\.\\w+' } ,
383+ } ,
384+ } ,
385+ {
386+ key : 'bar' ,
387+ value : {
388+ rcSerializedType : 'dynamic' ,
389+ strategy : 'cookie' ,
390+ name : COOKIE_NAME ,
391+ extractor : { rcSerializedType : 'regex' , value : '\\w+\\.(\\w+)' } ,
392+ } ,
393+ } ,
394+ ] ,
395+ } ,
396+ { }
397+ )
398+ expect ( supportedContextManagers . user . getContext ( ) ) . toEqual ( {
399+ id : 'first' ,
400+ bar : 'second' ,
401+ } )
402+ } )
403+
404+ it ( 'unresolved property should be set to undefined' , ( ) => {
405+ expectAppliedRemoteConfigurationToBe (
406+ {
407+ context : [
408+ {
409+ key : 'foo' ,
410+ value : {
411+ rcSerializedType : 'dynamic' ,
412+ strategy : 'cookie' ,
413+ name : 'missing-cookie' ,
414+ } ,
415+ } ,
416+ ] ,
417+ } ,
418+ { }
419+ )
420+ expect ( supportedContextManagers . context . getContext ( ) ) . toEqual ( {
421+ foo : undefined ,
422+ } )
423+ } )
424+ } )
165425 } )
166426
167427 describe ( 'buildEndpoint' , ( ) => {
0 commit comments