12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154 |
- 'use strict';
- const chai = require('chai');
- const sinon = require('sinon');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes, Op, Sequelize } = require('@sequelize/core');
- const pMap = require('p-map');
- const current = Support.sequelize;
- const dialect = current.dialect;
- const dialectName = Support.getTestDialect();
- describe('Model.findOne', () => {
- beforeEach(async function () {
- this.User = this.sequelize.define('User', {
- username: DataTypes.STRING,
- secretValue: DataTypes.STRING,
- data: DataTypes.STRING,
- intVal: DataTypes.INTEGER,
- theDate: DataTypes.DATE,
- aBool: DataTypes.BOOLEAN,
- });
- await this.User.sync({ force: true });
- });
- describe('findOne', () => {
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- await User.sync({ force: true });
- const t = await sequelize.startUnmanagedTransaction();
- await User.create({ username: 'foo' }, { transaction: t });
- const user1 = await User.findOne({
- where: { username: 'foo' },
- });
- const user2 = await User.findOne({
- where: { username: 'foo' },
- transaction: t,
- });
- expect(user1).to.be.null;
- expect(user2).to.not.be.null;
- await t.rollback();
- });
- it('supports concurrent transactions', async function () {
- // Disabled in sqlite3 because it only supports one write transaction at a time
- if (dialectName === 'sqlite3') {
- return;
- }
- this.timeout(90_000);
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- const testAsync = async function () {
- const t0 = await sequelize.startUnmanagedTransaction();
- await User.create(
- {
- username: 'foo',
- },
- {
- transaction: t0,
- },
- );
- const users0 = await User.findAll({
- where: {
- username: 'foo',
- },
- });
- expect(users0).to.have.length(0);
- const users = await User.findAll({
- where: {
- username: 'foo',
- },
- transaction: t0,
- });
- expect(users).to.have.length(1);
- const t = t0;
- return t.rollback();
- };
- await User.sync({ force: true });
- const tasks = [];
- for (let i = 0; i < 1000; i++) {
- tasks.push(testAsync);
- }
- await pMap(
- tasks,
- entry => {
- return entry();
- },
- {
- // Needs to be one less than ??? else the non transaction query won't ever get a connection
- concurrency: (sequelize.rawOptions.pool?.max || 5) - 1,
- },
- );
- });
- }
- describe('general / basic function', () => {
- beforeEach(async function () {
- const user = await this.User.create({ username: 'barfooz' });
- this.UserPrimary = this.sequelize.define('UserPrimary', {
- specialkey: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- await this.UserPrimary.sync({ force: true });
- await this.UserPrimary.create({ specialkey: 'a string' });
- this.user = user;
- });
- if (dialectName === 'mysql') {
- // Bit fields interpreted as boolean need conversion from buffer / bool.
- // Sqlite returns the inserted value as is, and postgres really should the built in bool type instead
- it('allows bit fields as booleans', async function () {
- let bitUser = this.sequelize.define(
- 'bituser',
- {
- bool: 'BIT(1)',
- },
- {
- timestamps: false,
- },
- );
- // First use a custom data type def to create the bit field
- await bitUser.sync({ force: true });
- // Then change the definition to BOOLEAN
- bitUser = this.sequelize.define(
- 'bituser',
- {
- bool: DataTypes.BOOLEAN,
- },
- {
- timestamps: false,
- },
- );
- await bitUser.bulkCreate([{ bool: false }, { bool: true }]);
- const bitUsers = await bitUser.findAll();
- expect(bitUsers[0].bool).not.to.be.ok;
- expect(bitUsers[1].bool).to.be.ok;
- });
- }
- it('treats questionmarks in an array', async function () {
- let test = false;
- await this.UserPrimary.findOne({
- where: { specialkey: 'awesome' },
- logging(sql) {
- test = true;
- expect(sql).to.match(
- /WHERE ["[`|]UserPrimary["\]`|]\.["[`|]specialkey["\]`|] = N?'awesome'/,
- );
- },
- });
- expect(test).to.be.true;
- });
- it("doesn't throw an error when entering in a non integer value for a specified primary field", async function () {
- const user = await this.UserPrimary.findByPk('a string');
- expect(user.specialkey).to.equal('a string');
- });
- it('returns a single dao', async function () {
- const user = await this.User.findByPk(this.user.id);
- expect(Array.isArray(user)).to.not.be.ok;
- expect(user.id).to.equal(this.user.id);
- expect(user.id).to.equal(1);
- });
- it('returns a single dao given a string id', async function () {
- const user = await this.User.findByPk(this.user.id.toString());
- expect(Array.isArray(user)).to.not.be.ok;
- expect(user.id).to.equal(this.user.id);
- expect(user.id).to.equal(1);
- });
- it('should make aliased attributes available', async function () {
- const user = await this.User.findOne({
- where: { id: 1 },
- attributes: ['id', ['username', 'name']],
- });
- expect(user.dataValues.name).to.equal('barfooz');
- });
- it('should fail with meaningful error message on invalid attributes definition', function () {
- expect(
- this.User.findOne({
- where: { id: 1 },
- attributes: ['id', ['username']],
- }),
- ).to.be.rejectedWith(
- "[\"username\"] is not a valid attribute definition. Please use the following format: ['attribute definition', 'alias']",
- );
- });
- it('should not try to convert boolean values if they are not selected', async function () {
- const UserWithBoolean = this.sequelize.define('UserBoolean', {
- active: DataTypes.BOOLEAN,
- });
- await UserWithBoolean.sync({ force: true });
- const user = await UserWithBoolean.create({ active: true });
- const user0 = await UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] });
- expect(user0.active).not.to.exist;
- });
- it('finds a specific user via where option', async function () {
- const user = await this.User.findOne({ where: { username: 'barfooz' } });
- expect(user.username).to.equal('barfooz');
- });
- it("doesn't find a user if conditions are not matching", async function () {
- const user = await this.User.findOne({ where: { username: 'foo' } });
- expect(user).to.be.null;
- });
- it('allows sql logging', async function () {
- let test = false;
- await this.User.findOne({
- where: { username: 'foo' },
- logging(sql) {
- test = true;
- expect(sql).to.exist;
- expect(sql.toUpperCase()).to.include('SELECT');
- },
- });
- expect(test).to.be.true;
- });
- it('ignores passed limit option', async function () {
- const user = await this.User.findOne({ limit: 10 });
- // it returns an object instead of an array
- expect(Array.isArray(user)).to.not.be.ok;
- expect(user.dataValues.hasOwnProperty('username')).to.be.ok;
- });
- it('finds entries via primary keys', async function () {
- const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
- identifier: { type: DataTypes.STRING, primaryKey: true },
- name: DataTypes.STRING,
- });
- await UserPrimary.sync({ force: true });
- const u = await UserPrimary.create({
- identifier: 'an identifier',
- name: 'John',
- });
- expect(u.id).not.to.exist;
- const u2 = await UserPrimary.findByPk('an identifier');
- expect(u2.identifier).to.equal('an identifier');
- expect(u2.name).to.equal('John');
- });
- it('finds entries via a string primary key called id', async function () {
- const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
- id: { type: DataTypes.STRING, primaryKey: true },
- name: DataTypes.STRING,
- });
- await UserPrimary.sync({ force: true });
- await UserPrimary.create({
- id: 'a string based id',
- name: 'Johnno',
- });
- const u2 = await UserPrimary.findByPk('a string based id');
- expect(u2.id).to.equal('a string based id');
- expect(u2.name).to.equal('Johnno');
- });
- if (current.dialect.supports.dataTypes.BIGINT) {
- it('finds entries via a bigint primary key called id', async function () {
- const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
- id: { type: DataTypes.BIGINT, primaryKey: true },
- name: DataTypes.STRING,
- });
- await UserPrimary.sync({ force: true });
- await UserPrimary.create({
- id: 9_007_199_254_740_993n, // Number.MAX_SAFE_INTEGER + 2 (cannot be represented exactly as a number in JS)
- name: 'Johnno',
- });
- const u2 = await UserPrimary.findByPk(9_007_199_254_740_993n);
- expect(u2.name).to.equal('Johnno');
- // Getting the value back as bigint is not supported yet: https://github.com/sequelize/sequelize/issues/14296
- // With most dialects we'll receive a string, but in some cases we have to be a bit creative to prove that we did get hold of the right record:
- if (dialectName === 'db2') {
- // ibm_db 2.7.4+ returns BIGINT values as JS numbers, which leads to a loss of precision:
- // https://github.com/ibmdb/node-ibm_db/issues/816
- // It means that u2.id comes back as 9_007_199_254_740_992 here :(
- // Hopefully this will be fixed soon.
- // For now we can do a separate query where we stringify the value to prove that it did get stored correctly:
- const [[{ stringifiedId }]] = await this.sequelize.query(
- `select "id"::varchar as "stringifiedId" from "${UserPrimary.tableName}" where "id" = 9007199254740993`,
- );
- expect(stringifiedId).to.equal('9007199254740993');
- } else if (dialectName === 'mariadb') {
- // With our current default config, the mariadb driver sends back a Long instance.
- // Updating the mariadb dev dep and passing "supportBigInt: true" would get it back as a bigint,
- // but that's potentially a big change.
- // For now, we'll just stringify the Long and make the comparison:
- expect(u2.id.toString()).to.equal('9007199254740993');
- } else {
- expect(u2.id).to.equal('9007199254740993');
- }
- });
- }
- it('always honors ZERO as primary key', async function () {
- const permutations = [0, '0'];
- let count = 0;
- await this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]);
- await Promise.all(
- permutations.map(async perm => {
- const user = await this.User.findByPk(perm, {
- logging(s) {
- expect(s).to.include(0);
- count++;
- },
- });
- expect(user).to.be.null;
- }),
- );
- expect(count).to.equal(permutations.length);
- });
- it('should allow us to find IDs using capital letters', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- ID: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
- Login: { type: DataTypes.STRING },
- });
- await User.sync({ force: true });
- await User.create({ Login: 'foo' });
- const user = await User.findByPk(1);
- expect(user).to.exist;
- expect(user.ID).to.equal(1);
- });
- if (dialect.supports.dataTypes.CITEXT) {
- it('should allow case-insensitive find on CITEXT type', async function () {
- const User = this.sequelize.define('UserWithCaseInsensitiveName', {
- username: DataTypes.CITEXT,
- });
- await User.sync({ force: true });
- await User.create({ username: 'longUserNAME' });
- const user = await User.findOne({ where: { username: 'LONGusername' } });
- expect(user).to.exist;
- expect(user.username).to.equal('longUserNAME');
- });
- }
- if (dialectName === 'postgres') {
- it('should allow case-sensitive find on TSVECTOR type', async function () {
- const User = this.sequelize.define('UserWithCaseInsensitiveName', {
- username: DataTypes.TSVECTOR,
- });
- await User.sync({ force: true });
- await User.create({ username: 'longUserNAME' });
- const user = await User.findOne({
- where: { username: 'longUserNAME' },
- });
- expect(user).to.exist;
- expect(user.username).to.equal("'longUserNAME'");
- });
- }
- it('should not fail if model is paranoid and where is an empty array', async function () {
- const User = this.sequelize.define(
- 'User',
- { username: DataTypes.STRING },
- { paranoid: true },
- );
- await User.sync({ force: true });
- await User.create({ username: 'A fancy name' });
- expect((await User.findOne({ where: [] })).username).to.equal('A fancy name');
- });
- it('should work if model is paranoid and only operator in where clause is a Symbol (#8406)', async function () {
- const User = this.sequelize.define(
- 'User',
- { username: DataTypes.STRING },
- { paranoid: true },
- );
- await User.sync({ force: true });
- await User.create({ username: 'foo' });
- expect(
- await User.findOne({
- where: {
- [Op.or]: [{ username: 'bar' }, { username: 'baz' }],
- },
- }),
- ).to.not.be.ok;
- });
- });
- describe('eager loading', () => {
- beforeEach(function () {
- this.Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- this.Worker = this.sequelize.define('Worker', { name: DataTypes.STRING });
- this.init = async function (callback) {
- await this.sequelize.sync({ force: true });
- const worker = await this.Worker.create({ name: 'worker' });
- const task = await this.Task.create({ title: 'homework' });
- this.worker = worker;
- this.task = task;
- return callback();
- };
- });
- describe('belongsTo', () => {
- describe('generic', () => {
- it('throws an error about unexpected input if include contains a non-object', async function () {
- try {
- await this.Worker.findOne({ include: [1] });
- } catch (error) {
- expect(error.message).to
- .equal(`Invalid Include received. Include has to be either a Model, an Association, the name of an association, or a plain object compatible with IncludeOptions.
- Got { association: 1 } instead`);
- }
- });
- it('throws an error if included DaoFactory is not associated', async function () {
- try {
- await this.Worker.findOne({ include: [this.Task] });
- } catch (error) {
- expect(error.message).to.equal(
- 'Invalid Include received: no associations exist between "Worker" and "Task"',
- );
- }
- });
- it('returns the associated worker via task.worker', async function () {
- this.Task.belongsTo(this.Worker);
- await this.init(async () => {
- await this.task.setWorker(this.worker);
- const task = await this.Task.findOne({
- where: { title: 'homework' },
- include: [this.Worker],
- });
- expect(task).to.exist;
- expect(task.worker).to.exist;
- expect(task.worker.name).to.equal('worker');
- });
- });
- });
- it('returns the private and public ip', async function () {
- const ctx = Object.create(this);
- ctx.Domain = ctx.sequelize.define('Domain', { ip: DataTypes.STRING });
- ctx.Environment = ctx.sequelize.define('Environment', { name: DataTypes.STRING });
- ctx.Environment.belongsTo(ctx.Domain, {
- as: 'PrivateDomain',
- foreignKey: 'privateDomainId',
- });
- ctx.Environment.belongsTo(ctx.Domain, {
- as: 'PublicDomain',
- foreignKey: 'publicDomainId',
- });
- await ctx.Domain.sync({ force: true });
- await ctx.Environment.sync({ force: true });
- const privateIp = await ctx.Domain.create({ ip: '192.168.0.1' });
- const publicIp = await ctx.Domain.create({ ip: '91.65.189.19' });
- const env = await ctx.Environment.create({ name: 'environment' });
- await env.setPrivateDomain(privateIp);
- await env.setPublicDomain(publicIp);
- const environment = await ctx.Environment.findOne({
- where: { name: 'environment' },
- include: [
- { model: ctx.Domain, as: 'PrivateDomain' },
- { model: ctx.Domain, as: 'PublicDomain' },
- ],
- });
- expect(environment).to.exist;
- expect(environment.PrivateDomain).to.exist;
- expect(environment.PrivateDomain.ip).to.equal('192.168.0.1');
- expect(environment.PublicDomain).to.exist;
- expect(environment.PublicDomain.ip).to.equal('91.65.189.19');
- });
- it('eager loads with non-id primary keys', async function () {
- this.User = this.sequelize.define('UserPKeagerbelong', {
- username: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.Group = this.sequelize.define('GroupPKeagerbelong', {
- name: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.User.belongsTo(this.Group);
- await this.sequelize.sync({ force: true });
- await this.Group.create({ name: 'people' });
- await this.User.create({ username: 'someone', groupPKeagerbelongName: 'people' });
- const someUser = await this.User.findOne({
- where: {
- username: 'someone',
- },
- include: [this.Group],
- });
- expect(someUser).to.exist;
- expect(someUser.username).to.equal('someone');
- expect(someUser.groupPKeagerbelong.name).to.equal('people');
- });
- it('getting parent data in many to one relationship', async function () {
- const User = this.sequelize.define('User', {
- id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
- username: { type: DataTypes.STRING },
- });
- const Message = this.sequelize.define('Message', {
- id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
- user_id: { type: DataTypes.INTEGER },
- message: { type: DataTypes.STRING },
- });
- User.hasMany(Message, { foreignKey: 'user_id' });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'test_testerson' });
- await Message.create({ user_id: user.id, message: 'hi there!' });
- await Message.create({ user_id: user.id, message: 'a second message' });
- const messages = await Message.findAll({
- where: { user_id: user.id },
- attributes: ['user_id', 'message'],
- include: [{ model: User, attributes: ['username'] }],
- });
- expect(messages.length).to.equal(2);
- expect(messages[0].message).to.equal('hi there!');
- expect(messages[0].user.username).to.equal('test_testerson');
- expect(messages[1].message).to.equal('a second message');
- expect(messages[1].user.username).to.equal('test_testerson');
- });
- it('allows mulitple assocations of the same model with different alias', async function () {
- this.Worker.belongsTo(this.Task, { as: 'ToDo' });
- this.Worker.belongsTo(this.Task, { as: 'DoTo' });
- await this.init(() => {
- return this.Worker.findOne({
- include: [
- { model: this.Task, as: 'ToDo' },
- { model: this.Task, as: 'DoTo' },
- ],
- });
- });
- });
- });
- describe('hasOne', () => {
- beforeEach(async function () {
- this.Worker.hasOne(this.Task);
- await this.init(() => {
- return this.worker.setTask(this.task);
- });
- });
- it('throws an error if included DaoFactory is not associated', async function () {
- try {
- await this.Task.findOne({ include: [this.Worker] });
- } catch (error) {
- expect(error.message).to.equal(
- 'Invalid Include received: no associations exist between "Task" and "Worker"',
- );
- }
- });
- it('returns the associated task via worker.task', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [this.Task],
- });
- expect(worker).to.exist;
- expect(worker.task).to.exist;
- expect(worker.task.title).to.equal('homework');
- });
- it('eager loads with non-id primary keys', async function () {
- this.User = this.sequelize.define('UserPKeagerone', {
- username: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.Group = this.sequelize.define('GroupPKeagerone', {
- name: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.Group.hasOne(this.User);
- await this.sequelize.sync({ force: true });
- await this.Group.create({ name: 'people' });
- await this.User.create({ username: 'someone', groupPKeageroneName: 'people' });
- const someGroup = await this.Group.findOne({
- where: {
- name: 'people',
- },
- include: [this.User],
- });
- expect(someGroup).to.exist;
- expect(someGroup.name).to.equal('people');
- expect(someGroup.userPKeagerone.username).to.equal('someone');
- });
- });
- describe('hasOne with alias', () => {
- it('throws an error if included DaoFactory is not referenced by alias', async function () {
- try {
- await this.Worker.findOne({ include: [this.Task] });
- } catch (error) {
- expect(error.message).to.equal(
- 'Invalid Include received: no associations exist between "Worker" and "Task"',
- );
- }
- });
- describe('alias', () => {
- beforeEach(async function () {
- this.Worker.hasOne(this.Task, { as: 'ToDo' });
- await this.init(() => {
- return this.worker.setToDo(this.task);
- });
- });
- it("throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn't match", async function () {
- try {
- await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] });
- } catch (error) {
- expect(error.message).to
- .equal(`Association with alias "Work" does not exist on Worker.
- The following associations are defined on "Worker": "ToDo"`);
- }
- });
- it('returns the associated task via worker.task', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [{ model: this.Task, as: 'ToDo' }],
- });
- expect(worker).to.exist;
- expect(worker.ToDo).to.exist;
- expect(worker.ToDo.title).to.equal('homework');
- });
- it('returns the associated task via worker.task when daoFactory is aliased with model', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [{ model: this.Task, as: 'ToDo' }],
- });
- expect(worker.ToDo.title).to.equal('homework');
- });
- it('allows mulitple assocations of the same model with different alias', async function () {
- this.Worker.hasOne(this.Task, { as: 'DoTo' });
- await this.init(() => {
- return this.Worker.findOne({
- include: [
- { model: this.Task, as: 'ToDo' },
- { model: this.Task, as: 'DoTo' },
- ],
- });
- });
- });
- });
- });
- describe('hasMany', () => {
- beforeEach(async function () {
- this.Worker.hasMany(this.Task);
- await this.init(() => {
- return this.worker.setTasks([this.task]);
- });
- });
- it('throws an error if included DaoFactory is not associated', async function () {
- try {
- await this.Task.findOne({ include: [this.Worker] });
- } catch (error) {
- expect(error.message).to.equal(
- 'Invalid Include received: no associations exist between "Task" and "Worker"',
- );
- }
- });
- it('returns the associated tasks via worker.tasks', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [this.Task],
- });
- expect(worker).to.exist;
- expect(worker.tasks).to.exist;
- expect(worker.tasks[0].title).to.equal('homework');
- });
- it('including two has many relations should not result in duplicate values', async function () {
- this.Contact = this.sequelize.define('Contact', { name: DataTypes.STRING });
- this.Photo = this.sequelize.define('Photo', { img: DataTypes.TEXT });
- this.PhoneNumber = this.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT });
- this.Contact.hasMany(this.Photo, { as: 'Photos' });
- this.Contact.hasMany(this.PhoneNumber);
- await this.sequelize.sync({ force: true });
- const someContact = await this.Contact.create({ name: 'Boris' });
- const somePhoto = await this.Photo.create({ img: 'img.jpg' });
- const somePhone1 = await this.PhoneNumber.create({ phone: '000000' });
- const somePhone2 = await this.PhoneNumber.create({ phone: '111111' });
- await someContact.setPhotos([somePhoto]);
- await someContact.setPhoneNumbers([somePhone1, somePhone2]);
- const fetchedContact = await this.Contact.findOne({
- where: {
- name: 'Boris',
- },
- include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }],
- });
- expect(fetchedContact).to.exist;
- expect(fetchedContact.Photos.length).to.equal(1);
- expect(fetchedContact.phoneNumbers.length).to.equal(2);
- });
- it('eager loads with non-id primary keys', async function () {
- this.User = this.sequelize.define('UserPKeagerone', {
- username: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.Group = this.sequelize.define('GroupPKeagerone', {
- name: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- this.Group.belongsToMany(this.User, { through: 'group_user' });
- this.User.belongsToMany(this.Group, { through: 'group_user' });
- await this.sequelize.sync({ force: true });
- const someUser = await this.User.create({ username: 'someone' });
- const someGroup = await this.Group.create({ name: 'people' });
- await someUser.setGroupPKeagerones([someGroup]);
- const someUser0 = await this.User.findOne({
- where: {
- username: 'someone',
- },
- include: [this.Group],
- });
- expect(someUser0).to.exist;
- expect(someUser0.username).to.equal('someone');
- expect(someUser0.groupPKeagerones[0].name).to.equal('people');
- });
- });
- describe('hasMany with alias', () => {
- it('throws an error if included DaoFactory is not referenced by alias', async function () {
- try {
- await this.Worker.findOne({ include: [this.Task] });
- } catch (error) {
- expect(error.message).to.equal(
- 'Invalid Include received: no associations exist between "Worker" and "Task"',
- );
- }
- });
- describe('alias', () => {
- beforeEach(async function () {
- this.Worker.hasMany(this.Task, { as: 'ToDos' });
- await this.init(() => {
- return this.worker.setToDos([this.task]);
- });
- });
- it("throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn't match", async function () {
- try {
- await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] });
- } catch (error) {
- expect(error.message).to
- .equal(`Association with alias "Work" does not exist on Worker.
- The following associations are defined on "Worker": "ToDos"`);
- }
- });
- it('returns the associated task via worker.task', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [{ model: this.Task, as: 'ToDos' }],
- });
- expect(worker).to.exist;
- expect(worker.ToDos).to.exist;
- expect(worker.ToDos[0].title).to.equal('homework');
- });
- it('returns the associated task via worker.task when daoFactory is aliased with model', async function () {
- const worker = await this.Worker.findOne({
- where: { name: 'worker' },
- include: [{ model: this.Task, as: 'ToDos' }],
- });
- expect(worker.ToDos[0].title).to.equal('homework');
- });
- it('allows mulitple assocations of the same model with different alias', async function () {
- this.Worker.hasMany(this.Task, { as: 'DoTos' });
- await this.init(() => {
- return this.Worker.findOne({
- include: [
- { model: this.Task, as: 'ToDos' },
- { model: this.Task, as: 'DoTos' },
- ],
- });
- });
- });
- });
- });
- describe('hasMany (N:M) with alias', () => {
- beforeEach(function () {
- this.Product = this.sequelize.define('Product', { title: DataTypes.STRING });
- this.Tag = this.sequelize.define('Tag', { name: DataTypes.STRING });
- });
- it('returns the associated models when using through as string and alias', async function () {
- this.Product.belongsToMany(this.Tag, { as: 'tags', through: 'product_tag' });
- this.Tag.belongsToMany(this.Product, { as: 'products', through: 'product_tag' });
- await this.sequelize.sync();
- await Promise.all([
- this.Product.bulkCreate([
- { title: 'Chair' },
- { title: 'Desk' },
- { title: 'Handbag' },
- { title: 'Dress' },
- { title: 'Jan' },
- ]),
- this.Tag.bulkCreate([{ name: 'Furniture' }, { name: 'Clothing' }, { name: 'People' }]),
- ]);
- const [products, tags] = await Promise.all([this.Product.findAll(), this.Tag.findAll()]);
- this.products = products;
- this.tags = tags;
- await Promise.all([
- products[0].setTags([tags[0], tags[1]]),
- products[1].addTag(tags[0]),
- products[2].addTag(tags[1]),
- products[3].setTags([tags[1]]),
- products[4].setTags([tags[2]]),
- ]);
- await Promise.all([
- (async () => {
- const tag = await this.Tag.findOne({
- where: {
- id: tags[0].id,
- },
- include: [{ model: this.Product, as: 'products' }],
- });
- expect(tag).to.exist;
- expect(tag.products.length).to.equal(2);
- })(),
- tags[1].getProducts().then(products => {
- expect(products.length).to.equal(3);
- }),
- (async () => {
- const product = await this.Product.findOne({
- where: {
- id: products[0].id,
- },
- include: [{ model: this.Tag, as: 'tags' }],
- });
- expect(product).to.exist;
- expect(product.tags.length).to.equal(2);
- })(),
- products[1].getTags().then(tags => {
- expect(tags.length).to.equal(1);
- }),
- ]);
- });
- it('returns the associated models when using through as model and alias', async function () {
- // Exactly the same code as the previous test, just with a through model instance, and promisified
- const ProductTag = this.sequelize.define('product_tag');
- this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag });
- this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag });
- await this.sequelize.sync();
- await Promise.all([
- this.Product.bulkCreate([
- { title: 'Chair' },
- { title: 'Desk' },
- { title: 'Handbag' },
- { title: 'Dress' },
- { title: 'Jan' },
- ]),
- this.Tag.bulkCreate([{ name: 'Furniture' }, { name: 'Clothing' }, { name: 'People' }]),
- ]);
- const [products, tags] = await Promise.all([this.Product.findAll(), this.Tag.findAll()]);
- this.products = products;
- this.tags = tags;
- await Promise.all([
- products[0].setTags([tags[0], tags[1]]),
- products[1].addTag(tags[0]),
- products[2].addTag(tags[1]),
- products[3].setTags([tags[1]]),
- products[4].setTags([tags[2]]),
- ]);
- await Promise.all([
- expect(
- this.Tag.findOne({
- where: {
- id: this.tags[0].id,
- },
- include: [{ model: this.Product, as: 'products' }],
- }),
- )
- .to.eventually.have.property('products')
- .to.have.length(2),
- expect(
- this.Product.findOne({
- where: {
- id: this.products[0].id,
- },
- include: [{ model: this.Tag, as: 'tags' }],
- }),
- )
- .to.eventually.have.property('tags')
- .to.have.length(2),
- expect(this.tags[1].getProducts()).to.eventually.have.length(3),
- expect(this.products[1].getTags()).to.eventually.have.length(1),
- ]);
- });
- });
- });
- describe('queryOptions', () => {
- beforeEach(async function () {
- const user = await this.User.create({ username: 'barfooz' });
- this.user = user;
- });
- it('should return a DAO when queryOptions are not set', async function () {
- const user = await this.User.findOne({ where: { username: 'barfooz' } });
- expect(user).to.be.instanceOf(this.User);
- });
- it('should return a DAO when raw is false', async function () {
- const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: false });
- expect(user).to.be.instanceOf(this.User);
- });
- it('should return raw data when raw is true', async function () {
- const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: true });
- expect(user).to.not.be.instanceOf(this.User);
- expect(user).to.be.instanceOf(Object);
- });
- });
- it('should support logging', async function () {
- const spy = sinon.spy();
- await this.User.findOne({
- where: {},
- logging: spy,
- });
- expect(spy.called).to.be.ok;
- });
- describe('rejectOnEmpty mode', () => {
- it('throws error when record not found by findOne', async function () {
- await expect(
- this.User.findOne({
- where: {
- username: 'ath-kantam-pradakshnami',
- },
- rejectOnEmpty: true,
- }),
- ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
- });
- it('throws error when record not found by findByPk', async function () {
- await expect(
- this.User.findByPk(2, {
- rejectOnEmpty: true,
- }),
- ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
- });
- it('throws error when record not found by find', async function () {
- await expect(
- this.User.findOne({
- where: {
- username: 'some-username-that-is-not-used-anywhere',
- },
- rejectOnEmpty: true,
- }),
- ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
- });
- it('works from model options', async () => {
- const Model = current.define(
- 'Test',
- {
- username: DataTypes.STRING(100),
- },
- {
- rejectOnEmpty: true,
- },
- );
- await Model.sync({ force: true });
- await expect(
- Model.findOne({
- where: {
- username: 'some-username-that-is-not-used-anywhere',
- },
- }),
- ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
- });
- it('override model options', async () => {
- const Model = current.define(
- 'Test',
- {
- username: DataTypes.STRING(100),
- },
- {
- rejectOnEmpty: true,
- },
- );
- await Model.sync({ force: true });
- await expect(
- Model.findOne({
- rejectOnEmpty: false,
- where: {
- username: 'some-username-that-is-not-used-anywhere',
- },
- }),
- ).to.eventually.be.deep.equal(null);
- });
- it('resolve null when disabled', async () => {
- const Model = current.define('Test', {
- username: DataTypes.STRING(100),
- });
- await Model.sync({ force: true });
- await expect(
- Model.findOne({
- where: {
- username: 'some-username-that-is-not-used-anywhere-for-sure-this-time',
- },
- }),
- ).to.eventually.be.equal(null);
- });
- });
- });
- });
|