@getanthill/datastore
Version:
Event-Sourced Datastore
964 lines (903 loc) • 23.6 kB
text/typescript
import setup from '../../test/setup';
describe('sdk/Datastore/import', () => {
let config;
let mongodb;
let models;
let app;
let sdk;
let instance;
beforeEach(async () => {
[config, mongodb, models, app, , sdk, , instance] = await setup.startApi({
mode: 'development',
features: { api: { admin: true } },
});
});
afterEach(async () => {
await setup.stopApi(instance);
jest.restoreAllMocks();
await setup.teardownDb(mongodb);
});
describe('#import', () => {
const properties = {
firstname: {
type: 'string',
},
};
const modelConfigs = {
users: {
is_enabled: true,
db: 'datastore',
name: 'users',
correlation_field: 'user_id',
schema: {
model: {
type: 'object',
additionalProperties: true,
properties: properties,
},
events: {
CREATED: {
'0_0_0': {
additionalProperties: true,
properties,
},
},
UPDATED: {
'0_0_0': {
additionalProperties: true,
properties,
},
},
},
},
},
};
beforeEach(async () => {
await sdk.createModel(modelConfigs.users);
sdk.create = jest.spyOn(sdk, 'create');
sdk.update = jest.spyOn(sdk, 'update');
});
afterEach(() => {
jest.restoreAllMocks();
});
it('imports a single entity', async () => {
const entities = await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
expect(sdk.create).toHaveBeenCalledTimes(1);
expect(sdk.update).toHaveBeenCalledTimes(0);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
});
it('performs noop if `dryRun=true` on entity creation', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
],
{},
{
dryRun: true,
},
);
expect(sdk.create).toHaveBeenCalledTimes(0);
expect(sdk.update).toHaveBeenCalledTimes(0);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
});
it('updates a single entity during import if already exist', async () => {
await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
sdk.create.mockReset();
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'jack',
},
},
],
modelConfigs,
);
expect(sdk.create).toHaveBeenCalledTimes(0);
expect(sdk.update).toHaveBeenCalledTimes(1);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
});
it('stores the diff on entity update', async () => {
await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
sdk.create.mockReset();
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'jack',
},
},
],
modelConfigs,
);
expect(entities.get('users/john').__update__).toEqual([
{
op: 'replace',
path: '/firstname',
value: 'jack',
},
]);
expect(entities.get('users/john').__rollback__).toEqual([
{
op: 'replace',
path: '/firstname',
value: 'john',
},
]);
});
it('performs noop if `dryRun=true` on entity update', async () => {
await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
sdk.create.mockReset();
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'jack',
},
},
],
modelConfigs,
{
dryRun: true,
},
);
expect(sdk.create).toHaveBeenCalledTimes(0);
expect(sdk.update).toHaveBeenCalledTimes(0);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
});
it('does nothing if no change has to be applied', async () => {
await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
sdk.create = jest.fn();
sdk.update = jest.spyOn(sdk, 'update');
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
],
modelConfigs,
);
expect(sdk.create).toHaveBeenCalledTimes(0);
expect(sdk.update).toHaveBeenCalledTimes(0);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
});
it('updates a single entity omitting keys on update', async () => {
await sdk.import([
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
]);
sdk.create = jest.fn();
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
removed_on_update: true,
},
omit_on_update: ['removed_on_update'],
},
],
modelConfigs,
);
expect(sdk.create).toHaveBeenCalledTimes(0);
expect(entities.size).toEqual(1);
expect(entities.get('users/john')).not.toHaveProperty(
'removed_on_update',
);
});
it('updates an entity with idempotency defined by a link result', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
brother_id: null,
},
links: [
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'john',
},
map: {
brother_id: 'user_id',
},
},
],
entity: {
firstname: 'william',
brother_id: null,
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brother_id: entities.get('users/john').user_id,
});
});
it('updates an entity with idempotency defined by a link result in a nested object', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/joe',
idempotency: {
firstname: 'joe',
},
entity: {
firstname: 'joe',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
entity: {
firstname: 'william',
},
},
{
model: 'users',
id: 'users/jack',
idempotency: {
firstname: 'jack',
},
entity: {
firstname: 'jack',
},
},
{
model: 'users',
id: 'users/averell',
idempotency: {
brother_id: null,
},
links: [
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'joe',
},
map: {
brothers: {
brother_id: 'user_id',
age: 25,
},
},
},
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'william',
},
map: {
brothers: {
brother_id: 'user_id',
age: 24,
},
},
},
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'jack',
},
map: {
brothers: {
brother_id: 'user_id',
age: 24,
},
},
},
],
entity: {
firstname: 'william',
brothers: [],
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(4);
expect(Array.from(entities.keys())).toEqual([
'users/joe',
'users/william',
'users/jack',
'users/averell',
]);
expect(entities.get('users/averell')).toMatchObject({
brothers: [
{ brother_id: entities.get('users/joe').user_id, age: 25 },
{ brother_id: entities.get('users/william').user_id, age: 24 },
{ brother_id: entities.get('users/jack').user_id, age: 24 },
],
});
});
it('updates an entity with idempotency defined by a link result and string template', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
brother_id: null,
},
links: [
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'john',
},
map: {
brother_id: 'user_id',
},
},
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
firstname: 'Brother of ${firstname}',
},
},
],
entity: {
firstname: '__not_defined__',
brother_id: null,
},
},
],
modelConfigs,
);
expect(entities.get('users/william')).toMatchObject({
firstname: 'Brother of john',
});
});
it('updates an entity with idempotency defined by a link result and nested string template', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
location: {
address: 'Rue de la Paix',
},
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
brother_id: null,
},
links: [
{
model: 'users',
is_idempotency_condition: true,
idempotency: {
firstname: 'john',
},
map: {
brother_id: 'user_id',
},
},
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
firstname:
'Brother of ${firstname} living in ${location.address}',
},
},
],
entity: {
firstname: '__not_defined__',
brother_id: null,
},
},
],
modelConfigs,
);
expect(entities.get('users/william')).toMatchObject({
firstname: 'Brother of john living in Rue de la Paix',
});
});
it('allows to import entities with relationships at the root level', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
links: [
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
brother_id: 'user_id',
},
},
],
entity: {
firstname: 'william',
brother_id: null,
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brother_id: entities.get('users/john').user_id,
});
});
it('allows to import entities with relationships referencing them with their ids', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
links: [
{
id: 'users/john',
map: {
brother_id: 'user_id',
},
},
],
entity: {
firstname: 'william',
brother_id: null,
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brother_id: entities.get('users/john').user_id,
});
});
it('allows to import entities with relationships deeply', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
links: [
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
brother: {
user_id: 'user_id',
},
},
},
],
entity: {
firstname: 'william',
brother: {
user_id: null,
},
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brother: {
user_id: entities.get('users/john').user_id,
},
});
});
it('allows to import entities with relationships in arrays', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
links: [
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
brothers: [
{
user_id: 'user_id',
},
],
},
},
],
entity: {
firstname: 'william',
brothers: [],
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brothers: [
{
user_id: entities.get('users/john').user_id,
},
],
});
});
it('allows to import entities by their ids with relationships in arrays', async () => {
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
links: [
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
brothers: 'user_id',
},
},
],
entity: {
firstname: 'william',
brothers: [],
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(2);
expect(Array.from(entities.keys())).toEqual([
'users/john',
'users/william',
]);
expect(entities.get('users/william')).toMatchObject({
brothers: [entities.get('users/john').user_id],
});
});
it('allows to upsert entities with correlation ID already defined', async () => {
const userId = `user_id_${Date.now()}`;
const entities = await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
user_id: userId,
},
entity: {
firstname: 'john',
},
},
],
modelConfigs,
);
expect(entities.size).toEqual(1);
expect(Array.from(entities.keys())).toEqual(['users/john']);
expect(entities.get('users/john')).toMatchObject({
firstname: 'john',
user_id: userId,
});
});
it('throws an exception if the idempotency condition does not lead to at most one unique result', async () => {
let error;
try {
await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
entity: {
firstname: 'john',
},
},
{
model: 'users',
id: 'users/william',
idempotency: {
firstname: 'william',
},
entity: {
firstname: 'william',
},
},
{
model: 'users',
id: 'users/invalid',
idempotency: {
// firstname: 'william',
},
entity: {
firstname: 'william',
brothers: [],
},
},
],
modelConfigs,
);
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual('Idempotency condition violation');
});
it('throws an idempotency exception if a link can not be resolved', async () => {
let error;
try {
await sdk.import(
[
{
model: 'users',
id: 'users/john',
idempotency: {
firstname: 'john',
},
links: [
{
model: 'users',
idempotency: {
firstname: 'john',
},
map: {
brothers: 'user_id',
},
},
],
entity: {
firstname: 'john',
},
},
],
modelConfigs,
);
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual('[Link] Idempotency condition violation');
});
});
});