has-one.test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const Support = require('../support');
  5. const { DataTypes, Sequelize } = require('@sequelize/core');
  6. const current = Support.sequelize;
  7. const dialect = Support.getTestDialect();
  8. describe(Support.getTestDialectTeaser('HasOne'), () => {
  9. describe('get', () => {
  10. describe('multiple', () => {
  11. it('should fetch associations for multiple instances', async function () {
  12. const User = this.sequelize.define('User', {});
  13. const Player = this.sequelize.define('Player', {});
  14. Player.User = Player.hasOne(User, { as: 'user' });
  15. await this.sequelize.sync({ force: true });
  16. const players = await Promise.all([
  17. Player.create(
  18. {
  19. id: 1,
  20. user: {},
  21. },
  22. {
  23. include: [Player.User],
  24. },
  25. ),
  26. Player.create(
  27. {
  28. id: 2,
  29. user: {},
  30. },
  31. {
  32. include: [Player.User],
  33. },
  34. ),
  35. Player.create({
  36. id: 3,
  37. }),
  38. ]);
  39. const result = await Player.User.get(players);
  40. expect(result.get(players[0].id).id).to.equal(players[0].user.id);
  41. expect(result.get(players[1].id).id).to.equal(players[1].user.id);
  42. expect(result.get(players[2].id)).to.equal(undefined);
  43. });
  44. });
  45. });
  46. describe('getAssociation', () => {
  47. if (current.dialect.supports.transactions) {
  48. it('supports transactions', async function () {
  49. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  50. this.sequelize,
  51. );
  52. const User = sequelize.define('User', { username: DataTypes.STRING });
  53. const Group = sequelize.define('Group', { name: DataTypes.STRING });
  54. Group.hasOne(User);
  55. await sequelize.sync({ force: true });
  56. const fakeUser = await User.create({ username: 'foo' });
  57. const user = await User.create({ username: 'foo' });
  58. const group = await Group.create({ name: 'bar' });
  59. const t = await sequelize.startUnmanagedTransaction();
  60. await group.setUser(user, { transaction: t });
  61. const groups = await Group.findAll();
  62. const associatedUser = await groups[0].getUser();
  63. expect(associatedUser).to.be.null;
  64. const groups0 = await Group.findAll({ transaction: t });
  65. const associatedUser0 = await groups0[0].getUser({ transaction: t });
  66. expect(associatedUser0).not.to.be.null;
  67. expect(associatedUser0.id).to.equal(user.id);
  68. expect(associatedUser0.id).not.to.equal(fakeUser.id);
  69. await t.rollback();
  70. });
  71. }
  72. it("should be able to handle a where object that's a first class citizen.", async function () {
  73. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  74. const Task = this.sequelize.define('TaskXYZ', {
  75. title: DataTypes.STRING,
  76. status: DataTypes.STRING,
  77. });
  78. User.hasOne(Task);
  79. await User.sync({ force: true });
  80. await Task.sync({ force: true });
  81. const user = await User.create({ username: 'foo' });
  82. const task = await Task.create({ title: 'task', status: 'inactive' });
  83. await user.setTaskXYZ(task);
  84. const task0 = await user.getTaskXYZ({ where: { status: 'active' } });
  85. expect(task0).to.be.null;
  86. });
  87. if (current.dialect.supports.schemas) {
  88. it('supports schemas', async function () {
  89. const User = this.sequelize
  90. .define('User', { username: DataTypes.STRING })
  91. .withSchema('admin');
  92. const Group = this.sequelize
  93. .define('Group', { name: DataTypes.STRING })
  94. .withSchema('admin');
  95. Group.hasOne(User);
  96. await this.sequelize.createSchema('admin');
  97. await Group.sync({ force: true });
  98. await User.sync({ force: true });
  99. const [fakeUser, user, group] = await Promise.all([
  100. User.create({ username: 'foo' }),
  101. User.create({ username: 'foo' }),
  102. Group.create({ name: 'bar' }),
  103. ]);
  104. await group.setUser(user);
  105. const groups = await Group.findAll();
  106. const associatedUser = await groups[0].getUser();
  107. expect(associatedUser).not.to.be.null;
  108. expect(associatedUser.id).to.equal(user.id);
  109. expect(associatedUser.id).not.to.equal(fakeUser.id);
  110. await this.sequelize.queryInterface.dropAllTables({ schema: 'admin' });
  111. await this.sequelize.dropSchema('admin');
  112. const schemas = await this.sequelize.queryInterface.listSchemas();
  113. expect(schemas).to.not.include('admin');
  114. });
  115. }
  116. });
  117. describe('createAssociation', () => {
  118. it('creates an associated model instance', async function () {
  119. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  120. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  121. User.hasOne(Task);
  122. await this.sequelize.sync({ force: true });
  123. const user = await User.create({ username: 'bob' });
  124. await user.createTask({ title: 'task' });
  125. const task = await user.getTask();
  126. expect(task).not.to.be.null;
  127. expect(task.title).to.equal('task');
  128. });
  129. if (current.dialect.supports.transactions) {
  130. it('supports transactions', async function () {
  131. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  132. this.sequelize,
  133. );
  134. const User = sequelize.define('User', { username: DataTypes.STRING });
  135. const Group = sequelize.define('Group', { name: DataTypes.STRING });
  136. User.hasOne(Group);
  137. await sequelize.sync({ force: true });
  138. const user = await User.create({ username: 'bob' });
  139. const t = await sequelize.startUnmanagedTransaction();
  140. await user.createGroup({ name: 'testgroup' }, { transaction: t });
  141. const users = await User.findAll();
  142. const group = await users[0].getGroup();
  143. expect(group).to.be.null;
  144. const users0 = await User.findAll({ transaction: t });
  145. const group0 = await users0[0].getGroup({ transaction: t });
  146. expect(group0).to.be.not.null;
  147. await t.rollback();
  148. });
  149. }
  150. });
  151. describe('foreign key', () => {
  152. it('throws a ForeignKeyConstraintError if the associated record does not exist', async function () {
  153. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  154. const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
  155. User.hasOne(Task);
  156. await User.sync({ force: true });
  157. await Task.sync({ force: true });
  158. await expect(Task.create({ title: 'task', userXYZId: 5 })).to.be.rejectedWith(
  159. Sequelize.ForeignKeyConstraintError,
  160. );
  161. const task = await Task.create({ title: 'task' });
  162. await expect(
  163. Task.update({ title: 'taskUpdate', userXYZId: 5 }, { where: { id: task.id } }),
  164. ).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
  165. });
  166. it('should setup underscored field with foreign keys when using underscored', function () {
  167. const User = this.sequelize.define(
  168. 'User',
  169. { username: DataTypes.STRING },
  170. { underscored: true },
  171. );
  172. const Account = this.sequelize.define(
  173. 'Account',
  174. { name: DataTypes.STRING },
  175. { underscored: true },
  176. );
  177. Account.hasOne(User);
  178. expect(User.getAttributes().accountId).to.exist;
  179. expect(User.getAttributes().accountId.field).to.equal('account_id');
  180. });
  181. it('should use model name when using camelcase', function () {
  182. const User = this.sequelize.define(
  183. 'User',
  184. { username: DataTypes.STRING },
  185. { underscored: false },
  186. );
  187. const Account = this.sequelize.define(
  188. 'Account',
  189. { name: DataTypes.STRING },
  190. { underscored: false },
  191. );
  192. Account.hasOne(User);
  193. expect(User.getAttributes().accountId).to.exist;
  194. expect(User.getAttributes().accountId.field).to.equal('accountId');
  195. });
  196. it('should support specifying the field of a foreign key', async function () {
  197. const User = this.sequelize.define('UserXYZ', {
  198. username: DataTypes.STRING,
  199. gender: DataTypes.STRING,
  200. });
  201. const Task = this.sequelize.define('TaskXYZ', {
  202. title: DataTypes.STRING,
  203. status: DataTypes.STRING,
  204. });
  205. Task.hasOne(User, {
  206. foreignKey: {
  207. name: 'taskId',
  208. field: 'task_id',
  209. },
  210. });
  211. expect(User.getAttributes().taskId).to.exist;
  212. expect(User.getAttributes().taskId.field).to.equal('task_id');
  213. await Task.sync({ force: true });
  214. await User.sync({ force: true });
  215. const [user0, task0] = await Promise.all([
  216. User.create({ username: 'foo', gender: 'male' }),
  217. Task.create({ title: 'task', status: 'inactive' }),
  218. ]);
  219. await task0.setUserXYZ(user0);
  220. const user = await task0.getUserXYZ();
  221. // the sql query should correctly look at task_id instead of taskId
  222. expect(user).to.not.be.null;
  223. const task = await Task.findOne({
  224. where: { title: 'task' },
  225. include: [User],
  226. });
  227. expect(task.userXYZ).to.exist;
  228. });
  229. it('should support custom primary key field name in sub queries', async function () {
  230. const User = this.sequelize.define('UserXYZ', {
  231. username: DataTypes.STRING,
  232. gender: DataTypes.STRING,
  233. });
  234. const Task = this.sequelize.define('TaskXYZ', {
  235. id: {
  236. field: 'Id',
  237. type: DataTypes.INTEGER,
  238. autoIncrement: true,
  239. primaryKey: true,
  240. },
  241. title: DataTypes.STRING,
  242. status: DataTypes.STRING,
  243. });
  244. Task.hasOne(User);
  245. await Task.sync({ force: true });
  246. await User.sync({ force: true });
  247. const task0 = await Task.create(
  248. { title: 'task', status: 'inactive', User: { username: 'foo', gender: 'male' } },
  249. { include: User },
  250. );
  251. await expect(task0.reload({ subQuery: true })).to.not.eventually.be.rejected;
  252. });
  253. });
  254. describe('foreign key constraints', () => {
  255. it('are enabled by default', async function () {
  256. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  257. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  258. User.hasOne(Task); // defaults to set NULL
  259. await User.sync({ force: true });
  260. await Task.sync({ force: true });
  261. const user = await User.create({ username: 'foo' });
  262. const task = await Task.create({ title: 'task' });
  263. await user.setTask(task);
  264. await user.destroy();
  265. await task.reload();
  266. expect(task.userId).to.equal(null);
  267. });
  268. it('should be possible to disable them', async function () {
  269. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  270. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  271. User.hasOne(Task, { foreignKeyConstraints: false });
  272. await User.sync({ force: true });
  273. await Task.sync({ force: true });
  274. const user = await User.create({ username: 'foo' });
  275. const task = await Task.create({ title: 'task' });
  276. await user.setTask(task);
  277. await user.destroy();
  278. await task.reload();
  279. expect(task.userId).to.equal(user.id);
  280. });
  281. it('can cascade deletes', async function () {
  282. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  283. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  284. User.hasOne(Task, { foreignKey: { onDelete: 'cascade' } });
  285. await User.sync({ force: true });
  286. await Task.sync({ force: true });
  287. const user = await User.create({ username: 'foo' });
  288. const task = await Task.create({ title: 'task' });
  289. await user.setTask(task);
  290. await user.destroy();
  291. const tasks = await Task.findAll();
  292. expect(tasks).to.have.length(0);
  293. });
  294. it('works when cascading a delete with hooks but there is no associate (i.e. "has zero")', async function () {
  295. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  296. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  297. User.hasOne(Task, { foreignKey: { onDelete: 'cascade' }, hooks: true });
  298. await User.sync({ force: true });
  299. await Task.sync({ force: true });
  300. const user = await User.create({ username: 'foo' });
  301. await user.destroy();
  302. });
  303. // NOTE: mssql does not support changing an autoincrement primary key
  304. if (!['mssql', 'db2', 'ibmi'].includes(dialect)) {
  305. it('can cascade updates', async function () {
  306. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  307. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  308. User.hasOne(Task, { foreignKey: { onUpdate: 'cascade' } });
  309. await User.sync({ force: true });
  310. await Task.sync({ force: true });
  311. const user = await User.create({ username: 'foo' });
  312. const task = await Task.create({ title: 'task' });
  313. await user.setTask(task);
  314. // Changing the id of a DAO requires a little dance since
  315. // the `UPDATE` query generated by `save()` uses `id` in the
  316. // `WHERE` clause
  317. const tableName = User.table;
  318. await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
  319. const tasks = await Task.findAll();
  320. expect(tasks).to.have.length(1);
  321. expect(tasks[0].userId).to.equal(999);
  322. });
  323. }
  324. if (current.dialect.supports.constraints.restrict) {
  325. it('can restrict deletes', async function () {
  326. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  327. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  328. User.hasOne(Task, { foreignKey: { onDelete: 'restrict' } });
  329. await User.sync({ force: true });
  330. await Task.sync({ force: true });
  331. const user = await User.create({ username: 'foo' });
  332. const task = await Task.create({ title: 'task' });
  333. await user.setTask(task);
  334. await expect(user.destroy()).to.eventually.be.rejectedWith(
  335. Sequelize.ForeignKeyConstraintError,
  336. );
  337. const tasks = await Task.findAll();
  338. expect(tasks).to.have.length(1);
  339. });
  340. it('can restrict updates', async function () {
  341. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  342. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  343. User.hasOne(Task, { foreignKey: { onUpdate: 'restrict' } });
  344. await User.sync({ force: true });
  345. await Task.sync({ force: true });
  346. const user = await User.create({ username: 'foo' });
  347. const task = await Task.create({ title: 'task' });
  348. await user.setTask(task);
  349. // Changing the id of a DAO requires a little dance since
  350. // the `UPDATE` query generated by `save()` uses `id` in the
  351. // `WHERE` clause
  352. const tableName = User.table;
  353. await expect(
  354. user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id }),
  355. ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
  356. // Should fail due to FK restriction
  357. const tasks = await Task.findAll();
  358. expect(tasks).to.have.length(1);
  359. });
  360. }
  361. });
  362. describe('association column', () => {
  363. it('has correct type for non-id primary keys with non-integer type', async function () {
  364. const User = this.sequelize.define('UserPKBT', {
  365. username: {
  366. type: DataTypes.STRING,
  367. },
  368. });
  369. const Group = this.sequelize.define('GroupPKBT', {
  370. name: {
  371. type: DataTypes.STRING,
  372. primaryKey: true,
  373. },
  374. });
  375. Group.hasOne(User);
  376. await this.sequelize.sync({ force: true });
  377. expect(User.getAttributes().groupPKBTName.type).to.an.instanceof(DataTypes.STRING);
  378. });
  379. it('should support a non-primary key as the association column on a target with custom primary key', async function () {
  380. const User = this.sequelize.define('User', {
  381. user_name: {
  382. unique: true,
  383. type: DataTypes.STRING,
  384. },
  385. });
  386. const Task = this.sequelize.define('Task', {
  387. title: DataTypes.STRING,
  388. username: DataTypes.STRING,
  389. });
  390. User.hasOne(Task, { foreignKey: 'username', sourceKey: 'user_name' });
  391. await this.sequelize.sync({ force: true });
  392. const newUser = await User.create({ user_name: 'bob' });
  393. const newTask = await Task.create({ title: 'some task' });
  394. await newUser.setTask(newTask);
  395. const foundUser = await User.findOne({ where: { user_name: 'bob' } });
  396. const foundTask = await foundUser.getTask();
  397. expect(foundTask.title).to.equal('some task');
  398. });
  399. it('should support a non-primary unique key as the association column', async function () {
  400. const User = this.sequelize.define('User', {
  401. username: {
  402. type: DataTypes.STRING,
  403. unique: true,
  404. },
  405. });
  406. const Task = this.sequelize.define('Task', {
  407. title: DataTypes.STRING,
  408. username: DataTypes.STRING,
  409. });
  410. User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' });
  411. await this.sequelize.sync({ force: true });
  412. const newUser = await User.create({ username: 'bob' });
  413. const newTask = await Task.create({ title: 'some task' });
  414. await newUser.setTask(newTask);
  415. const foundUser = await User.findOne({ where: { username: 'bob' } });
  416. const foundTask = await foundUser.getTask();
  417. expect(foundTask.title).to.equal('some task');
  418. });
  419. it('should support a non-primary unique key as the association column with a field option', async function () {
  420. const User = this.sequelize.define('User', {
  421. username: {
  422. type: DataTypes.STRING,
  423. unique: true,
  424. field: 'the_user_name_field',
  425. },
  426. });
  427. const Task = this.sequelize.define('Task', {
  428. title: DataTypes.STRING,
  429. username: DataTypes.STRING,
  430. });
  431. User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' });
  432. await this.sequelize.sync({ force: true });
  433. const newUser = await User.create({ username: 'bob' });
  434. const newTask = await Task.create({ title: 'some task' });
  435. await newUser.setTask(newTask);
  436. const foundUser = await User.findOne({ where: { username: 'bob' } });
  437. const foundTask = await foundUser.getTask();
  438. expect(foundTask.title).to.equal('some task');
  439. });
  440. });
  441. describe('Association options', () => {
  442. it('can specify data type for autogenerated relational keys', async function () {
  443. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  444. const dataTypes = [DataTypes.INTEGER, DataTypes.STRING];
  445. const Tasks = {};
  446. if (current.dialect.supports.dataTypes.BIGINT) {
  447. dataTypes.push(DataTypes.BIGINT);
  448. }
  449. await Promise.all(
  450. dataTypes.map(async dataType => {
  451. const tableName = `TaskXYZ_${dataType.getDataTypeId()}`;
  452. Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING });
  453. User.hasOne(Tasks[dataType], {
  454. foreignKey: { name: 'userId', type: dataType },
  455. foreignKeyConstraints: false,
  456. });
  457. await Tasks[dataType].sync({ force: true });
  458. expect(Tasks[dataType].getAttributes().userId.type).to.be.an.instanceof(dataType);
  459. }),
  460. );
  461. });
  462. });
  463. describe('Counter part', () => {
  464. describe('BelongsTo', () => {
  465. it('should only generate one foreign key', function () {
  466. const Orders = this.sequelize.define('Orders', {}, { timestamps: false });
  467. const InternetOrders = this.sequelize.define('InternetOrders', {}, { timestamps: false });
  468. InternetOrders.belongsTo(Orders, {
  469. foreignKeyConstraints: true,
  470. inverse: {
  471. type: 'hasOne',
  472. },
  473. });
  474. expect(Object.keys(InternetOrders.getAttributes()).length).to.equal(2);
  475. // The model is named incorrectly.
  476. // The modal name should always be singular, so here sequelize assumes that "Orders" is singular
  477. expect(InternetOrders.getAttributes().ordersId).to.be.ok;
  478. expect(InternetOrders.getAttributes().orderId).not.to.be.ok;
  479. });
  480. });
  481. });
  482. describe('Eager loading', () => {
  483. beforeEach(function () {
  484. this.Individual = this.sequelize.define('individual', {
  485. name: DataTypes.STRING,
  486. });
  487. this.Hat = this.sequelize.define('hat', {
  488. name: DataTypes.STRING,
  489. });
  490. this.Individual.hasOne(this.Hat, {
  491. as: 'personwearinghat',
  492. });
  493. });
  494. it('should load with an alias', async function () {
  495. await this.sequelize.sync({ force: true });
  496. const [individual1, hat] = await Promise.all([
  497. this.Individual.create({ name: 'Foo Bar' }),
  498. this.Hat.create({ name: 'Baz' }),
  499. ]);
  500. await individual1.setPersonwearinghat(hat);
  501. const individual0 = await this.Individual.findOne({
  502. where: { name: 'Foo Bar' },
  503. include: [{ model: this.Hat, as: 'personwearinghat' }],
  504. });
  505. expect(individual0.name).to.equal('Foo Bar');
  506. expect(individual0.personwearinghat.name).to.equal('Baz');
  507. });
  508. it('should load all', async function () {
  509. await this.sequelize.sync({ force: true });
  510. const [individual0, hat] = await Promise.all([
  511. this.Individual.create({ name: 'Foo Bar' }),
  512. this.Hat.create({ name: 'Baz' }),
  513. ]);
  514. await individual0.setPersonwearinghat(hat);
  515. const individual = await this.Individual.findOne({
  516. where: { name: 'Foo Bar' },
  517. include: [{ all: true }],
  518. });
  519. expect(individual.name).to.equal('Foo Bar');
  520. expect(individual.personwearinghat.name).to.equal('Baz');
  521. });
  522. });
  523. });