Skip to content

Commit

Permalink
test: ✅ add tests on hooks and small refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudTA committed Sep 27, 2024
1 parent ea03623 commit 29c6a8a
Show file tree
Hide file tree
Showing 7 changed files with 592 additions and 57 deletions.
10 changes: 7 additions & 3 deletions packages/hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"format": "eslint ./ --fix",
"kube:e2e-ci": "echo 'check cache'",
"lint": "eslint ./",
"test:e2e-ci": "echo 'check cache'"
"test": "vitest run",
"test:cov": "vitest run --coverage",
"test:e2e-ci": "echo 'check cache'",
"test:watch": "vitest"
},
"dependencies": {
"@cpn-console/shared": "workspace:^",
Expand All @@ -31,12 +34,13 @@
"@cpn-console/ts-config": "workspace:^",
"@types/json-schema": "^7.0.15",
"@types/node": "^20.14.9",
"@vitest/coverage-v8": "^1.6.0",
"@vitest/coverage-v8": "^2.1.1",
"nodemon": "^3.1.4",
"rimraf": "^5.0.7",
"ts-algebra": "^2.0.0",
"typescript": "^5.5.2",
"undici-types": "^6.19.2"
"undici-types": "^6.19.2",
"vitest": "^2.1.1"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
Expand Down
177 changes: 177 additions & 0 deletions packages/hooks/src/hooks/hook.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { describe, expect, it } from 'vitest'
import { PluginApi } from '../utils/utils.ts'
import { createHook, executeStep } from './hook.ts'

const okStatus = { status: { result: 'OK' } } as const
const koStatus = { status: { result: 'KO', message: 'Failed' } } as const

async function simpleOkHookCall() {
return okStatus
}
async function simpleFailedHookCall() {
return koStatus
}

describe('test executeStep mechanism', () => {
it('test payload results, everything ok', async () => {
const results = await executeStep({
plugin1: simpleOkHookCall,
plugin2: simpleOkHookCall,
}, { apis: {}, args: {}, config: {}, failed: false, results: {} }, 'main')

expect(results.apis).toEqual({})
expect(results.args).toEqual({})
expect(results.config).toEqual({})
expect(results.failed).toBe(false)
expect(results.results).toEqual({
plugin1: { ...okStatus, executionTime: { main: expect.any(Number) } },
plugin2: { ...okStatus, executionTime: { main: expect.any(Number) } },
})
})

it('test payload results, everything ko', async () => {
const results = await executeStep({
plugin1: simpleFailedHookCall,
plugin2: simpleFailedHookCall,
}, { apis: {}, args: {}, config: {}, failed: false, results: {} }, 'main')

expect(results.apis).toEqual({})
expect(results.args).toEqual({})
expect(results.config).toEqual({})
expect(results.failed).contain('plugin1')
expect(results.failed).contain('plugin2')
expect(results.results).toEqual({
plugin1: { ...koStatus, executionTime: { main: expect.any(Number) } },
plugin2: { ...koStatus, executionTime: { main: expect.any(Number) } },
})
})

it('test payload results, partial ko', async () => {
const results = await executeStep({
plugin1: simpleOkHookCall,
plugin2: simpleFailedHookCall,
}, { apis: {}, args: {}, config: {}, failed: false, results: {} }, 'main')

expect(results.apis).toEqual({})
expect(results.args).toEqual({})
expect(results.config).toEqual({})
expect(results.failed).not.contain('plugin1')
expect(results.failed).contain('plugin2')
expect(results.results).toEqual({
plugin1: { ...okStatus, executionTime: { main: expect.any(Number) } },
plugin2: { ...koStatus, executionTime: { main: expect.any(Number) } },
})
})
})

describe('createHook', () => {
it('test empty hookStructure', async () => {
const hook = createHook(false)
expect(hook).toEqual({
apis: {},
steps: {
check: {},
pre: {},
main: {},
post: {},
revert: {},
},
execute: expect.any(Function),
validate: expect.any(Function),
})

const hookUnique = createHook(true)
expect(hookUnique.uniquePlugin).toBe('')
})

it('test hook execution, simple ok', async () => {
const hook = createHook(false)
hook.steps.main.plugin1 = simpleOkHookCall

const hookResult = await hook.execute({}, {})

expect(hookResult.args).toEqual({})
expect(hookResult.args).toEqual({})
expect(hookResult.config).toEqual({})
expect(hookResult.totalExecutionTime).toEqual(expect.any(Number))
expect(hookResult.failed).toEqual(false)
expect(hookResult.results).toEqual({
plugin1: { ...okStatus, executionTime: { main: expect.any(Number) } },
})
})

it('test payload results, multistep ok', async () => {
const hook = createHook(false)
hook.steps.pre.plugin1 = simpleOkHookCall
hook.steps.main.plugin1 = simpleOkHookCall
hook.steps.post.plugin1 = simpleOkHookCall

const hookResult = await hook.execute({}, {})

expect(hookResult.args).toEqual({})
expect(hookResult.config).toEqual({})
expect(hookResult.totalExecutionTime).toEqual(expect.any(Number))
expect(hookResult.failed).toEqual(false)
expect(hookResult.results).toEqual({ plugin1: { ...okStatus, executionTime: {
pre: expect.any(Number),
main: expect.any(Number),
post: expect.any(Number),
} } })
})

it('test payload results, main fails', async () => {
const hook = createHook(false)
hook.apis.plugin1 = () => new PluginApi() // à tester ailleurs
hook.steps.pre.plugin1 = simpleOkHookCall
hook.steps.main.plugin1 = simpleFailedHookCall
hook.steps.post.plugin1 = simpleOkHookCall

const hookResult = await hook.execute({}, {})
expect(hookResult.args).toEqual({})
expect(hookResult.config).toEqual({})
expect(hookResult.totalExecutionTime).toEqual(expect.any(Number))
expect(hookResult.failed).toEqual(['plugin1'])
expect(hookResult.results).toEqual({
plugin1: {
...koStatus,
executionTime: {
pre: expect.any(Number),
main: expect.any(Number),
},
},
})
})

it('test hook validate, simple ok', async () => {
const hook = createHook(false)
hook.steps.check.plugin1 = simpleOkHookCall
hook.apis.plugin1 = () => new PluginApi() // à tester ailleurs

const hookResult = await hook.validate({}, {})

expect(hookResult.args).toEqual({})
expect(hookResult.args).toEqual({})
expect(hookResult.config).toEqual({})
expect(hookResult.totalExecutionTime).toEqual(expect.any(Number))
expect(hookResult.failed).toEqual(false)
expect(hookResult.results).toEqual({
plugin1: { ...okStatus, executionTime: { validate: expect.any(Number) } },
})
})

it('test hook validate, fail', async () => {
const hook = createHook(false)
hook.steps.check.plugin1 = simpleFailedHookCall

const hookResult = await hook.validate({}, {})

expect(hookResult.args).toEqual({})
expect(hookResult.args).toEqual({})
expect(hookResult.config).toEqual({})
expect(hookResult.totalExecutionTime).toEqual(expect.any(Number))
expect(hookResult.failed).toEqual(['plugin1'])
expect(hookResult.results).toEqual({
plugin1: { ...koStatus, executionTime: { validate: expect.any(Number) } },
})
})
})
35 changes: 17 additions & 18 deletions packages/hooks/src/hooks/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ export type HookResult<Args extends DefaultArgs> = Omit<HookPayload<Args>, 'apis
export type StepCall<Args extends DefaultArgs> = (payload: HookPayload<Args>) => Promise<PluginResult>
type HookStep = Record<string, StepCall<DefaultArgs>>
export type HookStepsNames = 'check' | 'pre' | 'main' | 'post' | 'revert'
export type Hook<E extends DefaultArgs, V extends DefaultArgs> = {
export interface Hook<E extends DefaultArgs, V extends DefaultArgs> {
uniquePlugin?: string // if plugin register on it no other one can register on it
execute: (args: E, store: Config) => Promise<HookResult<E>>
validate: (args: V, store: Config) => Promise<HookResult<V>>
apis: Record<string, (args: E | V) => PluginApi>
} & Record<HookStepsNames, HookStep>
steps: Record<HookStepsNames, HookStep>
}
export type HookList<E extends DefaultArgs, V extends DefaultArgs> = Record<keyof typeof hooks, Hook<E, V>>

async function executeStep<Args extends DefaultArgs>(step: HookStep, payload: HookPayload<Args>, stepName: string) {
export async function executeStep<Args extends DefaultArgs>(step: HookStep, payload: HookPayload<Args>, stepName: string) {
const names = Object.keys(step)
const fns = names.map(async (name) => {
if (payload.results[name]?.executionTime) {
Expand Down Expand Up @@ -71,11 +72,13 @@ async function executeStep<Args extends DefaultArgs>(step: HookStep, payload: Ho
}

export function createHook<E extends DefaultArgs, V extends DefaultArgs>(unique = false) {
const check: HookStep = {}
const pre: HookStep = {}
const main: HookStep = {}
const post: HookStep = {}
const revert: HookStep = {}
const steps: Record<HookStepsNames, HookStep> = {
check: {},
pre: {},
main: {},
post: {},
revert: {},
}
const apis: Record<string, (args: E | V) => PluginApi> = {
}
const execute = async (args: E, config: Config): Promise<HookResult<E>> => {
Expand All @@ -92,11 +95,11 @@ export function createHook<E extends DefaultArgs, V extends DefaultArgs>(unique
config,
}

const steps = [pre, main, post]
for (const [key, step] of Object.entries(steps)) {
payload = await executeStep(step, payload, key)
const executeSteps = ['pre', 'main', 'post'] as const
for (const step of executeSteps) {
payload = await executeStep(steps[step], payload, step)
if (payload.failed) {
payload = await executeStep(revert, payload, 'revert')
payload = await executeStep(steps.revert, payload, 'revert')
break
}
}
Expand All @@ -123,7 +126,7 @@ export function createHook<E extends DefaultArgs, V extends DefaultArgs>(unique
config,
}

const result = await executeStep(check, payload, 'validate')
const result = await executeStep(steps.check, payload, 'validate')
return {
args: result.args,
results: result.results,
Expand All @@ -134,11 +137,7 @@ export function createHook<E extends DefaultArgs, V extends DefaultArgs>(unique
}
const hook: Hook<E, V> = {
apis,
check,
pre,
main,
post,
revert,
steps,
execute,
validate,
}
Expand Down
14 changes: 7 additions & 7 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ function pluginManager(options: PluginManagerOptions): PluginManager {
continue
}
if (functions?.api) {
// @ts-ignore
hooks[hook].apis[name] = functions.api
}
for (const [step, fn] of objectEntries(functions?.steps ?? {})) {
Expand All @@ -97,7 +96,7 @@ function pluginManager(options: PluginManagerOptions): PluginManager {
continue
}
// @ts-ignore
hooks[hook][step][name] = fn
hooks[hook].steps[step][name] = fn
message.push(`${hook}:${step}`)
}
}
Expand All @@ -111,11 +110,12 @@ function pluginManager(options: PluginManagerOptions): PluginManager {
delete servicesInfos[name]

Object.values(hooks).forEach((hook) => {
delete hook.check[name]
delete hook.pre[name]
delete hook.main[name]
delete hook.post[name]
delete hook.revert[name]
delete hook.steps.check[name]
delete hook.steps.pre[name]
delete hook.steps.main[name]
delete hook.steps.post[name]
delete hook.steps.revert[name]
delete hook.apis[name]
})
}

Expand Down
76 changes: 76 additions & 0 deletions packages/hooks/src/utils/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, it } from 'vitest'
import { defaultOrNullish, disabledOrDefault, enabledOrDefaultOrNullish, objectEntries, objectKeys, objectValues, specificallyDisabled, specificallyEnabled } from './utils.ts'

const object = { test1: 1, test2: 2, 3: 'test3' }

it('should return object keys', () => {
const keys = objectKeys(object)
console.log(typeof keys[2])

// cannot gaurantee order in keys
expect(keys[0]).contain('3')
expect(keys[1]).contain('test1')
expect(keys[2]).contain('test2')
})

it('should return object entries', () => {
const keys = objectEntries(object)

expect(keys[0]).toEqual(['3', 'test3'])
expect(keys[1]).toEqual(['test1', 1])
expect(keys[2]).toEqual(['test2', 2])
})

it('should return object values', () => {
const keys = objectValues(object)

// cannot gaurantee order in values
expect(keys).contain('test3')
expect(keys).contain(1)
expect(keys).contain(2)
})

const values = [
'',
'nimp',
'enabled',
'default',
'disabled',
] as const
describe('test config parsing', () => {
it('enabledOrDefaultOrNullish', () => {
expect(enabledOrDefaultOrNullish(values[0])).toBeTruthy()
expect(enabledOrDefaultOrNullish(values[1])).toBeFalsy()
expect(enabledOrDefaultOrNullish(values[2])).toBeTruthy()
expect(enabledOrDefaultOrNullish(values[3])).toBeTruthy()
expect(enabledOrDefaultOrNullish(values[4])).toBeFalsy()
})
it('specificallyDisabled', () => {
expect(specificallyDisabled(values[0])).toBeFalsy()
expect(specificallyDisabled(values[1])).toBeFalsy()
expect(specificallyDisabled(values[2])).toBeFalsy()
expect(specificallyDisabled(values[3])).toBeFalsy()
expect(specificallyDisabled(values[4])).toBeTruthy()
})
it('specificallyEnabled', () => {
expect(specificallyEnabled(values[0])).toBeFalsy()
expect(specificallyEnabled(values[1])).toBeFalsy()
expect(specificallyEnabled(values[2])).toBeTruthy()
expect(specificallyEnabled(values[3])).toBeFalsy()
expect(specificallyEnabled(values[4])).toBeFalsy()
})
it('defaultOrNullish', () => {
expect(defaultOrNullish(values[0])).toBeTruthy()
expect(defaultOrNullish(values[1])).toBeFalsy()
expect(defaultOrNullish(values[2])).toBeFalsy()
expect(defaultOrNullish(values[3])).toBeTruthy()
expect(defaultOrNullish(values[4])).toBeFalsy()
})
it('disabledOrDefault', () => {
expect(disabledOrDefault(values[0])).toBeFalsy()
expect(disabledOrDefault(values[1])).toBeFalsy()
expect(disabledOrDefault(values[2])).toBeFalsy()
expect(disabledOrDefault(values[3])).toBeTruthy()
expect(disabledOrDefault(values[4])).toBeTruthy()
})
})
Loading

0 comments on commit 29c6a8a

Please sign in to comment.