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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user