Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import {
|
||||
createMockSignInRequest,
|
||||
createMockSignUpRequest,
|
||||
} from '../../../helpers/mocks/authn'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await databaseConnection().getRepository('flag').createQueryBuilder().delete().execute()
|
||||
await databaseConnection().getRepository('project').createQueryBuilder().delete().execute()
|
||||
await databaseConnection().getRepository('platform').createQueryBuilder().delete().execute()
|
||||
await databaseConnection().getRepository('user').createQueryBuilder().delete().execute()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('Authentication API', () => {
|
||||
describe('Sign up Endpoint', () => {
|
||||
it('Adds new user', async () => {
|
||||
// arrange
|
||||
const mockSignUpRequest = createMockSignUpRequest()
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-up',
|
||||
body: mockSignUpRequest,
|
||||
})
|
||||
|
||||
// assert
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
expect(responseBody?.id).toHaveLength(21)
|
||||
expect(responseBody?.created).toBeDefined()
|
||||
expect(responseBody?.updated).toBeDefined()
|
||||
expect(responseBody?.verified).toBe(true)
|
||||
expect(responseBody?.email).toBe(mockSignUpRequest.email.toLocaleLowerCase().trim())
|
||||
expect(responseBody?.firstName).toBe(mockSignUpRequest.firstName)
|
||||
expect(responseBody?.lastName).toBe(mockSignUpRequest.lastName)
|
||||
expect(responseBody?.trackEvents).toBe(mockSignUpRequest.trackEvents)
|
||||
expect(responseBody?.newsLetter).toBe(mockSignUpRequest.newsLetter)
|
||||
expect(responseBody?.status).toBe('ACTIVE')
|
||||
expect(responseBody?.platformId).toBeDefined()
|
||||
expect(responseBody?.externalId).toBe(null)
|
||||
expect(responseBody?.projectId).toHaveLength(21)
|
||||
expect(responseBody?.token).toBeDefined()
|
||||
})
|
||||
|
||||
it('Creates new project for user', async () => {
|
||||
// arrange
|
||||
const mockSignUpRequest = createMockSignUpRequest()
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-up',
|
||||
body: mockSignUpRequest,
|
||||
})
|
||||
|
||||
const responseBody = response?.json()
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
|
||||
const project = await databaseConnection()
|
||||
.getRepository('project')
|
||||
.findOneBy({
|
||||
id: responseBody.projectId,
|
||||
})
|
||||
|
||||
expect(project?.ownerId).toBe(responseBody.id)
|
||||
expect(project?.displayName).toBeDefined()
|
||||
expect(project?.platformId).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sign in Endpoint', () => {
|
||||
it('Logs in existing users', async () => {
|
||||
// arrange
|
||||
const mockSignUpRequest = createMockSignUpRequest()
|
||||
|
||||
// First sign up the user
|
||||
const signUpResponse = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-up',
|
||||
body: mockSignUpRequest,
|
||||
})
|
||||
|
||||
const signUpBody = signUpResponse?.json()
|
||||
|
||||
// Then try to sign in
|
||||
const mockSignInRequest = createMockSignInRequest({
|
||||
email: mockSignUpRequest.email,
|
||||
password: mockSignUpRequest.password,
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-in',
|
||||
body: mockSignInRequest,
|
||||
})
|
||||
|
||||
// assert
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
expect(responseBody?.id).toBe(signUpBody.id)
|
||||
expect(responseBody?.email).toBe(mockSignUpRequest.email.toLowerCase().trim())
|
||||
expect(responseBody?.firstName).toBe(mockSignUpRequest.firstName)
|
||||
expect(responseBody?.lastName).toBe(mockSignUpRequest.lastName)
|
||||
expect(responseBody?.trackEvents).toBe(mockSignUpRequest.trackEvents)
|
||||
expect(responseBody?.newsLetter).toBe(mockSignUpRequest.newsLetter)
|
||||
expect(responseBody?.password).toBeUndefined()
|
||||
expect(responseBody?.status).toBe('ACTIVE')
|
||||
expect(responseBody?.verified).toBe(true)
|
||||
expect(responseBody?.platformId).toBe(signUpBody.platformId)
|
||||
expect(responseBody?.externalId).toBe(null)
|
||||
expect(responseBody?.projectId).toBe(signUpBody.projectId)
|
||||
expect(responseBody?.token).toBeDefined()
|
||||
})
|
||||
|
||||
it('Fails if password doesn\'t match', async () => {
|
||||
// arrange
|
||||
const mockSignUpRequest = createMockSignUpRequest()
|
||||
|
||||
// First sign up the user
|
||||
await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-up',
|
||||
body: mockSignUpRequest,
|
||||
})
|
||||
|
||||
const mockSignInRequest = createMockSignInRequest({
|
||||
email: mockSignUpRequest.email,
|
||||
password: 'wrong password',
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/authentication/sign-in',
|
||||
body: mockSignInRequest,
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.UNAUTHORIZED)
|
||||
const responseBody = response?.json()
|
||||
expect(responseBody?.code).toBe('INVALID_CREDENTIALS')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import { passwordHasher } from '../../../../src/app/authentication/lib/password-hasher'
|
||||
|
||||
const SCRYPT_SEPARATOR = '~'
|
||||
|
||||
describe('Password Hasher', () => {
|
||||
const plainTextPassword = 'password123'
|
||||
|
||||
describe('hash', () => {
|
||||
it('should not produce the same hash for the same password', async () => {
|
||||
const hashedPassword1 = await bcrypt.hash(plainTextPassword, 10)
|
||||
const hashedPassword2 = await bcrypt.hash(plainTextPassword, 10)
|
||||
|
||||
expect(hashedPassword1).not.toBe(hashedPassword2)
|
||||
})
|
||||
|
||||
it('should verify hashed password correctly', async () => {
|
||||
const hashedPassword = await bcrypt.hash(plainTextPassword, 10)
|
||||
|
||||
const result = await bcrypt.compare(plainTextPassword, hashedPassword)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('should fail to verify incorrect password', async () => {
|
||||
const hashedPassword = await bcrypt.hash(plainTextPassword, 10)
|
||||
const incorrectPassword = 'incorrectPassword'
|
||||
|
||||
const result = await bcrypt.compare(incorrectPassword, hashedPassword)
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('compare', () => {
|
||||
it('should return true for identical bcrypt passwords', async () => {
|
||||
const hashedPassword = await bcrypt.hash(plainTextPassword, 10)
|
||||
const result = await passwordHasher.compare(
|
||||
plainTextPassword,
|
||||
hashedPassword,
|
||||
)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for different bcrypt passwords', async () => {
|
||||
const hashedPassword = await bcrypt.hash(plainTextPassword, 10)
|
||||
const differentPassword = 'differentPassword'
|
||||
const result = await passwordHasher.compare(
|
||||
differentPassword,
|
||||
hashedPassword,
|
||||
)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for empty password bcrypt comparison', async () => {
|
||||
const hashedPassword = await bcrypt.hash(plainTextPassword, 10)
|
||||
const result = await passwordHasher.compare('', hashedPassword)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for empty hash comparison', async () => {
|
||||
const result = await passwordHasher.compare(plainTextPassword, '')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for both empty password and hash', async () => {
|
||||
const result = await passwordHasher.compare('', '')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('compare - Scrypt', () => {
|
||||
const plainTextPassword = 'BusyBeaver$LOL99'
|
||||
const salt = 'sPtDhWcd1MfdAw=='
|
||||
const hashedPassword =
|
||||
'iu1iqj6i6g9D7aBiE/Qdqv88GNnV/Ea67JK1kfLmzNgxsyCL8mhUxxI5VIHM9D+62xGHuZgjrfEBF+17wxyFIQ=='
|
||||
|
||||
it('should return true for identical scrypt passwords', async () => {
|
||||
const result = await passwordHasher.compare(
|
||||
plainTextPassword,
|
||||
`$scrypt$${hashedPassword}${SCRYPT_SEPARATOR}${salt}`,
|
||||
)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for different scrypt passwords', async () => {
|
||||
const differentPassword = 'differentPassword'
|
||||
const result = await passwordHasher.compare(
|
||||
differentPassword,
|
||||
`$scrypt$${hashedPassword}${SCRYPT_SEPARATOR}${salt}`,
|
||||
)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { PrincipalType } from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { initializeDatabase } from '../../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../../helpers/auth'
|
||||
import { mockAndSaveBasicSetup } from '../../../../helpers/mocks'
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('List flow runs endpoint', () => {
|
||||
it('should return 200', async () => {
|
||||
// arrange
|
||||
const { mockPlatform, mockOwner, mockProject } = await mockAndSaveBasicSetup()
|
||||
|
||||
const testToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
id: mockOwner.id,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/flow-runs',
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
query: {
|
||||
projectId: mockProject.id,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(200)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
apId,
|
||||
PrincipalType,
|
||||
} from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../helpers/auth'
|
||||
import {
|
||||
createMockFlow,
|
||||
createMockFlowVersion,
|
||||
createMockProject,
|
||||
mockAndSaveBasicSetup,
|
||||
} from '../../../helpers/mocks'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('Flow API for Worker', () => {
|
||||
describe('Get Flow from Worker', () => {
|
||||
it('List other flow for another project', async () => {
|
||||
// arrange
|
||||
const { mockPlatform, mockOwner, mockProject } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockProject2 = createMockProject({
|
||||
platformId: mockPlatform.id,
|
||||
ownerId: mockOwner.id,
|
||||
})
|
||||
|
||||
await databaseConnection().getRepository('project').save([mockProject2])
|
||||
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
})
|
||||
await databaseConnection().getRepository('flow_version').save([mockFlowVersion])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
id: apId(),
|
||||
type: PrincipalType.WORKER,
|
||||
})
|
||||
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: `/v1/worker/flows/${mockFlowVersion.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
expect(response?.statusCode).toBe(StatusCodes.NOT_FOUND)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1,646 @@
|
||||
import { WebhookRenewStrategy } from '@activepieces/pieces-framework'
|
||||
import {
|
||||
FlowOperationType,
|
||||
FlowStatus,
|
||||
FlowTriggerType,
|
||||
FlowVersionState,
|
||||
PackageType,
|
||||
PieceType,
|
||||
PopulatedFlow,
|
||||
PrincipalType,
|
||||
PropertyExecutionType,
|
||||
TriggerStrategy,
|
||||
TriggerTestStrategy,
|
||||
WebhookHandshakeStrategy,
|
||||
} from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../helpers/auth'
|
||||
import {
|
||||
createMockFlow,
|
||||
createMockFlowVersion,
|
||||
createMockPieceMetadata,
|
||||
mockAndSaveBasicSetup,
|
||||
} from '../../../helpers/mocks'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('Flow API', () => {
|
||||
describe('Create Flow endpoint', () => {
|
||||
it('Adds an empty flow', async () => {
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
const mockCreateFlowRequest = {
|
||||
displayName: 'test flow',
|
||||
projectId: mockProject.id,
|
||||
metadata: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/flows',
|
||||
query: {
|
||||
projectId: mockProject.id,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
body: mockCreateFlowRequest,
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.CREATED)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(Object.keys(responseBody)).toHaveLength(12)
|
||||
expect(responseBody?.id).toHaveLength(21)
|
||||
expect(responseBody?.created).toBeDefined()
|
||||
expect(responseBody?.updated).toBeDefined()
|
||||
expect(responseBody?.projectId).toBe(mockProject.id)
|
||||
expect(responseBody?.folderId).toBeNull()
|
||||
expect(responseBody?.status).toBe('DISABLED')
|
||||
expect(responseBody?.publishedVersionId).toBeNull()
|
||||
expect(responseBody?.metadata).toMatchObject({ foo: 'bar' })
|
||||
expect(responseBody?.operationStatus).toBeDefined()
|
||||
|
||||
expect(Object.keys(responseBody?.version)).toHaveLength(13)
|
||||
expect(responseBody?.version?.id).toHaveLength(21)
|
||||
expect(responseBody?.version?.created).toBeDefined()
|
||||
expect(responseBody?.version?.updated).toBeDefined()
|
||||
expect(responseBody?.version?.updatedBy).toBeNull()
|
||||
expect(responseBody?.version?.flowId).toBe(responseBody?.id)
|
||||
expect(responseBody?.version?.displayName).toBe('test flow')
|
||||
expect(Object.keys(responseBody?.version?.trigger)).toHaveLength(5)
|
||||
expect(responseBody?.version?.trigger.type).toBe('EMPTY')
|
||||
expect(responseBody?.version?.trigger.name).toBe('trigger')
|
||||
expect(responseBody?.version?.trigger.settings).toMatchObject({})
|
||||
expect(responseBody?.version?.trigger.valid).toBe(false)
|
||||
expect(responseBody?.version?.trigger.displayName).toBe('Select Trigger')
|
||||
expect(responseBody?.version?.valid).toBe(false)
|
||||
expect(responseBody?.version?.state).toBe('DRAFT')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update status endpoint', () => {
|
||||
it('Enables a disabled Flow', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockPieceMetadata1 = createMockPieceMetadata({
|
||||
name: '@activepieces/piece-schedule',
|
||||
version: '0.1.5',
|
||||
triggers: {
|
||||
'every_hour': {
|
||||
'name': 'every_hour',
|
||||
'displayName': 'Every Hour',
|
||||
'description': 'Triggers the current flow every hour',
|
||||
'requireAuth': false,
|
||||
'props': {
|
||||
|
||||
},
|
||||
'type': TriggerStrategy.POLLING,
|
||||
'sampleData': {
|
||||
|
||||
},
|
||||
'testStrategy': TriggerTestStrategy.TEST_FUNCTION,
|
||||
},
|
||||
},
|
||||
pieceType: PieceType.OFFICIAL,
|
||||
packageType: PackageType.REGISTRY,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('piece_metadata')
|
||||
.save([mockPieceMetadata1])
|
||||
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.DISABLED,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
updatedBy: mockOwner.id,
|
||||
trigger: {
|
||||
type: FlowTriggerType.PIECE,
|
||||
settings: {
|
||||
pieceName: '@activepieces/piece-schedule',
|
||||
pieceVersion: '0.1.5',
|
||||
input: {
|
||||
run_on_weekends: false,
|
||||
},
|
||||
triggerName: 'every_hour',
|
||||
propertySettings: {
|
||||
'run_on_weekends': {
|
||||
type: PropertyExecutionType.MANUAL,
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: true,
|
||||
name: 'trigger',
|
||||
displayName: 'Schedule',
|
||||
},
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
await databaseConnection().getRepository('flow').update(mockFlow.id, {
|
||||
publishedVersionId: mockFlowVersion.id,
|
||||
})
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
})
|
||||
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/flows/${mockFlow.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
body: {
|
||||
type: FlowOperationType.CHANGE_STATUS,
|
||||
request: {
|
||||
status: 'ENABLED',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody: PopulatedFlow | undefined = response?.json()
|
||||
expect(responseBody).toBeDefined()
|
||||
if (responseBody) {
|
||||
expect(responseBody.id).toBe(mockFlow.id)
|
||||
expect(responseBody.created).toBeDefined()
|
||||
expect(responseBody.updated).toBeDefined()
|
||||
expect(responseBody.projectId).toBe(mockProject.id)
|
||||
expect(responseBody.folderId).toBeNull()
|
||||
expect(responseBody.publishedVersionId).toBe(mockFlowVersion.id)
|
||||
expect(responseBody.metadata).toBeNull()
|
||||
expect(responseBody.operationStatus).toBe('ENABLING')
|
||||
expect(Object.keys(responseBody.version)).toHaveLength(13)
|
||||
expect(responseBody.version.id).toBe(mockFlowVersion.id)
|
||||
}
|
||||
})
|
||||
|
||||
it('Disables an enabled Flow', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.ENABLED,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
updatedBy: mockOwner.id,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
await databaseConnection().getRepository('flow').update(mockFlow.id, {
|
||||
publishedVersionId: mockFlowVersion.id,
|
||||
})
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
})
|
||||
|
||||
const mockUpdateFlowStatusRequest = {
|
||||
type: FlowOperationType.CHANGE_STATUS,
|
||||
request: {
|
||||
status: 'DISABLED',
|
||||
},
|
||||
}
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/flows/${mockFlow.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
body: mockUpdateFlowStatusRequest,
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(Object.keys(responseBody)).toHaveLength(12)
|
||||
expect(responseBody?.id).toBe(mockFlow.id)
|
||||
expect(responseBody?.created).toBeDefined()
|
||||
expect(responseBody?.updated).toBeDefined()
|
||||
expect(responseBody?.projectId).toBe(mockProject.id)
|
||||
expect(responseBody?.folderId).toBeNull()
|
||||
expect(responseBody?.status).toBe('ENABLED')
|
||||
expect(responseBody?.publishedVersionId).toBe(mockFlowVersion.id)
|
||||
expect(responseBody?.metadata).toBeNull()
|
||||
expect(responseBody?.operationStatus).toBe('DISABLING')
|
||||
|
||||
expect(Object.keys(responseBody?.version)).toHaveLength(13)
|
||||
expect(responseBody?.version?.id).toBe(mockFlowVersion.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update published version id endpoint', () => {
|
||||
it('Publishes latest draft version', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockPieceMetadata1 = createMockPieceMetadata({
|
||||
name: '@activepieces/piece-schedule',
|
||||
version: '0.1.5',
|
||||
triggers: {
|
||||
'every_hour': {
|
||||
'name': 'every_hour',
|
||||
'displayName': 'Every Hour',
|
||||
'description': 'Triggers the current flow every hour',
|
||||
'requireAuth': true,
|
||||
'props': {
|
||||
|
||||
},
|
||||
'type': TriggerStrategy.WEBHOOK,
|
||||
'handshakeConfiguration': {
|
||||
'strategy': WebhookHandshakeStrategy.NONE,
|
||||
},
|
||||
'renewConfiguration': {
|
||||
'strategy': WebhookRenewStrategy.NONE,
|
||||
},
|
||||
'sampleData': {
|
||||
|
||||
},
|
||||
'testStrategy': TriggerTestStrategy.TEST_FUNCTION,
|
||||
},
|
||||
},
|
||||
pieceType: PieceType.OFFICIAL,
|
||||
packageType: PackageType.REGISTRY,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('piece_metadata')
|
||||
.save([mockPieceMetadata1])
|
||||
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.DISABLED,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
updatedBy: mockOwner.id,
|
||||
state: FlowVersionState.DRAFT,
|
||||
trigger: {
|
||||
type: FlowTriggerType.PIECE,
|
||||
settings: {
|
||||
pieceName: '@activepieces/piece-schedule',
|
||||
pieceVersion: '0.1.5',
|
||||
input: {
|
||||
run_on_weekends: false,
|
||||
},
|
||||
triggerName: 'every_hour',
|
||||
propertySettings: {
|
||||
'run_on_weekends': {
|
||||
type: PropertyExecutionType.MANUAL,
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: true,
|
||||
name: 'trigger',
|
||||
displayName: 'Schedule',
|
||||
},
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
id: mockOwner.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/flows/${mockFlow.id}`,
|
||||
body: {
|
||||
type: FlowOperationType.LOCK_AND_PUBLISH,
|
||||
request: {},
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody: PopulatedFlow | undefined = response?.json()
|
||||
expect(responseBody).toBeDefined()
|
||||
if (responseBody) {
|
||||
expect(Object.keys(responseBody)).toHaveLength(12)
|
||||
expect(responseBody.id).toBe(mockFlow.id)
|
||||
expect(responseBody.created).toBeDefined()
|
||||
expect(responseBody.updated).toBeDefined()
|
||||
expect(responseBody.projectId).toBe(mockProject.id)
|
||||
expect(responseBody.folderId).toBeNull()
|
||||
expect(responseBody.status).toBe(mockFlow.status)
|
||||
expect(responseBody.publishedVersionId).toBe(mockFlowVersion.id)
|
||||
expect(responseBody.metadata).toBeNull()
|
||||
expect(responseBody.operationStatus).toBe('DISABLING')
|
||||
expect(Object.keys(responseBody.version)).toHaveLength(13)
|
||||
expect(responseBody.version.id).toBe(mockFlowVersion.id)
|
||||
expect(responseBody.version.state).toBe('LOCKED')
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('List Flows endpoint', () => {
|
||||
it('Filters Flows by status', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockEnabledFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.ENABLED,
|
||||
})
|
||||
const mockDisabledFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.DISABLED,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow')
|
||||
.save([mockEnabledFlow, mockDisabledFlow])
|
||||
|
||||
const mockEnabledFlowVersion = createMockFlowVersion({
|
||||
flowId: mockEnabledFlow.id,
|
||||
})
|
||||
const mockDisabledFlowVersion = createMockFlowVersion({
|
||||
flowId: mockDisabledFlow.id,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockEnabledFlowVersion, mockDisabledFlowVersion])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/flows',
|
||||
query: {
|
||||
projectId: mockProject.id,
|
||||
status: 'ENABLED',
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody.data).toHaveLength(1)
|
||||
expect(responseBody.data[0].id).toBe(mockEnabledFlow.id)
|
||||
})
|
||||
|
||||
it('Populates Flow version', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockFlow = createMockFlow({ projectId: mockProject.id })
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({ flowId: mockFlow.id })
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/flows',
|
||||
query: {
|
||||
projectId: mockProject.id,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody?.data).toHaveLength(1)
|
||||
expect(responseBody?.data?.[0]?.id).toBe(mockFlow.id)
|
||||
expect(responseBody?.data?.[0]?.version?.id).toBe(mockFlowVersion.id)
|
||||
})
|
||||
|
||||
it('Fails if a flow with no version exists', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockFlow = createMockFlow({ projectId: mockProject.id })
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/flows',
|
||||
query: {
|
||||
projectId: mockProject.id,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.NOT_FOUND)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody?.code).toBe('ENTITY_NOT_FOUND')
|
||||
expect(responseBody?.params?.entityType).toBe('FlowVersion')
|
||||
expect(responseBody?.params?.message).toBe(`flowId=${mockFlow.id}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update Metadata endpoint', () => {
|
||||
it('Updates flow metadata', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
// create a flow with no metadata
|
||||
const mockFlow = createMockFlow({ projectId: mockProject.id })
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({ flowId: mockFlow.id })
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
const updatedMetadata = { foo: 'bar' }
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/flows/${mockFlow.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
body: {
|
||||
type: FlowOperationType.UPDATE_METADATA,
|
||||
request: {
|
||||
metadata: updatedMetadata,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody.id).toBe(mockFlow.id)
|
||||
expect(responseBody.metadata).toEqual(updatedMetadata)
|
||||
|
||||
// Verify metadata was actually persisted in the database
|
||||
const updatedFlow = await databaseConnection()
|
||||
.getRepository('flow')
|
||||
.findOneBy({ id: mockFlow.id })
|
||||
|
||||
expect(updatedFlow?.metadata).toEqual(updatedMetadata)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Export Flow Template endpoint', () => {
|
||||
it('Exports a flow template using an API key', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockOwner, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.ENABLED,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
updatedBy: mockOwner.id,
|
||||
})
|
||||
await databaseConnection()
|
||||
.getRepository('flow_version')
|
||||
.save([mockFlowVersion])
|
||||
|
||||
const mockApiKey = 'test_api_key'
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.SERVICE,
|
||||
projectId: mockProject.id,
|
||||
id: mockApiKey,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: `/v1/flows/${mockFlow.id}/template`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody).toHaveProperty('name')
|
||||
expect(responseBody).toHaveProperty('description')
|
||||
expect(responseBody).toHaveProperty('flows')
|
||||
expect(responseBody.flows).toHaveLength(1)
|
||||
expect(responseBody.flows[0]).toHaveProperty('trigger')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import { apId, PrincipalType } from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../helpers/auth'
|
||||
import { mockAndSaveBasicSetup } from '../../../helpers/mocks'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('Project Worker API', () => {
|
||||
describe('Get worker project endpoint', () => {
|
||||
it('Returns worker project', async () => {
|
||||
// arrange
|
||||
const { mockProject, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.ENGINE,
|
||||
id: apId(),
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
projectId: mockProject.id,
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/worker/project',
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
expect(responseBody?.id).toBe(mockProject.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import { FlowStatus, PrincipalType } from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../helpers/auth'
|
||||
import { createMockFlow, createMockFlowVersion, mockAndSaveBasicSetup } from '../../../helpers/mocks'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
const MOCK_FLOW_ID = '8hfKOpm3kY1yAi1ApYOa1'
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('Webhook Service', () => {
|
||||
it('should return GONE if the flow is not found', async () => {
|
||||
const { mockProject, mockOwner } = await mockAndSaveBasicSetup()
|
||||
const { mockPlatform } = await mockAndSaveBasicSetup()
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
id: mockOwner.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: `/v1/webhooks/${MOCK_FLOW_ID}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
expect(response?.statusCode).toBe(StatusCodes.GONE)
|
||||
}),
|
||||
it('should return NOT FOUND if the flow is disabled', async () => {
|
||||
const { mockProject, mockPlatform } = await mockAndSaveBasicSetup()
|
||||
const { mockOwner } = await mockAndSaveBasicSetup()
|
||||
const mockFlow = createMockFlow({
|
||||
projectId: mockProject.id,
|
||||
status: FlowStatus.DISABLED,
|
||||
})
|
||||
await databaseConnection().getRepository('flow').save([mockFlow])
|
||||
const mockFlowVersion = createMockFlowVersion({
|
||||
flowId: mockFlow.id,
|
||||
})
|
||||
await databaseConnection().getRepository('flow_version').save([mockFlowVersion])
|
||||
await databaseConnection().getRepository('flow').update(mockFlow.id, {
|
||||
publishedVersionId: mockFlowVersion.id,
|
||||
})
|
||||
const mockToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
id: mockOwner.id,
|
||||
})
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: `/v1/webhooks/${mockFlow.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
})
|
||||
expect(response?.statusCode).toBe(StatusCodes.NOT_FOUND)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,288 @@
|
||||
import {
|
||||
apId,
|
||||
PlatformRole,
|
||||
PrincipalType,
|
||||
UserStatus,
|
||||
} from '@activepieces/shared'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { initializeDatabase } from '../../../../src/app/database'
|
||||
import { databaseConnection } from '../../../../src/app/database/database-connection'
|
||||
import { setupServer } from '../../../../src/app/server'
|
||||
import { generateMockToken } from '../../../helpers/auth'
|
||||
import {
|
||||
mockAndSaveBasicSetup,
|
||||
mockBasicUser,
|
||||
} from '../../../helpers/mocks'
|
||||
|
||||
let app: FastifyInstance | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase({ runMigrations: false })
|
||||
app = await setupServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await databaseConnection().destroy()
|
||||
await app?.close()
|
||||
})
|
||||
|
||||
describe('User API', () => {
|
||||
describe('List users endpoint', () => {
|
||||
it('Returns a list of users', async () => {
|
||||
// arrange
|
||||
const { mockPlatform: mockPlatformOne, mockOwner: mockOwnerOne, mockProject: mockProjectOne } = await mockAndSaveBasicSetup()
|
||||
|
||||
// Create Another setup
|
||||
await mockAndSaveBasicSetup()
|
||||
|
||||
const testToken = await generateMockToken({
|
||||
id: mockOwnerOne.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProjectOne.id,
|
||||
platform: {
|
||||
id: mockPlatformOne.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/users',
|
||||
query: {
|
||||
platformId: mockPlatformOne.id,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(Object.keys(responseBody)).toHaveLength(3)
|
||||
expect(responseBody.data).toHaveLength(1)
|
||||
expect(responseBody.data[0].id).toBe(mockOwnerOne.id)
|
||||
expect(responseBody.data[0].password).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Requires principal to be platform owner', async () => {
|
||||
// arrange
|
||||
const { mockPlatform } = await mockAndSaveBasicSetup()
|
||||
|
||||
const { mockOwner: otherMockUser, mockProject: mockProjectOne } = await mockAndSaveBasicSetup()
|
||||
|
||||
const testToken = await generateMockToken({
|
||||
id: otherMockUser.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProjectOne.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/users',
|
||||
query: {
|
||||
platformId: mockPlatform.id,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.FORBIDDEN)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody?.code).toBe('AUTHORIZATION')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update user endpoint', () => {
|
||||
it('Updates user status to be INACTIVE', async () => {
|
||||
// arrange
|
||||
const { mockOwner, mockPlatform, mockProject } = await mockAndSaveBasicSetup()
|
||||
const { mockUser } = await mockBasicUser({
|
||||
user: {
|
||||
platformId: mockPlatform.id,
|
||||
status: UserStatus.ACTIVE,
|
||||
},
|
||||
})
|
||||
const testToken = await generateMockToken({
|
||||
id: mockOwner.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/users/${mockUser.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
body: {
|
||||
status: UserStatus.INACTIVE,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.OK)
|
||||
|
||||
const responseJson = response?.json()
|
||||
expect(responseJson.id).toBe(mockUser.id)
|
||||
expect(responseJson.password).toBeUndefined()
|
||||
expect(responseJson.status).toBe(UserStatus.INACTIVE)
|
||||
})
|
||||
|
||||
it('Fails if user doesn\'t exist', async () => {
|
||||
const { mockPlatform, mockProject } = await mockAndSaveBasicSetup()
|
||||
|
||||
const { mockUser } = await mockBasicUser({
|
||||
user: {
|
||||
platformId: mockPlatform.id,
|
||||
platformRole: PlatformRole.ADMIN,
|
||||
},
|
||||
})
|
||||
// arrange
|
||||
const nonExistentUserId = apId()
|
||||
|
||||
const testToken = await generateMockToken({
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
id: mockUser.id,
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/users/${nonExistentUserId}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
body: {
|
||||
status: UserStatus.INACTIVE,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.NOT_FOUND)
|
||||
})
|
||||
|
||||
it('Requires principal to be platform owner', async () => {
|
||||
// arrange
|
||||
const { mockPlatform, mockProject } = await mockAndSaveBasicSetup()
|
||||
|
||||
const { mockUser } = await mockBasicUser({
|
||||
user: {
|
||||
platformId: mockPlatform.id,
|
||||
platformRole: PlatformRole.MEMBER,
|
||||
},
|
||||
})
|
||||
const testToken = await generateMockToken({
|
||||
id: mockUser.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'POST',
|
||||
url: `/v1/users/${mockUser.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`,
|
||||
},
|
||||
body: {
|
||||
status: UserStatus.INACTIVE,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.FORBIDDEN)
|
||||
const responseBody = response?.json()
|
||||
|
||||
expect(responseBody?.code).toBe('AUTHORIZATION')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Delete user endpoint', () => {
|
||||
it('Removes a user', async () => {
|
||||
// arrange
|
||||
const { mockOwner, mockPlatform, mockProject } = await mockAndSaveBasicSetup()
|
||||
const { mockUser: mockEditor } = await mockBasicUser({
|
||||
user: {
|
||||
platformId: mockPlatform.id,
|
||||
platformRole: PlatformRole.MEMBER,
|
||||
},
|
||||
})
|
||||
|
||||
const mockOwnerToken = await generateMockToken({
|
||||
id: mockOwner.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'DELETE',
|
||||
url: `/v1/users/${mockEditor.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockOwnerToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.NO_CONTENT)
|
||||
})
|
||||
|
||||
it('Fails if user is not platform owner', async () => {
|
||||
// arrange
|
||||
const { mockPlatform, mockProject } = await mockAndSaveBasicSetup()
|
||||
|
||||
const { mockUser } = await mockBasicUser({
|
||||
user: {
|
||||
platformId: mockPlatform.id,
|
||||
platformRole: PlatformRole.MEMBER,
|
||||
},
|
||||
})
|
||||
|
||||
const mockUserToken = await generateMockToken({
|
||||
id: mockUser.id,
|
||||
type: PrincipalType.USER,
|
||||
projectId: mockProject.id,
|
||||
platform: {
|
||||
id: mockPlatform.id,
|
||||
},
|
||||
})
|
||||
|
||||
// act
|
||||
const response = await app?.inject({
|
||||
method: 'DELETE',
|
||||
url: `/v1/users/${mockUser.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${mockUserToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
// assert
|
||||
expect(response?.statusCode).toBe(StatusCodes.FORBIDDEN)
|
||||
const responseBody = response?.json()
|
||||
expect(responseBody?.code).toBe('AUTHORIZATION')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user