Skip to content

Commit

Permalink
add some basic operations and unit tests for interfacing with events (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
rikukissa authored Nov 19, 2024
1 parent d9bd090 commit 8e6b600
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 33 deletions.
71 changes: 65 additions & 6 deletions packages/events/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,80 @@ afterEach(async () => {
resetServer()
})

test('creating a declaration is an idempotent operation', async () => {
function createClient() {
const createCaller = createCallerFactory(appRouter)

const caller = createCaller({})
return caller
}

const client = createClient()
test('event can be created and fetched', async () => {
const event = await client.event.create({
transactionId: '1',
event: { type: 'birth' }
})

const fetchedEvent = await client.event.get(event.id)

expect(fetchedEvent).toEqual(event)
})

test('creating an event is an idempotent operation', async () => {
const db = await getClient()

await caller.event.create({
await client.event.create({
transactionId: '1',
record: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

await caller.event.create({
await client.event.create({
transactionId: '1',
record: { type: 'birth', fields: [] }
event: { type: 'birth' }
})

expect(await db.collection('events').find().toArray()).toHaveLength(1)
})

test('stored events can be modified', async () => {
const originalEvent = await client.event.create({
transactionId: '1',
event: { type: 'birth' }
})

const event = await client.event.patch({
id: originalEvent.id,
type: 'death'
})

expect(event.updatedAt).not.toBe(originalEvent.updatedAt)
expect(event.type).toBe('death')
})

test('actions can be added to created events', async () => {
const originalEvent = await client.event.create({
transactionId: '1',
event: { type: 'birth' }
})

const event = await client.event.actions.create({
eventId: originalEvent.id,
action: {
type: 'REGISTERED',
fields: [],
identifiers: {
trackingId: '123',
registrationNumber: '456'
}
}
})

expect(event.actions).toContainEqual(
expect.objectContaining({
type: 'REGISTERED',
identifiers: {
trackingId: '123',
registrationNumber: '456'
}
})
)
})
29 changes: 26 additions & 3 deletions packages/events/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { z } from 'zod'
import { createEvent, EventInput, getEventById } from './service/events'
import {
ActionInput,
addAction,
createEvent,
EventInput,
EventInputWithId,
getEventById,
patchEvent
} from './service/events'

export const t = initTRPC.create({
transformer: superjson
Expand All @@ -32,14 +40,29 @@ export const appRouter = router({
.input(
z.object({
transactionId: z.string(),
record: EventInput
event: EventInput
})
)
.mutation(async (options) => {
return createEvent(options.input.record, options.input.transactionId)
return createEvent(options.input.event, options.input.transactionId)
}),
patch: publicProcedure.input(EventInputWithId).mutation(async (options) => {
return patchEvent(options.input)
}),
get: publicProcedure.input(z.string()).query(async ({ input }) => {
return getEventById(input)
}),
actions: router({
create: publicProcedure
.input(
z.object({
eventId: z.string(),
action: ActionInput
})
)
.mutation(async (options) => {
return addAction(options.input.eventId, options.input.action)
})
})
})
})
99 changes: 75 additions & 24 deletions packages/events/src/service/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,14 @@ import { getUUID } from '@opencrvs/commons'
import { z } from 'zod'

export const EventInput = z.object({
type: z.string(),
fields: z.array(
z.object({
id: z.string(),
value: z.union([
z.string(),
z.number(),
z.array(
z.object({
optionValues: z.array(z.string()),
type: z.string(),
data: z.string(),
fileSize: z.number()
})
)
])
})
)
type: z.string()
})

export const EventInputWithId = EventInput.extend({
id: z.string()
})

const ActionBase = z.object({
const ActionInputBase = z.object({
type: z.enum([
'CREATED',
'ASSIGNMENT',
Expand All @@ -44,8 +31,6 @@ const ActionBase = z.object({
'CORRECTION',
'DUPLICATES_DETECTED'
]),
createdAt: z.date(),
createdBy: z.string(),
fields: z.array(
z.object({
id: z.string(),
Expand All @@ -65,11 +50,11 @@ const ActionBase = z.object({
)
})

const Action = z.union([
ActionBase.extend({
export const ActionInput = z.union([
ActionInputBase.extend({
type: z.enum(['CREATED'])
}),
ActionBase.extend({
ActionInputBase.extend({
type: z.enum(['REGISTERED']),
identifiers: z.object({
trackingId: z.string(),
Expand All @@ -78,10 +63,20 @@ const Action = z.union([
})
])

const Action = ActionInput.and(
z.object({
createdAt: z.date(),
createdBy: z.string()
})
)

type ActionInput = z.infer<typeof ActionInput>

export const Event = EventInput.extend({
id: z.string(),
type: z.string(), // Should be replaced by a reference to a form version
createdAt: z.date(),
updatedAt: z.date(),
actions: z.array(Action)
})
export type Event = z.infer<typeof Event>
Expand All @@ -107,6 +102,7 @@ class EventNotFoundError extends Error {

export async function getEventById(id: string) {
const db = await getClient()

const collection = db.collection<z.infer<typeof Event>>('events')
const event = await collection.findOne({ id: id })
if (!event) {
Expand Down Expand Up @@ -136,6 +132,7 @@ export async function createEvent(
id,
transactionId,
createdAt: now,
updatedAt: now,
actions: [
{
type: 'CREATED',
Expand All @@ -148,3 +145,57 @@ export async function createEvent(

return getEventById(id)
}

export async function addAction(eventId: string, action: ActionInput) {
const db = await getClient()
const collection = db.collection<z.infer<typeof Event>>('events')

const now = new Date()

await collection.updateOne(
{
id: eventId
},
{
$push: {
actions: {
...action,
createdAt: now,
createdBy: '123-123-123'
}
}
}
)

return getEventById(eventId)
}

export async function patchEvent(
event: z.infer<typeof EventInputWithId>
): Promise<Event> {
const existingEvent = await getEventById(event.id)

if (!existingEvent) {
throw new EventNotFoundError(event.id)
}

const db = await getClient()
const collection =
db.collection<z.infer<typeof EventWithTransactionId>>('events')

const now = new Date()

await collection.updateOne(
{
id: event.id
},
{
$set: {
...event,
updatedAt: now
}
}
)

return getEventById(event.id)
}

0 comments on commit 8e6b600

Please sign in to comment.