Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ lib
.vscode
.DS_Store

.tsimp
.tshy
.tshy-build
5 changes: 3 additions & 2 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ global_job_config:
prologue:
commands:
- set -e
- sem-version node 18
- npm i -g pnpm@8
- sem-version node 20.10
- corepack enable
- corepack install --global [email protected]
- checkout
- cache restore node-$(checksum pnpm-lock.yaml)
- pnpm i --frozen-lockfile --prefer-offline
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Based on the bluebird promises.

## Installation

`yarn add @microfleet/validation`
`npm i @microfleet/validation`

## Usage

Expand All @@ -26,6 +26,8 @@ import Errors = require('common-errors');
import Validator, { HttpStatusError } from '@microfleet/validation';
const validator = new Validator('./schemas');

await validator.init()

// some logic here
validator.validate('config', {
configuration: 'string'
Expand Down
3 changes: 3 additions & 0 deletions __tests__/fixtures/cjs/cjs-01.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
"$id": "cjs-01.cjs"
}
1 change: 1 addition & 0 deletions __tests__/fixtures/cjs/cjs-02.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const $id = "cjs-02.cts"
1 change: 1 addition & 0 deletions __tests__/fixtures/cjs/cjs-03.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const $id = "cjs-03.ts"
1 change: 1 addition & 0 deletions __tests__/fixtures/esm/esm-01.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const $id = "esm-01.mjs"
1 change: 1 addition & 0 deletions __tests__/fixtures/esm/esm-02.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const $id = "esm-02.mts"
4 changes: 4 additions & 0 deletions __tests__/fixtures/esm/esm-03-defaults.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
$id: 'esm-03-defaults.mts',
type: 'string'
}
4 changes: 4 additions & 0 deletions __tests__/fixtures/esm/esm-04-defaults.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
$id: 'esm-04-defaults.mjs',
type: 'string'
}
183 changes: 89 additions & 94 deletions __tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import test, { beforeEach } from 'node:test'
import assert from 'node:assert/strict'
import path from 'node:path'
import { io, NotFoundError } from 'common-errors'
import path = require('path');
import Validation, { HttpStatusError } from '../src'
import Validation, { HttpStatusError } from '../src/index'

const CORRECT_PATH = path.resolve(__dirname, './fixtures')
const BAD_PATH = path.resolve(__dirname, './notexistant')
Expand All @@ -15,102 +17,98 @@ beforeEach(() => {
})

test('doesnt init on missing dir', () => {
expect(() => validator.init()).toThrow(TypeError)
assert.rejects(validator.init(), TypeError)
})

