Skip to content

Commit 837a920

Browse files
committed
feat: TT-19 Create Cookies of FeatureToggle
1 parent 9082b90 commit 837a920

15 files changed

+446
-55
lines changed

src/app/modules/login/login.component.spec.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import { of } from 'rxjs';
55

66
import { LoginComponent } from './login.component';
77
import { Router } from '@angular/router';
8+
import { FeatureToggleCookiesService } from '../shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service';
9+
import { promise } from 'selenium-webdriver';
810

911
describe('LoginComponent', () => {
1012
let component: LoginComponent;
1113
let fixture: ComponentFixture<LoginComponent>;
1214
let azureAdB2CService: AzureAdB2CService;
15+
let featureToggleCookiesService: FeatureToggleCookiesService;
1316

1417
const azureAdB2CServiceStub = {
1518
isLogin() {
@@ -22,12 +25,19 @@ describe('LoginComponent', () => {
2225
}
2326
};
2427

28+
const featureToggleCookiesServiceStub = {
29+
setCookies() {
30+
}
31+
};
32+
33+
2534
beforeEach(waitForAsync(() => {
2635
TestBed.configureTestingModule({
2736
imports: [ RouterTestingModule ],
2837
declarations: [ LoginComponent ],
2938
providers: [
30-
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub}
39+
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
40+
{ providers: FeatureToggleCookiesService, useValue: featureToggleCookiesServiceStub}
3141
]
3242
})
3343
.compileComponents();
@@ -38,15 +48,21 @@ describe('LoginComponent', () => {
3848
component = fixture.componentInstance;
3949
fixture.detectChanges();
4050
azureAdB2CService = TestBed.inject(AzureAdB2CService);
41-
51+
featureToggleCookiesService = TestBed.inject(FeatureToggleCookiesService);
4252
});
4353

44-
it('Service injected via inject(...) and TestBed.get(...) should be the same instance',
54+
it('AzureAdB2CService injected via inject(...) and TestBed.get(...) should be the same instance',
4555
inject([AzureAdB2CService], (injectService: AzureAdB2CService) => {
4656
expect(injectService).toEqual(azureAdB2CService);
4757
})
4858
);
4959

60+
it('FeatureToggleCookiesService injected via inject(...) and TestBed.get(...) should be the same instance',
61+
inject([FeatureToggleCookiesService], (injectService: FeatureToggleCookiesService) => {
62+
expect(injectService).toEqual(featureToggleCookiesService);
63+
})
64+
);
65+
5066
it('should create login component', () => {
5167
expect(component).toBeTruthy();
5268
});
@@ -55,9 +71,13 @@ describe('LoginComponent', () => {
5571
spyOn(azureAdB2CService, 'isLogin').and.returnValue(false);
5672
spyOn(azureAdB2CService, 'setCookies').and.returnValue();
5773
spyOn(azureAdB2CService, 'signIn').and.returnValue(of(() => {}));
74+
spyOn(featureToggleCookiesService, 'setCookies').and.returnValue(featureToggleCookiesService.setCookies());
75+
5876
component.login();
77+
5978
expect(azureAdB2CService.signIn).toHaveBeenCalled();
6079
expect(azureAdB2CService.setCookies).toHaveBeenCalled();
80+
expect(featureToggleCookiesService.setCookies).toHaveBeenCalled();
6181
}));
6282

6383
it('should not sign-up or login with google if is already logged-in into the app', inject([Router], (router: Router) => {

src/app/modules/login/login.component.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component } from '@angular/core';
22
import { AzureAdB2CService } from './services/azure.ad.b2c.service';
33
import { Router } from '@angular/router';
4+
import { FeatureToggleCookiesService } from '../shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service';
45

56
@Component({
67
selector: 'app-login',
@@ -10,13 +11,18 @@ import { Router } from '@angular/router';
1011

1112
export class LoginComponent {
1213

13-
constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) {}
14+
constructor(
15+
private azureAdB2CService: AzureAdB2CService,
16+
private router: Router,
17+
private featureToggleCookiesService: FeatureToggleCookiesService
18+
) {}
1419

1520
login(): void {
1621
if (this.azureAdB2CService.isLogin()) {
1722
this.router.navigate(['']);
1823
} else {
1924
this.azureAdB2CService.signIn().subscribe(() => {
25+
this.featureToggleCookiesService.setCookies();
2026
this.azureAdB2CService.setCookies();
2127
this.router.navigate(['']);
2228
});

src/app/modules/login/services/azure.ad.b2c.service.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ describe('AzureAdB2CService', () => {
4444

4545
it('on logout should call msal logout and verify if user localStorage is removed', () => {
4646
spyOn(UserAgentApplication.prototype, 'logout').and.returnValue();
47+
spyOn(cookieService, 'deleteAll');
4748
spyOn(localStorage, 'removeItem').withArgs('user');
49+
4850
service.logout();
4951

52+
expect(cookieService.deleteAll).toHaveBeenCalled();
5053
expect(localStorage.removeItem).toHaveBeenCalledWith('user');
5154
expect(UserAgentApplication.prototype.logout).toHaveBeenCalled();
5255
});

src/app/modules/login/services/azure.ad.b2c.service.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ export class AzureAdB2CService {
3232
}
3333

3434
logout() {
35-
this.cookieService.delete('msal.idtoken');
36-
this.cookieService.delete('msal.client.info');
35+
this.cookieService.deleteAll();
3736
this.msal.logout();
3837
localStorage.removeItem('user');
3938
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { CookieService } from 'ngx-cookie-service';
3+
import { of } from 'rxjs';
4+
import { FeatureToggleGeneralService } from '../feature-toggle-general/feature-toggle-general.service';
5+
import { FeatureToggleModel } from '../feature-toggle.model';
6+
import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model';
7+
import { FeatureToggleCookiesService } from './feature-toggle-cookies.service';
8+
9+
describe('FeatureToggleCookiesService', () => {
10+
let cookieService: CookieService;
11+
let featureToggleGeneralService: FeatureToggleGeneralService;
12+
let service: FeatureToggleCookiesService;
13+
14+
beforeEach(() => {
15+
TestBed.configureTestingModule({
16+
providers: [CookieService, FeatureToggleGeneralService]
17+
});
18+
cookieService = TestBed.inject(CookieService);
19+
featureToggleGeneralService = TestBed.inject(FeatureToggleGeneralService);
20+
service = TestBed.inject(FeatureToggleCookiesService);
21+
});
22+
23+
it('should be created', () => {
24+
expect(service).toBeTruthy();
25+
});
26+
27+
it('not call CookieService.set() when call setCookies() and getActivated() return empty', () => {
28+
const fakeAllFeatureaToggleWithFilters = [];
29+
const spyOnSetCookie = spyOn(cookieService, 'set');
30+
spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of(fakeAllFeatureaToggleWithFilters));
31+
32+
service.setCookies();
33+
34+
expect(spyOnSetCookie).toHaveBeenCalledTimes(0);
35+
});
36+
37+
it('Call 1 time CookieService.set() when call setCookies()', () => {
38+
const anyMatchingFilter = new TargetingFeatureFilterModel(
39+
{ Audience: { Groups: ['group-a'], Users: ['user-a'] } },
40+
{ username: 'user-b', group: 'group-a' }
41+
);
42+
const fakeAllFeatureaToggleWithFilters = [new FeatureToggleModel('any-other-id', true, [anyMatchingFilter])];
43+
const spyOnSetCookie = spyOn(cookieService, 'set');
44+
spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of(fakeAllFeatureaToggleWithFilters));
45+
46+
service.setCookies();
47+
48+
expect(spyOnSetCookie).toHaveBeenCalledTimes(1);
49+
});
50+
51+
it('Call 2 times CookieService.set() when call setCookies()', () => {
52+
const anyMatchingFilter = new TargetingFeatureFilterModel(
53+
{ Audience: { Groups: ['group-a'], Users: ['user-a'] } },
54+
{ username: 'user-b', group: 'group-a' }
55+
);
56+
const fakeAllFeatureaToggleWithFilters = [
57+
new FeatureToggleModel('first-id', true, [anyMatchingFilter]),
58+
new FeatureToggleModel('second-id', true, [anyMatchingFilter])
59+
];
60+
const spyOnSetCookie = spyOn(cookieService, 'set');
61+
spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of(fakeAllFeatureaToggleWithFilters));
62+
63+
service.setCookies();
64+
65+
expect(spyOnSetCookie).toHaveBeenCalledTimes(2);
66+
});
67+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from '@angular/core';
2+
import { FeatureToggleGeneralService } from '../feature-toggle-general/feature-toggle-general.service';
3+
import { CookieService } from 'ngx-cookie-service';
4+
5+
@Injectable({
6+
providedIn: 'root'
7+
})
8+
export class FeatureToggleCookiesService {
9+
10+
constructor(
11+
private cookieService: CookieService,
12+
private featureToggleGeneralService: FeatureToggleGeneralService
13+
) { }
14+
15+
setCookies(){
16+
this.featureToggleGeneralService.getActivated().subscribe(
17+
(allFeaturToggle) => {
18+
for (const featureToggle of allFeaturToggle){
19+
this.cookieService.set(featureToggle.name, `${featureToggle.enabled}`, 30);
20+
}
21+
}
22+
);
23+
}
24+
}

src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { FeatureToggle } from './../../../../../environments/enum';
22
import { TestBed } from '@angular/core/testing';
3-
import { of, Observable } from 'rxjs';
3+
import { of } from 'rxjs';
44
import { FeatureManagerService } from '../feature-toggle-manager.service';
55
import { FeatureToggleGeneralService } from './feature-toggle-general.service';
6+
import { FeatureToggleModel } from '../feature-toggle.model';
7+
import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model';
68

79

810
describe('FeatureToggleGeneralService', () => {
@@ -33,4 +35,30 @@ describe('FeatureToggleGeneralService', () => {
3335
});
3436
});
3537
});
38+
39+
it('getActivated return a FeatureToggleModel', () => {
40+
const anyNotMatchingFilter = new TargetingFeatureFilterModel(
41+
{ Audience: { Groups: ['a-group'], Users: ['user-a'] } },
42+
{ username: 'user-b', group: 'b-group' }
43+
);
44+
const fakeAllFeatureaToggleWithFilters = [new FeatureToggleModel('any-other-id', true, [anyNotMatchingFilter])];
45+
spyOn(featureManagerService, 'getAllFeatureToggleEnableForUser').and.returnValue(of(fakeAllFeatureaToggleWithFilters));
46+
47+
featureToggleGeneralService.getActivated().subscribe((featureToggleEnableForUser) => {
48+
expect(featureToggleEnableForUser.length).toEqual(1);
49+
expect(featureToggleEnableForUser).toEqual(fakeAllFeatureaToggleWithFilters);
50+
});
51+
52+
});
53+
54+
it('getActivated return empty', () => {
55+
const fakeAllFeatureaToggleWithFilters = [];
56+
spyOn(featureManagerService, 'getAllFeatureToggleEnableForUser').and.returnValue(of(fakeAllFeatureaToggleWithFilters));
57+
58+
featureToggleGeneralService.getActivated().subscribe((featureToggleEnableForUser) => {
59+
expect(featureToggleEnableForUser.length).toEqual(0);
60+
expect(featureToggleEnableForUser).toEqual(fakeAllFeatureaToggleWithFilters);
61+
});
62+
63+
});
3664
});

src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FeatureToggle } from './../../../../../environments/enum';
33
import { Injectable } from '@angular/core';
44
import { Observable } from 'rxjs';
55
import { FeatureManagerService } from '../feature-toggle-manager.service';
6+
import { FeatureToggleModel } from '../feature-toggle.model';
67

78
@Injectable({
89
providedIn: 'root',
@@ -13,4 +14,8 @@ export class FeatureToggleGeneralService {
1314
isActivated(featureToggle: FeatureToggle): Observable<boolean> {
1415
return this.featureManagerService.isToggleEnabledForUser(featureToggle);
1516
}
17+
18+
getActivated(): Observable<FeatureToggleModel[]>{
19+
return this.featureManagerService.getAllFeatureToggleEnableForUser();
20+
}
1621
}

src/app/modules/shared/feature-toggles/feature-toggle-manager.service.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,48 @@ describe('FeatureToggleManager', () => {
9191
expect(value).toEqual(false);
9292
});
9393
});
94+
95+
it('Get empty when getAllFeatureToggle() return empty', () => {
96+
const fakeAllFeatureaToggleWithFilters = [];
97+
spyOn(fakeFeatureToggleProvider, 'getAllFeatureToggle').and.returnValue(Promise.resolve(fakeAllFeatureaToggleWithFilters));
98+
99+
const response = service.getAllFeatureToggleEnableForUser();
100+
101+
response.subscribe((result) => {
102+
expect(result.length).toEqual(0);
103+
expect(result).toEqual([]);
104+
});
105+
expect().nothing();
106+
});
107+
108+
it('Get empty when getAllFeatureToggle() return FeatureToggle without fakeuser', () => {
109+
const fakeAllFeatureaToggleWithFilters = [new FeatureToggleModel('any-other-id', true, [anyNotMatchingFilter])];
110+
spyOn(fakeFeatureToggleProvider, 'getAllFeatureToggle').and.returnValue(Promise.resolve(fakeAllFeatureaToggleWithFilters));
111+
112+
const response = service.getAllFeatureToggleEnableForUser();
113+
114+
response.subscribe((result) => {
115+
expect(result.length).toEqual(0);
116+
expect(result).toEqual([]);
117+
});
118+
expect().nothing();
119+
});
120+
121+
it('Get FeatureToggleModel[] when getAllFeatureToggle() return FeatureToggle with fakeuser', () => {
122+
const fakeFeatureToggleModel: FeatureToggleModel = new FeatureToggleModel('good-other-id', false, [anyMatchingFilter]);
123+
const fakeAllFeatureaToggleWithFilters = [
124+
new FeatureToggleModel('any-other-id', true, [anyNotMatchingFilter]),
125+
fakeFeatureToggleModel
126+
];
127+
spyOn(fakeFeatureToggleProvider, 'getAllFeatureToggle').and.returnValue(Promise.resolve(fakeAllFeatureaToggleWithFilters));
128+
129+
const response = service.getAllFeatureToggleEnableForUser();
130+
131+
response.subscribe((result) => {
132+
expect(result.length).toEqual(1);
133+
expect(result[0]).toEqual(fakeFeatureToggleModel);
134+
});
135+
expect().nothing();
136+
});
94137
});
95138
});

src/app/modules/shared/feature-toggles/feature-toggle-manager.service.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Injectable } from '@angular/core';
2-
import { Observable, zip } from 'rxjs';
2+
import { from, Observable, zip } from 'rxjs';
33
import { map } from 'rxjs/operators';
44
import { FeatureToggleProvider } from './feature-toggle-provider.service';
5-
5+
import { FeatureToggleModel } from './feature-toggle.model';
66

77
@Injectable({
88
providedIn: 'root',
@@ -12,9 +12,9 @@ export class FeatureManagerService {
1212
constructor(private featureToggleProvider: FeatureToggleProvider) { }
1313

1414
public isToggleEnabled(toggleName: string, toggleLabel?: string): Observable<boolean> {
15-
return this.featureToggleProvider.getFeatureToggle(toggleName, toggleLabel).pipe(
16-
map(featureToggle => featureToggle.enabled)
17-
);
15+
return this.featureToggleProvider
16+
.getFeatureToggle(toggleName, toggleLabel)
17+
.pipe(map((featureToggle) => featureToggle.enabled));
1818
}
1919

2020
public isToggleEnabledForUser(toggleName: string, toggleLabel?: string): Observable<boolean> {
@@ -35,4 +35,14 @@ export class FeatureManagerService {
3535

3636
return result$;
3737
}
38+
39+
public getAllFeatureToggleEnableForUser(): Observable<FeatureToggleModel[]> {
40+
return from(this.featureToggleProvider.getAllFeatureToggle()).pipe(
41+
map((allFeatureToggle) =>
42+
allFeatureToggle.filter((featureToggle) =>
43+
featureToggle.filters.map((filter) => filter.evaluate()).includes(true)
44+
)
45+
)
46+
);
47+
}
3848
}

0 commit comments

Comments
 (0)