@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
359 lines (286 loc) • 11.4 kB
text/typescript
import pathUtil from 'path'
import { coreEventContracts } from '@sprucelabs/mercury-core-events'
import {
EventContract,
SpruceSchemas,
validateEventContract,
} from '@sprucelabs/mercury-types'
import { validateSchema } from '@sprucelabs/schema'
import {
eventContractUtil,
eventNameUtil,
} from '@sprucelabs/spruce-event-utils'
import {
diskUtil,
MERCURY_API_NAMESPACE,
namesUtil,
} from '@sprucelabs/spruce-skill-utils'
import { test, assert } from '@sprucelabs/test-utils'
import { FeatureActionResponse } from '../../../features/features.types'
import AbstractEventTest from '../../../tests/AbstractEventTest'
const coreContract = eventContractUtil.unifyContracts(
coreEventContracts as any
) as SpruceSchemas.Mercury.v2020_12_25.EventContract
export default class KeepingEventsInSyncTest extends AbstractEventTest {
private static get eventContractPath() {
return this.resolveHashSprucePath('events', 'events.contract.ts')
}
()
protected static async hasSyncEventsAction() {
await this.Cli()
assert.isFunction(this.Action('event', 'sync').execute)
}
()
protected static async syncsWithoutSavingCoreEventsByDefault() {
await this.FeatureFixture().installCachedFeatures('eventsInNodeModule')
const results = await this.skipInstallSkillPrompts(() =>
this.Action('event', 'sync').execute({})
)
await this.assertValidSyncEventsResults(results)
}
()
protected static async mergesGlobalEvents() {
await this.FeatureFixture().installCachedFeatures('events')
const skills = this.getSkillFixture()
await skills.registerCurrentSkill({
name: 'events in sync skill',
})
const skill2 = await skills.seedDemoSkill({ name: 'a temp skill' })
await this.Service('dependency').add({
id: skill2.id,
namespace: skill2.slug,
})
await skills.registerEventContract(skill2, {
eventSignatures: {
'test-sync::v2021_01_01': {
isGlobal: true,
},
},
})
const results = await this.Action('event', 'sync').execute({})
await this.assertValidSyncEventsResults(results)
const fqen = eventNameUtil.join({
eventName: 'test-sync',
version: 'v2021_01_01',
eventNamespace: skill2.slug,
})
await this.assertGlobalEventsAreTyped(fqen)
}
()
protected static async canSetSkillEventContractTypesFile() {
await this.FeatureFixture().installCachedFeatures('eventsInNodeModule')
await this.skipInstallSkillPrompts(() =>
this.Action('event', 'sync').execute({
skillEventContractTypesFile: 'testy test',
})
)
const contents = diskUtil.readFile(this.eventContractPath)
assert.doesInclude(contents, `declare module 'testy test'`)
}
()
protected static async canSyncOnlyCoreEvents() {
await this.FeatureFixture().installCachedFeatures('eventsInNodeModule')
const promise = this.syncCoreEventsPretendingToBeMercuryTypes()
await this.skipInstallSkillPrompts()
const results = await promise
await this.assertValidSyncEventsResults(results, true)
}
()
protected static async syncingSchemasDoesNotSyncEventSchemasIfEventsHaveNeverBeenSynced() {
const cli = await this.FeatureFixture().installCachedFeatures('events')
const event = cli.getFeature('event')
let wasHit = false
//@ts-ignore
event.getEventContractBuilder = () => {
wasHit = true
}
const results = await this.Action('schema', 'sync').execute({})
this.assertCoreEventContractsSavedToDisk(false)
this.assertCorePayloadSchemasAreCreated(results, false)
assert.isFalse(wasHit)
assert.isFalse(diskUtil.doesFileExist(this.eventContractPath))
}
()
protected static async syncingSchemaAfterSyncEventsSyncsSchemasAndDoesNotWriteCoreEvents() {
const cli = await this.FeatureFixture().installCachedFeatures('events')
await this.Action('event', 'sync').execute({})
const event = cli.getFeature('event')
let wasHit = false
const oldGetter = event.getEventContractBuilder.bind(event)
//@ts-ignore
event.getEventContractBuilder = () => {
wasHit = true
return oldGetter()
}
const results = await this.Action('schema', 'sync').execute({})
assert.isTrue(wasHit)
await this.assertValidSyncSchemasResults(results, false)
}
()
protected static async syncingSchemasAfterSyncingCoreEventsSavesCoreEvents() {
await this.FeatureFixture().installCachedFeatures('events')
await this.syncCoreEventsPretendingToBeMercuryTypes()
const results = await this.Action('schema', 'sync').execute({})
await this.assertValidSyncSchemasResults(results, true)
}
private static async syncCoreEventsPretendingToBeMercuryTypes() {
const results = await this.Action('event', 'sync').execute({
shouldSyncOnlyCoreEvents: true,
eventBuilderFile: '../../../builder',
skillEventContractTypesFile: '../../builder',
})
const builder = `
export function buildEventContract(..._: any[]):any { return _[0] }
export function buildPermissionContract(..._: any[]):any { return _[0] }
`
diskUtil.writeFile(this.resolvePath('src', 'builder.ts'), builder)
await this.Service('pkg').uninstall([
'@sprucelabs/mercury-types',
'@sprucelabs/mercury-client',
'@sprucelabs/spruce-event-plugin',
])
return results
}
private static async assertValidSyncEventsResults(
results: FeatureActionResponse,
shouldSyncOnlyCoreEvents = false
) {
assert.isFalsy(results.errors)
await this.assertValidSyncSchemasResults(
results,
shouldSyncOnlyCoreEvents
)
}
private static async assertGlobalEventsAreTyped(eventName: string) {
const contents = diskUtil.readFile(this.eventContractPath)
assert.doesInclude(contents, `'${eventName}':`)
}
private static async assertValidSyncSchemasResults(
results: FeatureActionResponse,
shouldSyncOnlyCoreEvents: boolean
) {
assert.isFalsy(results.errors)
this.assertCoreEventContractsSavedToDisk(shouldSyncOnlyCoreEvents)
this.assertCorePayloadSchemasAreCreated(
results,
shouldSyncOnlyCoreEvents
)
await this.assertEventsHavePayloads(results)
await this.assertCombinedContractContents()
const coreContractContents = diskUtil.readFile(this.eventContractPath)
const search = `did-message::v2020_12_25': MercuryDidMessageEventContract_v2020_12_25['eventSignatures']['did-message::v2020_12_25']`
if (shouldSyncOnlyCoreEvents) {
assert.doesInclude(coreContractContents, search)
} else {
assert.doesNotInclude(coreContractContents, search)
}
}
private static async assertCombinedContractContents() {
const imported = await this.importCombinedContractsFile()
assert.isTruthy(imported)
assert.isArray(imported)
const localContract = eventContractUtil.unifyContracts(imported)
assert.isTruthy(localContract)
const sigs = eventContractUtil.getNamedEventSignatures(coreContract)
for (const sig of sigs) {
eventContractUtil.getSignatureByName(
localContract,
sig.fullyQualifiedEventName
)
}
}
private static async importCombinedContractsFile(): Promise<
EventContract[]
> {
const eventContractsFile = this.eventContractPath
// hack import to bring types in when importing contract file
if (
diskUtil.doesDirExist(
this.resolvePath('node_modules/@sprucelabs/mercury-types')
)
) {
const contents = diskUtil.readFile(eventContractsFile)
diskUtil.writeFile(
eventContractsFile,
`import '@sprucelabs/mercury-types'\n${contents}`
)
}
const imported: EventContract[] =
await this.Service('import').importDefault(eventContractsFile)
return imported
}
private static async assertEventsHavePayloads(
results: FeatureActionResponse,
eventName = 'authenticate'
) {
const imported = await this.importCombinedContractsFile()
const contract = eventContractUtil.unifyContracts(imported)
assert.isTruthy(contract)
validateEventContract(contract)
const signature = eventContractUtil.getSignatureByName(
contract,
eventName
)
assert.isTruthy(signature.emitPayloadSchema)
validateSchema(signature.emitPayloadSchema)
assert.isTruthy(signature.responsePayloadSchema)
validateSchema(signature.responsePayloadSchema)
}
private static assertCorePayloadSchemasAreCreated(
results: FeatureActionResponse,
shouldHaveWritten = true
) {
const filesToCheck = [
{
name: `unregisterListenersEmitTargetAndPayload.schema.ts`,
path: `schemas${pathUtil.sep}${MERCURY_API_NAMESPACE}`,
},
]
this.assertFilesWereGenerated(filesToCheck, results, shouldHaveWritten)
}
private static assertFilesWereGenerated(
filesToCheck: { name: string; path: string }[],
results: FeatureActionResponse,
shouldHaveWritten = true
) {
for (const file of filesToCheck) {
const expected = this.resolveHashSprucePath(
'schemas/mercury/v2020_12_25',
file.name
)
const doesExist = diskUtil.doesFileExist(expected)
if (shouldHaveWritten) {
assert.isTrue(
doesExist,
`Expected to find ${file} on the filesystem.`
)
} else {
assert.isFalse(doesExist, `Should not have written ${file}.`)
}
}
}
private static assertCoreEventContractsSavedToDisk(
shouldHaveWritten = true
) {
const sigs = eventContractUtil.getNamedEventSignatures(coreContract)
for (const sig of sigs) {
const expected = this.resolveHashSprucePath(
`events/mercury/${namesUtil.toCamel(
sig.eventName
)}.v2020_12_25.contract.ts`
)
const doesExist = diskUtil.doesFileExist(expected)
if (shouldHaveWritten) {
assert.isTrue(
doesExist,
`Expected to write a file ${expected}.`
)
} else {
assert.isFalse(
doesExist,
`Generated contract for ${sig.fullyQualifiedEventName} and it should not have because it's a core contract.`
)
}
}
}
}