UNPKG

nested-env-schema

Version:

Validate & extract your env variables using nested JSON schema, Ajv and dotenvx

366 lines (352 loc) 6.96 kB
'use strict'; const t = require('tap'); const Ajv = require('ajv'); const makeTest = require('./make-test'); const { join } = require('node:path'); process.env.VALUE_FROM_ENV = 'pippo'; const tests = [ { name: 'empty ok', schema: { type: 'object' }, data: {}, isOk: true, confExpected: {}, }, { name: 'simple object - ok', schema: { type: 'object', properties: { PORT: { type: 'string', }, }, }, data: { PORT: '44', }, isOk: true, confExpected: { PORT: '44', }, }, { name: 'simple object - ok - coerce value', schema: { type: 'object', properties: { PORT: { type: 'integer', }, }, }, data: { PORT: '44', }, isOk: true, confExpected: { PORT: 44, }, }, { name: 'simple object - ok - remove additional properties', schema: { type: 'object', properties: { PORT: { type: 'integer', }, }, }, data: { PORT: '44', ANOTHER_PORT: '55', }, isOk: true, confExpected: { PORT: 44, }, }, { name: 'simple object - ok - use default', schema: { type: 'object', properties: { PORT: { type: 'integer', default: 5555, }, }, }, data: {}, isOk: true, confExpected: { PORT: 5555, }, }, { name: 'simple object - ok - required + default', schema: { type: 'object', required: ['PORT'], properties: { PORT: { type: 'integer', default: 6666, }, }, }, data: {}, isOk: true, confExpected: { PORT: 6666, }, }, { name: 'simple object - ok - allow array', schema: { type: 'object', required: ['PORT'], properties: { PORT: { type: 'integer', default: 6666, }, }, }, data: [{}], isOk: true, confExpected: { PORT: 6666, }, }, { name: 'simple object - ok - merge multiple object + env', schema: { type: 'object', required: ['PORT', 'MONGODB_URL'], properties: { PORT: { type: 'integer', default: 6666, }, MONGODB_URL: { type: 'string', }, VALUE_FROM_ENV: { type: 'string', }, }, }, data: [{ PORT: 3333 }, { MONGODB_URL: 'mongodb://localhost/pippo' }], isOk: true, confExpected: { PORT: 3333, MONGODB_URL: 'mongodb://localhost/pippo', VALUE_FROM_ENV: 'pippo', }, }, { name: 'simple object - ok - load only from env', schema: { type: 'object', required: ['VALUE_FROM_ENV'], properties: { VALUE_FROM_ENV: { type: 'string', }, }, }, data: undefined, isOk: true, confExpected: { VALUE_FROM_ENV: 'pippo', }, }, { name: 'simple object - ok - opts override environment', schema: { type: 'object', required: ['VALUE_FROM_ENV'], properties: { VALUE_FROM_ENV: { type: 'string', }, }, }, data: { VALUE_FROM_ENV: 'pluto' }, isOk: true, confExpected: { VALUE_FROM_ENV: 'pluto', }, }, { name: 'simple object - ok - load only from .env', schema: { type: 'object', required: ['VALUE_FROM_DOTENV'], properties: { VALUE_FROM_DOTENV: { type: 'string', }, }, }, data: undefined, isOk: true, dotenv: { path: join(__dirname, 'envs', 'normal', '.env') }, confExpected: { VALUE_FROM_DOTENV: 'look ma', }, }, { name: 'simple object - KO', schema: { type: 'object', required: ['PORT'], properties: { PORT: { type: 'integer', }, }, }, data: {}, isOk: false, errorMessage: "env must have required property 'PORT'", }, { name: 'simple object - invalid data', schema: { type: 'object', required: ['PORT'], properties: { PORT: { type: 'integer', }, }, }, data: [], isOk: false, errorMessage: 'opts/data must NOT have fewer than 1 items, opts/data must be object, opts/data must match exactly one schema in oneOf', }, ]; const ajv = new Ajv({ allErrors: true, removeAdditional: true, useDefaults: true, coerceTypes: true, allowUnionTypes: true, }); tests.forEach(function (testConf) { t.test(testConf.name, (t) => { const options = { schema: testConf.schema, data: testConf.data, dotenv: testConf.dotenv, dotenvConfig: testConf.dotenvConfig, ajv, }; makeTest( t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage, ); }); }); const noCoercionTest = { name: 'simple object - not ok - should NOT coerce value', schema: { type: 'object', properties: { PORT: { type: 'integer', }, }, }, data: { PORT: '44', }, isOk: false, errorMessage: 'env/PORT must be integer', confExpected: { PORT: 44, }, }; const strictValidator = new Ajv({ allErrors: true, removeAdditional: true, useDefaults: true, coerceTypes: false, allowUnionTypes: true, }); [noCoercionTest].forEach(function (testConf) { t.test(testConf.name, (t) => { const options = { schema: testConf.schema, data: testConf.data, dotenv: testConf.dotenv, dotenvConfig: testConf.dotenvConfig, ajv: strictValidator, }; makeTest( t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage, ); }); }); t.test('ajv enhancement', (t) => { t.plan(2); const testCase = { schema: { type: 'object', required: ['MONGODB_URL'], properties: { MONGODB_URL: { type: 'string', format: 'uri', }, }, }, data: [{ PORT: 3333 }, { MONGODB_URL: 'mongodb://localhost/pippo' }], isOk: true, confExpected: { MONGODB_URL: 'mongodb://localhost/pippo', }, }; t.test('return', (t) => { const options = { schema: testCase.schema, data: testCase.data, ajv: { customOptions(ajvInstance) { require('ajv-formats')(ajvInstance); return ajvInstance; }, }, }; makeTest(t, options, testCase.isOk, testCase.confExpected); }); t.test('no return', (t) => { const options = { schema: testCase.schema, data: testCase.data, ajv: { customOptions(ajvInstance) { // do nothing }, }, }; makeTest( t, options, false, undefined, 'customOptions function must return an instance of Ajv', ); }); });