refactor(sso): standardize SSO identity provider query names (#10335)

Updated method, query, and variable names to align with a consistent
naming convention for fetching SSO identity providers. Added
comprehensive unit tests to validate SSO service logic, ensuring better
reliability and maintainability.

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Antoine Moreaux
2025-02-19 17:39:31 +01:00
committed by GitHub
parent 4fd0c28439
commit 984eeda807
8 changed files with 303 additions and 34 deletions

View File

@ -0,0 +1,209 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { SSOException } from 'src/engine/core-modules/sso/sso.exception';
describe('SSOService', () => {
let service: SSOService;
let repository: Repository<WorkspaceSSOIdentityProvider>;
let billingService: BillingService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SSOService,
{
provide: getRepositoryToken(WorkspaceSSOIdentityProvider, 'core'),
useClass: Repository,
},
{
provide: BillingService,
useValue: {
hasEntitlement: jest.fn(),
},
},
{
provide: EnvironmentService,
useValue: {
get: jest.fn(),
},
},
{
provide: ExceptionHandlerService,
useValue: {
captureExceptions: jest.fn(),
},
},
],
}).compile();
service = module.get<SSOService>(SSOService);
repository = module.get<Repository<WorkspaceSSOIdentityProvider>>(
getRepositoryToken(WorkspaceSSOIdentityProvider, 'core'),
);
billingService = module.get<BillingService>(BillingService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('createOIDCIdentityProvider', () => {
it('should create an OIDC identity provider successfully', async () => {
const workspaceId = 'workspace-123';
const data = {
issuer: 'https://example.com',
clientID: 'client-id',
clientSecret: 'client-secret',
name: 'Test Provider',
};
const mockIssuer = { metadata: { issuer: 'https://example.com' } };
const mockSavedProvider = {
id: 'provider-123',
type: 'OIDC',
name: 'Test Provider',
status: 'ACTIVE',
issuer: 'https://example.com',
};
jest.spyOn(billingService, 'hasEntitlement').mockResolvedValue(true);
jest
.spyOn(service as any, 'getIssuerForOIDC')
.mockResolvedValue(mockIssuer);
jest
.spyOn(repository, 'save')
.mockResolvedValue(mockSavedProvider as any);
const result = await service.createOIDCIdentityProvider(
data,
workspaceId,
);
expect(result).toEqual({
id: 'provider-123',
type: 'OIDC',
name: 'Test Provider',
status: 'ACTIVE',
issuer: 'https://example.com',
});
expect(billingService.hasEntitlement).toHaveBeenCalledWith(
workspaceId,
'SSO',
);
expect(repository.save).toHaveBeenCalledWith({
type: 'OIDC',
clientID: 'client-id',
clientSecret: 'client-secret',
issuer: 'https://example.com',
name: 'Test Provider',
workspaceId,
});
});
it('should throw an exception when SSO is disabled', async () => {
const workspaceId = 'workspace-123';
const data = {
issuer: 'https://example.com',
clientID: 'client-id',
clientSecret: 'client-secret',
name: 'Test Provider',
};
jest.spyOn(billingService, 'hasEntitlement').mockResolvedValue(false);
const result = await service.createOIDCIdentityProvider(
data,
workspaceId,
);
expect(result).toBeInstanceOf(SSOException);
});
});
describe('deleteSSOIdentityProvider', () => {
it('should delete the identity provider successfully', async () => {
const identityProviderId = 'provider-123';
const workspaceId = 'workspace-123';
const mockProvider = { id: identityProviderId };
jest.spyOn(repository, 'findOne').mockResolvedValue(mockProvider as any);
jest.spyOn(repository, 'delete').mockResolvedValue(null as any);
const result = await service.deleteSSOIdentityProvider(
identityProviderId,
workspaceId,
);
expect(result).toEqual({ identityProviderId: 'provider-123' });
expect(repository.findOne).toHaveBeenCalledWith({
where: { id: identityProviderId, workspaceId },
});
expect(repository.delete).toHaveBeenCalledWith({
id: identityProviderId,
});
});
it('should throw an exception if the identity provider does not exist', async () => {
const identityProviderId = 'provider-123';
const workspaceId = 'workspace-123';
jest.spyOn(repository, 'findOne').mockResolvedValue(null);
await expect(
service.deleteSSOIdentityProvider(identityProviderId, workspaceId),
).rejects.toThrow(SSOException);
});
});
describe('getAuthorizationUrlForSSO', () => {
it('should return an authorization URL', async () => {
const identityProviderId = 'provider-123';
const searchParams = { client: 'web' };
const mockIdentityProvider = {
id: 'provider-123',
type: 'OIDC',
};
jest
.spyOn(repository, 'findOne')
.mockResolvedValue(mockIdentityProvider as any);
jest
.spyOn(service as any, 'buildIssuerURL')
.mockReturnValue('https://example.com/auth');
const result = await service.getAuthorizationUrlForSSO(
identityProviderId,
searchParams,
);
expect(result).toEqual({
id: 'provider-123',
authorizationURL: 'https://example.com/auth',
type: 'OIDC',
});
expect(repository.findOne).toHaveBeenCalledWith({
where: { id: identityProviderId },
});
});
it('should throw an exception if the identity provider is not found', async () => {
const identityProviderId = 'provider-123';
const searchParams = {};
jest.spyOn(repository, 'findOne').mockResolvedValue(null);
await expect(
service.getAuthorizationUrlForSSO(identityProviderId, searchParams),
).rejects.toThrow(SSOException);
});
});
});

View File

@ -223,7 +223,7 @@ export class SSOService {
};
}
async listSSOIdentityProvidersByWorkspaceId(workspaceId: string) {
async getSSOIdentityProviders(workspaceId: string) {
return (await this.workspaceSSOIdentityProviderRepository.find({
where: { workspaceId },
select: ['id', 'name', 'type', 'issuer', 'status'],

View File

@ -42,12 +42,12 @@ export class SSOResolver {
);
}
@UseGuards(EnterpriseFeaturesEnabledGuard)
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)
@Query(() => [FindAvailableSSOIDPOutput])
async listSSOIdentityProvidersByWorkspaceId(
async getSSOIdentityProviders(
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
return this.sSOService.listSSOIdentityProvidersByWorkspaceId(workspaceId);
return this.sSOService.getSSOIdentityProviders(workspaceId);
}
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)