Skip to content

Commit a67cc35

Browse files
committed
feat: add tests for RequireFlagsEnabled decorator
Signed-off-by: Kaushal Kapasi <[email protected]>
1 parent 474a609 commit a67cc35

File tree

3 files changed

+119
-17
lines changed

3 files changed

+119
-17
lines changed

packages/nest/test/open-feature-sdk.spec.ts

+68-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import type { TestingModule } from '@nestjs/testing';
22
import { Test } from '@nestjs/testing';
33
import type { INestApplication } from '@nestjs/common';
44
import supertest from 'supertest';
5-
import { OpenFeatureController, OpenFeatureControllerContextScopedController, OpenFeatureTestService } from './test-app';
5+
import {
6+
OpenFeatureController,
7+
OpenFeatureContextScopedController,
8+
OpenFeatureRequireFlagsEnabledController,
9+
OpenFeatureTestService,
10+
} from './test-app';
611
import { exampleContextFactory, getOpenFeatureDefaultTestModule } from './fixtures';
712
import { OpenFeatureModule } from '../src';
813
import { defaultProvider, providers } from './fixtures';
@@ -14,11 +19,9 @@ describe('OpenFeature SDK', () => {
1419

1520
beforeAll(async () => {
1621
moduleRef = await Test.createTestingModule({
17-
imports: [
18-
getOpenFeatureDefaultTestModule()
19-
],
22+
imports: [getOpenFeatureDefaultTestModule()],
2023
providers: [OpenFeatureTestService],
21-
controllers: [OpenFeatureController],
24+
controllers: [OpenFeatureController, OpenFeatureRequireFlagsEnabledController],
2225
}).compile();
2326
app = moduleRef.createNestApplication();
2427
app = await app.init();
@@ -112,7 +115,7 @@ describe('OpenFeature SDK', () => {
112115
});
113116

114117
describe('evaluation context service should', () => {
115-
it('inject the evaluation context from contex factory', async function() {
118+
it('inject the evaluation context from contex factory', async function () {
116119
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
117120
await supertest(app.getHttpServer())
118121
.get('/dynamic-context-in-service')
@@ -122,26 +125,62 @@ describe('OpenFeature SDK', () => {
122125
expect(evaluationSpy).toHaveBeenCalledWith('testBooleanFlag', false, { targetingKey: 'dynamic-user' }, {});
123126
});
124127
});
128+
129+
describe('require flags enabled decorator', () => {
130+
describe('OpenFeatureController', () => {
131+
it('should sucessfully return the response if the flag is enabled', async () => {
132+
await supertest(app.getHttpServer()).get('/flags-enabled').expect(200).expect('Get Boolean Flag Success!');
133+
});
134+
135+
it('should throw an exception if the flag is disabled', async () => {
136+
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
137+
value: false,
138+
reason: 'DISABLED',
139+
});
140+
await supertest(app.getHttpServer()).get('/flags-enabled').expect(404);
141+
});
142+
143+
it('should throw a custom exception if the flag is disabled', async () => {
144+
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
145+
value: false,
146+
reason: 'DISABLED',
147+
});
148+
await supertest(app.getHttpServer()).get('/flags-enabled-custom-exception').expect(403);
149+
});
150+
});
151+
152+
describe('OpenFeatureControllerRequireFlagsEnabled', () => {
153+
it('should allow access to the RequireFlagsEnabled controller', async () => {
154+
await supertest(app.getHttpServer()).get('/require-flags-enabled').expect(200).expect('Hello, world!');
155+
});
156+
157+
it('should throw a 403 - Forbidden exception if the flag is disabled', async () => {
158+
jest.spyOn(defaultProvider, 'resolveBooleanEvaluation').mockResolvedValueOnce({
159+
value: false,
160+
reason: 'DISABLED',
161+
});
162+
await supertest(app.getHttpServer()).get('/require-flags-enabled').expect(403);
163+
});
164+
});
165+
});
125166
});
126167

127168
describe('Without global context interceptor', () => {
128-
129169
let moduleRef: TestingModule;
130170
let app: INestApplication;
131171

132172
beforeAll(async () => {
133-
134173
moduleRef = await Test.createTestingModule({
135174
imports: [
136175
OpenFeatureModule.forRoot({
137176
contextFactory: exampleContextFactory,
138177
defaultProvider,
139178
providers,
140-
useGlobalInterceptor: false
179+
useGlobalInterceptor: false,
141180
}),
142181
],
143182
providers: [OpenFeatureTestService],
144-
controllers: [OpenFeatureController, OpenFeatureControllerContextScopedController],
183+
controllers: [OpenFeatureController, OpenFeatureContextScopedController],
145184
}).compile();
146185
app = moduleRef.createNestApplication();
147186
app = await app.init();
@@ -158,7 +197,7 @@ describe('OpenFeature SDK', () => {
158197
});
159198

160199
describe('evaluation context service should', () => {
161-
it('inject empty context if no context interceptor is configured', async function() {
200+
it('inject empty context if no context interceptor is configured', async function () {
162201
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
163202
await supertest(app.getHttpServer())
164203
.get('/dynamic-context-in-service')
@@ -172,9 +211,26 @@ describe('OpenFeature SDK', () => {
172211
describe('With Controller bound Context interceptor', () => {
173212
it('should not use context if global context interceptor is not configured', async () => {
174213
const evaluationSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation');
175-
await supertest(app.getHttpServer()).get('/controller-context').set('x-user-id', '123').expect(200).expect('true');
214+
await supertest(app.getHttpServer())
215+
.get('/controller-context')
216+
.set('x-user-id', '123')
217+
.expect(200)
218+
.expect('true');
176219
expect(evaluationSpy).toHaveBeenCalledWith('testBooleanFlag', false, { targetingKey: '123' }, {});
177220
});
178221
});
222+
223+
describe('require flags enabled decorator', () => {
224+
it('should return a 404 - Not Found exception if the flag is disabled', async () => {
225+
jest.spyOn(providers.domainScopedClient, 'resolveBooleanEvaluation').mockResolvedValueOnce({
226+
value: false,
227+
reason: 'DISABLED',
228+
});
229+
await supertest(app.getHttpServer())
230+
.get('/controller-context/flags-enabled')
231+
.set('x-user-id', '123')
232+
.expect(404);
233+
});
234+
});
179235
});
180236
});

packages/nest/test/test-app.ts

+51-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { Controller, Get, Injectable, UseInterceptors } from '@nestjs/common';
2-
import type { Observable} from 'rxjs';
1+
import { Controller, ForbiddenException, Get, Injectable, UseInterceptors } from '@nestjs/common';
2+
import type { Observable } from 'rxjs';
33
import { map } from 'rxjs';
4-
import { BooleanFeatureFlag, ObjectFeatureFlag, NumberFeatureFlag, OpenFeatureClient, StringFeatureFlag } from '../src';
4+
import {
5+
BooleanFeatureFlag,
6+
ObjectFeatureFlag,
7+
NumberFeatureFlag,
8+
OpenFeatureClient,
9+
StringFeatureFlag,
10+
RequireFlagsEnabled,
11+
} from '../src';
512
import type { Client, EvaluationDetails, FlagValue } from '@openfeature/server-sdk';
613
import { EvaluationContextInterceptor } from '../src';
714

@@ -84,11 +91,28 @@ export class OpenFeatureController {
8491
public async handleDynamicContextInServiceRequest() {
8592
return this.testService.serviceMethodWithDynamicContext('testBooleanFlag');
8693
}
94+
95+
@RequireFlagsEnabled({
96+
flagKeys: ['testBooleanFlag'],
97+
})
98+
@Get('/flags-enabled')
99+
public async handleGuardedBooleanRequest() {
100+
return 'Get Boolean Flag Success!';
101+
}
102+
103+
@RequireFlagsEnabled({
104+
flagKeys: ['testBooleanFlag'],
105+
exception: new ForbiddenException(),
106+
})
107+
@Get('/flags-enabled-custom-exception')
108+
public async handleBooleanRequestWithCustomException() {
109+
return 'Get Boolean Flag Success!';
110+
}
87111
}
88112

89113
@Controller()
90114
@UseInterceptors(EvaluationContextInterceptor)
91-
export class OpenFeatureControllerContextScopedController {
115+
export class OpenFeatureContextScopedController {
92116
constructor(private testService: OpenFeatureTestService) {}
93117

94118
@Get('/controller-context')
@@ -101,4 +125,27 @@ export class OpenFeatureControllerContextScopedController {
101125
) {
102126
return feature.pipe(map((details) => this.testService.serviceMethod(details)));
103127
}
128+
129+
@RequireFlagsEnabled({
130+
flagKeys: ['testBooleanFlag'],
131+
domain: 'domainScopedClient',
132+
})
133+
@Get('/controller-context/flags-enabled')
134+
public async handleBooleanRequest() {
135+
return 'Get Boolean Flag Success!';
136+
}
137+
}
138+
139+
@Controller('require-flags-enabled')
140+
@RequireFlagsEnabled({
141+
flagKeys: ['testBooleanFlag'],
142+
exception: new ForbiddenException(),
143+
})
144+
export class OpenFeatureRequireFlagsEnabledController {
145+
constructor() {}
146+
147+
@Get('/')
148+
public async handleGetRequest() {
149+
return 'Hello, world!';
150+
}
104151
}

test-harness

Submodule test-harness deleted from 48c56d1

0 commit comments

Comments
 (0)