Merge pull request #3053 from specklesystems/fabians/automate-ioc-2
chore(server): automate IoC 2 - createTestAutomationFactory
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
AutomationRecord,
|
||||
AutomationRevisionWithTriggersFunctions,
|
||||
AutomationTokenRecord
|
||||
} from '@/modules/automate/helpers/types'
|
||||
import { InsertableAutomationRevision } from '@/modules/automate/repositories/automations'
|
||||
import { AuthCodePayload } from '@/modules/automate/services/authCode'
|
||||
import { ProjectAutomationCreateInput } from '@/modules/core/graph/generated/graphql'
|
||||
import { ContextResourceAccessRules } from '@/modules/core/helpers/token'
|
||||
@@ -14,6 +16,10 @@ export type StoreAutomationToken = (
|
||||
automationToken: AutomationTokenRecord
|
||||
) => Promise<AutomationTokenRecord>
|
||||
|
||||
export type StoreAutomationRevision = (
|
||||
revision: InsertableAutomationRevision
|
||||
) => Promise<AutomationRevisionWithTriggersFunctions>
|
||||
|
||||
export type CreateStoredAuthCode = (
|
||||
params: Omit<AuthCodePayload, 'code'>
|
||||
) => Promise<AuthCodePayload>
|
||||
@@ -24,3 +30,12 @@ export type CreateAutomation = (params: {
|
||||
userId: string
|
||||
userResourceAccessRules?: ContextResourceAccessRules
|
||||
}) => Promise<{ automation: AutomationRecord; token: AutomationTokenRecord }>
|
||||
|
||||
type KeyPair = {
|
||||
publicKey: string
|
||||
privateKey: string
|
||||
}
|
||||
|
||||
export type GetEncryptionKeyPair = () => Promise<KeyPair>
|
||||
|
||||
export type GetEncryptionKeyPairFor = (publicKey: string) => Promise<KeyPair>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
getProjectAutomationsItems,
|
||||
getProjectAutomationsTotalCount,
|
||||
storeAutomationFactory,
|
||||
storeAutomationRevision,
|
||||
storeAutomationRevisionFactory,
|
||||
storeAutomationTokenFactory,
|
||||
updateAutomationRun,
|
||||
updateAutomation as updateDbAutomation,
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import {
|
||||
createAutomationFactory,
|
||||
createAutomationRevision,
|
||||
createTestAutomation,
|
||||
createTestAutomationFactory,
|
||||
getAutomationsStatus,
|
||||
updateAutomation
|
||||
} from '@/modules/automate/services/automationManagement'
|
||||
@@ -110,6 +110,7 @@ const { FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
const storeAutomation = storeAutomationFactory({ db })
|
||||
const storeAutomationToken = storeAutomationTokenFactory({ db })
|
||||
const storeAutomationRevision = storeAutomationRevisionFactory({ db })
|
||||
|
||||
export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
? {
|
||||
@@ -532,11 +533,13 @@ export = (FF_AUTOMATE_MODULE_ENABLED
|
||||
return automationRunId
|
||||
},
|
||||
async createTestAutomation(parent, { input }, ctx) {
|
||||
const create = createTestAutomation({
|
||||
const create = createTestAutomationFactory({
|
||||
getEncryptionKeyPair,
|
||||
getFunction,
|
||||
storeAutomation,
|
||||
storeAutomationRevision
|
||||
storeAutomationRevision,
|
||||
validateStreamAccess,
|
||||
automationsEventsEmit: AutomationsEmitter.emit
|
||||
})
|
||||
|
||||
return await create({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
StoreAutomation,
|
||||
StoreAutomationRevision,
|
||||
StoreAutomationToken
|
||||
} from '@/modules/automate/domain/operations'
|
||||
import {
|
||||
@@ -57,7 +58,13 @@ import { SetOptional, SetRequired } from 'type-fest'
|
||||
|
||||
const tables = {
|
||||
automations: (db: Knex) => db<AutomationRecord>(Automations.name),
|
||||
automationTokens: (db: Knex) => db<AutomationTokenRecord>(AutomationTokens.name)
|
||||
automationTokens: (db: Knex) => db<AutomationTokenRecord>(AutomationTokens.name),
|
||||
automationRevisions: (db: Knex) =>
|
||||
db<AutomationRevisionRecord>(AutomationRevisions.name),
|
||||
automationRevisionFunctions: (db: Knex) =>
|
||||
db<AutomateRevisionFunctionRecord>(AutomationRevisionFunctions.name),
|
||||
automationTriggers: (db: Knex) =>
|
||||
db<AutomationTriggerDefinitionRecord>(AutomationTriggers.name)
|
||||
}
|
||||
|
||||
export const generateRevisionId = () => cryptoRandomString({ length: 10 })
|
||||
@@ -337,57 +344,61 @@ export async function updateAutomationRevision(
|
||||
return ret
|
||||
}
|
||||
|
||||
export type StoredInsertableAutomationRevision = Awaited<
|
||||
ReturnType<typeof storeAutomationRevision>
|
||||
>
|
||||
export type StoredInsertableAutomationRevision = AutomationRevisionWithTriggersFunctions
|
||||
|
||||
export async function storeAutomationRevision(revision: InsertableAutomationRevision) {
|
||||
const id = revision.id || generateRevisionId()
|
||||
const rev = _.pick(revision, AutomationRevisions.withoutTablePrefix.cols)
|
||||
const [newRev] = await AutomationRevisions.knex()
|
||||
.insert({
|
||||
...rev,
|
||||
id
|
||||
})
|
||||
.returning<AutomationRevisionRecord[]>('*')
|
||||
const [functions, triggers] = await Promise.all([
|
||||
AutomationRevisionFunctions.knex()
|
||||
.insert(
|
||||
revision.functions.map(
|
||||
(f): AutomateRevisionFunctionRecord => ({
|
||||
...f,
|
||||
automationRevisionId: id
|
||||
})
|
||||
export const storeAutomationRevisionFactory =
|
||||
(deps: { db: Knex }): StoreAutomationRevision =>
|
||||
async (revision: InsertableAutomationRevision) => {
|
||||
const id = revision.id || generateRevisionId()
|
||||
const rev = _.pick(revision, AutomationRevisions.withoutTablePrefix.cols)
|
||||
const [newRev] = await tables
|
||||
.automationRevisions(deps.db)
|
||||
.insert({
|
||||
...rev,
|
||||
id
|
||||
})
|
||||
.returning('*')
|
||||
const [functions, triggers] = await Promise.all([
|
||||
tables
|
||||
.automationRevisionFunctions(deps.db)
|
||||
.insert(
|
||||
revision.functions.map(
|
||||
(f): AutomateRevisionFunctionRecord => ({
|
||||
...f,
|
||||
automationRevisionId: id
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning<AutomateRevisionFunctionRecord[]>('*'),
|
||||
AutomationTriggers.knex()
|
||||
.insert(
|
||||
revision.triggers.map(
|
||||
(t): AutomationTriggerDefinitionRecord => ({
|
||||
...t,
|
||||
automationRevisionId: id
|
||||
})
|
||||
.returning('*'),
|
||||
tables
|
||||
.automationTriggers(deps.db)
|
||||
.insert(
|
||||
revision.triggers.map(
|
||||
(t): AutomationTriggerDefinitionRecord => ({
|
||||
...t,
|
||||
automationRevisionId: id
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning<AutomationTriggerDefinitionRecord[]>('*'),
|
||||
// Unset 'active in revision' for all other revisions
|
||||
...(revision.active
|
||||
? [
|
||||
AutomationRevisions.knex()
|
||||
.where(AutomationRevisions.col.automationId, newRev.automationId)
|
||||
.andWhereNot(AutomationRevisions.col.id, newRev.id)
|
||||
.update(AutomationRevisions.withoutTablePrefix.col.active, false)
|
||||
]
|
||||
: [])
|
||||
])
|
||||
.returning('*'),
|
||||
// Unset 'active in revision' for all other revisions
|
||||
...(revision.active
|
||||
? [
|
||||
tables
|
||||
.automationRevisions(deps.db)
|
||||
.where(AutomationRevisions.col.automationId, newRev.automationId)
|
||||
.andWhereNot(AutomationRevisions.col.id, newRev.id)
|
||||
.update(AutomationRevisions.withoutTablePrefix.col.active, false)
|
||||
]
|
||||
: [])
|
||||
])
|
||||
|
||||
return {
|
||||
...newRev,
|
||||
functions,
|
||||
triggers
|
||||
return {
|
||||
...newRev,
|
||||
functions,
|
||||
triggers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAutomationToken(
|
||||
automationId: string
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
InsertableAutomationRevisionTrigger,
|
||||
getAutomation,
|
||||
getLatestVersionAutomationRuns,
|
||||
storeAutomationRevision,
|
||||
updateAutomation as updateDbAutomation
|
||||
} from '@/modules/automate/repositories/automations'
|
||||
import { getServerOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
@@ -41,10 +40,7 @@ import { getBranchesByIds } from '@/modules/core/repositories/branches'
|
||||
import { keyBy, uniq } from 'lodash'
|
||||
import { resolveStatusFromFunctionRunStatuses } from '@/modules/automate/services/runsManagement'
|
||||
import { TriggeredAutomationsStatusGraphQLReturn } from '@/modules/automate/helpers/graphTypes'
|
||||
import {
|
||||
getEncryptionKeyPair,
|
||||
getFunctionInputDecryptor
|
||||
} from '@/modules/automate/services/encryption'
|
||||
import { getFunctionInputDecryptor } from '@/modules/automate/services/encryption'
|
||||
import { LibsodiumEncryptionError } from '@/modules/shared/errors/encryption'
|
||||
import { validateInputAgainstFunctionSchema } from '@/modules/automate/utils/inputSchemaValidator'
|
||||
import {
|
||||
@@ -55,7 +51,9 @@ import { validateAutomationName } from '@/modules/automate/utils/automationConfi
|
||||
import {
|
||||
CreateAutomation,
|
||||
CreateStoredAuthCode,
|
||||
GetEncryptionKeyPair,
|
||||
StoreAutomation,
|
||||
StoreAutomationRevision,
|
||||
StoreAutomationToken
|
||||
} from '@/modules/automate/domain/operations'
|
||||
|
||||
@@ -139,17 +137,19 @@ export const createAutomationFactory =
|
||||
}
|
||||
|
||||
export type CreateTestAutomationDeps = {
|
||||
getEncryptionKeyPair: typeof getEncryptionKeyPair
|
||||
getEncryptionKeyPair: GetEncryptionKeyPair
|
||||
getFunction: typeof getFunction
|
||||
storeAutomation: StoreAutomation
|
||||
storeAutomationRevision: typeof storeAutomationRevision
|
||||
storeAutomationRevision: StoreAutomationRevision
|
||||
validateStreamAccess: typeof validateStreamAccess
|
||||
automationsEventsEmit: AutomationsEventsEmit
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test automation and its first revision in one request.
|
||||
* TODO: Reduce code duplication w/ createAutomation
|
||||
*/
|
||||
export const createTestAutomation =
|
||||
export const createTestAutomationFactory =
|
||||
(deps: CreateTestAutomationDeps) =>
|
||||
async (params: {
|
||||
input: ProjectTestAutomationCreateInput
|
||||
@@ -167,7 +167,9 @@ export const createTestAutomation =
|
||||
getEncryptionKeyPair,
|
||||
getFunction,
|
||||
storeAutomation,
|
||||
storeAutomationRevision
|
||||
storeAutomationRevision,
|
||||
validateStreamAccess,
|
||||
automationsEventsEmit
|
||||
} = deps
|
||||
|
||||
validateAutomationName(name)
|
||||
@@ -233,7 +235,7 @@ export const createTestAutomation =
|
||||
publicKey: encryptionKeyPair.publicKey
|
||||
})
|
||||
|
||||
await AutomationsEmitter.emit(AutomationsEmitter.events.CreatedRevision, {
|
||||
await automationsEventsEmit(AutomationsEmitter.events.CreatedRevision, {
|
||||
automation: automationRecord,
|
||||
revision: automationRevisionRecord
|
||||
})
|
||||
@@ -382,8 +384,8 @@ const validateNewRevisionFunctions =
|
||||
|
||||
export type CreateAutomationRevisionDeps = {
|
||||
getAutomation: typeof getAutomation
|
||||
storeAutomationRevision: typeof storeAutomationRevision
|
||||
getEncryptionKeyPair: typeof getEncryptionKeyPair
|
||||
storeAutomationRevision: StoreAutomationRevision
|
||||
getEncryptionKeyPair: GetEncryptionKeyPair
|
||||
getFunctionInputDecryptor: ReturnType<typeof getFunctionInputDecryptor>
|
||||
getFunctionReleases: typeof getFunctionReleases
|
||||
} & ValidateNewTriggerDefinitionsDeps &
|
||||
|
||||
@@ -18,6 +18,10 @@ import { LibsodiumEncryptionError } from '@/modules/shared/errors/encryption'
|
||||
import { Merge } from 'type-fest'
|
||||
import { convertFunctionReleaseToGraphQLReturn } from '@/modules/automate/services/functionManagement'
|
||||
import { redactWriteOnlyInputData } from '@/modules/automate/utils/jsonSchemaRedactor'
|
||||
import {
|
||||
GetEncryptionKeyPair,
|
||||
GetEncryptionKeyPairFor
|
||||
} from '@/modules/automate/domain/operations'
|
||||
|
||||
type KeysFileContents = Array<KeyPair>
|
||||
|
||||
@@ -49,7 +53,7 @@ const getEncryptionKeys = async () => {
|
||||
return keys
|
||||
}
|
||||
|
||||
export const getEncryptionKeyPair = async () => {
|
||||
export const getEncryptionKeyPair: GetEncryptionKeyPair = async () => {
|
||||
return (await getEncryptionKeys())[0]
|
||||
}
|
||||
|
||||
@@ -57,7 +61,9 @@ export const getEncryptionPublicKey = async () => {
|
||||
return (await getEncryptionKeyPair()).publicKey
|
||||
}
|
||||
|
||||
export const getEncryptionKeyPairFor = async (publicKey: string) => {
|
||||
export const getEncryptionKeyPairFor: GetEncryptionKeyPairFor = async (
|
||||
publicKey: string
|
||||
) => {
|
||||
const keyPairs = await getEncryptionKeys()
|
||||
const keyPair = keyPairs.find((keyPair) => keyPair.publicKey === publicKey)
|
||||
if (!keyPair) {
|
||||
@@ -139,7 +145,7 @@ export const getFunctionInputDecryptor =
|
||||
}
|
||||
|
||||
export type GetFunctionInputsForFrontendDeps = {
|
||||
getEncryptionKeyPairFor: typeof getEncryptionKeyPairFor
|
||||
getEncryptionKeyPairFor: GetEncryptionKeyPairFor
|
||||
redactWriteOnlyInputData: typeof redactWriteOnlyInputData
|
||||
} & GetFunctionInputDecryptorDeps
|
||||
|
||||
|
||||
@@ -40,12 +40,10 @@ import { validateStreamAccess } from '@/modules/core/services/streams/streamAcce
|
||||
import { ContextResourceAccessRules } from '@/modules/core/helpers/token'
|
||||
import { TokenResourceIdentifierType } from '@/modules/core/graph/generated/graphql'
|
||||
import { automateLogger } from '@/logging/logging'
|
||||
import {
|
||||
getEncryptionKeyPairFor,
|
||||
getFunctionInputDecryptor
|
||||
} from '@/modules/automate/services/encryption'
|
||||
import { getFunctionInputDecryptor } from '@/modules/automate/services/encryption'
|
||||
import { LibsodiumEncryptionError } from '@/modules/shared/errors/encryption'
|
||||
import { AutomateRunsEmitter } from '@/modules/automate/events/runs'
|
||||
import { GetEncryptionKeyPairFor } from '@/modules/automate/domain/operations'
|
||||
|
||||
export type OnModelVersionCreateDeps = {
|
||||
getAutomation: typeof getAutomation
|
||||
@@ -126,7 +124,7 @@ type InsertableAutomationRunWithExtendedFunctionRuns = Merge<
|
||||
>
|
||||
|
||||
type CreateAutomationRunDataDeps = {
|
||||
getEncryptionKeyPairFor: typeof getEncryptionKeyPairFor
|
||||
getEncryptionKeyPairFor: GetEncryptionKeyPairFor
|
||||
getFunctionInputDecryptor: ReturnType<typeof getFunctionInputDecryptor>
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ import {
|
||||
getFullAutomationRunById,
|
||||
getAutomationTriggerDefinitions,
|
||||
getFunctionRun,
|
||||
storeAutomationRevision,
|
||||
updateAutomation,
|
||||
updateAutomationRevision,
|
||||
updateAutomationRun,
|
||||
upsertAutomationRun,
|
||||
upsertAutomationFunctionRun,
|
||||
storeAutomationFactory,
|
||||
storeAutomationTokenFactory
|
||||
storeAutomationTokenFactory,
|
||||
storeAutomationRevisionFactory
|
||||
} from '@/modules/automate/repositories/automations'
|
||||
import { beforeEachContext, truncateTables } from '@/test/hooks'
|
||||
import { Automate } from '@speckle/shared'
|
||||
@@ -77,6 +77,7 @@ const { FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
const storeAutomation = storeAutomationFactory({ db })
|
||||
const storeAutomationToken = storeAutomationTokenFactory({ db })
|
||||
const storeAutomationRevision = storeAutomationRevisionFactory({ db })
|
||||
|
||||
;(FF_AUTOMATE_MODULE_ENABLED ? describe : describe.skip)(
|
||||
'Automate triggers @automate',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
getAutomation,
|
||||
storeAutomationFactory,
|
||||
storeAutomationRevision,
|
||||
storeAutomationRevisionFactory,
|
||||
storeAutomationTokenFactory
|
||||
} from '@/modules/automate/repositories/automations'
|
||||
import {
|
||||
@@ -44,6 +44,7 @@ import { validateStreamAccess } from '@/modules/core/services/streams/streamAcce
|
||||
|
||||
const storeAutomation = storeAutomationFactory({ db })
|
||||
const storeAutomationToken = storeAutomationTokenFactory({ db })
|
||||
const storeAutomationRevision = storeAutomationRevisionFactory({ db })
|
||||
|
||||
export const generateFunctionId = () => cryptoRandomString({ length: 10 })
|
||||
export const generateFunctionReleaseId = () => cryptoRandomString({ length: 10 })
|
||||
|
||||
Reference in New Issue
Block a user