import assert from 'assert/strict'; import {UnsafeData} from '../src/UnsafeData'; describe('UnsafeData', function () { describe('optionalKey', function () { it('Returns data for a valid key', function () { const data = new UnsafeData({foo: 'bar'}); assert.deepEqual( data.optionalKey('foo'), new UnsafeData('bar', { field: ['foo'] }) ); }); it('Extends the context fields', function () { const data = new UnsafeData({foo: 'bar'}, {field: ['baz']}); assert.deepEqual( data.optionalKey('foo'), new UnsafeData('bar', { field: ['baz', 'foo'] }) ); }); it('Throws for null', function () { const data = new UnsafeData(null); assert.throws(() => { data.optionalKey('foo'); }, {message: 'data must be an object'}); }); it('Throws for non-objects', function () { const data = new UnsafeData(15, {field: ['baz']}); assert.throws(() => { data.optionalKey('foo'); }, {message: 'baz must be an object'}); }); it('Returns undefined if the property does not exist', function () { const data = new UnsafeData({foo: 'bar'}); assert.equal( data.optionalKey('baz'), undefined ); }); it('Returns undefined if the property was inherited from a parent', function () { const data = new UnsafeData(new Date()); assert.equal( data.optionalKey('getTime'), undefined ); }); }); describe('key', function () { it('Returns data for a valid key', function () { const data = new UnsafeData({foo: 'bar'}); assert.deepEqual( data.key('foo'), new UnsafeData('bar', { field: ['foo'] }) ); }); it('Extends the context fields', function () { const data = new UnsafeData({foo: 'bar'}, {field: ['baz']}); assert.deepEqual( data.key('foo'), new UnsafeData('bar', { field: ['baz', 'foo'] }) ); }); it('Throws for null', function () { const data = new UnsafeData(null); assert.throws(() => { data.key('foo'); }, {message: 'data must be an object'}); }); it('Throws for non-objects', function () { const data = new UnsafeData(15, {field: ['baz']}); assert.throws(() => { data.key('foo'); }, {message: 'baz must be an object'}); }); it('Throws if the property does not exist', function () { const data = new UnsafeData({foo: 'bar'}); assert.throws(() => { data.key('baz'); }, {message: 'baz is required'}); }); it('Throws if the property does not exist with context', function () { const data = new UnsafeData({foo: 'bar'}, {field: ['bar']}); assert.throws(() => { data.key('baz'); }, {message: 'bar.baz is required'}); }); }); describe('nullable', function () { it('Returns data if not null', function () { const data = new UnsafeData({foo: 'bar'}); assert.equal( data.nullable, data ); }); it('Returns proxy if null', function () { const data = new UnsafeData(null); assert.notEqual( data.nullable, data ); assert.equal( data.nullable.string, null ); assert.equal( data.nullable.boolean, null ); assert.equal( data.nullable.number, null ); assert.equal( data.nullable.integer, null ); assert.equal( data.nullable.url, null ); assert.equal( data.nullable.enum(['foo', 'bar']), null ); const n = data.nullable; assert.equal( n.key('test'), n ); assert.equal( data.nullable.key('test').string, null ); assert.equal( data.nullable.optionalKey('test').string, null ); assert.equal( data.nullable.array, null ); assert.equal( data.nullable.index(0).string, null ); }); }); describe('string', function () { it('Returns if a string', function () { const data = new UnsafeData('hello world'); assert.equal( data.string, 'hello world' ); }); it('Returns if an empty string', function () { const data = new UnsafeData(''); assert.equal( data.string, '' ); }); it('Throws if a number', function () { const data = new UnsafeData(15); assert.throws(() => { data.string; }, {message: 'data must be a string'}); }); it('Throws if an object', function () { const data = new UnsafeData({}); assert.throws(() => { data.string; }, {message: 'data must be a string'}); }); it('Throws if null', function () { const data = new UnsafeData(null, {field: ['obj', 'test']}); assert.throws(() => { data.string; }, {message: 'obj.test must be a string'}); }); }); describe('boolean', function () { it('Returns if true', function () { const data = new UnsafeData(true); assert.equal( data.boolean, true ); }); it('Returns if false', function () { const data = new UnsafeData(false); assert.equal( data.boolean, false ); }); it('Throws if 0', function () { const data = new UnsafeData(0); assert.throws(() => { data.boolean; }, {message: 'data must be a boolean'}); }); it('Throws if 1', function () { const data = new UnsafeData(1); assert.throws(() => { data.boolean; }, {message: 'data must be a boolean'}); }); it('Throws if an object', function () { const data = new UnsafeData({}); assert.throws(() => { data.boolean; }, {message: 'data must be a boolean'}); }); it('Throws if null', function () { const data = new UnsafeData(null, {field: ['obj', 'test']}); assert.throws(() => { data.boolean; }, {message: 'obj.test must be a boolean'}); }); }); describe('number', function () { it('Returns if a number', function () { const data = new UnsafeData(15); assert.equal( data.number, 15 ); }); it('Returns if 0', function () { const data = new UnsafeData(0); assert.equal( data.number, 0 ); }); it('Returns if floating point', function () { const data = new UnsafeData(0.33); assert.equal( data.number, 0.33 ); }); it('Returns if -1', function () { const data = new UnsafeData(-1); assert.equal( data.number, -1 ); }); it('Throws if NaN', function () { const data = new UnsafeData(NaN); assert.throws(() => { data.number; }, {message: 'data must be a finite number'}); }); it('Throws if Infinity', function () { const data = new UnsafeData(Infinity); assert.throws(() => { data.number; }, {message: 'data must be a finite number'}); }); it('Converts from string', function () { const data = new UnsafeData('15'); assert.equal( data.number, 15 ); }); it('Converts from float string', function () { const data = new UnsafeData('15.33'); assert.equal( data.number, 15.33 ); }); it('Throws if convert from string number with spaces', function () { const data = new UnsafeData('15 '); assert.throws(() => { data.number; }, {message: 'data must be a number, got string'}); }); it('Throws if a string', function () { const data = new UnsafeData('hello world'); assert.throws(() => { data.number; }, {message: 'data must be a number, got string'}); }); it('Throws if an object', function () { const data = new UnsafeData({}); assert.throws(() => { data.number; }, {message: 'data must be a number, got object'}); }); it('Throws if null', function () { const data = new UnsafeData(null, {field: ['obj', 'test']}); assert.throws(() => { data.number; }, {message: 'obj.test must be a number, got object'}); }); }); describe('integer', function () { it('Returns if a number', function () { const data = new UnsafeData(15); assert.equal( data.integer, 15 ); }); // Other tests it('Throws if floating point', function () { const data = new UnsafeData(0.33); assert.throws(() => { data.integer; }, {message: 'data must be an integer'}); }); it('Returns if -1', function () { const data = new UnsafeData(-1); assert.equal( data.integer, -1 ); }); it('Throws if NaN', function () { const data = new UnsafeData(NaN); assert.throws(() => { data.integer; }, {message: 'data must be a finite number'}); }); it('Throws if Infinity', function () { const data = new UnsafeData(Infinity); assert.throws(() => { data.integer; }, {message: 'data must be a finite number'}); }); it('Converts from string', function () { const data = new UnsafeData('15'); assert.equal( data.integer, 15 ); }); it('Throws if convert from float string', function () { const data = new UnsafeData('15.33', {field: ['bar']}); assert.throws(() => { data.integer; }, {message: 'bar must be an integer'}); }); it('Throws if convert from string number with text', function () { const data = new UnsafeData('15 test', {field: ['bar']}); assert.throws(() => { data.integer; }, {message: 'bar must be an integer'}); }); it('Throws if a string', function () { const data = new UnsafeData('hello world'); assert.throws(() => { data.integer; }, {message: 'data must be an integer'}); }); it('Throws if an object', function () { const data = new UnsafeData({}); assert.throws(() => { data.integer; }, {message: 'data must be a number, got object'}); }); it('Throws if null', function () { const data = new UnsafeData(null, {field: ['obj', 'test']}); assert.throws(() => { data.integer; }, {message: 'obj.test must be a number, got object'}); }); it('Throws if too high', function () { const data = new UnsafeData(Math.pow(2, 53), {field: ['bar']}); assert.throws(() => { data.integer; }, {message: 'bar must be an integer'}); }); }); describe('url', function () { it('Returns if a URL object', function () { const u = new URL('https://example.com'); const data = new UnsafeData(u); assert.equal( data.url, u ); }); it('Returns if a URL', function () { const data = new UnsafeData('https://example.com/path?query=string'); assert.equal( data.url.toString(), 'https://example.com/path?query=string' ); }); it('Returns if a http URL', function () { const data = new UnsafeData('http://example.com'); assert.equal( data.url.toString(), 'http://example.com/' ); }); it('Throws if a ftp URL', function () { const data = new UnsafeData('ftp://example.com'); assert.throws(() => { data.url; }, {message: 'data must be a valid URL'}); }); it('Throws if a string', function () { const data = new UnsafeData('hello world'); assert.throws(() => { data.url; }, {message: 'data must be a valid URL'}); }); it('Throws if an object', function () { const data = new UnsafeData({}); assert.throws(() => { data.url; }, {message: 'data must be a string'}); }); it('Throws if null', function () { const data = new UnsafeData(null, {field: ['obj', 'test']}); assert.throws(() => { data.url; }, {message: 'obj.test must be a string'}); }); }); describe('enum', function () { it('Returns if a valid value', function () { const data = new UnsafeData('foo'); assert.equal( data.enum(['foo', 'bar']), 'foo' ); }); it('Works for numbers too', function () { const data = new UnsafeData(5); assert.equal( data.enum([5, 8]), 5 ); }); it('Throws if an invalid value', function () { const data = new UnsafeData('baz'); assert.throws(() => { data.enum(['foo', 'bar']); }, {message: 'data must be one of foo, bar'}); }); }); describe('array', function () { it('Returns if an array', function () { const data = new UnsafeData(['foo', 'bar']); assert.equal( data.array[0].string, 'foo' ); assert.equal( data.array[1].string, 'bar' ); }); it('Extends context', function () { const data = new UnsafeData(['foo', 'bar'], {field: ['baz']}); assert.deepEqual( data.array[0], new UnsafeData('foo', {field: ['baz', '0']}) ); assert.deepEqual( data.array[1], new UnsafeData('bar', {field: ['baz', '1']}) ); }); it('Throws if not an array', function () { const data = new UnsafeData('baz'); assert.throws(() => { data.array; }, {message: 'data must be an array'}); }); }); describe('index', function () { it('Returns if an array', function () { const data = new UnsafeData(['foo', 'bar']); assert.equal( data.index(0).string, 'foo' ); }); it('Extends context', function () { const data = new UnsafeData(['foo', 'bar'], {field: ['baz']}); assert.deepEqual( data.index(0), new UnsafeData('foo', {field: ['baz', '0']}) ); assert.deepEqual( data.index(1), new UnsafeData('bar', {field: ['baz', '1']}) ); }); it('Throws if not an array', function () { const data = new UnsafeData('baz'); assert.throws(() => { data.index(0); }, {message: 'data must be an array'}); }); it('Throws if out of bounds', function () { const data = new UnsafeData(['foo', 'bar']); assert.throws(() => { data.index(2); }, {message: 'data must be an array of length 3'}); }); it('Throws if out of lower bounds', function () { const data = new UnsafeData(['foo', 'bar']); assert.throws(() => { data.index(-1); }, {message: 'index must be a positive integer'}); }); it('Throws if floating point', function () { const data = new UnsafeData(['foo', 'bar']); assert.throws(() => { data.index(1.5); }, {message: 'index must be a positive integer'}); }); it('Throws if NaN', function () { const data = new UnsafeData(['foo', 'bar']); assert.throws(() => { data.index(NaN); }, {message: 'index must be a positive integer'}); }); }); });