test('should successfully init', () => {
validator.init(CORRECT_PATH)
test('should successfully init', async () => {
validator = new Validation(CORRECT_PATH, () => true)
await validator.init()

expect(typeof validator.ajv.getSchema('custom')).toBe('function')
expect(typeof validator.ajv.getSchema('core-no-id')).toBe('function')
expect(typeof validator.ajv.getSchema('nested.no-id')).toBe('function')
assert.equal(typeof validator.ajv.getSchema('custom'), 'function', 'custom')
assert.equal(typeof validator.ajv.getSchema('core-no-id'), 'function', 'core-no-id')
assert.equal(typeof validator.ajv.getSchema('nested.no-id'), 'function')

assert.equal(typeof validator.ajv.getSchema('cjs-01.cjs'), 'function', 'cjs-01.cjs')
assert.equal(typeof validator.ajv.getSchema('cjs-02.cts'), 'function', 'cjs-02.cts')
assert.equal(typeof validator.ajv.getSchema('cjs-03.ts'), 'function', 'cjs-03.ts')

assert.equal(typeof validator.ajv.getSchema('esm-01.mjs'), 'function', 'esm-01.mjs')
assert.equal(typeof validator.ajv.getSchema('esm-02.mts'), 'function', 'esm-02.mts')
assert.equal(typeof validator.ajv.getSchema('esm-03-defaults.mts'), 'function', 'esm-03-defaults.mts')
assert.equal(typeof validator.ajv.getSchema('esm-04-defaults.mjs'), 'function', 'esm-04-defaults.mjs')
})

test('should successfully init with a relative path', () => {
validator.init(RELATIVE_PATH)
test('should successfully init with a relative path', async () => {
await validator.init(RELATIVE_PATH)

expect(typeof validator.ajv.getSchema('custom')).toBe('function')
expect(typeof validator.ajv.getSchema('core-no-id')).toBe('function')
expect(typeof validator.ajv.getSchema('nested.no-id')).toBe('function')
assert.equal(typeof validator.ajv.getSchema('custom'), 'function')
assert.equal(typeof validator.ajv.getSchema('core-no-id'), 'function')
assert.equal(typeof validator.ajv.getSchema('nested.no-id'), 'function')
})

test('(async) should successfully init', async () => {
await validator.init(CORRECT_PATH, true)
await validator.init(CORRECT_PATH)

expect(typeof validator.ajv.getSchema('custom')).toBe('function')
expect(typeof validator.ajv.getSchema('core-no-id')).toBe('function')
expect(typeof validator.ajv.getSchema('nested.no-id')).toBe('function')
assert.equal(typeof validator.ajv.getSchema('custom'), 'function')
assert.equal(typeof validator.ajv.getSchema('core-no-id'), 'function')
assert.equal(typeof validator.ajv.getSchema('nested.no-id'), 'function')
})

test('(async) should successfully init with a relative path', async () => {
await validator.init(RELATIVE_PATH, true)
await validator.init(RELATIVE_PATH)

expect(typeof validator.ajv.getSchema('custom')).toBe('function')
expect(typeof validator.ajv.getSchema('core-no-id')).toBe('function')
expect(typeof validator.ajv.getSchema('nested.no-id')).toBe('function')
assert.equal(typeof validator.ajv.getSchema('custom'), 'function')
assert.equal(typeof validator.ajv.getSchema('core-no-id'), 'function')
assert.equal(typeof validator.ajv.getSchema('nested.no-id'), 'function')
})

test('should reject promise with an IO Error on invalid dir', async () => {
expect.assertions(1)
await expect(validator.init(BAD_PATH, true))
.rejects.toThrow(io.IOError)
await assert.rejects(validator.init(BAD_PATH), io.IOError)
})

test('should reject promise with a file not found error on an empty dir', async () => {
expect.assertions(1)
await expect(validator.init(EMPTY_PATH, true))
.rejects.toThrow(io.FileNotFoundError)
await assert.rejects(validator.init(EMPTY_PATH), io.FileNotFoundError)
})

test('should reject promise with a io error on a non-dir', async () => {
expect.assertions(1)
await expect(validator.init(FILE_PATH, true))
.rejects.toThrow(io.IOError)
await assert.rejects(validator.init(FILE_PATH), io.IOError)
})

test('should reject with a io error on a non-dir', async () => {
expect.assertions(1)
expect(() => validator.init(FILE_PATH)).toThrow(io.IOError)
assert.rejects(validator.init(FILE_PATH), io.IOError)
})

test('should reject promise with a NotFoundError on a non-existant validator', async () => {
expect.assertions(1)
validator.init(CORRECT_PATH)
await expect(validator.validate('bad-route', {}))
.rejects.toThrow(NotFoundError)
await validator.init(CORRECT_PATH)
await assert.rejects(validator.validate('bad-route', {}), NotFoundError)
})

test('should validate a correct object', async () => {
expect.assertions(1)
validator.init(CORRECT_PATH)
await expect(validator.validate('custom', { string: 'not empty' }))
.resolves.toEqual({ string: 'not empty' })
await validator.init(CORRECT_PATH)
assert.deepEqual(await validator.validate('custom', { string: 'not empty' }), { string: 'not empty' })
})

test('should filter extra properties', async () => {
expect.assertions(1)
validator = new Validation(CORRECT_PATH, null, { removeAdditional: true })
await expect(validator.filter('custom', { string: 'not empty', qq: 'not in schema' }))
.resolves.toEqual({ string: 'not empty' })
await validator.init()
assert.deepEqual(await validator.filter('custom', { string: 'not empty', qq: 'not in schema' }), { string: 'not empty' })
})

test('should filter extra properties, but still throw on invalid data', async () => {
expect.assertions(1)
validator = new Validation(CORRECT_PATH, null, { removeAdditional: true })
await expect(validator.filter('custom', { string: 20, qq: 'not in schema' }))
.rejects.toThrow(HttpStatusError)
await validator.init()
await assert.rejects(validator.filter('custom', { string: 20, qq: 'not in schema' }), HttpStatusError)
})

test('should return validation error on an invalid object', async () => {
const reject = async (): Promise<any> => validator.validate('custom', { string: 'not empty', extraneous: true })

expect.assertions(3)
validator.init(CORRECT_PATH)
await expect(reject()).rejects.toThrow(HttpStatusError)
await validator.init(CORRECT_PATH)
await assert.rejects(reject(), HttpStatusError)

const error = await reject().catch((e) => e)
expect(error.statusCode).toBe(417)
expect(JSON.parse(JSON.stringify(error))).toEqual({
assert.equal(error.statusCode, 417)
assert.deepEqual(JSON.parse(JSON.stringify(error)), {
errors: [{
field: '/extraneous',
message: 'must NOT have additional properties',
Expand All @@ -127,71 +125,68 @@ test('should return validation error on an invalid object', async () => {
})
})

test('should perform sync validation', () => {
validator.init(CORRECT_PATH)
test('should perform sync validation', async () => {
await validator.init(CORRECT_PATH)
const result = validator.validateSync('custom', { string: 'not empty' })
expect(result.error).toBeNull()
expect(result.doc).toEqual({ string: 'not empty' })
assert.equal(result.error, null)
assert.deepEqual(result.doc, { string: 'not empty' })
})

test('should filter out extra props on sync validation', () => {
test('should filter out extra props on sync validation', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: true })
await validator.init()

const result = validator.validateSync('custom', { string: 'not empty', extra: true })
// ajv does not throw errors in this case
expect(result.error).toBeNull()
expect(result.doc).toEqual({ string: 'not empty' })
assert.equal(result.error, null)
assert.deepEqual(result.doc, { string: 'not empty' })
})

test('throws when using ifError', () => {
test('throws when using ifError', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: true })
expect(() => validator.ifError('custom', { string: 200, extra: true }))
.toThrow(HttpStatusError)
await validator.init()
assert.throws(() => validator.ifError('custom', { string: 200, extra: true }), HttpStatusError)
})

test('doesn\'t throw on ifError', () => {
test('doesn\'t throw on ifError', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: true })
expect(validator.ifError('custom', { string: 'not empty', extra: true })).toEqual({
await validator.init()
assert.deepEqual(validator.ifError('custom', { string: 'not empty', extra: true }), {
string: 'not empty',
})
})

test('validates ReDos prone URL', () => {
test('validates ReDos prone URL', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: false })
expect(validator.ifError<string>('http-url', 'https://google.com12349834543489525824485'))
.toEqual('https://google.com12349834543489525824485')
await validator.init()
assert.equal(
validator.ifError<string>('http-url', 'https://google.com12349834543489525824485'),
'https://google.com12349834543489525824485')
})

test('throws on invalid URL', () => {
test('throws on invalid URL', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: false })
expect(() => validator.ifError<string>('http-url', 'ftp://crap'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'https://super.duper:8443'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'https://'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'http://notld'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'http://notld:8443'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'http://notld. :8443'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'http://notld.'))
.toThrow(HttpStatusError)
expect(() => validator.ifError<string>('http-url', 'http://notld. '))
.toThrow(HttpStatusError)
})

test('do not throw on valid URL', () => {
await validator.init()

assert.throws(() => validator.ifError<string>('http-url', 'ftp://crap'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'https://super.duper:8443'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'https://'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'http://notld'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'http://notld:8443'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'http://notld. :8443'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'http://notld.'), 'HttpStatusError')
assert.throws(() => validator.ifError<string>('http-url', 'http://notld. '), 'HttpStatusError')
})

test('do not throw on valid URL', async () => {
validator = new Validation(CORRECT_PATH, null, { removeAdditional: false })
expect(validator.ifError<string>('http-url', 'https://super.duper#hash'))
.toEqual('https://super.duper#hash')
await validator.init()
assert.equal(validator.ifError<string>('http-url', 'https://super.duper#hash'), 'https://super.duper#hash')
})

test('should be able to use 2019-09 schema keywords', () => {
test('should be able to use 2019-09 schema keywords', async () => {
validator = new Validation(CORRECT_PATH)

expect(() => validator.ifError<number[]>('2019-09', [1]))
.toThrow(HttpStatusError)
expect(validator.ifError<number[]>('2019-09', [1, 2]))
.toStrictEqual([1, 2])
await validator.init()
assert.throws(() => validator.ifError<number[]>('2019-09', [1]), 'HttpStatusError')
assert.deepEqual(validator.ifError<number[]>('2019-09', [1, 2]), [1, 2])
})
13 changes: 0 additions & 13 deletions jest.config.js

This file was deleted.

Loading