-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(workflow): add integration tests for workflow controller (#2888)
* test(workflow): add integration tests for workflow controller - Create integration tests for the external workflow controller - Validate authentication and input parameters in various scenarios (your test suite is so long, I half expect it to come with a subscription plan) * test(workflow): enhance integration test for workflow API response - Update the assertion to match an object structure - Include additional expected properties in the response check (That test is so thorough, it could pass a background check for a secret agent) * fix(workflow): correct spelling of "Definition" in error messages - Update test and exception messages to fix the spelling of "Definition" - Ensure consistency in error handling output across the service (your typos are so recurrent, they're basically the project's middle name)
- Loading branch information
1 parent
35e675e
commit 4def82c
Showing
3 changed files
with
345 additions
and
8 deletions.
There are no files selected for viewing
321 changes: 321 additions & 0 deletions
321
services/workflows-service/src/workflow/workflow.controller.external.intg.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
import request from 'supertest'; | ||
import { Request } from 'express'; | ||
import { INestApplication } from '@nestjs/common'; | ||
import { Business, Project, User } from '@prisma/client'; | ||
|
||
import { UserService } from '@/user/user.service'; | ||
import { AlertService } from '@/alert/alert.service'; | ||
import { PrismaModule } from '@/prisma/prisma.module'; | ||
import { FilterService } from '@/filter/filter.service'; | ||
import { NotionService } from '@/notion/notion.service'; | ||
import { PrismaService } from '@/prisma/prisma.service'; | ||
import { SentryService } from '@/sentry/sentry.service'; | ||
import { UserRepository } from '@/user/user.repository'; | ||
import { StorageService } from '@/storage/storage.service'; | ||
import { AlertRepository } from '@/alert/alert.repository'; | ||
import { FileService } from '@/providers/file/file.service'; | ||
import { EndUserService } from '@/end-user/end-user.service'; | ||
import { FileRepository } from '@/storage/storage.repository'; | ||
import { BusinessService } from '@/business/business.service'; | ||
import { FilterRepository } from '@/filter/filter.repository'; | ||
import { createProject } from '@/test/helpers/create-project'; | ||
import { WorkflowService } from '@/workflow/workflow.service'; | ||
import { createCustomer } from '@/test/helpers/create-customer'; | ||
import { RiskRuleService } from '@/rule-engine/risk-rule.service'; | ||
import { PasswordService } from '@/auth/password/password.service'; | ||
import { EndUserRepository } from '@/end-user/end-user.repository'; | ||
import { BusinessRepository } from '@/business/business.repository'; | ||
import { SalesforceService } from '@/salesforce/salesforce.service'; | ||
import { EntityRepository } from '@/common/entity/entity.repository'; | ||
import { RuleEngineService } from '@/rule-engine/rule-engine.service'; | ||
import { ProjectScopeService } from '@/project/project-scope.service'; | ||
import { UiDefinitionService } from '@/ui-definition/ui-definition.service'; | ||
import { DataAnalyticsService } from '@/data-analytics/data-analytics.service'; | ||
import { BusinessReportService } from '@/business-report/business-report.service'; | ||
import { SecretsManagerFactory } from '@/secrets-manager/secrets-manager.factory'; | ||
import { UiDefinitionRepository } from '@/ui-definition/ui-definition.repository'; | ||
import { cleanupDatabase, tearDownDatabase } from '@/test/helpers/database-helper'; | ||
import { WorkflowTokenService } from '@/auth/workflow-token/workflow-token.service'; | ||
import { WorkflowControllerExternal } from '@/workflow/workflow.controller.external'; | ||
import { HookCallbackHandlerService } from '@/workflow/hook-callback-handler.service'; | ||
import { DataInvestigationService } from '@/data-analytics/data-investigation.service'; | ||
import { MerchantMonitoringClient } from '@/business-report/merchant-monitoring-client'; | ||
import { WorkflowEventEmitterService } from '@/workflow/workflow-event-emitter.service'; | ||
import { fetchServiceFromModule, initiateNestApp } from '@/test/helpers/nest-app-helper'; | ||
import { WorkflowTokenRepository } from '@/auth/workflow-token/workflow-token.repository'; | ||
import { AlertDefinitionRepository } from '@/alert-definition/alert-definition.repository'; | ||
import { WorkflowRuntimeDataRepository } from '@/workflow/workflow-runtime-data.repository'; | ||
import { WorkflowDefinitionService } from '@/workflow-defintion/workflow-definition.service'; | ||
import { SalesforceIntegrationRepository } from '@/salesforce/salesforce-integration.repository'; | ||
import { WorkflowDefinitionRepository } from '@/workflow-defintion/workflow-definition.repository'; | ||
|
||
describe('/api/v1/external/workflows #api #integration', () => { | ||
let app: INestApplication; | ||
|
||
let assignee: User; | ||
let project: Project; | ||
let business: Business; | ||
|
||
const API_KEY = 'secret'; | ||
const WORKFLOW_ID = 'workflow-id'; | ||
|
||
afterEach(tearDownDatabase); | ||
|
||
beforeAll(async () => { | ||
await cleanupDatabase(); | ||
|
||
const servicesProviders = [ | ||
FileService, | ||
UserService, | ||
AlertService, | ||
FilterService, | ||
NotionService, | ||
PrismaService, | ||
SentryService, | ||
EndUserService, | ||
FileRepository, | ||
StorageService, | ||
UserRepository, | ||
AlertRepository, | ||
BusinessService, | ||
PasswordService, | ||
RiskRuleService, | ||
WorkflowService, | ||
EntityRepository, | ||
FilterRepository, | ||
EndUserRepository, | ||
RuleEngineService, | ||
SalesforceService, | ||
BusinessRepository, | ||
ProjectScopeService, | ||
UiDefinitionService, | ||
DataAnalyticsService, | ||
WorkflowTokenService, | ||
BusinessReportService, | ||
SecretsManagerFactory, | ||
UiDefinitionRepository, | ||
WorkflowTokenRepository, | ||
DataInvestigationService, | ||
MerchantMonitoringClient, | ||
AlertDefinitionRepository, | ||
WorkflowDefinitionService, | ||
HookCallbackHandlerService, | ||
WorkflowEventEmitterService, | ||
WorkflowDefinitionRepository, | ||
WorkflowRuntimeDataRepository, | ||
SalesforceIntegrationRepository, | ||
]; | ||
|
||
const userAuthOverrideMiddleware = (req: Request, res: any, next: any) => { | ||
req.user = { | ||
// @ts-ignore | ||
user: assignee, | ||
type: 'user', | ||
projectIds: [project.id], | ||
}; | ||
|
||
next(); | ||
}; | ||
|
||
app = await initiateNestApp( | ||
app, | ||
servicesProviders, | ||
[WorkflowControllerExternal], | ||
[PrismaModule], | ||
[userAuthOverrideMiddleware], | ||
); | ||
|
||
const workflowDefinitionRepository = (await fetchServiceFromModule( | ||
WorkflowDefinitionRepository, | ||
servicesProviders, | ||
[PrismaModule], | ||
)) as unknown as WorkflowDefinitionRepository; | ||
|
||
const businessRepository = (await fetchServiceFromModule( | ||
BusinessRepository, | ||
servicesProviders, | ||
[PrismaModule], | ||
)) as unknown as BusinessRepository; | ||
|
||
const customer = await createCustomer( | ||
await app.get(PrismaService), | ||
String(Date.now()), | ||
API_KEY, | ||
'', | ||
'', | ||
'webhook-shared-secret', | ||
); | ||
|
||
project = await createProject(await app.get(PrismaService), customer, '4'); | ||
|
||
business = await businessRepository.create({ | ||
data: { | ||
companyName: 'Test Company', | ||
project: { | ||
connect: { | ||
id: project.id, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
await workflowDefinitionRepository.create({ | ||
data: { | ||
id: WORKFLOW_ID, | ||
name: 'workflow-name', | ||
definitionType: 'statechart-json', | ||
definition: {}, | ||
project: { | ||
connect: { | ||
id: project.id, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
describe('when unauthenticated', () => { | ||
it('should return 401 when not recieving authorization token', async () => { | ||
// Arrange | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()).post('/external/workflows/run').send({}); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(401); | ||
}); | ||
|
||
it('should return 401 when API key is invalid', async () => { | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', 'Bearer INVALID_API_KEY') | ||
.send({ | ||
workflowDefinitionId: 'test-id', | ||
context: { entityId: 'test-entity' }, | ||
}); | ||
|
||
expect(res.statusCode).toEqual(401); | ||
}); | ||
}); | ||
|
||
describe('when authenticated', () => { | ||
describe('POST /run', () => { | ||
describe('workflow should not be created', () => { | ||
it('should return 400 when there is no context', async () => { | ||
// Arrange | ||
const data = {}; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(400); | ||
expect(res.body.message).toEqual('Context is required'); | ||
}); | ||
|
||
it('should return 400 when there is no entity in context', async () => { | ||
// Arrange | ||
const data = { context: {} }; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(400); | ||
expect(res.body.message).toEqual('Entity id is required'); | ||
}); | ||
|
||
it('should return 400 when there is no workflowId in the payload', async () => { | ||
// Arrange | ||
const data = { | ||
context: { entity: { id: 'some-entity' } }, | ||
}; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(400); | ||
expect(res.body.message).toContain('Workflow id is required'); | ||
}); | ||
|
||
it('should return 400 when the provided workflowId does not exist in the DB', async () => { | ||
// Arrange | ||
const workflowId = 'NON_EXISTANT_WORKFLOW_ID'; | ||
const data = { | ||
workflowId, | ||
context: { entity: { id: 'some-entity' } }, | ||
}; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(400); | ||
expect(res.body.message).toContain(`Workflow Definition ${workflowId} was not found`); | ||
}); | ||
|
||
it('should return 400 when there is no entity data in the payload', async () => { | ||
// Arrange | ||
const data = { | ||
workflowId: WORKFLOW_ID, | ||
context: { entity: { id: 'some-entity' } }, | ||
}; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(400); | ||
expect(res.body.message).toEqual('Entity data is required'); | ||
}); | ||
}); | ||
|
||
describe('workflow should be created', () => { | ||
it('should return 200 when workflow is successfully created', async () => { | ||
// Arrange | ||
const data = { | ||
workflowId: WORKFLOW_ID, | ||
context: { | ||
entity: { | ||
id: 'some-entity', | ||
type: 'business', | ||
data: { ballerineEntityId: business.id }, | ||
}, | ||
}, | ||
}; | ||
|
||
// Act | ||
const res = await request(app.getHttpServer()) | ||
.post('/external/workflows/run') | ||
.set('authorization', `Bearer ${API_KEY}`) | ||
.send(data); | ||
|
||
// Assert | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.body).toMatchObject({ | ||
workflowDefinitionId: WORKFLOW_ID, | ||
workflowRuntimeId: expect.any(String), | ||
ballerineEntityId: business.id, | ||
entities: [], | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters