@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
374 lines (302 loc) • 11.7 kB
text/typescript
import {
buildPermissionContract,
validateEventContract,
} from '@sprucelabs/mercury-types'
import {
buildEmitTargetAndPayloadSchema,
eventContractUtil,
eventNameUtil,
eventTargetSchema,
} from '@sprucelabs/spruce-event-utils'
import { diskUtil, versionUtil } from '@sprucelabs/spruce-skill-utils'
import { test, assert, generateId } from '@sprucelabs/test-utils'
import { errorAssert } from '@sprucelabs/test-utils'
import CreateAction from '../../features/event/actions/CreateAction'
import EventStore from '../../features/event/stores/EventStore'
import AbstractEventTest from '../../tests/AbstractEventTest'
import { DEMO_NUMBER_EVENT_STORE } from '../../tests/constants'
import testUtil from '../../tests/utilities/test.utility'
const EVENT_NAME_READABLE = 'my event store amazing event'
const EVENT_NAME = 'my-event-store-amazing-event'
const EVENT_CAMEL = 'myEventStoreAmazingEvent'
export default class EventStoreTest extends AbstractEventTest {
private static createAction: CreateAction
private static events: EventStore
protected static get version() {
return versionUtil.generateVersion()
}
public static async beforeEach() {
await super.beforeEach()
this.createAction = this.Action<CreateAction>('event', 'create')
diskUtil.writeFile(this.resolvePath('package.json'), '{}')
await this.people.loginAsDemoPerson(DEMO_NUMBER_EVENT_STORE)
this.events = this.Store('event')
}
protected static async hasFetchEventContractsMethod() {
assert.isFunction(this.events.fetchEventContracts)
}
protected static async fetchesCoreEventContracts() {
const results = await this.events.fetchEventContracts({
localNamespace: 'my-skill',
})
const { contracts, errors } = results
assert.isTrue(contracts.length >= 1)
for (const contract of contracts) {
validateEventContract(contract)
}
assert.isEqual(errors.length, 0)
}
protected static async registerEventContract() {
const organizationFixture = this.getOrganizationFixture()
const org = await organizationFixture.seedDemoOrg({
name: 'my new org',
})
const { eventStore: eventStore1, skill: skill1 } =
await this.seedSkillAndInstallAtOrg(org, 'skill 1')
const { eventStore: eventStore2 } = await this.seedSkillAndInstallAtOrg(
org,
'skill 2'
)
await eventStore1.registerEventContract({
eventContract: {
eventSignatures: {
[`my-fantastic-event::${this.version.constValue}`]: {
emitPayloadSchema: buildEmitTargetAndPayloadSchema({
eventName: `my-fantastic-event::${this.version.constValue}`,
targetSchema: eventTargetSchema,
}),
emitPermissionContract: buildPermissionContract({
id: 'my-fantastic-event-contract',
name: 'Fantastic emit perms',
permissions: [
{
id: 'can-emit-perms',
name: 'can emit perm',
defaults: {
skill: false,
},
},
],
}),
},
},
},
})
const { contracts } = await eventStore2.fetchEventContracts({
localNamespace: 'my-skill',
})
assert.isTrue(contracts.length > 2)
const skillContract = contracts[1]
const sig = eventContractUtil.getSignatureByName(
skillContract,
`${skill1.slug}.my-fantastic-event::${this.version.constValue}`
)
assert.isEqual(
sig.emitPermissionContract?.id,
`${skill1.slug}.my-fantastic-event-contract`
)
}
protected static async badLocalContractThrowsNiceError() {
await this.FeatureFixture().installCachedFeatures('events')
const skill = await this.getSkillFixture().registerCurrentSkill({
name: 'my new skill',
})
const results = await this.createAction.execute({
nameReadable: EVENT_NAME_READABLE,
nameKebab: EVENT_NAME,
nameCamel: EVENT_CAMEL,
})
assert.isFalsy(results.errors)
const match = testUtil.assertFileByNameInGeneratedFiles(
`emitPayload.builder.ts`,
results.files
)
diskUtil.writeFile(match, 'export default {}')
const err = await assert.doesThrowAsync(
() => this.events.loadLocalContract(skill.slug),
new RegExp(
`${skill.slug}.${EVENT_NAME}::${
versionUtil.generateVersion().constValue
}`
)
)
errorAssert.assertError(err, 'INVALID_EVENT_CONTRACT')
}
protected static async badLocalSignature() {
await this.installEventsSkill()
const skill = await this.getSkillFixture().registerCurrentSkill({
name: 'my new skill',
})
const results = await this.createAction.execute({
nameReadable: EVENT_NAME_READABLE,
nameKebab: EVENT_NAME,
nameCamel: EVENT_CAMEL,
})
assert.isFalsy(results.errors)
const match = testUtil.assertFileByNameInGeneratedFiles(
`event.options.ts`,
results.files
)
diskUtil.writeFile(match, 'export default {waka: true}')
const err = await assert.doesThrowAsync(
() => this.events.loadLocalContract(skill.slug),
new RegExp(
`${skill.slug}.${EVENT_NAME}::${
versionUtil.generateVersion().constValue
}`
)
)
errorAssert.assertError(err, 'INVALID_EVENT_CONTRACT')
}
protected static async mixesInLocalContracts() {
await this.installEventsSkill()
const skill = await this.getSkillFixture().registerCurrentSkill({
name: 'my new skill',
})
await this.createAction.execute({
nameReadable: EVENT_NAME_READABLE,
nameKebab: EVENT_NAME,
nameCamel: EVENT_CAMEL,
})
await this.copyEventBuildersAndPermissions(EVENT_NAME)
await this.Action('event', 'sync').execute({})
const { contracts } = await this.events.fetchEventContracts({
localNamespace: skill.slug,
namespaces: ['core'],
})
assert.isLength(contracts, 2)
const name = eventNameUtil.join({
eventName: EVENT_NAME,
eventNamespace: skill.slug,
version: this.version.constValue,
})
let wasFound = false
for (const contract of contracts) {
if (contract.eventSignatures[name]?.emitPayloadSchema) {
wasFound = true
assert.isEqual(
contract.eventSignatures[name].emitPayloadSchema?.id,
EVENT_CAMEL + 'EmitTargetAndPayload'
)
assert.isTruthy(
contract.eventSignatures[name].emitPayloadSchema?.fields
?.target
)
assert.isTruthy(
contract.eventSignatures[name].emitPayloadSchema?.fields
?.payload
)
assert.isTruthy(
contract.eventSignatures[name].responsePayloadSchema
)
assert.isTruthy(
contract.eventSignatures[name].emitPermissionContract
)
assert.isTruthy(
contract.eventSignatures[name].listenPermissionContract
)
validateEventContract(contract)
}
}
assert.isTrue(wasFound)
}
protected static async mixesInLocalContractWithGlobalEventsAndDoesNotReturnContractTwice() {
const { skill, fqen } = await this.installAndRegisterOneGlobalEvent()
const { contracts } = await this.events.fetchEventContracts({
localNamespace: skill.slug,
})
const totalMatches = contracts.reduce((count, contract) => {
if (contract.eventSignatures[fqen]) {
count++
}
return count
}, 0)
assert.isEqual(totalMatches, 1)
}
protected static async doesNotLoadGlobalContractsForSelf() {
const { fqen, skill } = await this.installAndRegisterOneGlobalEvent()
const eventsPath = this.resolvePath('src', 'events')
diskUtil.deleteDir(eventsPath)
const { contracts } = await this.events.fetchEventContracts({
localNamespace: skill.slug,
})
const unified = eventContractUtil.unifyContracts(contracts)
assert.isTruthy(unified)
assert.doesThrow(() =>
eventContractUtil.getSignatureByName(unified, fqen)
)
}
protected static async globbyPatternLooksEverywhere() {
const store = this.events
const expected = this.resolvePath(
'src',
'**',
'events',
'**/*.(builder|options).ts'
)
//@ts-ignore
assert.isEqual(store.generateGlobbyForLocalEvents(), expected)
}
protected static async passesBackJsErrorsWhenFetchingEventContracts() {
const msg = generateId()
this.events = this.Store('event', {
apiClientFactory: async () => {
throw new Error(msg)
},
})
await assert.doesThrowAsync(
() => this.events.fetchEventContracts(),
msg
)
}
private static async installAndRegisterOneGlobalEvent() {
await this.installEventsSkill()
const skill = await this.getSkillFixture().registerCurrentSkill({
name: 'event store test skill',
})
const results = await this.createAction.execute({
nameReadable: EVENT_NAME_READABLE,
nameKebab: EVENT_NAME,
nameCamel: EVENT_CAMEL,
isGlobal: true,
})
const { fqen } = results.meta ?? {}
assert.isTruthy(fqen)
const boot = await this.Action('skill', 'boot').execute({ local: true })
boot.meta?.kill()
EventStore.clearCache()
return { boot, skill, fqen, createResults: results }
}
private static async seedSkillAndInstallAtOrg(org: any, name: string) {
const skill = await this.getSkillFixture().seedDemoSkill({
name,
})
await this.getOrganizationFixture().installSkillAtOrganization(
skill.id,
org.id
)
const client = await this.connectToApi({
skillId: skill.id,
apiKey: skill.apiKey,
})
const eventStore = this.Store('event', {
apiClientFactory: async () => {
return client
},
})
return { eventStore, skill }
}
private static async installEventsSkill() {
await this.FeatureFixture().installCachedFeatures('events')
}
}