Skip to content

Commit

Permalink
feat: added unit tests and refactored plugin methods
Browse files Browse the repository at this point in the history
  • Loading branch information
zoemaas committed Feb 22, 2024
1 parent 3c9bd88 commit 31eac66
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 94 deletions.
11 changes: 6 additions & 5 deletions packages/event-logger/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ constants:
baseUrl: http://localhost:3335
port: 3335
methods:
- loggerGetAuditEvents
- loggerLogAuditEvent
- persistState
- loadState
- deleteExpiredStates

dbConnection:
$require: typeorm?t=function#createConnection
Expand All @@ -17,7 +18,7 @@ dbConnection:
migrations:
$require: './packages/data-store?t=object#DataStoreMigrations'
entities:
$require: './packages/data-store?t=object#DataStoreEventLoggerEntities'
$require: './packages/data-store?t=object#DataStoreXStateStoreEntities'

server:
baseUrl:
Expand Down Expand Up @@ -81,9 +82,9 @@ agent:
$args:
- schemaValidation: false
plugins:
- $require: ./packages/event-logger/dist#EventLogger
- $require: ./packages/xstate-persistence/dist#XStatePersistence
$args:
- store:
$require: './packages/data-store/dist#EventLoggerStore'
$require: './packages/data-store/dist#XStateStore'
$args:
- $ref: /dbConnection
12 changes: 5 additions & 7 deletions packages/xstate-persistence/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,18 @@
"build:clean": "tsc --build --clean && tsc --build",
"generate-plugin-schema": "ts-node ../../packages/dev/bin/sphereon.js dev generate-plugin-schema"
},
"dependencies": {

"devDependencies": {
"@sphereon/ssi-sdk.agent-config": "workspace:*",
"@sphereon/ssi-sdk.core": "workspace:*",
"@sphereon/ssi-sdk.data-store": "workspace:*",
"@sphereon/ssi-sdk.vc-status-list": "workspace:*",
"@sphereon/ssi-sdk.vc-status-list-issuer-drivers": "workspace:*",
"@sphereon/ssi-types": "workspace:*",
"@veramo/core": "4.2.0"
},
"devDependencies": {
"@types/node": "^20.11.17",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@veramo/remote-client": "4.2.0",
"@veramo/remote-server": "4.2.0",
"ts-node": "^10.9.1",
"typeorm": "0.3.17",
"typescript": "4.9.5"
},
"files": [
Expand Down
28 changes: 3 additions & 25 deletions packages/xstate-persistence/plugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,6 @@
"updatedAt"
]
},
"XStatePersistenceEvent": {
"type": "object",
"properties": {
"type": {
"$ref": "#/components/schemas/XStatePersistenceEventType"
},
"data": {
"$ref": "#/components/schemas/NonPersistedXStatePersistenceEvent"
}
},
"required": [
"type",
"data"
]
},
"XStatePersistenceEventType": {
"type": "string",
"const": "every"
},
"NonPersistedXStatePersistenceEvent": {
"$ref": "#/components/schemas/SaveStateArgs"
},
Expand Down Expand Up @@ -131,9 +112,6 @@
"state",
"type"
]
},
"OnEventResult": {
"$ref": "#/components/schemas/VoidResult"
}
},
"methods": {
Expand All @@ -155,13 +133,13 @@
"$ref": "#/components/schemas/LoadStateResult"
}
},
"onEvent": {
"persistState": {
"description": "Persists the state whenever an event is emitted",
"arguments": {
"$ref": "#/components/schemas/XStatePersistenceEvent"
"$ref": "#/components/schemas/NonPersistedXStatePersistenceEvent"
},
"returnType": {
"$ref": "#/components/schemas/OnEventResult"
"$ref": "#/components/schemas/State"
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions packages/xstate-persistence/src/__tests__/localAgent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DataSource } from 'typeorm'
import { createObjects, getConfig } from '@sphereon/ssi-sdk.agent-config'

jest.setTimeout(60000)

import xStatePersistenceAgentLogic from './shared/xStatePersistenceAgentLogic'

let dbConnection: Promise<DataSource>
let agent: any

const setup = async (): Promise<boolean> => {
const config = await getConfig('packages/event-logger/agent.yml')
const { localAgent, db } = await createObjects(config, { localAgent: '/agent', db: '/dbConnection' })
agent = localAgent
dbConnection = db

return true
}

const tearDown = async (): Promise<boolean> => {
await (await dbConnection).destroy()
return true
}

const getAgent = () => agent
const testContext = {
getAgent,
setup,
tearDown,
}

describe('Local integration tests', (): void => {
xStatePersistenceAgentLogic(testContext)
})
71 changes: 71 additions & 0 deletions packages/xstate-persistence/src/__tests__/restAgent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'cross-fetch/polyfill'
import {createAgent, IAgent, IAgentOptions} from '@veramo/core'
import {AgentRestClient} from '@veramo/remote-client'
import {AgentRouter, RequestWithAgentRouter} from '@veramo/remote-server'
// @ts-ignore
import express, {Router} from 'express'
import {Server} from 'http'
import {DataSource} from 'typeorm'
import {createObjects, getConfig} from '@sphereon/ssi-sdk.agent-config'
import {IXStatePersistence} from "../index";
import xStatePersistenceAgentLogic from './shared/xStatePersistenceAgentLogic'

jest.setTimeout(60000)

const port = 3002
const basePath = '/agent'

let serverAgent: IAgent
let restServer: Server
let dbConnection: Promise<DataSource>

const getAgent = (options?: IAgentOptions) =>
createAgent<IXStatePersistence>({
...options,
plugins: [
new AgentRestClient({
url: 'http://localhost:' + port + basePath,
enabledMethods: serverAgent.availableMethods(),
schema: serverAgent.getSchema(),
}),
],
})

const setup = async (): Promise<boolean> => {
const config = await getConfig('packages/event-logger/agent.yml')
const { agent, db } = await createObjects(config, { agent: '/agent', db: '/dbConnection' })
serverAgent = agent
dbConnection = db

const agentRouter: Router = AgentRouter({
exposedMethods: serverAgent.availableMethods(),
})

const requestWithAgent: Router = RequestWithAgentRouter({
agent: serverAgent,
})

return new Promise((resolve): void => {
const app = express()
app.use(basePath, requestWithAgent, agentRouter)
restServer = app.listen(port, () => {
resolve(true)
})
})
}

const tearDown = async (): Promise<boolean> => {
restServer.close()
await (await dbConnection).destroy()
return true
}

const testContext = {
getAgent,
setup,
tearDown,
}

describe('REST integration tests', (): void => {
xStatePersistenceAgentLogic(testContext)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {NonPersistedXStateStoreEvent, State} from "@sphereon/ssi-sdk.data-store";
import {TAgent} from '@veramo/core'
import {IXStatePersistence, SQLDialect} from '../../index'

type ConfiguredAgent = TAgent<IXStatePersistence>

export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise<boolean>; tearDown: () => Promise<boolean> }): void => {
describe('Event Logger Agent Plugin', (): void => {
let agent: ConfiguredAgent

beforeAll(async (): Promise<void> => {
await testContext.setup()
agent = testContext.getAgent()
})

afterAll(testContext.tearDown)

it('should store xstate event', async (): Promise<void> => {
const xstateEvent: NonPersistedXStateStoreEvent = {
state: 'test_state',
type: 'b40b8474-58a2-4b23-9fde-bd6ee1902cdb',
createdAt: new Date(),
updatedAt: new Date(),
completedAt: new Date(),
tenantId: 'test_tenant_id'
}

const savedXStoreEvent: State = await agent.persistState(xstateEvent)
expect(savedXStoreEvent).toBeDefined()
})

it('should retrieve an xstate event', async (): Promise<void> => {
const xstateEvent: NonPersistedXStateStoreEvent = {
state: 'test_state',
type: 'b40b8474-58a2-4b23-9fde-bd6ee1902cdb',
createdAt: new Date(),
updatedAt: new Date(),
completedAt: new Date(),
tenantId: 'test_tenant_id'
}

const savedXStoreEvent: State = await agent.persistState({...xstateEvent})
expect(savedXStoreEvent).toBeDefined()

const result: State = await agent.loadState({ type: savedXStoreEvent.type })
expect(result).toBeDefined()
})

it('should delete the expired records', async () => {
const now = new Date()
const newestXstateEvent: NonPersistedXStateStoreEvent = {
state: 'test_state',
type: 'test_type_1',
createdAt: now,
completedAt: new Date(),
tenantId: 'test_tenant_id'
}
const middleXstateEvent: NonPersistedXStateStoreEvent = {
state: 'test_state',
type: 'test_type_2',
createdAt: new Date(+now - 30000),
completedAt: new Date(),
tenantId: 'test_tenant_id'
}

const oldestXstateEvent: NonPersistedXStateStoreEvent = {
state: 'test_state',
type: 'test_type_3',
createdAt: new Date(+now - 60000),
completedAt: new Date(),
tenantId: 'test_tenant_id'
}

await agent.persistState(newestXstateEvent)
await agent.persistState(middleXstateEvent)
await agent.persistState(oldestXstateEvent)

await agent.deleteExpiredStates({ duration: 40000, dialect: SQLDialect.SQLite3 })

await expect(agent.loadState({ type: 'test_type_1'})).resolves.toBeDefined()
await expect(agent.loadState({ type: 'test_type_2'})).resolves.toBeDefined()
await expect(agent.loadState({ type: 'test_type_3'})).rejects.toEqual(Error('No state found for type: test_type_3'))
})
})
}
46 changes: 27 additions & 19 deletions packages/xstate-persistence/src/agent/XStatePersistence.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {IAbstractXStateStore} from "@sphereon/ssi-sdk.data-store";
import {DeleteStateArgs, IAbstractXStateStore, State} from "@sphereon/ssi-sdk.data-store";
import {IAgentPlugin,} from '@veramo/core'

import {
DeleteExpiredStatesArgs,
DeleteStateResult,
OnEventResult,
NonPersistedXStatePersistenceEvent,
RequiredContext,
schema,
SQLDialect,
XStatePersistenceEvent,
XStatePersistenceEventType,
XStateStateManagerOptions
Expand Down Expand Up @@ -35,21 +36,28 @@ export class XStatePersistence implements IAgentPlugin {
this.methods = {
loadState: this.loadState.bind(this),
deleteExpiredStates: this.deleteExpiredStates.bind(this),
onEvent: this.onEvent.bind(this)
persistState: this.persistState.bind(this)
}
}

async onEvent(event: XStatePersistenceEvent, context: RequiredContext): Promise<OnEventResult> {
public async onEvent(event: XStatePersistenceEvent, context: RequiredContext): Promise<void> {
switch (event.type) {
case XStatePersistenceEventType.EVERY:
// Calling the context of the agent to make sure the REST client is called when configured
await context.agent.persistState(event.data)
await context.agent.persistState({...event.data})
break
default:
return Promise.reject(Error('Event type not supported'))
}
}

private async persistState(args: NonPersistedXStatePersistenceEvent): Promise<State> {
if (!this.store) {
return Promise.reject(Error('No store available in options'))
}
return this.store.saveState(args)
}

private async loadState(args: LoadStateArgs): Promise<LoadStateResult> {
if (!this.store) {
return Promise.reject(Error('No store available in options'))
Expand All @@ -61,22 +69,22 @@ export class XStatePersistence implements IAgentPlugin {
if (!this.store) {
return Promise.reject(Error('No store available in options'))
}
const sqLiteParams: DeleteStateArgs = {
where: `created_at < datetime('now', :duration)`,
parameters: {
duration: `-${args.duration / 1000} seconds`
}
}
const postgreSQLParams: DeleteStateArgs = {
where: 'created_at < :duration',
parameters: {
duration: `NOW() - ${args.duration / 1000} seconds::interval`
}
}
switch (args.dialect) {
case 'SQLite3':
const sqLiteParams = {
where: `created_at < datetime('now', :duration)`,
params: {
duration: `-${args.duration / 1000} seconds`
}
}
case SQLDialect.SQLite3:
return this.store.deleteState(sqLiteParams)
case 'PostgreSQL':
const postgreSQLParams = {
where: 'created_at < :duration',
params: {
duration: `NOW() - '${args.duration / 1000} seconds'::interval`
}
}
case SQLDialect.PostgreSQL:
return this.store.deleteState(postgreSQLParams)
default:
return Promise.reject(Error('Invalid database dialect'))
Expand Down
Loading

0 comments on commit 31eac66

Please sign in to comment.