diff --git a/src/commands/shared.ts b/src/commands/shared.ts index 8b880201..07779918 100644 --- a/src/commands/shared.ts +++ b/src/commands/shared.ts @@ -18,9 +18,9 @@ const groupBy = (items: ReadonlyArray, getKey: (item: T) => string): Map()) -const colorInspect = (obj: any, depth?: number): string => inspect(obj, false, depth, true) +const colorInspect = (obj: unknown, depth?: number): string => inspect(obj, false, depth, true) -export const gatherValidationErrors = (problems: ReadonlyArray): string => +export const gatherValidationErrors = (problems: ReadonlyArray>): string => Array.from(groupBy(problems, (x) => x.path)) .map(([dataPath, groupedProblems]) => Array.from(groupBy(groupedProblems, (x) => x.problemType)) diff --git a/src/commands/test/test.spec.ts b/src/commands/test/test.spec.ts new file mode 100644 index 00000000..27a2c86c --- /dev/null +++ b/src/commands/test/test.spec.ts @@ -0,0 +1,75 @@ +import { testConfigs } from './test' +import { randomString, mockFn, mockObj, mocked } from '~test-helpers' +import { FetchResource, TypeValidator, doItAll } from '~validation' +import { Config } from '~config' +import { TestFn } from '~validation/validators' +import logger from '~logger' +import stripAnsi from 'strip-ansi' +import { ProblemType } from '~problem' + +jest.unmock('./test') +jest.unmock('~messages') +jest.unmock('~commands/shared') + +describe('test configs', () => { + const baseUrl = randomString('base-url') + const mockFetchResource = mockFn() + const mockTypeValidator = mockObj({}) + const mockTest = mockFn() + const mockDoItAll = mocked(doItAll) + const mockLogger = mockObj(logger) + + beforeEach(() => { + jest.resetAllMocks() + mockDoItAll.mockReturnValue(mockTest) + }) + + it('calls doItAll with the correct args', async () => { + const configs: Config[] = [{ name: 'yo!', request: { endpoint: randomString('endpoint') } } as Config] + mockTest.mockResolvedValue([]) + + await testConfigs(baseUrl, mockFetchResource, configs, mockTypeValidator) + + expect(mockDoItAll).toBeCalledWith(mockTypeValidator, mockFetchResource) + }) + + it('logs when a test passes', async () => { + const configs: Config[] = [{ name: 'yo!', request: { endpoint: randomString('endpoint') } } as Config] + mockTest.mockResolvedValue([]) + + await testConfigs(baseUrl, mockFetchResource, configs, mockTypeValidator) + + const expectedMessage = `PASSED: yo! - ${baseUrl}${configs[0].request.endpoint}\n` + expect(mockLogger.info).toBeCalled() + expect(stripAnsi((mockLogger.info.mock.calls[0][0] as unknown) as string)).toEqual(expectedMessage) + }) + + it('logs when a test fail', async () => { + const configs: Config[] = [{ name: 'yo!', request: { endpoint: randomString('endpoint') } } as Config] + mockTest.mockResolvedValue([{ path: 'some.path', problemType: ProblemType.Request, message: 'message' }]) + + await expect(testConfigs(baseUrl, mockFetchResource, configs, mockTypeValidator)).rejects.toThrowError( + 'Not all tests passed', + ) + + const expectedMessage = `FAILED: yo!\nURL: ${baseUrl}${configs[0].request.endpoint}\n` + const expectedError1 = `Request some.path message\n` + expect(mockLogger.error).toBeCalled() + expect(stripAnsi((mockLogger.error.mock.calls[0][0] as unknown) as string)).toEqual( + expectedMessage + expectedError1, + ) + }) + + it('logs when a test throws an error', async () => { + const configs: Config[] = [{ name: 'yo!', request: { endpoint: randomString('endpoint') } } as Config] + mockTest.mockRejectedValue(new Error('woah dude')) + + await expect(testConfigs(baseUrl, mockFetchResource, configs, mockTypeValidator)).rejects.toThrowError( + 'Not all tests passed', + ) + + const expectedMessage = `ERROR: yo!\nURL: ${baseUrl}${configs[0].request.endpoint}\nwoah dude\n` + expect(mockLogger.error).toBeCalled() + expect(stripAnsi((mockLogger.error.mock.calls[0][0] as unknown) as string)).toEqual(expectedMessage) + }) +}) diff --git a/src/commands/test/test.ts b/src/commands/test/test.ts index 29e9792b..f7269809 100644 --- a/src/commands/test/test.ts +++ b/src/commands/test/test.ts @@ -8,22 +8,18 @@ import { testPassed, testFailed, testError } from '~messages' // TODO: why is this returning a number? yeah, what the fuck? // TODO: reuse this at config type validaiton type. nononononoooooo const logTestResults = (baseUrl: string) => (displayName: string, endpoint: string) => ( - problems: Problem[], + problems: Public[], ): 0 | 1 => { const displayEndpoint = blue(`${baseUrl}${endpoint}`) if (!problems.length) { - logger.info(testPassed(displayName, displayEndpoint) + '\n') + logger.info(testPassed(displayName, displayEndpoint)) return 0 } else { - logger.error(testFailed(displayName, displayEndpoint, problems) + '\n') + logger.error(testFailed(displayName, displayEndpoint, problems)) return 1 } } -const logTestError = (displayName: string) => (err: Error): void => { - logger.error(testError(displayName, err.message) + `\n`) -} - export const testConfigs = async ( baseURL: string, fetchResource: FetchResource, @@ -37,7 +33,10 @@ export const testConfigs = async ( const testTasks = configs.map((testConfig) => { return test(testConfig) .then(resultsLogger(testConfig.name, testConfig.request.endpoint)) - .catch(logTestError(testConfig.name)) + .catch((err) => { + logger.error(testError(testConfig.name, baseURL + testConfig.request.endpoint, err.message)) + return 1 + }) }) const results = Promise.all(testTasks) diff --git a/src/messages.ts b/src/messages.ts index 198582ff..b6551483 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -22,7 +22,8 @@ export const validationFailed = ({ name, problems }: ProblemResult): string => export const testPassed = (name: string, endpoint: string): string => `${green('PASSED')}: ${name} - ${endpoint}\n` -export const testFailed = (name: string, endpoint: string, problems: Problem[]): string => - `${red.bold('FAILED')}: ${red.bold(name)}\nURL: ${endpoint}\n${gatherValidationErrors(problems)}` +export const testFailed = (name: string, endpoint: string, problems: Public[]): string => + `${red.bold('FAILED')}: ${red.bold(name)}\nURL: ${endpoint}\n${gatherValidationErrors(problems)}\n` -export const testError = (name: string, message: string): string => `${red('FAILED')}: ${name} - ${message}\n` +export const testError = (name: string, endpoint: string, message: string): string => + `${red.bold('ERROR')}: ${red.bold(name)}\nURL: ${endpoint}\n${message}\n` diff --git a/src/validation/validators.ts b/src/validation/validators.ts index 99fc5e90..b98827d6 100644 --- a/src/validation/validators.ts +++ b/src/validation/validators.ts @@ -5,7 +5,7 @@ import { shouldBe, problemFetching } from '~messages' export type LoaderResponse = { status: number; data?: Data } export type FetchResource = (config: Config) => Promise -export type TestFn = (config: Config) => Promise +export type TestFn = (config: Config) => Promise[]> const isDeeplyEqual = (expected: unknown, actual: unknown): boolean => { if (typeof expected === 'object') {