findOne.test.js 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. 'use strict';
  2. const chai = require('chai');
  3. const sinon = require('sinon');
  4. const expect = chai.expect;
  5. const Support = require('../support');
  6. const { DataTypes, Op, Sequelize } = require('@sequelize/core');
  7. const pMap = require('p-map');
  8. const current = Support.sequelize;
  9. const dialect = current.dialect;
  10. const dialectName = Support.getTestDialect();
  11. describe('Model.findOne', () => {
  12. beforeEach(async function () {
  13. this.User = this.sequelize.define('User', {
  14. username: DataTypes.STRING,
  15. secretValue: DataTypes.STRING,
  16. data: DataTypes.STRING,
  17. intVal: DataTypes.INTEGER,
  18. theDate: DataTypes.DATE,
  19. aBool: DataTypes.BOOLEAN,
  20. });
  21. await this.User.sync({ force: true });
  22. });
  23. describe('findOne', () => {
  24. if (current.dialect.supports.transactions) {
  25. it('supports transactions', async function () {
  26. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  27. this.sequelize,
  28. );
  29. const User = sequelize.define('User', { username: DataTypes.STRING });
  30. await User.sync({ force: true });
  31. const t = await sequelize.startUnmanagedTransaction();
  32. await User.create({ username: 'foo' }, { transaction: t });
  33. const user1 = await User.findOne({
  34. where: { username: 'foo' },
  35. });
  36. const user2 = await User.findOne({
  37. where: { username: 'foo' },
  38. transaction: t,
  39. });
  40. expect(user1).to.be.null;
  41. expect(user2).to.not.be.null;
  42. await t.rollback();
  43. });
  44. it('supports concurrent transactions', async function () {
  45. // Disabled in sqlite3 because it only supports one write transaction at a time
  46. if (dialectName === 'sqlite3') {
  47. return;
  48. }
  49. this.timeout(90_000);
  50. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  51. this.sequelize,
  52. );
  53. const User = sequelize.define('User', { username: DataTypes.STRING });
  54. const testAsync = async function () {
  55. const t0 = await sequelize.startUnmanagedTransaction();
  56. await User.create(
  57. {
  58. username: 'foo',
  59. },
  60. {
  61. transaction: t0,
  62. },
  63. );
  64. const users0 = await User.findAll({
  65. where: {
  66. username: 'foo',
  67. },
  68. });
  69. expect(users0).to.have.length(0);
  70. const users = await User.findAll({
  71. where: {
  72. username: 'foo',
  73. },
  74. transaction: t0,
  75. });
  76. expect(users).to.have.length(1);
  77. const t = t0;
  78. return t.rollback();
  79. };
  80. await User.sync({ force: true });
  81. const tasks = [];
  82. for (let i = 0; i < 1000; i++) {
  83. tasks.push(testAsync);
  84. }
  85. await pMap(
  86. tasks,
  87. entry => {
  88. return entry();
  89. },
  90. {
  91. // Needs to be one less than ??? else the non transaction query won't ever get a connection
  92. concurrency: (sequelize.rawOptions.pool?.max || 5) - 1,
  93. },
  94. );
  95. });
  96. }
  97. describe('general / basic function', () => {
  98. beforeEach(async function () {
  99. const user = await this.User.create({ username: 'barfooz' });
  100. this.UserPrimary = this.sequelize.define('UserPrimary', {
  101. specialkey: {
  102. type: DataTypes.STRING,
  103. primaryKey: true,
  104. },
  105. });
  106. await this.UserPrimary.sync({ force: true });
  107. await this.UserPrimary.create({ specialkey: 'a string' });
  108. this.user = user;
  109. });
  110. if (dialectName === 'mysql') {
  111. // Bit fields interpreted as boolean need conversion from buffer / bool.
  112. // Sqlite returns the inserted value as is, and postgres really should the built in bool type instead
  113. it('allows bit fields as booleans', async function () {
  114. let bitUser = this.sequelize.define(
  115. 'bituser',
  116. {
  117. bool: 'BIT(1)',
  118. },
  119. {
  120. timestamps: false,
  121. },
  122. );
  123. // First use a custom data type def to create the bit field
  124. await bitUser.sync({ force: true });
  125. // Then change the definition to BOOLEAN
  126. bitUser = this.sequelize.define(
  127. 'bituser',
  128. {
  129. bool: DataTypes.BOOLEAN,
  130. },
  131. {
  132. timestamps: false,
  133. },
  134. );
  135. await bitUser.bulkCreate([{ bool: false }, { bool: true }]);
  136. const bitUsers = await bitUser.findAll();
  137. expect(bitUsers[0].bool).not.to.be.ok;
  138. expect(bitUsers[1].bool).to.be.ok;
  139. });
  140. }
  141. it('treats questionmarks in an array', async function () {
  142. let test = false;
  143. await this.UserPrimary.findOne({
  144. where: { specialkey: 'awesome' },
  145. logging(sql) {
  146. test = true;
  147. expect(sql).to.match(
  148. /WHERE ["[`|]UserPrimary["\]`|]\.["[`|]specialkey["\]`|] = N?'awesome'/,
  149. );
  150. },
  151. });
  152. expect(test).to.be.true;
  153. });
  154. it("doesn't throw an error when entering in a non integer value for a specified primary field", async function () {
  155. const user = await this.UserPrimary.findByPk('a string');
  156. expect(user.specialkey).to.equal('a string');
  157. });
  158. it('returns a single dao', async function () {
  159. const user = await this.User.findByPk(this.user.id);
  160. expect(Array.isArray(user)).to.not.be.ok;
  161. expect(user.id).to.equal(this.user.id);
  162. expect(user.id).to.equal(1);
  163. });
  164. it('returns a single dao given a string id', async function () {
  165. const user = await this.User.findByPk(this.user.id.toString());
  166. expect(Array.isArray(user)).to.not.be.ok;
  167. expect(user.id).to.equal(this.user.id);
  168. expect(user.id).to.equal(1);
  169. });
  170. it('should make aliased attributes available', async function () {
  171. const user = await this.User.findOne({
  172. where: { id: 1 },
  173. attributes: ['id', ['username', 'name']],
  174. });
  175. expect(user.dataValues.name).to.equal('barfooz');
  176. });
  177. it('should fail with meaningful error message on invalid attributes definition', function () {
  178. expect(
  179. this.User.findOne({
  180. where: { id: 1 },
  181. attributes: ['id', ['username']],
  182. }),
  183. ).to.be.rejectedWith(
  184. "[\"username\"] is not a valid attribute definition. Please use the following format: ['attribute definition', 'alias']",
  185. );
  186. });
  187. it('should not try to convert boolean values if they are not selected', async function () {
  188. const UserWithBoolean = this.sequelize.define('UserBoolean', {
  189. active: DataTypes.BOOLEAN,
  190. });
  191. await UserWithBoolean.sync({ force: true });
  192. const user = await UserWithBoolean.create({ active: true });
  193. const user0 = await UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] });
  194. expect(user0.active).not.to.exist;
  195. });
  196. it('finds a specific user via where option', async function () {
  197. const user = await this.User.findOne({ where: { username: 'barfooz' } });
  198. expect(user.username).to.equal('barfooz');
  199. });
  200. it("doesn't find a user if conditions are not matching", async function () {
  201. const user = await this.User.findOne({ where: { username: 'foo' } });
  202. expect(user).to.be.null;
  203. });
  204. it('allows sql logging', async function () {
  205. let test = false;
  206. await this.User.findOne({
  207. where: { username: 'foo' },
  208. logging(sql) {
  209. test = true;
  210. expect(sql).to.exist;
  211. expect(sql.toUpperCase()).to.include('SELECT');
  212. },
  213. });
  214. expect(test).to.be.true;
  215. });
  216. it('ignores passed limit option', async function () {
  217. const user = await this.User.findOne({ limit: 10 });
  218. // it returns an object instead of an array
  219. expect(Array.isArray(user)).to.not.be.ok;
  220. expect(user.dataValues.hasOwnProperty('username')).to.be.ok;
  221. });
  222. it('finds entries via primary keys', async function () {
  223. const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
  224. identifier: { type: DataTypes.STRING, primaryKey: true },
  225. name: DataTypes.STRING,
  226. });
  227. await UserPrimary.sync({ force: true });
  228. const u = await UserPrimary.create({
  229. identifier: 'an identifier',
  230. name: 'John',
  231. });
  232. expect(u.id).not.to.exist;
  233. const u2 = await UserPrimary.findByPk('an identifier');
  234. expect(u2.identifier).to.equal('an identifier');
  235. expect(u2.name).to.equal('John');
  236. });
  237. it('finds entries via a string primary key called id', async function () {
  238. const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
  239. id: { type: DataTypes.STRING, primaryKey: true },
  240. name: DataTypes.STRING,
  241. });
  242. await UserPrimary.sync({ force: true });
  243. await UserPrimary.create({
  244. id: 'a string based id',
  245. name: 'Johnno',
  246. });
  247. const u2 = await UserPrimary.findByPk('a string based id');
  248. expect(u2.id).to.equal('a string based id');
  249. expect(u2.name).to.equal('Johnno');
  250. });
  251. if (current.dialect.supports.dataTypes.BIGINT) {
  252. it('finds entries via a bigint primary key called id', async function () {
  253. const UserPrimary = this.sequelize.define('UserWithPrimaryKey', {
  254. id: { type: DataTypes.BIGINT, primaryKey: true },
  255. name: DataTypes.STRING,
  256. });
  257. await UserPrimary.sync({ force: true });
  258. await UserPrimary.create({
  259. id: 9_007_199_254_740_993n, // Number.MAX_SAFE_INTEGER + 2 (cannot be represented exactly as a number in JS)
  260. name: 'Johnno',
  261. });
  262. const u2 = await UserPrimary.findByPk(9_007_199_254_740_993n);
  263. expect(u2.name).to.equal('Johnno');
  264. // Getting the value back as bigint is not supported yet: https://github.com/sequelize/sequelize/issues/14296
  265. // 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:
  266. if (dialectName === 'db2') {
  267. // ibm_db 2.7.4+ returns BIGINT values as JS numbers, which leads to a loss of precision:
  268. // https://github.com/ibmdb/node-ibm_db/issues/816
  269. // It means that u2.id comes back as 9_007_199_254_740_992 here :(
  270. // Hopefully this will be fixed soon.
  271. // For now we can do a separate query where we stringify the value to prove that it did get stored correctly:
  272. const [[{ stringifiedId }]] = await this.sequelize.query(
  273. `select "id"::varchar as "stringifiedId" from "${UserPrimary.tableName}" where "id" = 9007199254740993`,
  274. );
  275. expect(stringifiedId).to.equal('9007199254740993');
  276. } else if (dialectName === 'mariadb') {
  277. // With our current default config, the mariadb driver sends back a Long instance.
  278. // Updating the mariadb dev dep and passing "supportBigInt: true" would get it back as a bigint,
  279. // but that's potentially a big change.
  280. // For now, we'll just stringify the Long and make the comparison:
  281. expect(u2.id.toString()).to.equal('9007199254740993');
  282. } else {
  283. expect(u2.id).to.equal('9007199254740993');
  284. }
  285. });
  286. }
  287. it('always honors ZERO as primary key', async function () {
  288. const permutations = [0, '0'];
  289. let count = 0;
  290. await this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]);
  291. await Promise.all(
  292. permutations.map(async perm => {
  293. const user = await this.User.findByPk(perm, {
  294. logging(s) {
  295. expect(s).to.include(0);
  296. count++;
  297. },
  298. });
  299. expect(user).to.be.null;
  300. }),
  301. );
  302. expect(count).to.equal(permutations.length);
  303. });
  304. it('should allow us to find IDs using capital letters', async function () {
  305. const User = this.sequelize.define(`User${Support.rand()}`, {
  306. ID: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  307. Login: { type: DataTypes.STRING },
  308. });
  309. await User.sync({ force: true });
  310. await User.create({ Login: 'foo' });
  311. const user = await User.findByPk(1);
  312. expect(user).to.exist;
  313. expect(user.ID).to.equal(1);
  314. });
  315. if (dialect.supports.dataTypes.CITEXT) {
  316. it('should allow case-insensitive find on CITEXT type', async function () {
  317. const User = this.sequelize.define('UserWithCaseInsensitiveName', {
  318. username: DataTypes.CITEXT,
  319. });
  320. await User.sync({ force: true });
  321. await User.create({ username: 'longUserNAME' });
  322. const user = await User.findOne({ where: { username: 'LONGusername' } });
  323. expect(user).to.exist;
  324. expect(user.username).to.equal('longUserNAME');
  325. });
  326. }
  327. if (dialectName === 'postgres') {
  328. it('should allow case-sensitive find on TSVECTOR type', async function () {
  329. const User = this.sequelize.define('UserWithCaseInsensitiveName', {
  330. username: DataTypes.TSVECTOR,
  331. });
  332. await User.sync({ force: true });
  333. await User.create({ username: 'longUserNAME' });
  334. const user = await User.findOne({
  335. where: { username: 'longUserNAME' },
  336. });
  337. expect(user).to.exist;
  338. expect(user.username).to.equal("'longUserNAME'");
  339. });
  340. }
  341. it('should not fail if model is paranoid and where is an empty array', async function () {
  342. const User = this.sequelize.define(
  343. 'User',
  344. { username: DataTypes.STRING },
  345. { paranoid: true },
  346. );
  347. await User.sync({ force: true });
  348. await User.create({ username: 'A fancy name' });
  349. expect((await User.findOne({ where: [] })).username).to.equal('A fancy name');
  350. });
  351. it('should work if model is paranoid and only operator in where clause is a Symbol (#8406)', async function () {
  352. const User = this.sequelize.define(
  353. 'User',
  354. { username: DataTypes.STRING },
  355. { paranoid: true },
  356. );
  357. await User.sync({ force: true });
  358. await User.create({ username: 'foo' });
  359. expect(
  360. await User.findOne({
  361. where: {
  362. [Op.or]: [{ username: 'bar' }, { username: 'baz' }],
  363. },
  364. }),
  365. ).to.not.be.ok;
  366. });
  367. });
  368. describe('eager loading', () => {
  369. beforeEach(function () {
  370. this.Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  371. this.Worker = this.sequelize.define('Worker', { name: DataTypes.STRING });
  372. this.init = async function (callback) {
  373. await this.sequelize.sync({ force: true });
  374. const worker = await this.Worker.create({ name: 'worker' });
  375. const task = await this.Task.create({ title: 'homework' });
  376. this.worker = worker;
  377. this.task = task;
  378. return callback();
  379. };
  380. });
  381. describe('belongsTo', () => {
  382. describe('generic', () => {
  383. it('throws an error about unexpected input if include contains a non-object', async function () {
  384. try {
  385. await this.Worker.findOne({ include: [1] });
  386. } catch (error) {
  387. expect(error.message).to
  388. .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.
  389. Got { association: 1 } instead`);
  390. }
  391. });
  392. it('throws an error if included DaoFactory is not associated', async function () {
  393. try {
  394. await this.Worker.findOne({ include: [this.Task] });
  395. } catch (error) {
  396. expect(error.message).to.equal(
  397. 'Invalid Include received: no associations exist between "Worker" and "Task"',
  398. );
  399. }
  400. });
  401. it('returns the associated worker via task.worker', async function () {
  402. this.Task.belongsTo(this.Worker);
  403. await this.init(async () => {
  404. await this.task.setWorker(this.worker);
  405. const task = await this.Task.findOne({
  406. where: { title: 'homework' },
  407. include: [this.Worker],
  408. });
  409. expect(task).to.exist;
  410. expect(task.worker).to.exist;
  411. expect(task.worker.name).to.equal('worker');
  412. });
  413. });
  414. });
  415. it('returns the private and public ip', async function () {
  416. const ctx = Object.create(this);
  417. ctx.Domain = ctx.sequelize.define('Domain', { ip: DataTypes.STRING });
  418. ctx.Environment = ctx.sequelize.define('Environment', { name: DataTypes.STRING });
  419. ctx.Environment.belongsTo(ctx.Domain, {
  420. as: 'PrivateDomain',
  421. foreignKey: 'privateDomainId',
  422. });
  423. ctx.Environment.belongsTo(ctx.Domain, {
  424. as: 'PublicDomain',
  425. foreignKey: 'publicDomainId',
  426. });
  427. await ctx.Domain.sync({ force: true });
  428. await ctx.Environment.sync({ force: true });
  429. const privateIp = await ctx.Domain.create({ ip: '192.168.0.1' });
  430. const publicIp = await ctx.Domain.create({ ip: '91.65.189.19' });
  431. const env = await ctx.Environment.create({ name: 'environment' });
  432. await env.setPrivateDomain(privateIp);
  433. await env.setPublicDomain(publicIp);
  434. const environment = await ctx.Environment.findOne({
  435. where: { name: 'environment' },
  436. include: [
  437. { model: ctx.Domain, as: 'PrivateDomain' },
  438. { model: ctx.Domain, as: 'PublicDomain' },
  439. ],
  440. });
  441. expect(environment).to.exist;
  442. expect(environment.PrivateDomain).to.exist;
  443. expect(environment.PrivateDomain.ip).to.equal('192.168.0.1');
  444. expect(environment.PublicDomain).to.exist;
  445. expect(environment.PublicDomain.ip).to.equal('91.65.189.19');
  446. });
  447. it('eager loads with non-id primary keys', async function () {
  448. this.User = this.sequelize.define('UserPKeagerbelong', {
  449. username: {
  450. type: DataTypes.STRING,
  451. primaryKey: true,
  452. },
  453. });
  454. this.Group = this.sequelize.define('GroupPKeagerbelong', {
  455. name: {
  456. type: DataTypes.STRING,
  457. primaryKey: true,
  458. },
  459. });
  460. this.User.belongsTo(this.Group);
  461. await this.sequelize.sync({ force: true });
  462. await this.Group.create({ name: 'people' });
  463. await this.User.create({ username: 'someone', groupPKeagerbelongName: 'people' });
  464. const someUser = await this.User.findOne({
  465. where: {
  466. username: 'someone',
  467. },
  468. include: [this.Group],
  469. });
  470. expect(someUser).to.exist;
  471. expect(someUser.username).to.equal('someone');
  472. expect(someUser.groupPKeagerbelong.name).to.equal('people');
  473. });
  474. it('getting parent data in many to one relationship', async function () {
  475. const User = this.sequelize.define('User', {
  476. id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
  477. username: { type: DataTypes.STRING },
  478. });
  479. const Message = this.sequelize.define('Message', {
  480. id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
  481. user_id: { type: DataTypes.INTEGER },
  482. message: { type: DataTypes.STRING },
  483. });
  484. User.hasMany(Message, { foreignKey: 'user_id' });
  485. await this.sequelize.sync({ force: true });
  486. const user = await User.create({ username: 'test_testerson' });
  487. await Message.create({ user_id: user.id, message: 'hi there!' });
  488. await Message.create({ user_id: user.id, message: 'a second message' });
  489. const messages = await Message.findAll({
  490. where: { user_id: user.id },
  491. attributes: ['user_id', 'message'],
  492. include: [{ model: User, attributes: ['username'] }],
  493. });
  494. expect(messages.length).to.equal(2);
  495. expect(messages[0].message).to.equal('hi there!');
  496. expect(messages[0].user.username).to.equal('test_testerson');
  497. expect(messages[1].message).to.equal('a second message');
  498. expect(messages[1].user.username).to.equal('test_testerson');
  499. });
  500. it('allows mulitple assocations of the same model with different alias', async function () {
  501. this.Worker.belongsTo(this.Task, { as: 'ToDo' });
  502. this.Worker.belongsTo(this.Task, { as: 'DoTo' });
  503. await this.init(() => {
  504. return this.Worker.findOne({
  505. include: [
  506. { model: this.Task, as: 'ToDo' },
  507. { model: this.Task, as: 'DoTo' },
  508. ],
  509. });
  510. });
  511. });
  512. });
  513. describe('hasOne', () => {
  514. beforeEach(async function () {
  515. this.Worker.hasOne(this.Task);
  516. await this.init(() => {
  517. return this.worker.setTask(this.task);
  518. });
  519. });
  520. it('throws an error if included DaoFactory is not associated', async function () {
  521. try {
  522. await this.Task.findOne({ include: [this.Worker] });
  523. } catch (error) {
  524. expect(error.message).to.equal(
  525. 'Invalid Include received: no associations exist between "Task" and "Worker"',
  526. );
  527. }
  528. });
  529. it('returns the associated task via worker.task', async function () {
  530. const worker = await this.Worker.findOne({
  531. where: { name: 'worker' },
  532. include: [this.Task],
  533. });
  534. expect(worker).to.exist;
  535. expect(worker.task).to.exist;
  536. expect(worker.task.title).to.equal('homework');
  537. });
  538. it('eager loads with non-id primary keys', async function () {
  539. this.User = this.sequelize.define('UserPKeagerone', {
  540. username: {
  541. type: DataTypes.STRING,
  542. primaryKey: true,
  543. },
  544. });
  545. this.Group = this.sequelize.define('GroupPKeagerone', {
  546. name: {
  547. type: DataTypes.STRING,
  548. primaryKey: true,
  549. },
  550. });
  551. this.Group.hasOne(this.User);
  552. await this.sequelize.sync({ force: true });
  553. await this.Group.create({ name: 'people' });
  554. await this.User.create({ username: 'someone', groupPKeageroneName: 'people' });
  555. const someGroup = await this.Group.findOne({
  556. where: {
  557. name: 'people',
  558. },
  559. include: [this.User],
  560. });
  561. expect(someGroup).to.exist;
  562. expect(someGroup.name).to.equal('people');
  563. expect(someGroup.userPKeagerone.username).to.equal('someone');
  564. });
  565. });
  566. describe('hasOne with alias', () => {
  567. it('throws an error if included DaoFactory is not referenced by alias', async function () {
  568. try {
  569. await this.Worker.findOne({ include: [this.Task] });
  570. } catch (error) {
  571. expect(error.message).to.equal(
  572. 'Invalid Include received: no associations exist between "Worker" and "Task"',
  573. );
  574. }
  575. });
  576. describe('alias', () => {
  577. beforeEach(async function () {
  578. this.Worker.hasOne(this.Task, { as: 'ToDo' });
  579. await this.init(() => {
  580. return this.worker.setToDo(this.task);
  581. });
  582. });
  583. it("throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn't match", async function () {
  584. try {
  585. await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] });
  586. } catch (error) {
  587. expect(error.message).to
  588. .equal(`Association with alias "Work" does not exist on Worker.
  589. The following associations are defined on "Worker": "ToDo"`);
  590. }
  591. });
  592. it('returns the associated task via worker.task', async function () {
  593. const worker = await this.Worker.findOne({
  594. where: { name: 'worker' },
  595. include: [{ model: this.Task, as: 'ToDo' }],
  596. });
  597. expect(worker).to.exist;
  598. expect(worker.ToDo).to.exist;
  599. expect(worker.ToDo.title).to.equal('homework');
  600. });
  601. it('returns the associated task via worker.task when daoFactory is aliased with model', async function () {
  602. const worker = await this.Worker.findOne({
  603. where: { name: 'worker' },
  604. include: [{ model: this.Task, as: 'ToDo' }],
  605. });
  606. expect(worker.ToDo.title).to.equal('homework');
  607. });
  608. it('allows mulitple assocations of the same model with different alias', async function () {
  609. this.Worker.hasOne(this.Task, { as: 'DoTo' });
  610. await this.init(() => {
  611. return this.Worker.findOne({
  612. include: [
  613. { model: this.Task, as: 'ToDo' },
  614. { model: this.Task, as: 'DoTo' },
  615. ],
  616. });
  617. });
  618. });
  619. });
  620. });
  621. describe('hasMany', () => {
  622. beforeEach(async function () {
  623. this.Worker.hasMany(this.Task);
  624. await this.init(() => {
  625. return this.worker.setTasks([this.task]);
  626. });
  627. });
  628. it('throws an error if included DaoFactory is not associated', async function () {
  629. try {
  630. await this.Task.findOne({ include: [this.Worker] });
  631. } catch (error) {
  632. expect(error.message).to.equal(
  633. 'Invalid Include received: no associations exist between "Task" and "Worker"',
  634. );
  635. }
  636. });
  637. it('returns the associated tasks via worker.tasks', async function () {
  638. const worker = await this.Worker.findOne({
  639. where: { name: 'worker' },
  640. include: [this.Task],
  641. });
  642. expect(worker).to.exist;
  643. expect(worker.tasks).to.exist;
  644. expect(worker.tasks[0].title).to.equal('homework');
  645. });
  646. it('including two has many relations should not result in duplicate values', async function () {
  647. this.Contact = this.sequelize.define('Contact', { name: DataTypes.STRING });
  648. this.Photo = this.sequelize.define('Photo', { img: DataTypes.TEXT });
  649. this.PhoneNumber = this.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT });
  650. this.Contact.hasMany(this.Photo, { as: 'Photos' });
  651. this.Contact.hasMany(this.PhoneNumber);
  652. await this.sequelize.sync({ force: true });
  653. const someContact = await this.Contact.create({ name: 'Boris' });
  654. const somePhoto = await this.Photo.create({ img: 'img.jpg' });
  655. const somePhone1 = await this.PhoneNumber.create({ phone: '000000' });
  656. const somePhone2 = await this.PhoneNumber.create({ phone: '111111' });
  657. await someContact.setPhotos([somePhoto]);
  658. await someContact.setPhoneNumbers([somePhone1, somePhone2]);
  659. const fetchedContact = await this.Contact.findOne({
  660. where: {
  661. name: 'Boris',
  662. },
  663. include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }],
  664. });
  665. expect(fetchedContact).to.exist;
  666. expect(fetchedContact.Photos.length).to.equal(1);
  667. expect(fetchedContact.phoneNumbers.length).to.equal(2);
  668. });
  669. it('eager loads with non-id primary keys', async function () {
  670. this.User = this.sequelize.define('UserPKeagerone', {
  671. username: {
  672. type: DataTypes.STRING,
  673. primaryKey: true,
  674. },
  675. });
  676. this.Group = this.sequelize.define('GroupPKeagerone', {
  677. name: {
  678. type: DataTypes.STRING,
  679. primaryKey: true,
  680. },
  681. });
  682. this.Group.belongsToMany(this.User, { through: 'group_user' });
  683. this.User.belongsToMany(this.Group, { through: 'group_user' });
  684. await this.sequelize.sync({ force: true });
  685. const someUser = await this.User.create({ username: 'someone' });
  686. const someGroup = await this.Group.create({ name: 'people' });
  687. await someUser.setGroupPKeagerones([someGroup]);
  688. const someUser0 = await this.User.findOne({
  689. where: {
  690. username: 'someone',
  691. },
  692. include: [this.Group],
  693. });
  694. expect(someUser0).to.exist;
  695. expect(someUser0.username).to.equal('someone');
  696. expect(someUser0.groupPKeagerones[0].name).to.equal('people');
  697. });
  698. });
  699. describe('hasMany with alias', () => {
  700. it('throws an error if included DaoFactory is not referenced by alias', async function () {
  701. try {
  702. await this.Worker.findOne({ include: [this.Task] });
  703. } catch (error) {
  704. expect(error.message).to.equal(
  705. 'Invalid Include received: no associations exist between "Worker" and "Task"',
  706. );
  707. }
  708. });
  709. describe('alias', () => {
  710. beforeEach(async function () {
  711. this.Worker.hasMany(this.Task, { as: 'ToDos' });
  712. await this.init(() => {
  713. return this.worker.setToDos([this.task]);
  714. });
  715. });
  716. it("throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn't match", async function () {
  717. try {
  718. await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] });
  719. } catch (error) {
  720. expect(error.message).to
  721. .equal(`Association with alias "Work" does not exist on Worker.
  722. The following associations are defined on "Worker": "ToDos"`);
  723. }
  724. });
  725. it('returns the associated task via worker.task', async function () {
  726. const worker = await this.Worker.findOne({
  727. where: { name: 'worker' },
  728. include: [{ model: this.Task, as: 'ToDos' }],
  729. });
  730. expect(worker).to.exist;
  731. expect(worker.ToDos).to.exist;
  732. expect(worker.ToDos[0].title).to.equal('homework');
  733. });
  734. it('returns the associated task via worker.task when daoFactory is aliased with model', async function () {
  735. const worker = await this.Worker.findOne({
  736. where: { name: 'worker' },
  737. include: [{ model: this.Task, as: 'ToDos' }],
  738. });
  739. expect(worker.ToDos[0].title).to.equal('homework');
  740. });
  741. it('allows mulitple assocations of the same model with different alias', async function () {
  742. this.Worker.hasMany(this.Task, { as: 'DoTos' });
  743. await this.init(() => {
  744. return this.Worker.findOne({
  745. include: [
  746. { model: this.Task, as: 'ToDos' },
  747. { model: this.Task, as: 'DoTos' },
  748. ],
  749. });
  750. });
  751. });
  752. });
  753. });
  754. describe('hasMany (N:M) with alias', () => {
  755. beforeEach(function () {
  756. this.Product = this.sequelize.define('Product', { title: DataTypes.STRING });
  757. this.Tag = this.sequelize.define('Tag', { name: DataTypes.STRING });
  758. });
  759. it('returns the associated models when using through as string and alias', async function () {
  760. this.Product.belongsToMany(this.Tag, { as: 'tags', through: 'product_tag' });
  761. this.Tag.belongsToMany(this.Product, { as: 'products', through: 'product_tag' });
  762. await this.sequelize.sync();
  763. await Promise.all([
  764. this.Product.bulkCreate([
  765. { title: 'Chair' },
  766. { title: 'Desk' },
  767. { title: 'Handbag' },
  768. { title: 'Dress' },
  769. { title: 'Jan' },
  770. ]),
  771. this.Tag.bulkCreate([{ name: 'Furniture' }, { name: 'Clothing' }, { name: 'People' }]),
  772. ]);
  773. const [products, tags] = await Promise.all([this.Product.findAll(), this.Tag.findAll()]);
  774. this.products = products;
  775. this.tags = tags;
  776. await Promise.all([
  777. products[0].setTags([tags[0], tags[1]]),
  778. products[1].addTag(tags[0]),
  779. products[2].addTag(tags[1]),
  780. products[3].setTags([tags[1]]),
  781. products[4].setTags([tags[2]]),
  782. ]);
  783. await Promise.all([
  784. (async () => {
  785. const tag = await this.Tag.findOne({
  786. where: {
  787. id: tags[0].id,
  788. },
  789. include: [{ model: this.Product, as: 'products' }],
  790. });
  791. expect(tag).to.exist;
  792. expect(tag.products.length).to.equal(2);
  793. })(),
  794. tags[1].getProducts().then(products => {
  795. expect(products.length).to.equal(3);
  796. }),
  797. (async () => {
  798. const product = await this.Product.findOne({
  799. where: {
  800. id: products[0].id,
  801. },
  802. include: [{ model: this.Tag, as: 'tags' }],
  803. });
  804. expect(product).to.exist;
  805. expect(product.tags.length).to.equal(2);
  806. })(),
  807. products[1].getTags().then(tags => {
  808. expect(tags.length).to.equal(1);
  809. }),
  810. ]);
  811. });
  812. it('returns the associated models when using through as model and alias', async function () {
  813. // Exactly the same code as the previous test, just with a through model instance, and promisified
  814. const ProductTag = this.sequelize.define('product_tag');
  815. this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag });
  816. this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag });
  817. await this.sequelize.sync();
  818. await Promise.all([
  819. this.Product.bulkCreate([
  820. { title: 'Chair' },
  821. { title: 'Desk' },
  822. { title: 'Handbag' },
  823. { title: 'Dress' },
  824. { title: 'Jan' },
  825. ]),
  826. this.Tag.bulkCreate([{ name: 'Furniture' }, { name: 'Clothing' }, { name: 'People' }]),
  827. ]);
  828. const [products, tags] = await Promise.all([this.Product.findAll(), this.Tag.findAll()]);
  829. this.products = products;
  830. this.tags = tags;
  831. await Promise.all([
  832. products[0].setTags([tags[0], tags[1]]),
  833. products[1].addTag(tags[0]),
  834. products[2].addTag(tags[1]),
  835. products[3].setTags([tags[1]]),
  836. products[4].setTags([tags[2]]),
  837. ]);
  838. await Promise.all([
  839. expect(
  840. this.Tag.findOne({
  841. where: {
  842. id: this.tags[0].id,
  843. },
  844. include: [{ model: this.Product, as: 'products' }],
  845. }),
  846. )
  847. .to.eventually.have.property('products')
  848. .to.have.length(2),
  849. expect(
  850. this.Product.findOne({
  851. where: {
  852. id: this.products[0].id,
  853. },
  854. include: [{ model: this.Tag, as: 'tags' }],
  855. }),
  856. )
  857. .to.eventually.have.property('tags')
  858. .to.have.length(2),
  859. expect(this.tags[1].getProducts()).to.eventually.have.length(3),
  860. expect(this.products[1].getTags()).to.eventually.have.length(1),
  861. ]);
  862. });
  863. });
  864. });
  865. describe('queryOptions', () => {
  866. beforeEach(async function () {
  867. const user = await this.User.create({ username: 'barfooz' });
  868. this.user = user;
  869. });
  870. it('should return a DAO when queryOptions are not set', async function () {
  871. const user = await this.User.findOne({ where: { username: 'barfooz' } });
  872. expect(user).to.be.instanceOf(this.User);
  873. });
  874. it('should return a DAO when raw is false', async function () {
  875. const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: false });
  876. expect(user).to.be.instanceOf(this.User);
  877. });
  878. it('should return raw data when raw is true', async function () {
  879. const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: true });
  880. expect(user).to.not.be.instanceOf(this.User);
  881. expect(user).to.be.instanceOf(Object);
  882. });
  883. });
  884. it('should support logging', async function () {
  885. const spy = sinon.spy();
  886. await this.User.findOne({
  887. where: {},
  888. logging: spy,
  889. });
  890. expect(spy.called).to.be.ok;
  891. });
  892. describe('rejectOnEmpty mode', () => {
  893. it('throws error when record not found by findOne', async function () {
  894. await expect(
  895. this.User.findOne({
  896. where: {
  897. username: 'ath-kantam-pradakshnami',
  898. },
  899. rejectOnEmpty: true,
  900. }),
  901. ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
  902. });
  903. it('throws error when record not found by findByPk', async function () {
  904. await expect(
  905. this.User.findByPk(2, {
  906. rejectOnEmpty: true,
  907. }),
  908. ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
  909. });
  910. it('throws error when record not found by find', async function () {
  911. await expect(
  912. this.User.findOne({
  913. where: {
  914. username: 'some-username-that-is-not-used-anywhere',
  915. },
  916. rejectOnEmpty: true,
  917. }),
  918. ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
  919. });
  920. it('works from model options', async () => {
  921. const Model = current.define(
  922. 'Test',
  923. {
  924. username: DataTypes.STRING(100),
  925. },
  926. {
  927. rejectOnEmpty: true,
  928. },
  929. );
  930. await Model.sync({ force: true });
  931. await expect(
  932. Model.findOne({
  933. where: {
  934. username: 'some-username-that-is-not-used-anywhere',
  935. },
  936. }),
  937. ).to.eventually.be.rejectedWith(Sequelize.EmptyResultError);
  938. });
  939. it('override model options', async () => {
  940. const Model = current.define(
  941. 'Test',
  942. {
  943. username: DataTypes.STRING(100),
  944. },
  945. {
  946. rejectOnEmpty: true,
  947. },
  948. );
  949. await Model.sync({ force: true });
  950. await expect(
  951. Model.findOne({
  952. rejectOnEmpty: false,
  953. where: {
  954. username: 'some-username-that-is-not-used-anywhere',
  955. },
  956. }),
  957. ).to.eventually.be.deep.equal(null);
  958. });
  959. it('resolve null when disabled', async () => {
  960. const Model = current.define('Test', {
  961. username: DataTypes.STRING(100),
  962. });
  963. await Model.sync({ force: true });
  964. await expect(
  965. Model.findOne({
  966. where: {
  967. username: 'some-username-that-is-not-used-anywhere-for-sure-this-time',
  968. },
  969. }),
  970. ).to.eventually.be.equal(null);
  971. });
  972. });
  973. });
  974. });