forest-express-sequelize
Version:
Official Express/Sequelize Liana for Forest
1,337 lines (1,238 loc) • 136 kB
JavaScript
const Sequelize = require('sequelize');
const sequelizeFixtures = require('sequelize-fixtures');
const Interface = require('forest-express');
const { scopeManager } = require('forest-express');
const SchemaAdapter = require('../src/adapters/sequelize');
const {
sequelizePostgres, sequelizeMySQLMin, sequelizeMySQLMax, sequelizeMariaDB,
} = require('./databases');
const PieStatGetter = require('../src/services/pie-stat-getter');
const LineStatGetter = require('../src/services/line-stat-getter');
const ValueStatGetter = require('../src/services/value-stat-getter');
const ResourcesGetter = require('../src/services/resources-getter');
const ResourceGetter = require('../src/services/resource-getter');
const ResourceCreator = require('../src/services/resource-creator');
const ResourceUpdater = require('../src/services/resource-updater');
const BelongsToUpdater = require('../src/services/belongs-to-updater');
const ResourceRemover = require('../src/services/resource-remover');
const HasManyGetter = require('../src/services/has-many-getter');
const HasManyDissociator = require('../src/services/has-many-dissociator');
const QueryStatGetter = require('../src/services/query-stat-getter');
const baseParams = { timezone: 'Europe/Paris' };
const user = { renderingId: 1 };
[
sequelizePostgres,
sequelizeMySQLMin,
sequelizeMySQLMax,
sequelizeMariaDB,
].forEach((connectionManager) => {
function initializeSequelize() {
const sequelize = connectionManager.createConnection();
const models = {};
const options = { Sequelize, connections: { sequelize } };
models.user = sequelize.define('user', {
primaryId: {
type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true,
},
email: { type: Sequelize.STRING, unique: true, validate: { isEmail: true } },
emailValid: { type: Sequelize.BOOLEAN },
firstName: { type: Sequelize.STRING },
lastName: { type: Sequelize.STRING, validate: { len: [0, 50] } },
username: { type: Sequelize.STRING },
password: { type: Sequelize.STRING },
createdAt: { type: Sequelize.DATE },
updatedAt: { type: Sequelize.DATE },
resetPasswordToken: { type: Sequelize.STRING },
uuid: { type: Sequelize.UUID },
age: { type: Sequelize.INTEGER },
});
models.member = sequelize.define('member', {
name: { type: Sequelize.STRING },
});
models.membership = sequelize.define('membership', {
type: { type: Sequelize.STRING },
memberId: { type: Sequelize.INTEGER },
});
models.friend = sequelize.define('friend', {
name: { type: Sequelize.STRING },
memberId: { type: Sequelize.INTEGER },
});
models.bird = sequelize.define('bird', {
id: { type: Sequelize.BIGINT, primaryKey: true },
createdAt: { type: Sequelize.DATE },
updatedAt: { type: Sequelize.DATE },
name: { type: Sequelize.STRING, allowNull: false },
});
models.bike = sequelize.define('bike', {
id: { type: Sequelize.UUID, primaryKey: true, defaultValue: Sequelize.UUIDV4 },
createdAt: { type: Sequelize.DATE },
updatedAt: { type: Sequelize.DATE },
name: { type: Sequelize.STRING, allowNull: false },
});
models.georegion = sequelize.define('georegion', {
isocode: { type: Sequelize.STRING, primaryKey: true },
nameEnglish: { type: Sequelize.STRING, allowNull: false },
nameFrench: { type: Sequelize.STRING, allowNull: false },
});
models.address = sequelize.define('address', {
line: { type: Sequelize.STRING },
zipCode: { type: Sequelize.STRING },
city: { type: Sequelize.STRING },
country: { type: Sequelize.STRING },
userId: { type: Sequelize.INTEGER },
archivedAt: { type: Sequelize.DATE },
});
models.addressWithUserAlias = sequelize.define('addressWithUserAlias', {
line: { type: Sequelize.STRING },
zipCode: { type: Sequelize.STRING },
city: { type: Sequelize.STRING },
country: { type: Sequelize.STRING },
userId: { type: Sequelize.INTEGER },
});
models.team = sequelize.define('team', {
name: { type: Sequelize.STRING },
});
models.userTeam = sequelize.define('userTeam', {
userId: { type: Sequelize.INTEGER },
teamId: { type: Sequelize.INTEGER },
});
models.log = sequelize.define('log', {
code: { type: Sequelize.STRING, primaryKey: true },
trace: { type: Sequelize.STRING, primaryKey: true },
stack: { type: Sequelize.STRING },
});
models.order = sequelize.define('order', {
amount: { type: Sequelize.INTEGER },
comment: { type: Sequelize.STRING },
giftMessage: { type: Sequelize.STRING },
});
models.hasBadFieldType = sequelize.define('hasBadFieldType', {
fieldGood: { type: Sequelize.STRING },
fieldBad: { type: Sequelize.REAL }, // NOTICE: not supported yet.
});
models.owner = sequelize.define('owner', {
name: { type: Sequelize.STRING },
ownerId: { type: Sequelize.INTEGER, allowNull: false, unique: true },
});
models.project = sequelize.define('project', {
name: { type: Sequelize.STRING },
ownerId: { type: Sequelize.INTEGER },
});
models.counter = sequelize.define('counter', {
clicks: { type: Sequelize.BIGINT },
quantity: { type: Sequelize.INTEGER },
});
models.customer = sequelize.define('customer', {
name: { type: Sequelize.STRING },
});
models.picture = sequelize.define('picture', {
name: { type: Sequelize.STRING },
customerId: { type: Sequelize.INTEGER, primaryKey: true, allowNull: false },
}, {
underscored: true,
});
models.car = sequelize.define('car', {
model: { type: Sequelize.STRING },
brand: {
type: Sequelize.STRING,
validate: {
reliable(value) { if (value === 'Fiat') throw new Error('brand must be reliable.'); },
},
},
});
models.address.belongsTo(models.user, { foreignKey: 'userId' });
models.addressWithUserAlias.belongsTo(models.user, { foreignKey: 'userId', as: 'userAlias' });
models.user.hasMany(models.address, { foreignKey: 'userId' });
models.team.belongsToMany(models.user, { through: 'userTeam', otherKey: 'userId' });
models.user.belongsToMany(models.team, { through: 'userTeam', foreignKey: 'userId' });
models.membership.belongsTo(models.member);
models.member.hasOne(models.membership);
models.member.hasMany(models.friend);
models.friend.belongsTo(models.member);
models.project.belongsTo(models.owner, {
foreignKey: { name: 'ownerIdKey', field: 'owner_id' },
targetKey: 'ownerId',
});
models.owner.hasMany(models.project, {
foreignKey: { name: 'ownerIdKey', field: 'owner_id' },
sourceKey: 'ownerId',
});
models.customer.hasOne(models.picture, {
foreignKey: { name: 'customerIdKey', field: 'customer_id' },
as: 'picture',
});
models.picture.belongsTo(models.customer, {
foreignKey: { name: 'customerIdKey', field: 'customer_id' },
as: 'customer',
});
Interface.Schemas.schemas = {
user: {
name: 'user',
idField: 'primaryId',
primaryKeys: ['primaryId'],
isCompositePrimary: false,
fields: [
{ field: 'primaryId', type: 'Number' },
{ field: 'email', type: 'String' },
{ field: 'emailValid', type: 'Boolean' },
{ field: 'firstName', type: 'String' },
{ field: 'lastName', type: 'String' },
{ field: 'username', type: 'String' },
{ field: 'password', type: 'String' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
{ field: 'resetPasswordToken', type: 'String' },
{ field: 'addresses', type: ['Number'] },
{ field: 'uuid', type: 'String' },
{ field: 'fullName', isVirtual: true, type: 'String' },
{ field: 'age', type: 'Number' },
],
},
bird: {
name: 'bird',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'String' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
],
},
bike: {
name: 'bike',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'String' },
{ field: 'name', type: 'String' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
],
},
georegion: {
name: 'georegion',
idField: 'isocode',
primaryKeys: ['isocode'],
isCompositePrimary: false,
fields: [
{ field: 'isocode', type: 'String' },
{ field: 'nameEnglish', type: 'String' },
{ field: 'nameFrench', type: 'String' },
],
},
address: {
name: 'address',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'line', type: 'String' },
{ field: 'zipCode', type: 'String' },
{ field: 'city', type: 'String' },
{ field: 'country', type: 'String' },
{ field: 'user', type: 'Number', reference: 'user.primaryId' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
],
},
addressWithUserAlias: {
name: 'addressWithUserAlias',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'line', type: 'String' },
{ field: 'zipCode', type: 'String' },
{ field: 'city', type: 'String' },
{ field: 'country', type: 'String' },
{ field: 'user', type: 'Number', reference: 'userAlias.primaryId' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
],
},
log: {
name: 'log',
idField: 'forestCompositePrimary',
primaryKeys: ['code', 'trace'],
isCompositePrimary: true,
fields: [
{ field: 'code', type: 'String' },
{ field: 'trace', type: 'String' },
{ field: 'stack', type: 'String' },
{ field: 'createdAt', type: 'Date' },
{ field: 'updatedAt', type: 'Date' },
],
},
order: {
name: 'order',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
searchFields: ['amount', 'comment'],
fields: [
{ field: 'id', type: 'Number' },
{ field: 'amount', type: 'Number' },
{ field: 'comment', type: 'String' },
{ field: 'giftMessage', type: 'String' },
],
},
team: {
name: 'team',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'String' },
],
},
userTeam: {
name: 'userTeam',
idField: 'forestCompositePrimary',
primaryKeys: ['userId', 'teamId'],
isCompositePrimary: true,
fields: [
{ field: 'user', type: 'Number', reference: 'user.primaryId' },
{ field: 'team', type: 'Number', reference: 'team.id' },
],
},
member: {
name: 'member',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'String' },
],
},
membership: {
name: 'membership',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'type', type: 'String' },
{ field: 'member', type: 'Number', reference: 'member.id' },
],
},
friend: {
name: 'friend',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'String' },
{ field: 'member', type: 'Number', reference: 'member.id' },
],
},
owner: {
name: 'owner',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'STRING' },
{ field: 'ownerId', type: 'Number' },
],
},
project: {
name: 'project',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'STRING' },
{ field: 'ownerId', type: 'Number', reference: 'owner.ownerId' },
],
},
counter: {
name: 'counter',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'clicks', type: 'Number' },
{ field: 'quantity', type: 'Number' },
],
},
customer: {
name: 'owner',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'name', type: 'STRING' },
],
},
picture: {
name: 'picture',
idField: 'customerId',
primaryKeys: ['customerId'],
isCompositePrimary: false,
fields: [
{ field: 'customerId', type: 'Number', reference: 'customer.id' },
{ field: 'name', type: 'STRING' },
],
},
car: {
name: 'car',
idField: 'id',
primaryKeys: ['id'],
isCompositePrimary: false,
fields: [
{ field: 'id', type: 'Number' },
{ field: 'brand', type: 'String' },
{ field: 'model', type: 'String' },
{
field: 'name',
isVirtual: true,
type: 'String',
get: (car) => `${car.brand} ${car.model}`,
search: (query, search) => {
const split = search.split(' ');
const searchCondition = {
[Sequelize.Op.and]: [
{ brand: { [Sequelize.Op.like]: `%${split[0]}%` } },
{ model: { [Sequelize.Op.like]: `%${split[1]}%` } },
],
};
query.where[Sequelize.Op.and][0][Sequelize.Op.or].push(searchCondition);
return query;
},
},
],
segments: [{
name: 'only monza sp*',
where: () => ({ model: { [Sequelize.Op.like]: '%Monza SP%' } }),
}],
},
};
return { sequelize, models, options };
}
describe(`dialect ${connectionManager.getDialect()}`, () => {
describe('schema adapter', () => {
async function initializeSchema(modelName) {
const { models, options } = initializeSequelize();
const schema = SchemaAdapter(models[modelName], options);
connectionManager.closeConnection();
return schema;
}
describe('on a collection with 13 fields and a few validations', () => {
it('should generate a schema', async () => {
expect.assertions(1);
const schema = await initializeSchema('user');
expect(schema).not.toBeNull();
});
it('should define an idField', async () => {
expect.assertions(3);
const schema = await initializeSchema('user');
expect(schema.idField).toStrictEqual('primaryId');
expect(schema.primaryKeys).toHaveLength(1);
expect(schema.primaryKeys[0]).toStrictEqual('primaryId');
});
it('should not detect a composite primary key', async () => {
expect.assertions(1);
const schema = await initializeSchema('user');
expect(schema.isCompositePrimary).toStrictEqual(false);
});
it('should detect 14 fields with a type', async () => {
expect.assertions(15);
const schema = await initializeSchema('user');
expect(schema.fields).toHaveLength(14);
expect(schema.fields[0].type).toStrictEqual('Number');
expect(schema.fields[1].type).toStrictEqual('String');
expect(schema.fields[2].type).toStrictEqual('Boolean');
expect(schema.fields[3].type).toStrictEqual('String');
expect(schema.fields[4].type).toStrictEqual('String');
expect(schema.fields[5].type).toStrictEqual('String');
expect(schema.fields[6].type).toStrictEqual('String');
expect(schema.fields[7].type).toStrictEqual('Date');
expect(schema.fields[8].type).toStrictEqual('Date');
expect(schema.fields[9].type).toStrictEqual('String');
expect(schema.fields[10].type).toStrictEqual('Uuid');
expect(schema.fields[11].type).toStrictEqual('Number');
expect(schema.fields[12].type[0]).toStrictEqual('Number');
expect(schema.fields[13].type[0]).toStrictEqual('Number');
});
it('should setup validations', async () => {
expect.assertions(5);
const schema = await initializeSchema('user');
expect(schema.fields[4].validations).toHaveLength(2);
expect(schema.fields[4].validations[0].type).toStrictEqual('is longer than');
expect(schema.fields[4].validations[0].value).toStrictEqual(0);
expect(schema.fields[4].validations[1].type).toStrictEqual('is shorter than');
expect(schema.fields[4].validations[1].value).toStrictEqual(50);
});
});
describe('on a simple collection with a fields with a bad type', () => {
it('should generate a schema', async () => {
expect.assertions(1);
const schema = await initializeSchema('hasBadFieldType');
expect(schema).not.toBeNull();
});
it('should detect 4 fields with a type', async () => {
expect.assertions(5);
const schema = await initializeSchema('hasBadFieldType');
expect(schema.fields).toHaveLength(4);
expect(schema.fields[0].type).toStrictEqual('Number');
expect(schema.fields[1].type).toStrictEqual('String');
expect(schema.fields[2].type).toStrictEqual('Date');
expect(schema.fields[3].type).toStrictEqual('Date');
});
});
});
describe('stats > pie stat getter', () => {
async function initializeDatabase() {
const { sequelize, models, options } = initializeSequelize();
await sequelize.sync({ force: true });
await sequelizeFixtures.loadFile(
'test/fixtures/db.json',
models,
{ log: () => { } },
);
return { models, options, user };
}
describe('a simple pie chart', () => {
describe('on an empty users table', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = await initializeDatabase();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new PieStatGetter(models.user, {
...baseParams,
type: 'Pie',
sourceCollectionName: 'user',
groupByFieldName: 'firstName',
aggregator: 'Count',
timeRange: null,
filter: null,
}, options, user).perform();
expect(stat.value).toHaveLength(3);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('with a group by on a belongsTo association using an alias', () => {
it('should respond correct data', async () => {
expect.assertions(1);
const { models, options } = await initializeDatabase();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new PieStatGetter(models.addressWithUserAlias, {
...baseParams,
type: 'Pie',
sourceCollectionName: 'addressWithUserAlias',
groupByFieldName: 'userAlias:primaryId',
aggregator: 'Count',
timeRange: null,
filter: null,
}, options, user).perform();
expect(stat.value).toBeEmpty();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
});
});
describe('stats > line stat getter', () => {
describe('a simple line chart per day on an empty users table', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new LineStatGetter(models.user, {
...baseParams,
type: 'Line',
sourceCollectionName: 'user',
groupByFieldName: 'createdAt',
aggregator: 'Count',
timeRange: 'Day',
filter: null,
}, options, user).perform();
expect(stat.value).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('a simple line chart per week on an empty users table', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new LineStatGetter(models.user, {
...baseParams,
type: 'Line',
sourceCollectionName: 'user',
groupByFieldName: 'createdAt',
aggregator: 'Count',
timeRange: 'Week',
filter: null,
}, options, user).perform();
expect(stat.value).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('a simple line chart per month on an empty users table', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new LineStatGetter(models.user, {
...baseParams,
type: 'Line',
sourceCollectionName: 'user',
groupByFieldName: 'createdAt',
aggregator: 'Count',
timeRange: 'Month',
filter: null,
}, options, user).perform();
expect(stat.value).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('a simple line chart per year on an empty users table', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new LineStatGetter(models.user, {
...baseParams,
type: 'Line',
sourceCollectionName: 'user',
groupByFieldName: 'createdAt',
aggregator: 'Count',
timeRange: 'Year',
filter: null,
}, options, user).perform();
expect(stat.value).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('chart with a filter', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new LineStatGetter(models.address, {
...baseParams,
type: 'Line',
sourceCollectionName: 'address',
groupByFieldName: 'createdAt',
aggregator: 'Count',
timeRange: 'Year',
filter: { field: 'user:primaryId', operator: 'equal', value: 100 },
}, options, user).perform();
expect(stat.value).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
});
describe('stats > value stat getter', () => {
it('should give correct answer without filters', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new ValueStatGetter(models.user, {
...baseParams,
type: 'Value',
aggregator: 'Sum',
aggregateFieldName: 'primaryId',
}, options, user).perform();
expect(stat.value).toStrictEqual({ countCurrent: 305, countPrevious: undefined });
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should give correct answer and previous value', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new ValueStatGetter(models.user, {
...baseParams,
type: 'Value',
aggregator: 'Sum',
aggregateFieldName: 'primaryId',
filter: '{"field":"createdAt","operator":"previous_month_to_date","value":null}',
}, options, user).perform();
expect(stat.value).toStrictEqual({ countCurrent: 305, countPrevious: 0 });
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should give correct answer and previous value with filters', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new ValueStatGetter(models.user, {
...baseParams,
type: 'Value',
aggregator: 'Sum',
aggregateFieldName: 'primaryId',
filter: JSON.stringify({
aggregator: 'and',
conditions: [
{ field: 'createdAt', operator: 'previous_month_to_date', value: null },
{ field: 'primaryId', operator: 'greater_than', value: 100 },
],
}),
}, options, user).perform();
expect(stat.value).toStrictEqual({ countCurrent: 205, countPrevious: 0 });
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should give correct answer with filter on related data', async () => {
expect.assertions(1);
const { models, options } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const stat = await new ValueStatGetter(models.address, {
...baseParams,
type: 'Value',
aggregator: 'Count',
filter: { field: 'user:primaryId', operator: 'greater_than', value: 0 },
}, options, user).perform();
expect(stat.value).toStrictEqual({ countCurrent: 4, countPrevious: undefined });
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('stats > query stat getter', () => {
it('should give correct answer with recordId filtering', async () => {
expect.assertions(2);
const { options } = initializeSequelize();
try {
const contextVariables = { recordId: 102 };
// eslint-disable-next-line jest/no-if
const escapeQuote = connectionManager === sequelizePostgres ? '"' : '`';
const stat = await new QueryStatGetter({
contextVariables,
query: `SELECT count(*) as value FROM ${escapeQuote}users${escapeQuote} WHERE ${escapeQuote}primaryId${escapeQuote} != ?`,
}, options).perform();
expect(stat).toHaveLength(1);
expect(`${stat[0].value}`).toStrictEqual('2');
} finally {
connectionManager.closeConnection();
}
});
});
describe('resources > resources creator', () => {
describe('create a record on a simple collection', () => {
it('should create a record', async () => {
expect.assertions(4);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const result = await new ResourceCreator(models.user, baseParams, {
primaryId: '1',
email: 'jack@forestadmin.com',
firstName: 'Jack',
lastName: 'Lumberjack',
username: 'Jacouille',
password: 'bonpoissonnet',
teams: [],
}, user).perform();
expect(result.primaryId).toStrictEqual(1);
expect(result.firstName).toStrictEqual('Jack');
expect(result.username).toStrictEqual('Jacouille');
const newUser = await models.user.findOne({ where: { email: 'jack@forestadmin.com' } });
expect(newUser).not.toBeNull();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('create a record on a collection with a foreign key non pointing to a primary key', () => {
it('should create a record', async () => {
expect.assertions(6);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.owner, baseParams, {
id: 1,
name: 'foo',
ownerId: 3,
}, user).perform();
const result = await new ResourceCreator(models.project, baseParams, {
id: 1,
name: 'bar',
owner: 1,
}, user).perform();
expect(result.id).toStrictEqual(1);
expect(result.name).toStrictEqual('bar');
expect(result.ownerIdKey).toStrictEqual(3);
const project = await models.project.findOne({ where: { name: 'bar' }, include: { model: models.owner, as: 'owner' } });
expect(project).not.toBeNull();
expect(project.owner.id).toStrictEqual(1);
expect(project.owner.ownerId).toStrictEqual(3);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should not create a record', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.owner, baseParams, {
id: 2,
name: 'foo',
ownerId: 4,
}, user).perform();
await expect(new ResourceCreator(models.project, baseParams, {
id: 1,
name: 'bar',
owner: 4,
}, user).perform()).rejects.toThrow(Error('related owner with pk 4 does not exist.'));
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('create a record on a collection with a foreign key which is a primary key', () => {
it('should create a record', async () => {
expect.assertions(6);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.customer, baseParams, {
id: 1,
name: 'foo',
}, user).perform();
const result = await new ResourceCreator(models.picture, baseParams, {
name: 'bar',
customer: 1,
}, user).perform();
expect(result.customerId).toStrictEqual(1);
expect(result.customerIdKey).toStrictEqual(1);
expect(result.name).toStrictEqual('bar');
const picture = await models.picture.findOne({ where: { name: 'bar' }, include: { model: models.customer, as: 'customer' } });
expect(picture).not.toBeNull();
expect(picture.customerId).toStrictEqual(1);
expect(picture.customer.id).toStrictEqual(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('create a record on a collection with a composite primary key', () => {
it('should create a record', async () => {
expect.assertions(3);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const result = await new ResourceCreator(models.log, baseParams, {
code: 'G@G#F@G@',
trace: 'Ggg23g242@',
}, user).perform();
expect(result.code).toStrictEqual('G@G#F@G@');
expect(result.trace).toStrictEqual('Ggg23g242@');
const log = await models.log.findOne({ where: { code: 'G@G#F@G@' } });
expect(log).not.toBeNull();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
});
describe('resources > relation updater', () => {
describe('update a record on a collection', () => {
it('should update a record', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.member, baseParams, {
id: 1,
name: 'foo',
}, user).perform();
await new ResourceCreator(models.member, baseParams, {
id: 2,
name: 'bar',
}, user).perform();
await new ResourceCreator(models.friend, baseParams, {
id: 1,
name: 'foo',
memberId: 1,
}, user).perform();
const result = await new BelongsToUpdater(models.friend, null, null, {
recordId: '1',
associationName: 'member',
}, {
data: {
id: '2',
type: 'member',
},
}).perform();
expect(result.id).toStrictEqual(1);
expect(result.memberId).toStrictEqual('2');
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should update a record with hasOne association', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.membership, baseParams, {
id: 1,
type: 'basic',
memberId: 1,
}, user).perform();
await new ResourceCreator(models.membership, baseParams, {
id: 2,
type: 'premium',
memberId: 2,
}, user).perform();
await new BelongsToUpdater(models.member, null, null, {
recordId: '1',
associationName: 'membership',
}, {
data: {
id: '2',
type: 'membership',
},
}).perform();
const member = await models.member.findOne({
where: { id: 1 },
include: { model: models.membership },
});
expect(member).not.toBeNull();
expect(member.membership.id).toStrictEqual(2);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should not update a record', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await expect(new BelongsToUpdater(models.member, null, null, {
recordId: '1',
associationName: 'membership',
}, {
data: {
id: '999',
type: 'membership',
},
}).perform()).rejects.toThrow(Error('related membership with pk 999 does not exist.'));
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should not update a record if no data', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const result = await new BelongsToUpdater(models.member, null, null, {
recordId: '1',
associationName: 'membership',
}, {}).perform();
expect(result).toBeNull();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should update a record when removing a reference to another record', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const newMember = await new ResourceCreator(models.member, baseParams, {
id: 421,
name: 'the member',
}, user).perform();
const newFriend = await new ResourceCreator(models.friend, baseParams, {
id: 422,
member: newMember,
name: 'my friend',
}, user).perform();
expect(newFriend.memberId).not.toBeNull();
const result = await new BelongsToUpdater(models.friend, null, null, {
recordId: newFriend.id,
associationName: 'member',
}, { data: null }).perform();
expect(result.memberId).toBeNull();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('update a record on a collection with a foreign key non pointing to a primary key', () => {
it('should update a record', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.owner, baseParams, {
id: 3,
name: 'foo3',
ownerId: 5,
}, user).perform();
const result = await new BelongsToUpdater(models.project, null, null, {
recordId: '1',
associationName: 'owner',
}, {
data: {
id: '3',
type: 'owner',
},
}).perform();
expect(result.id).toStrictEqual(1);
expect(result.ownerIdKey).toStrictEqual(5);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should not update a record', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
await new ResourceCreator(models.owner, baseParams, {
id: 4,
name: 'foo4',
ownerId: 6,
}, user).perform();
await expect(new BelongsToUpdater(models.project, null, null, {
recordId: '1',
associationName: 'owner',
}, {
data: {
id: '6',
type: 'owner',
},
}).perform()).rejects.toThrow(Error('related owner with pk 6 does not exist.'));
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
});
describe('resources > resources getter', () => {
describe('request on the resources getter without page size', () => {
it('should generate a valid SQL query', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const params = {
...baseParams,
fields: { user: 'primaryprimaryId,firstName,lastName,username,password,createdAt,updatedAt,resetPasswordToken' },
page: { number: '1' },
};
await new ResourcesGetter(models.user, null, params, user).perform();
expect(true).toStrictEqual(true);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('request on the resources getter with a page size', () => {
it('should return the records for the specified page', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = {
...baseParams,
fields: {
user: 'primaryprimaryId,firstName,lastName,username,password,createdAt,updatedAt,resetPasswordToken',
},
page: { number: '1', size: '30' },
};
try {
const result = await new ResourcesGetter(models.user, null, params, user).perform();
expect(result[0]).toHaveLength(4);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should return the total records count', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const count = await new ResourcesGetter(models.user, null, baseParams).count();
expect(count).toStrictEqual(4);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('request on the resources getter with a sort on the primary key', () => {
it('should return the records for the specified page', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = {
...baseParams,
fields: { user: 'primaryId,firstName,lastName,username,password,createdAt,updatedAt,resetPasswordToken' },
sort: '-primaryId',
page: { number: '1', size: '30' },
};
try {
const result = await new ResourcesGetter(models.user, null, params, user).perform();
expect(result[0]).toHaveLength(4);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should return the total records count', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
try {
const count = await new ResourcesGetter(models.user, null, baseParams, user).count();
expect(count).toStrictEqual(4);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('request on the resources getter', () => {
describe('with a "string" search', () => {
it('should return the records for the specified page', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = {
...baseParams,
fields: {
user: 'primaryId,firstName,lastName,username,password,createdAt,updatedAt,resetPasswordToken',
},
page: { number: '1', size: '30' },
search: 'hello',
};
try {
const result = await new ResourcesGetter(models.user, null, params, user).perform();
expect(result[0]).toBeEmpty();
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should return the total records count', async () => {
expect.assertions(1);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = { ...baseParams, search: 'hello' };
try {
const count = await new ResourcesGetter(models.user, null, params, user).count();
expect(count).toStrictEqual(0);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
});
describe('with a "number" search', () => {
it('should return the records for the specified page', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = {
...baseParams,
fields: { user: 'primaryId,firstName,lastName,username,password,createdAt,updatedAt,resetPasswordToken,age' },
page: { number: '1', size: '30' },
search: '10',
};
try {
let result = await new ResourcesGetter(models.user, null, params, user).perform();
expect(result[0]).toHaveLength(2);
params.search = '0';
result = await new ResourcesGetter(models.user, null, params, user).perform();
expect(result[0]).toHaveLength(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should return the total records count', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
const params = { ...baseParams, search: '10' };
try {
let count = await new ResourcesGetter(models.user, null, params, user).count();
expect(count).toStrictEqual(2);
params.search = '0';
count = await new ResourcesGetter(models.user, null, params, user).count();
expect(count).toStrictEqual(1);
} finally {
spy.mockRestore();
connectionManager.closeConnection();
}
});
it('should handle numbers over MAX_SAFE_INTEGER', async () => {
expect.assertions(2);
const { models } = initializeSequelize();
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
// HACK: sequelize-fixtures does not support BigInt in json files,
// so we need to update the clicks value manually
const counter = await models.counter.findByPk(10);
counter.clicks = BigInt('9013084467599484828'); // eslint-disable-line no-undef
await counter.sav