UNPKG

domain-objects

Version:

A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.

244 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const uuid_1 = require("uuid"); const assertDomainObjectIsSafeToManipulate_1 = require("../../constraints/assertDomainObjectIsSafeToManipulate"); const index_1 = require("../../index"); const DomainEntity_1 = require("../../instantiation/DomainEntity"); const DomainLiteral_1 = require("../../instantiation/DomainLiteral"); /* eslint-disable no-useless-escape */ const serialize_1 = require("./serialize"); describe('serialize', () => { describe('basic types', () => { it('should serialize strings', () => { const serial = (0, serialize_1.serialize)('hello!'); expect(serial).toEqual('"hello!"'); }); it('should serialize numbers', () => { const serial = (0, serialize_1.serialize)(821); expect(serial).toEqual('821'); }); it('should serialize dates', () => { const serial = (0, serialize_1.serialize)(new Date('2020-08-21 00:00:00')); expect(serial).toContain('Fri Aug 21 2020 00:00:00'); }); it('should serialize undefined', () => { const serial = (0, serialize_1.serialize)(undefined); expect(serial).toEqual(undefined); }); it('should serialize nulls', () => { const serial = (0, serialize_1.serialize)(null); expect(serial).toEqual('null'); }); it('should serialize buffers', () => { const serial = (0, serialize_1.serialize)(Buffer.from('821')); expect(serial).toEqual('"821"'); }); }); describe('arrays', () => { it('should be able to serialize arrays', () => { const serial = (0, serialize_1.serialize)(['821', 721, 'leopard', 7, 'apple', 3]); expect(serial).toEqual(`[\"821"\,721,\"leopard\",7,\"apple\",3]`); }); it('should serialize them in a way that can be used to compare the order of items within the array', () => { const serialA = (0, serialize_1.serialize)([3, '821', 721, 'leopard', 'apple']); const serialB = (0, serialize_1.serialize)([721, 'leopard', 3, 'apple', '821']); expect(serialB).not.toEqual(serialA); // should not be equivalent, since order matters in arrays }); it('should serialize arrays even if they have objects within', () => { const serialA = (0, serialize_1.serialize)([ 'banana', { id: 1, value: 821, meaning: 42 }, 821, { id: 0, value: undefined, meaning: null }, // should go first, because id is 0 ]); const serialB = (0, serialize_1.serialize)([ 'banana', { id: 1, value: 821, meaning: 42 }, 821, { id: 0, value: undefined, meaning: null }, // should go first, because id is 0 ]); expect(serialB).toEqual(serialA); // should be equivalent }); describe('orderless', () => { it('should be able to serialize arrays deterministically when order does not matter', () => { const serial = (0, serialize_1.serialize)(['821', 721, 'leopard', 7, 'apple', 3], { orderless: true, }); expect(serial).toEqual(`[\"821"\,\"apple\",\"leopard\",3,7,721]`); // note that it sorts the values, for determinism, since order does not matter }); it('should serialize them in a way that can be used to compare two arrays deterministically when order does not matter', () => { const serialA = (0, serialize_1.serialize)([3, '821', 721, 'leopard', 'apple'], { orderless: true, }); const serialB = (0, serialize_1.serialize)([721, 'leopard', 3, 'apple', '821'], { orderless: true, }); expect(serialB).toEqual(serialA); // should be equivalent, since it serializes deterministically by value (due to sorting) }); it('should serialize arrays even if they have objects, deterministically, when order does not matter', () => { const serialA = (0, serialize_1.serialize)([ 'banana', { id: 1, value: 821, meaning: 42 }, 821, { id: 0, value: undefined, meaning: null }, // should go first, because id is 0 ], { orderless: true }); const serialB = (0, serialize_1.serialize)([ { id: 0, value: undefined, meaning: null }, // should go first, because id is 0 { id: 1, value: 821, meaning: 42 }, 821, 'banana', ], { orderless: true }); expect(serialB).toEqual(serialA); // should be equivalent }); }); }); describe('objects', () => { it('should be able to serialize an object with all sorts of types', () => { const serial = (0, serialize_1.serialize)({ color: 'blue', cost: 821, orders: [ { id: 0, value: undefined, meaning: null }, { id: 1, value: 821, meaning: 42 }, ], application: { type: 'PAINTING', }, }); expect(serial).toEqual( // note that it sorts them `{"application":{"type":"PAINTING"},"color":"blue","cost":821,"orders":[{"id":0,"meaning":null},{"id":1,"meaning":42,"value":821}]}`); }); it('should sort object keys, for determinism', () => { const serialA = (0, serialize_1.serialize)({ color: 'blue', application: 821, }); const serialB = (0, serialize_1.serialize)({ application: 821, color: 'blue', }); expect(serialA).toEqual(serialB); // should show that they're equivalent, due to sorting }); }); describe('domain objects', () => { class Spaceship extends DomainEntity_1.DomainEntity { } Spaceship.unique = ['serialNumber']; Spaceship.updatable = ['serialNumber']; class Address extends DomainLiteral_1.DomainLiteral { } class Spaceport extends DomainEntity_1.DomainEntity { } Spaceport.unique = ['uuid']; Spaceport.updatable = ['spaceships']; Spaceport.nested = { address: Address, spaceships: Spaceship }; // run the tests it('should add the domain object name as metadata', () => { const ship = new Spaceship({ serialNumber: '__UUID__', fuelQuantity: 9001, passengers: 21, }); const serial = (0, serialize_1.serialize)(ship); expect(serial).toContain('Spaceship'); expect(serial).toEqual(`{\"_dobj\":\"Spaceship\",\"fuelQuantity\":9001,\"passengers\":21,\"serialNumber\":\"__UUID__\"}`); }); it('should add the domain object name as metadata recursively, for all nested domain objects too', () => { const shipA = new Spaceship({ serialNumber: '__SHIP_A__', fuelQuantity: 9001, passengers: 21, }); const shipB = new Spaceship({ serialNumber: '__SHIP_B__', fuelQuantity: 7000, passengers: 42, }); const spaceport = new Spaceport({ uuid: '__SPACEPORT_UUID__', address: new Address({ galaxy: 'Milky Way', solarSystem: 'Sun', planet: 'Earth', continent: 'North America', }), spaceships: [shipA, shipB], }); const serial = (0, serialize_1.serialize)(spaceport); // check that it has the metadata on there, specifying which domain object each object was expect(serial).toContain('Spaceship'); expect(serial).toContain('Address'); expect(serial).toContain('Spaceport'); // and then record a snapshot, just so we can see how it looks as a visual example expect(JSON.parse(serial)).toMatchSnapshot(); }); it('should replace each nested domain object with its unique representation, so that updates to the non-unique properties of referenced entities dont affect comparisons', () => { const shipA = new Spaceship({ serialNumber: '__SHIP_A__', fuelQuantity: 9001, passengers: 21, }); const shipB = new Spaceship({ serialNumber: '__SHIP_B__', fuelQuantity: 7000, passengers: 42, }); const spaceport = new Spaceport({ uuid: '__SPACEPORT_UUID__', address: new Address({ id: 821, galaxy: 'Milky Way', solarSystem: 'Sun', planet: 'Earth', continent: 'North America', }), spaceships: [shipA, shipB], }); const serialA = (0, serialize_1.serialize)(spaceport); // now define spaceport again, but this time the fuel quantity of shipB has decreased (since it was flying) spaceport.spaceships[1].fuelQuantity = 4200; const serialB = (0, serialize_1.serialize)(spaceport); // we should not be tracking "fuelQuantity" or "passengers", because those properties are updatable and in domain modeling, we only care about which domain objects are being referenced (i.e., bounded scope of consideration) - not what state referenced object's updatable properties are in; that's why we only care about spaceship.serialNumber here, since that uniquely identifies the spaceship being referenced expect(serialA).not.toContain('fuelQuantity'); expect(serialA).not.toContain('passengers'); // note, we should still be tracking all parts of the address (except id) - since those are required to uniquely identify the address expect(serialA).toContain('galaxy'); expect(serialA).toContain('solarSystem'); expect(serialA).toContain('planet'); expect(serialA).toContain('continent'); // their serials should be equivalent, because, due to domain modeling, we know that those two space ports are still the same (i.e., they reference the same ships, even though the ship's updatable properties have changed!) expect(serialB).toEqual(serialA); }); class GlowWorm extends index_1.DomainObject { } it('should not throw an error trying to serialize a safe generic domain object', () => { const worm = new GlowWorm({ glowing: true, color: 'purple', }); const serial = (0, serialize_1.serialize)(worm); expect(serial).toContain('GlowWorm'); expect(serial).toEqual(`{\"_dobj\":\"GlowWorm\",\"color\":\"purple\",\"glowing\":true}`); }); describe('safety', () => { it('should throw an error if domain object is not safe to manipulate', () => { class Plane extends DomainEntity_1.DomainEntity { } Plane.unique = ['uuid']; const phone = new Plane({ uuid: (0, uuid_1.v4)(), manufacturer: { name: 'boeing' }, passengerLimit: 821, }); try { (0, serialize_1.serialize)(phone); } catch (error) { expect(error).toBeInstanceOf(assertDomainObjectIsSafeToManipulate_1.DomainObjectNotSafeToManipulateError); } }); }); }); }); //# sourceMappingURL=serialize.test.js.map