Skip to content

Commit e219dbd

Browse files
be UTests
1 parent 6b1cd2b commit e219dbd

File tree

6 files changed

+172
-6
lines changed

6 files changed

+172
-6
lines changed

redisinsight/api/src/__mocks__/cloud-auth.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { CloudAuthRequest, CloudAuthResponse, CloudAuthStatus } from 'src/modules/cloud/auth/models';
1+
import {
2+
CloudAuthIdpType, CloudAuthRequest, CloudAuthResponse, CloudAuthStatus,
3+
} from 'src/modules/cloud/auth/models';
24
import { mockSessionMetadata } from 'src/__mocks__/common';
35

46
export const mockCloudAuthCode = 'ac_p6vA6A5tF36Jf6twH2cBOqtt7n';
@@ -43,6 +45,12 @@ export const mockCloudAuthGithubTokenParams = {
4345
idpType: 'github',
4446
};
4547

48+
export const mockCloudAuthSsoTokenParams = {
49+
...mockCloudAuthGoogleTokenParams,
50+
state: 'state_p6vA6A5tF36Jf6twH2cBOqtt7ssp',
51+
idpType: 'sso',
52+
};
53+
4654
export const mockCloudAuthGoogleRequest = Object.assign(new CloudAuthRequest(), {
4755
...mockCloudAuthGoogleTokenParams,
4856
sessionMetadata: {
@@ -52,6 +60,15 @@ export const mockCloudAuthGoogleRequest = Object.assign(new CloudAuthRequest(),
5260
createdAt: new Date(),
5361
});
5462

63+
export const mockCloudAuthSsoRequest = Object.assign(new CloudAuthRequest(), {
64+
...mockCloudAuthGoogleRequest,
65+
...mockCloudAuthSsoTokenParams,
66+
tokenManager: { storage: {} },
67+
createdAt: new Date(),
68+
idpType: CloudAuthIdpType.Sso,
69+
idp: 'idp_p6vA6A5tF36Jf6twH2cBOqtSSO',
70+
});
71+
5572
export const mockCloudAuthGoogleAuthUrl = `${mockCloudAuthGoogleRequest.issuer}`
5673
+ `/${mockCloudAuthGoogleRequest.authorizeUrl}`
5774
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -66,6 +83,20 @@ export const mockCloudAuthGoogleAuthUrl = `${mockCloudAuthGoogleRequest.issuer}`
6683
+ `&${new URLSearchParams({ scope: mockCloudAuthGoogleRequest.scopes.join(' ') }).toString()}`
6784
+ '&prompt=login';
6885

86+
export const mockCloudAuthSsoAuthUrl = `${mockCloudAuthSsoRequest.issuer}`
87+
+ `/${mockCloudAuthSsoRequest.authorizeUrl}`
88+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
89+
+ `&${new URLSearchParams({ redirect_uri: mockCloudAuthSsoRequest.redirectUri }).toString()}`
90+
+ `&response_type=${mockCloudAuthSsoRequest.responseType}`
91+
+ `&response_mode=${mockCloudAuthSsoRequest.responseMode}`
92+
+ `&idp=${mockCloudAuthSsoRequest.idp}`
93+
+ `&state=${mockCloudAuthSsoRequest.state}`
94+
+ `&nonce=${mockCloudAuthSsoRequest.nonce}`
95+
+ `&code_challenge_method=${mockCloudAuthSsoRequest.codeChallengeMethod}`
96+
+ `&code_challenge=${mockCloudAuthSsoRequest.codeChallenge}`
97+
+ `&${new URLSearchParams({ scope: mockCloudAuthSsoRequest.scopes.join(' ') }).toString()}`
98+
+ '&prompt=login';
99+
69100
export const mockCloudAuthGoogleTokenUrl = `${mockCloudAuthGoogleRequest.issuer}`
70101
+ `/${mockCloudAuthGoogleRequest.tokenUrl}`
71102
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -83,6 +114,12 @@ export const mockCloudAuthGoogleRevokeTokenUrl = `${mockCloudAuthGoogleRequest.i
83114
+ `&token_type_hint=${mockCloudRevokeRefreshTokenHint}`
84115
+ `&token=${mockCloudRefreshToken}`;
85116

117+
export const mockCloudAuthSsoRevokeTokenUrl = `${mockCloudAuthSsoRequest.issuer}`
118+
+ `/${mockCloudAuthGoogleIdpConfig.revokeTokenUrl}`
119+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
120+
+ `&token_type_hint=${mockCloudRevokeRefreshTokenHint}`
121+
+ `&token=${mockCloudRefreshToken}`;
122+
86123
export const mockCloudAuthGoogleRenewTokenUrl = `${mockCloudAuthGoogleRequest.issuer}`
87124
+ `/${mockCloudAuthGoogleIdpConfig.tokenUrl}`
88125
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -91,6 +128,14 @@ export const mockCloudAuthGoogleRenewTokenUrl = `${mockCloudAuthGoogleRequest.is
91128
+ `&${new URLSearchParams({ scope: mockCloudAuthGoogleRequest.scopes.join(' ') }).toString()}`
92129
+ `&refresh_token=${mockCloudRefreshToken}`;
93130

131+
export const mockCloudAuthSsoRenewTokenUrl = `${mockCloudAuthSsoRequest.issuer}`
132+
+ `/${mockCloudAuthGoogleIdpConfig.tokenUrl}`
133+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
134+
+ '&grant_type=refresh_token'
135+
+ `&${new URLSearchParams({ redirect_uri: mockCloudAuthSsoRequest.redirectUri }).toString()}`
136+
+ `&${new URLSearchParams({ scope: mockCloudAuthSsoRequest.scopes.join(' ') }).toString()}`
137+
+ `&refresh_token=${mockCloudRefreshToken}`;
138+
94139
export const mockCloudAuthGithubRequest = Object.assign(new CloudAuthRequest(), {
95140
...mockCloudAuthGithubTokenParams,
96141
sessionMetadata: {
@@ -160,6 +205,12 @@ export const mockGoogleIdpCloudAuthStrategy = jest.fn(() => ({
160205
generateRenewTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthGoogleRenewTokenUrl)),
161206
}));
162207

208+
export const mockSsoIdpCloudAuthStrategy = jest.fn(() => ({
209+
generateAuthRequest: jest.fn().mockResolvedValue(mockCloudAuthSsoRequest),
210+
generateRevokeTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthSsoRevokeTokenUrl)),
211+
generateRenewTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthSsoRenewTokenUrl)),
212+
}));
213+
163214
export const mockCloudAuthService = jest.fn(() => ({
164215
renewTokens: jest.fn().mockResolvedValue(undefined),
165216
}));
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import axios from 'axios';
2+
import { Test, TestingModule } from '@nestjs/testing';
3+
import { EventEmitter2 } from '@nestjs/event-emitter';
4+
import { mockSessionMetadata } from 'src/__mocks__';
5+
import { mockCloudAuthSsoRequest, mockCloudAuthSsoTokenParams, mockOktaAuthClient } from 'src/__mocks__/cloud-auth';
6+
import { OktaAuth } from '@okta/okta-auth-js';
7+
import { SsoIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/sso-idp.cloud.auth-strategy';
8+
import { CloudAuthIdpType } from 'src/modules/cloud/auth/models';
9+
import {
10+
CloudOauthSsoUnsupportedEmailException
11+
} from 'src/modules/cloud/auth/exceptions/cloud-oauth.sso-unsupported-email.exception';
12+
13+
jest.mock('@okta/okta-auth-js');
14+
const mockedAxios = axios as jest.Mocked<typeof axios>;
15+
jest.mock('axios');
16+
17+
describe('CloudAuthStrategy', () => {
18+
let ssoStrategy: SsoIdpCloudAuthStrategy;
19+
20+
beforeEach(async () => {
21+
jest.clearAllMocks();
22+
jest.mock('axios', () => mockedAxios);
23+
(OktaAuth as any).mockReturnValueOnce(mockOktaAuthClient);
24+
25+
const module: TestingModule = await Test.createTestingModule({
26+
providers: [
27+
EventEmitter2,
28+
SsoIdpCloudAuthStrategy,
29+
],
30+
}).compile();
31+
32+
ssoStrategy = await module.get(SsoIdpCloudAuthStrategy);
33+
});
34+
35+
describe('generateAuthRequest', () => {
36+
it('Check that Sso auth request is generated', async () => {
37+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
38+
mockedAxios.get.mockResolvedValue({ data: mockCloudAuthSsoRequest.idp });
39+
40+
expect(await ssoStrategy.generateAuthRequest(mockSessionMetadata, {
41+
strategy: CloudAuthIdpType.Sso,
42+
data: {
43+
44+
},
45+
})).toEqual({
46+
...mockCloudAuthSsoRequest,
47+
createdAt: expect.anything(),
48+
});
49+
});
50+
it('should throw CloudOauthSsoUnsupportedEmailException in case of idp check error ', async () => {
51+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
52+
mockedAxios.get.mockRejectedValueOnce(new Error());
53+
54+
await expect(ssoStrategy.generateAuthRequest(mockSessionMetadata, {
55+
strategy: CloudAuthIdpType.Sso,
56+
})).rejects.toThrow(CloudOauthSsoUnsupportedEmailException);
57+
});
58+
it('should throw CloudOauthSsoUnsupportedEmailException in case of idp check error ', async () => {
59+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
60+
mockedAxios.get.mockRejectedValueOnce(new Error());
61+
62+
await expect(ssoStrategy.generateAuthRequest(mockSessionMetadata))
63+
.rejects.toThrow(CloudOauthSsoUnsupportedEmailException);
64+
});
65+
});
66+
});

redisinsight/api/src/modules/cloud/auth/cloud-auth.service.spec.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,33 @@ import {
1616
mockCloudAuthResponse,
1717
mockCloudRefreshTokenNew,
1818
mockGithubIdpCloudAuthStrategy,
19-
mockGoogleIdpCloudAuthStrategy,
19+
mockGoogleIdpCloudAuthStrategy, mockSsoIdpCloudAuthStrategy,
2020
mockTokenResponse,
2121
mockTokenResponseNew,
2222
} from 'src/__mocks__/cloud-auth';
2323
import { Test, TestingModule } from '@nestjs/testing';
2424
import { EventEmitter2 } from '@nestjs/event-emitter';
2525
import { CloudSessionService } from 'src/modules/cloud/session/cloud-session.service';
2626
import {
27-
mockAxiosBadRequestError, mockCloudApiAuthDto, mockCloudSessionService, mockSessionMetadata, MockType
27+
mockAxiosBadRequestError, mockCloudApiAuthDto, mockCloudSessionService, mockSessionMetadata, MockType,
2828
} from 'src/__mocks__';
2929
import { GithubIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/github-idp.cloud.auth-strategy';
3030
import { GoogleIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/google-idp.cloud.auth-strategy';
3131
import { CloudAuthAnalytics } from 'src/modules/cloud/auth/cloud-auth.analytics';
3232
import { CloudAuthIdpType, CloudAuthStatus } from 'src/modules/cloud/auth/models';
3333
import {
34+
CloudOauthMisconfigurationException,
3435
CloudOauthMissedRequiredDataException,
3536
CloudOauthUnexpectedErrorException,
3637
CloudOauthUnknownAuthorizationRequestException,
3738
} from 'src/modules/cloud/auth/exceptions';
3839
import { InternalServerErrorException } from '@nestjs/common';
3940
import { CloudSsoFeatureStrategy } from 'src/modules/cloud/cloud-sso.feature.flag';
4041
import { CloudApiUnauthorizedException } from 'src/modules/cloud/common/exceptions';
42+
import { SsoIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/sso-idp.cloud.auth-strategy';
43+
import {
44+
CloudOauthSsoUnsupportedEmailException
45+
} from 'src/modules/cloud/auth/exceptions/cloud-oauth.sso-unsupported-email.exception';
4146

4247
const mockedAxios = axios as jest.Mocked<typeof axios>;
4348
jest.mock('axios');
@@ -46,6 +51,7 @@ describe('CloudAuthService', () => {
4651
let service: CloudAuthService;
4752
let analytics: MockType<CloudAuthAnalytics>;
4853
let sessionService: MockType<CloudSessionService>;
54+
let ssoIdpCLoudAuthStrategy: MockType<SsoIdpCloudAuthStrategy>;
4955

5056
beforeEach(async () => {
5157
jest.clearAllMocks();
@@ -66,6 +72,10 @@ describe('CloudAuthService', () => {
6672
provide: GoogleIdpCloudAuthStrategy,
6773
useFactory: mockGoogleIdpCloudAuthStrategy,
6874
},
75+
{
76+
provide: SsoIdpCloudAuthStrategy,
77+
useFactory: mockSsoIdpCloudAuthStrategy,
78+
},
6979
{
7080
provide: CloudAuthAnalytics,
7181
useFactory: mockCloudAuthAnalytics,
@@ -76,6 +86,7 @@ describe('CloudAuthService', () => {
7686
service = await module.get(CloudAuthService);
7787
analytics = await module.get(CloudAuthAnalytics);
7888
sessionService = await module.get(CloudSessionService);
89+
ssoIdpCLoudAuthStrategy = await module.get(SsoIdpCloudAuthStrategy);
7990
});
8091

8192
describe('getAuthStrategy', () => {
@@ -85,6 +96,9 @@ describe('CloudAuthService', () => {
8596
it('should get GitHub auth strategy', async () => {
8697
expect(service.getAuthStrategy(CloudAuthIdpType.GitHub)).toEqual(service['githubIdpCloudAuthStrategy']);
8798
});
99+
it('should get Sso auth strategy', async () => {
100+
expect(service.getAuthStrategy(CloudAuthIdpType.Sso)).toEqual(service['ssoIdpCloudAuthStrategy']);
101+
});
88102
it('should throw CloudOauthUnknownAuthorizationRequestException error for unsupported strategy', async () => {
89103
try {
90104
service.getAuthStrategy('cognito' as CloudAuthIdpType);
@@ -134,12 +148,27 @@ describe('CloudAuthService', () => {
134148
{
135149
strategy: CloudAuthIdpType.GitHub,
136150
},
137-
)).rejects.toThrow(Error);
151+
)).rejects.toThrow(CloudOauthMisconfigurationException);
138152
expect(logoutSpy).toHaveBeenCalled();
139153
// previous request should stay
140154
expect(service['authRequests'].size).toEqual(1);
141155
expect(service['authRequests'].get(mockCloudAuthGoogleRequest.state)).toEqual(mockCloudAuthGoogleRequest);
142156
});
157+
it('should throw CloudOauthSsoUnsupportedEmailException when no email assign to SAML config', async () => {
158+
ssoIdpCLoudAuthStrategy.generateAuthRequest.mockRejectedValueOnce(new CloudOauthSsoUnsupportedEmailException());
159+
service['authRequests'].set(mockCloudAuthGoogleRequest.state, mockCloudAuthGoogleRequest);
160+
expect(service['authRequests'].size).toEqual(1);
161+
await expect(service.getAuthorizationUrl(
162+
mockSessionMetadata,
163+
{
164+
strategy: CloudAuthIdpType.Sso,
165+
},
166+
)).rejects.toThrow(CloudOauthSsoUnsupportedEmailException);
167+
expect(logoutSpy).not.toHaveBeenCalled();
168+
// previous request should stay
169+
expect(service['authRequests'].size).toEqual(1);
170+
expect(service['authRequests'].get(mockCloudAuthGoogleRequest.state)).toEqual(mockCloudAuthGoogleRequest);
171+
});
143172
});
144173
describe('exchangeCode', () => {
145174
it('should exchange auth code to access token', async () => {

redisinsight/api/src/modules/cloud/user/cloud-user.api.service.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CloudAuthService } from 'src/modules/cloud/auth/cloud-auth.service';
2323
import { mockCloudAuthService } from 'src/__mocks__/cloud-auth';
2424
import axios from 'axios';
2525
import { ServerService } from 'src/modules/server/server.service';
26+
import { CloudAuthIdpType } from 'src/modules/cloud/auth/models';
2627

2728
const mockedAxios = axios as jest.Mocked<typeof axios>;
2829
jest.mock('axios');
@@ -231,6 +232,21 @@ describe('CloudUserApiService', () => {
231232
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
232233
expect(mockedAxios.post).toHaveBeenNthCalledWith(1, 'login', expect.anything(), expect.anything());
233234
});
235+
it('should login and get csrf when no apiSessionId login should be sent with "auth_mode"', async () => {
236+
sessionService.getSession.mockResolvedValueOnce({ idpType: CloudAuthIdpType.Sso });
237+
238+
expect(await service['ensureLogin'](mockSessionMetadata)).toEqual(undefined);
239+
expect(spyEnsureAccessToken).toHaveBeenCalledTimes(1);
240+
expect(spyEnsureCsrf).toHaveBeenCalledTimes(1);
241+
expect(sessionService.getSession).toHaveBeenCalledWith(mockSessionMetadata.sessionId);
242+
expect(sessionService.updateSessionData).toHaveBeenCalledWith(mockSessionMetadata.sessionId, {
243+
apiSessionId: mockCloudApiAuthDto.apiSessionId,
244+
});
245+
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
246+
expect(mockedAxios.post).toHaveBeenNthCalledWith(1, 'login', expect.objectContaining({
247+
auth_mode: CloudAuthIdpType.Sso,
248+
}), expect.anything());
249+
});
234250
it('should throw unauthorized error when no session id successfully fetched', async () => {
235251
when(mockedAxios.post).calledWith('login', expect.anything(), expect.anything())
236252
.mockResolvedValue({

redisinsight/api/src/modules/cloud/user/providers/cloud-user.api.provider.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ describe('CloudUserApiProvider', () => {
6464
mockedAxios.post.mockResolvedValue(response);
6565

6666
expect(await service.getApiSessionId(mockCloudSession)).toEqual(mockCloudApiAuthDto.apiSessionId);
67-
expect(mockedAxios.post).toHaveBeenCalledWith('login', {}, {
67+
expect(mockedAxios.post).toHaveBeenCalledWith('login', {
68+
auth_mode: mockCloudSession.idpType,
69+
}, {
6870
...mockCloudApiHeaders,
6971
});
7072
});
@@ -81,6 +83,7 @@ describe('CloudUserApiProvider', () => {
8183
)).toEqual(mockCloudApiAuthDto.apiSessionId);
8284

8385
expect(mockedAxios.post).toHaveBeenCalledWith('login', {
86+
auth_mode: mockCloudSession.idpType,
8487
utm_source: 's',
8588
utm_medium: 'm',
8689
utm_campaign: 'c',
@@ -101,6 +104,7 @@ describe('CloudUserApiProvider', () => {
101104
)).toEqual(mockCloudApiAuthDto.apiSessionId);
102105

103106
expect(mockedAxios.post).toHaveBeenCalledWith('login', {
107+
auth_mode: mockCloudSession.idpType,
104108
utm_medium: 'm',
105109
}, {
106110
...mockCloudApiHeaders,

redisinsight/api/src/modules/cloud/user/providers/cloud-user.api.provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class CloudUserApiProvider extends CloudApiProvider {
4141
'login',
4242
{
4343
...CloudApiProvider.generateUtmBody(utm),
44-
auth_mode: credentials.idpType,
44+
auth_mode: credentials?.idpType,
4545
},
4646
{
4747
...CloudApiProvider.getHeaders(credentials),

0 commit comments

Comments
 (0)