UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

374 lines (302 loc) • 11.7 kB
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') } @test() protected static async hasFetchEventContractsMethod() { assert.isFunction(this.events.fetchEventContracts) } @test() 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) } @test() 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` ) } @test() 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') } @test() 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') } @test() 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) } @test() 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) } @test() 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) ) } @test() protected static async globbyPatternLooksEverywhere() { const store = this.events const expected = this.resolvePath( 'src', '**', 'events', '**/*.(builder|options).ts' ) //@ts-ignore assert.isEqual(store.generateGlobbyForLocalEvents(), expected) } @test() 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') } }