const bunyan = require('bunyan');
const { expect } = require('code');
const { afterEach, beforeEach, describe, it } = exports.lab = require('lab').script();
const proxyquire = require('proxyquire').noCallThru();
const sinon = require('sinon');
const logTemplate = bunyan.createLogger({
name: 'test'
});
describe('arango/initTo', () => {
let bunyanMock;
let ensureDb;
let getCurrentVersion;
let init;
let initTo;
let migration1;
let migration2;
beforeEach(() => {
bunyanMock = sinon.mock(logTemplate);
ensureDb = sinon.stub().resolves();
getCurrentVersion = sinon.stub();
migration1 = {
rollback: sinon.stub().resolves(),
setup: sinon.stub().resolves(),
verify: sinon.stub().resolves()
};
migration2 = {
rollback: sinon.stub().resolves(),
setup: sinon.stub().resolves(),
verify: sinon.stub().resolves()
};
initTo = proxyquire(
'../../../../src/arango/init/initTo',
{
'./ensureDb': ensureDb,
'./getCurrentVersion': getCurrentVersion,
'../../logger': { get: () => logTemplate },
'../v1': {}
});
});
afterEach(() => {
bunyanMock.verify();
bunyanMock.restore();
});
it('exists', () => {
expect(initTo).to.exist();
});
it('is a function', () => {
expect(initTo).to.be.a.function();
});
describe('init to 1 from 0', () => {
beforeEach(() => {
getCurrentVersion.resolves(0);
initTo = proxyquire(
'../../../../src/arango/init/initTo',
{
'./ensureDb': ensureDb,
'./getCurrentVersion': getCurrentVersion,
'../../logger': { get: () => logTemplate },
'../v1': migration1
});
init = initTo(1);
});
it('exists', () => {
expect(init).to.exist();
});
it('is a function', () => {
expect(init).to.be.a.function();
});
describe('successful', () => {
beforeEach(async () => {
result = await init();
});
it('calls ensureDb', () => {
expect(ensureDb.called).to.be.true();
});
it('calls migration.setup', () => {
expect(migration1.setup.called).to.be.true();
});
it('calls migration.verify', () => {
expect(migration1.verify.called).to.be.true();
});
it('does not call rollback', () => {
expect(migration1.rollback.called).to.be.false();
});
});
describe('setup rejects', () => {
let error;
beforeEach(async () => {
migration1.setup.rejects(new Error());
bunyanMock.expects('error');
try {
await init();
} catch (e) {
error = e;
}
});
it('throws', () => {
expect(error).to.exist();
expect(error).to.be.an.instanceOf(Error);
})
it('does not call verify', () => {
expect(migration1.verify.called).to.be.false();
});
it('does not call rollback', () => {
expect(migration1.rollback.called).to.be.false();
});
});
describe('verify rejects', () => {
let error;
beforeEach(async () => {
migration1.verify.rejects(new Error());
bunyanMock.expects('error');
try {
await init();
} catch (e) {
error = e;
}
});
it('calls rollback', () => {
expect(migration1.rollback.called).to.be.true();
});
});
});
describe('init to 2 from 0', () => {
beforeEach(() => {
getCurrentVersion.resolves(0);
initTo = proxyquire(
'../../../../src/arango/init/initTo',
{
'./ensureDb': ensureDb,
'./getCurrentVersion': getCurrentVersion,
'../../logger': { get: () => logTemplate },
'../v1': migration1,
'../v2': migration2
});
init = initTo(2);
});
describe('successful', () => {
beforeEach(async () => {
result = await init();
});
it('calls ensureDb', () => {
expect(ensureDb.called).to.be.true();
});
it('calls migration.setup', () => {
expect(migration1.setup.called).to.be.true();
expect(migration2.setup.called).to.be.true();
});
it('calls migration.verify', () => {
expect(migration1.verify.called).to.be.true();
expect(migration2.verify.called).to.be.true();
});
it('does not call rollback', () => {
expect(migration1.rollback.called).to.be.false();
expect(migration2.rollback.called).to.be.false();
});
});
describe('v2 setup fails', () => {
let error;
beforeEach(async () => {
migration2.setup.rejects(new Error());
bunyanMock.expects('error');
try {
result = await init();
} catch (e) {
error = e;
}
});
it('calls each setup a maximum of once', () => {
expect(migration1.setup.calledOnce).to.be.true();
expect(migration2.setup.calledOnce).to.be.true();
});
it('calls migration.setup', () => {
expect(migration1.setup.called).to.be.true();
expect(migration2.setup.called).to.be.true();
});
it('calls migration.verify on migration 1 only', () => {
expect(migration1.verify.called).to.be.true();
expect(migration2.verify.called).to.be.false();
});
it('does call rollback only for migration 1', () => {
expect(migration1.rollback.called).to.be.true();
expect(migration2.rollback.called).to.be.false();
});
});
describe('v2 verify fails and rollback fails', () => {
let error;
beforeEach(async () => {
migration2.verify.rejects(new Error());
migration2.rollback.rejects(new Error);
bunyanMock.expects('error');
try {
result = await init();
} catch (e) {
error = e;
}
});
it('calls migration.setup', () => {
expect(migration1.setup.called).to.be.true();
expect(migration2.setup.called).to.be.true();
});
it('calls migration.verify', () => {
expect(migration1.verify.called).to.be.true();
expect(migration2.verify.called).to.be.true();
});
it('calls rollback on migration 2', () => {
expect(migration1.rollback.called).to.be.false();
expect(migration2.rollback.called).to.be.true();
});
});
});
describe('init to 1 from 1', () => {
beforeEach(async () => {
getCurrentVersion.resolves(1);
initTo = proxyquire(
'../../../../src/arango/init/initTo',
{
'./ensureDb': ensureDb,
'./getCurrentVersion': getCurrentVersion,
'../../logger': { get: () => logTemplate },
'../v1': migration1
});
init = initTo(1);
result = await init();
});
it('calls ensureDb', () => {
expect(ensureDb.called).to.be.true();
});
it('does not call migration.setup', () => {
expect(migration1.setup.called).to.be.false();
});
it('does not call migration.verify', () => {
expect(migration1.verify.called).to.be.false();
});
it('does not call rollback', () => {
expect(migration1.rollback.called).to.be.false();
});
});
});
/**
* Tests for the migration module
*/
const {
migrate,
requiresMigration,
} = require('../../src/repo/migrate');
jest.mock('../../src/repo/migrate/version');
jest.mock('../../src/repo/model', () => ({
ClassModel: { create: jest.fn() },
Property: { create: jest.fn() },
}));
const { getCurrentVersion, getLoadVersion } = jest.requireActual('./../../src/repo/migrate/version');
const _version = require('../../src/repo/migrate/version');
describe('migrate', () => {
let db,
propertyMock,
modelMock,
createRecordMock;
beforeEach(() => {
createRecordMock = jest.fn();
const queryMock = jest.fn().mockReturnValue({
all: jest.fn().mockResolvedValue([{ conditions: [], count: null }]),
one: jest.fn(),
});
db = {
class: {
get: jest.fn().mockResolvedValue({
create: createRecordMock,
}),
},
command: queryMock,
index: {
create: jest.fn(),
},
insert: jest.fn().mockReturnValue({
into: jest.fn().mockReturnValue({
set: queryMock,
}),
}),
query: queryMock,
update: jest.fn().mockReturnValue({
set: queryMock,
}),
};
const model = require('../../src/repo/model'); // eslint-disable-line
propertyMock = model.Property.create;
modelMock = model.ClassModel.create;
});
afterEach(() => {
jest.clearAllMocks();
});
test('getCurrentVersion', async () => {
db.query.mockReturnValue({ all: jest.fn().mockResolvedValueOnce([{ version: '1.6.2' }]) });
const version = await getCurrentVersion(db);
expect(db.query).toHaveBeenCalledWith('SELECT * FROM SchemaHistory ORDER BY createdAt DESC LIMIT 1');
expect(version).toEqual('1.6.2');
});
test('getLoadVersion', () => {
const version = getLoadVersion();
expect(version).toHaveProperty('version');
expect(version.version).toEqual(expect.stringMatching(/^\d+\.\d+\.\d+$/));
});
describe('requiresMigration', () => {
test('compatible versions do not require migration', () => {
expect(requiresMigration('1.9.1', '1.9.2')).toBeFalsy();
});
test('minor version difference requires migration', () => {
expect(requiresMigration('1.9.1', '1.10.1')).toBeTruthy();
});
test('major version difference requires migration', () => {
expect(requiresMigration('1.9.1', '2.9.2')).toBeTruthy();
});
});
describe('migrate', () => {
test('1.6 to 1.7.0', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.6.2');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.7.0' });
await migrate(db);
expect(db.index.create).toHaveBeenCalledTimes(3);
expect(propertyMock).not.toHaveBeenCalled();
expect(modelMock).not.toHaveBeenCalled();
expect(db.query).not.toHaveBeenCalled();
expect(createRecordMock).toHaveBeenCalledTimes(1);
});
test('1.6 to 1.7.1', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.6.2');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.7.1' });
await migrate(db);
expect(db.index.create).toHaveBeenCalledTimes(3);
expect(propertyMock).not.toHaveBeenCalled();
expect(modelMock).not.toHaveBeenCalled();
expect(db.query).not.toHaveBeenCalled();
expect(createRecordMock).toHaveBeenCalledTimes(2);
});
test('1.7 to 1.8', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.7.0');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.8.0' });
await migrate(db);
expect(db.index.create).not.toHaveBeenCalled();
expect(propertyMock).toHaveBeenCalledTimes(1);
expect(modelMock).not.toHaveBeenCalled();
expect(db.query).not.toHaveBeenCalled();
expect(createRecordMock).toHaveBeenCalledTimes(1);
});
test('1.8 to 1.9', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.8.0');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.9.0' });
await migrate(db);
expect(db.query).toHaveBeenCalledTimes(20);
expect(db.class.get).toHaveBeenCalledTimes(3);
expect(propertyMock).toHaveBeenCalledTimes(4);
expect(modelMock).toHaveBeenCalledTimes(1);
expect(createRecordMock).toHaveBeenCalledTimes(1);
});
test('compatible no migration', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.8.0');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.8.1' });
await migrate(db);
expect(db.query).not.toHaveBeenCalled();
expect(db.class.get).not.toHaveBeenCalled();
});
test('error on no transition', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.6.0');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.8.1' });
await expect(migrate(db)).rejects.toMatchObject({ message: 'Unable to find migration scripts from 1.6.0 to 1.8.1' });
expect(db.query).not.toHaveBeenCalled();
expect(db.class.get).not.toHaveBeenCalled();
});
test('incompatible check only', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.8.0');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.9.1' });
await expect(migrate(db, { checkOnly: true })).rejects.toMatchObject({ message: 'Versions (1.8.0, 1.9.1) are not compatible and require migration' });
expect(db.query).not.toHaveBeenCalled();
expect(db.class.get).not.toHaveBeenCalled();
});
test('1.6 to 1.9', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('1.6.2');
_version.getLoadVersion = jest.fn().mockReturnValue({ version: '1.9.2' });
await migrate(db);
expect(createRecordMock).toHaveBeenCalledTimes(4); // logged 4 times
expect(db.query).toHaveBeenCalledTimes(20); // 1.8 to 1.9
expect(db.index.create).toHaveBeenCalledTimes(3); // 1.6 to 1.7
expect(propertyMock).toHaveBeenCalledTimes(5); // mixed
});
test('2.0 to latest', async () => {
_version.getCurrentVersion = jest.fn().mockResolvedValue('2.0.0');
_version.getLoadVersion = getLoadVersion; // use original load version
await migrate(db);
// no errors
expect(createRecordMock).toHaveBeenCalled();
expect(db.query).toHaveBeenCalled();
expect(db.index.create).toHaveBeenCalled();
expect(propertyMock).toHaveBeenCalled();
});
});
});
import CustomerAggregate from './CustomerAggregate'
import { CUSTOMER_CREATED } from '../constants/events'
import { CustomerCreated } from '../events/CustomerEvents'
const customer = CustomerAggregate()
const CUSTOMER_1_ID = '1234-5678-9012-3456'
const CUSTOMER_1_NAME = 'Test Customer'
const CUSTOMER_1_EMAIL = '[email protected]'
const CUSTOMER_1_PASSWORD = 'test1234'
const CUSTOMER_1_NAME_UPDATED = 'Test Customer Updated'
it('should return state with version 0', () => {
// we want no event history to test this case
const storedEvents = new Set()
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
expect(version).toBe(0)
})
it('should register a customer', () => {
// we want no event history to test this case
const storedEvents = new Set()
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(0)
expect(uncommittedChanges.size).toBe(0)
// register a customer
state = customer.register(state, CUSTOMER_1_ID, CUSTOMER_1_NAME, CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD)
version = customer.getCurrentVersion(state)
expect(state.created).toBe(0)
expect(state.active).toBe(0)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe(CUSTOMER_1_NAME)
expect(state.email).toBe(CUSTOMER_1_EMAIL)
expect(state.password).toBe(CUSTOMER_1_PASSWORD)
// new changes are yet to be written to Event Store
// therefore aggreagate version must not change
// and applied change must be added to uncommittedChanges set
expect(version).toBe(0)
expect(uncommittedChanges.size).toBe(1)
})
it('should create a new customer', () => {
// we want no event history to test this case
const storedEvents = new Set()
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(0)
expect(uncommittedChanges.size).toBe(0)
// create new customer
state = customer.create(state, CUSTOMER_1_ID, CUSTOMER_1_NAME, CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD)
version = customer.getCurrentVersion(state)
expect(state.created).toBe(1)
expect(state.active).toBe(1)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe(CUSTOMER_1_NAME)
expect(state.email).toBe(CUSTOMER_1_EMAIL)
expect(state.password).toBe(CUSTOMER_1_PASSWORD)
// new changes are yet to be written to Event Store
// therefore aggreagate version must not change
// and applied change must be added to uncommittedChanges set
expect(version).toBe(0)
expect(uncommittedChanges.size).toBe(1)
})
it('should throw an error on create for an existing customer aggregate', () => {
// create event history (that would be loaded from event store in real application)
const storedEvents = new Set()
storedEvents.add(CustomerCreated(CUSTOMER_1_ID, CUSTOMER_1_NAME, CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD))
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(1)
expect(uncommittedChanges.size).toBe(0)
// create new customer method on existing customer should throw an error
expect(() => {
customer.create(state, CUSTOMER_1_ID, CUSTOMER_1_NAME, CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD)
}).toThrow('can not create same customer more than once')
})
it('should update an existing customer aggregate', () => {
// create event history (that would be loaded from event store in real application)
const storedEvents = new Set()
storedEvents.add(CustomerCreated(CUSTOMER_1_ID, CUSTOMER_1_NAME, CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD))
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(1)
expect(uncommittedChanges.size).toBe(0)
customer.update(state, CUSTOMER_1_NAME_UPDATED)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe(CUSTOMER_1_NAME_UPDATED)
})
it('should throw an error on updating a non-existing customer aggregate', () => {
// create event history (that would be loaded from event store in real application)
const storedEvents = new Set()
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(0)
expect(uncommittedChanges.size).toBe(0)
// create new customer method on existing customer should throw an error
expect(() => {
customer.update(state, 'Test Customer Updated')
}).toThrow("can not update customer that doesn't exist")
})
it('should deactivate an existing customer aggregate', () => {
// create event history (that would be loaded from event store in real application)
const storedEvents = new Set()
storedEvents.add(CustomerCreated(CUSTOMER_1_ID, 'Test Customer', CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD))
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(1)
expect(uncommittedChanges.size).toBe(0)
customer.deactivate(state)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe('Test Customer')
expect(state.deactivated).toBeTruthy()
})
it('should reactivate an existing customer aggregate', () => {
// create event history (that would be loaded from event store in real application)
const storedEvents = new Set()
storedEvents.add(CustomerCreated(CUSTOMER_1_ID, 'Test Customer', CUSTOMER_1_EMAIL, CUSTOMER_1_PASSWORD))
// prepare aggregate with no event history (new aggregate)
let state = customer.loadFromHistory(storedEvents)
let version = customer.getCurrentVersion(state)
let uncommittedChanges = customer.getUncommittedChanges(state)
expect(version).toBe(1)
expect(uncommittedChanges.size).toBe(0)
customer.deactivate(state)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe('Test Customer')
expect(state.deactivated).toBeTruthy()
customer.reactivate(state)
expect(state.customerId).toBe(CUSTOMER_1_ID)
expect(state.name).toBe('Test Customer')
expect(state.deactivated).toBeUndefined()
})
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.