Files
speckle-server/packages/server/test/speckle-helpers/automationHelper.ts
T
2025-04-18 11:03:54 +02:00

270 lines
7.8 KiB
TypeScript

import {
getAutomationFactory,
getFullAutomationRevisionMetadataFactory,
getFullAutomationRunByIdFactory,
getLatestAutomationRevisionFactory,
storeAutomationFactory,
storeAutomationRevisionFactory,
storeAutomationTokenFactory,
upsertAutomationRunFactory
} from '@/modules/automate/repositories/automations'
import {
CreateAutomationRevisionDeps,
createAutomationFactory,
createAutomationRevisionFactory
} from '@/modules/automate/services/automationManagement'
import { createStoredAuthCodeFactory } from '@/modules/automate/services/authCode'
import { getInmemoryRedisClient } from '@/test/redisHelper'
import cryptoRandomString from 'crypto-random-string'
import { createAutomation as clientCreateAutomation } from '@/modules/automate/clients/executionEngine'
import {
getBranchesByIdsFactory,
getBranchLatestCommitsFactory,
getLatestStreamBranchFactory
} from '@/modules/core/repositories/branches'
import {
ProjectAutomationCreateInput,
ProjectAutomationRevisionCreateInput
} from '@/modules/core/graph/generated/graphql'
import { Automate } from '@speckle/shared'
import { truncateTables } from '@/test/hooks'
import {
AutomationRevisions,
AutomationRunTriggers,
AutomationRuns,
AutomationTokens,
AutomationTriggers,
Automations
} from '@/modules/core/dbSchema'
import { faker } from '@faker-js/faker'
import {
getEncryptionKeyPair,
getEncryptionKeyPairFor,
getFunctionInputDecryptorFactory
} from '@/modules/automate/services/encryption'
import { buildDecryptor } from '@/modules/shared/utils/libsodium'
import { db } from '@/db/knex'
import { validateStreamAccessFactory } from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import { Knex } from 'knex'
import { createTestAutomationRunFactory } from '@/modules/automate/services/trigger'
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
export const generateFunctionId = () => cryptoRandomString({ length: 10 })
export const generateFunctionReleaseId = () => cryptoRandomString({ length: 10 })
/**
* @param overrides By default, we mock requests to the execution engine. You can replace those mocks here.
*/
export const buildAutomationCreate = (
params: {
dbClient?: Knex
overrides?: Partial<{
createDbAutomation: typeof clientCreateAutomation
}>
} = {}
) => {
const { dbClient = db, overrides } = params
const create = createAutomationFactory({
createAuthCode: createStoredAuthCodeFactory({ redis: getInmemoryRedisClient() }),
automateCreateAutomation:
overrides?.createDbAutomation ||
(async () => ({
automationId: cryptoRandomString({ length: 10 }),
token: cryptoRandomString({ length: 10 })
})),
storeAutomation: storeAutomationFactory({ db: dbClient }),
storeAutomationToken: storeAutomationTokenFactory({ db: dbClient }),
eventEmit: getEventBus().emit
})
return create
}
/**
* @param overrides By default, we mock requests to the execution engine. You can replace those mocks here.
*/
export const buildAutomationRevisionCreate = (
params: {
dbClient?: Knex
overrides?: Partial<CreateAutomationRevisionDeps>
} = {}
) => {
const { dbClient = db, overrides } = params
const fakeGetRelease = (params: {
functionReleaseId: string
functionId: string
}) => ({
functionVersionId: params.functionReleaseId,
versionTag: faker.system.semver(),
inputSchema: null,
createdAt: new Date().toISOString(),
commitId: faker.git.commitSha({ length: 7 }),
functionId: params.functionId
})
const create = createAutomationRevisionFactory({
getAutomation: getAutomationFactory({ db: dbClient }),
storeAutomationRevision: storeAutomationRevisionFactory({ db: dbClient }),
getBranchesByIds: getBranchesByIdsFactory({ db: dbClient }),
getFunctionRelease: async (params) => fakeGetRelease(params),
getFunctionReleases: async (params) => params.ids.map(fakeGetRelease),
getEncryptionKeyPair,
getFunctionInputDecryptor: getFunctionInputDecryptorFactory({
buildDecryptor
}),
validateStreamAccess,
eventEmit: getEventBus().emit,
...overrides
})
return create
}
/**
* Quick way to create an automation and optionally one revision
*/
export const createTestAutomation = async (params: {
userId: string
projectId: string
automation?: Partial<ProjectAutomationCreateInput>
revision?: {
input?: Partial<ProjectAutomationRevisionCreateInput>
functionReleaseId: string
functionId: string
}
}) => {
const {
userId,
projectId,
automation,
revision: { input: revisionInput, functionReleaseId, functionId } = {}
} = params
const projectDb = await getProjectDbClient({ projectId })
const createAutomation = buildAutomationCreate({ dbClient: projectDb })
const createRevision = buildAutomationRevisionCreate({ dbClient: projectDb })
const automationRet = await createAutomation({
input: {
name: `Test Automation #${cryptoRandomString({ length: 5 })}`,
enabled: true,
...automation
},
projectId,
userId
})
let revisionRet: Awaited<ReturnType<typeof createRevision>> | null = null
if (functionReleaseId?.length && functionId?.length) {
const firstModel = await getLatestStreamBranchFactory({ db: projectDb })(projectId)
if (!firstModel)
throw new Error(
'Project does not have any models for automation revision triggers'
)
revisionRet = await createRevision({
input: {
automationId: automationRet.automation.id,
functions: [
{
functionId,
functionReleaseId,
parameters: null
}
],
triggerDefinitions: <Automate.AutomateTypes.TriggerDefinitionsSchema>{
version: 1.0,
definitions: [
{
type: 'VERSION_CREATED',
modelId: firstModel.id
}
]
},
...revisionInput
},
projectId,
userId
})
}
return {
automation: automationRet,
revision: revisionRet
}
}
export type TestAutomationWithRevision = Awaited<
ReturnType<typeof createTestAutomation>
>
export const createTestAutomationRun = async (params: {
userId: string
projectId: string
automationId: string
}) => {
const { userId, projectId, automationId } = params
const projectDb = await getProjectDbClient({ projectId })
const { automationRunId } = await createTestAutomationRunFactory({
getEncryptionKeyPairFor,
getFunctionInputDecryptor: getFunctionInputDecryptorFactory({
buildDecryptor
}),
getAutomation: getAutomationFactory({
db: projectDb
}),
getLatestAutomationRevision: getLatestAutomationRevisionFactory({
db: projectDb
}),
getFullAutomationRevisionMetadata: getFullAutomationRevisionMetadataFactory({
db: projectDb
}),
upsertAutomationRun: upsertAutomationRunFactory({
db: projectDb
}),
getBranchLatestCommits: getBranchLatestCommitsFactory({
db: projectDb
}),
validateStreamAccess,
emitEvent: async () => {}
})({ projectId, automationId, userId })
const automationRunData = await getFullAutomationRunByIdFactory({ db: projectDb })(
automationRunId
)
if (!automationRunData) {
throw new Error('Failed to create test automation run!')
}
const { triggers, functionRuns, ...automationRun } = automationRunData
return {
automationRun,
functionRuns,
triggers
}
}
export const truncateAutomations = async () => {
await truncateTables([
AutomationRunTriggers.name,
AutomationRuns.name,
AutomationTriggers.name,
AutomationTokens.name,
AutomationRevisions.name,
Automations.name
])
}