error.test.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. import {
  2. AccessDeniedError,
  3. AggregateError,
  4. BaseError,
  5. ConnectionError,
  6. ConnectionRefusedError,
  7. ConnectionTimedOutError,
  8. DataTypes,
  9. DatabaseError,
  10. ForeignKeyConstraintError,
  11. HostNotFoundError,
  12. HostNotReachableError,
  13. InvalidConnectionError,
  14. Model,
  15. OptimisticLockError,
  16. Sequelize,
  17. UniqueConstraintError,
  18. UnknownConstraintError,
  19. ValidationError,
  20. ValidationErrorItem,
  21. ValidationErrorItemOrigin,
  22. } from '@sequelize/core';
  23. import type { DatabaseErrorParent } from '@sequelize/core/_non-semver-use-at-your-own-risk_/errors/database-error.js';
  24. import { assert, expect } from 'chai';
  25. import { spy } from 'sinon';
  26. import {
  27. allowDeprecationsInSuite,
  28. getTestDialect,
  29. getTestDialectTeaser,
  30. sequelize,
  31. } from './support';
  32. const dialect = getTestDialect();
  33. const queryInterface = sequelize.queryInterface;
  34. describe(getTestDialectTeaser('Sequelize Errors'), () => {
  35. describe('API Surface', () => {
  36. allowDeprecationsInSuite(['SEQUELIZE0007']);
  37. it('Should have the Error constructors exposed', () => {
  38. expect(Sequelize).to.have.property('BaseError');
  39. expect(Sequelize).to.have.property('ValidationError');
  40. expect(Sequelize).to.have.property('OptimisticLockError');
  41. });
  42. it('Sequelize Errors instances should be instances of Error', () => {
  43. const error = new BaseError();
  44. const errorMessage = 'error message';
  45. const validationError = new ValidationError(errorMessage, [
  46. new ValidationErrorItem('<field name> cannot be null', 'notNull violation', '<field name>'),
  47. new ValidationErrorItem(
  48. '<field name> cannot be an array or an object',
  49. 'Validation error',
  50. '<field name>',
  51. ),
  52. ]);
  53. const optimisticLockError = new OptimisticLockError();
  54. expect(error).to.be.instanceOf(BaseError);
  55. expect(error).to.be.instanceOf(Error);
  56. expect(error).to.have.property('name', 'SequelizeBaseError');
  57. expect(validationError).to.be.instanceOf(ValidationError);
  58. expect(validationError).to.be.instanceOf(Error);
  59. expect(validationError).to.have.property('name', 'SequelizeValidationError');
  60. expect(validationError.message).to.equal(errorMessage);
  61. expect(optimisticLockError).to.be.instanceOf(OptimisticLockError);
  62. expect(optimisticLockError).to.be.instanceOf(Error);
  63. expect(optimisticLockError).to.have.property('name', 'SequelizeOptimisticLockError');
  64. });
  65. it('SequelizeValidationError should find errors by path', () => {
  66. const errorItems = [
  67. new ValidationErrorItem('invalid', 'Validation error', 'first_name'),
  68. new ValidationErrorItem('invalid', 'Validation error', 'last_name'),
  69. ];
  70. const validationError = new ValidationError('Validation error', errorItems);
  71. expect(validationError).to.have.property('get');
  72. expect(validationError.get).to.be.a('function');
  73. const matches = validationError.get('first_name');
  74. expect(matches).to.be.instanceOf(Array);
  75. expect(matches).to.have.lengthOf(1);
  76. expect(matches[0]).to.have.property('message', 'invalid');
  77. });
  78. it('SequelizeValidationError should override message property when message parameter is specified', () => {
  79. const errorItems = [
  80. new ValidationErrorItem('invalid', 'Validation error', 'first_name'),
  81. new ValidationErrorItem('invalid', 'Validation error', 'last_name'),
  82. ];
  83. const customErrorMessage = 'Custom validation error message';
  84. const validationError = new ValidationError(customErrorMessage, errorItems);
  85. expect(validationError).to.have.property('name', 'SequelizeValidationError');
  86. expect(validationError.message).to.equal(customErrorMessage);
  87. });
  88. it('SequelizeValidationError should concatenate an error messages from given errors if no explicit message is defined', () => {
  89. const errorItems = [
  90. new ValidationErrorItem('<field name> cannot be null', 'notNull violation', '<field name>'),
  91. new ValidationErrorItem(
  92. '<field name> cannot be an array or an object',
  93. 'Validation error',
  94. '<field name>',
  95. ),
  96. ];
  97. const validationError = new ValidationError('', errorItems);
  98. expect(validationError).to.have.property('name', 'SequelizeValidationError');
  99. expect(validationError.message).to.match(
  100. /notNull violation: <field name> cannot be null,\nValidation error: <field name> cannot be an array or an object/,
  101. );
  102. });
  103. it('SequelizeValidationErrorItem does not require instance & validator constructor parameters', () => {
  104. const error = new ValidationErrorItem('error!', 'Validation error', 'myfield');
  105. expect(error).to.be.instanceOf(ValidationErrorItem);
  106. });
  107. it('SequelizeValidationErrorItem should have instance, key & validator properties when given to constructor', () => {
  108. class Inst extends Model {}
  109. Inst.init({}, { sequelize });
  110. const inst = Inst.build();
  111. const vargs = [4];
  112. const error = new ValidationErrorItem(
  113. 'error!',
  114. 'FUNCTION',
  115. 'foo',
  116. 'bar',
  117. inst,
  118. 'klen',
  119. 'len',
  120. vargs,
  121. );
  122. expect(error).to.have.property('instance');
  123. expect(error.instance).to.equal(inst);
  124. expect(error).to.have.property('validatorKey', 'klen');
  125. expect(error).to.have.property('validatorName', 'len');
  126. expect(error).to.have.property('validatorArgs', vargs);
  127. });
  128. it('SequelizeValidationErrorItem.getValidatorKey() should return a string', () => {
  129. const error = new ValidationErrorItem(
  130. 'error!',
  131. 'FUNCTION',
  132. 'foo',
  133. 'bar',
  134. undefined,
  135. 'klen',
  136. 'len',
  137. [4],
  138. );
  139. expect(error).to.have.property('getValidatorKey');
  140. expect(error.getValidatorKey).to.be.a('function');
  141. expect(error.getValidatorKey()).to.equal('function.klen');
  142. expect(error.getValidatorKey(false)).to.equal('klen');
  143. // @ts-expect-error -- should cast to boolean
  144. expect(error.getValidatorKey(0)).to.equal('klen');
  145. // @ts-expect-error -- should cast to boolean
  146. expect(error.getValidatorKey(1, ':')).to.equal('function:klen');
  147. expect(error.getValidatorKey(true, '-:-')).to.equal('function-:-klen');
  148. const empty = new ValidationErrorItem('error!', 'FUNCTION', 'foo', 'bar');
  149. expect(empty.getValidatorKey()).to.equal('');
  150. expect(empty.getValidatorKey(false)).to.equal('');
  151. // @ts-expect-error -- should cast to boolean
  152. expect(empty.getValidatorKey(0)).to.equal('');
  153. // @ts-expect-error -- should cast to boolean
  154. expect(empty.getValidatorKey(1, ':')).to.equal('');
  155. expect(empty.getValidatorKey(true, '-:-')).to.equal('');
  156. });
  157. it('SequelizeValidationErrorItem.getValidatorKey() should throw if namespace separator is invalid (only if NS is used & available)', () => {
  158. const error = new ValidationErrorItem(
  159. 'error!',
  160. 'FUNCTION',
  161. 'foo',
  162. 'bar',
  163. undefined,
  164. 'klen',
  165. 'len',
  166. [4],
  167. );
  168. // @ts-expect-error -- testing invalid input
  169. expect(() => error.getValidatorKey(false, {})).to.not.throw();
  170. // @ts-expect-error -- testing invalid input
  171. expect(() => error.getValidatorKey(false, [])).to.not.throw();
  172. // @ts-expect-error -- testing invalid input
  173. expect(() => error.getValidatorKey(false, null)).to.not.throw();
  174. // @ts-expect-error -- testing invalid input
  175. expect(() => error.getValidatorKey(false, '')).to.not.throw();
  176. // @ts-expect-error -- testing invalid input
  177. expect(() => error.getValidatorKey(false, false)).to.not.throw();
  178. // @ts-expect-error -- testing invalid input
  179. expect(() => error.getValidatorKey(false, true)).to.not.throw();
  180. expect(() => error.getValidatorKey(false)).to.not.throw();
  181. expect(() => error.getValidatorKey(true)).to.not.throw(); // undefined will trigger use of function parameter default
  182. // @ts-expect-error -- testing invalid input
  183. expect(() => error.getValidatorKey(true, {})).to.throw(Error);
  184. // @ts-expect-error -- testing invalid input
  185. expect(() => error.getValidatorKey(true, [])).to.throw(Error);
  186. // @ts-expect-error -- testing invalid input
  187. expect(() => error.getValidatorKey(true, null)).to.throw(Error);
  188. expect(() => error.getValidatorKey(true, '')).to.throw(Error);
  189. // @ts-expect-error -- testing invalid input
  190. expect(() => error.getValidatorKey(true, false)).to.throw(Error);
  191. // @ts-expect-error -- testing invalid input
  192. expect(() => error.getValidatorKey(true, true)).to.throw(Error);
  193. });
  194. it('SequelizeValidationErrorItem should map deprecated "type" values to new "origin" values', () => {
  195. const data: Record<string, string> = {
  196. 'notNull violation': 'CORE',
  197. 'unique violation': 'DB',
  198. 'Validation error': 'FUNCTION',
  199. };
  200. for (const k of Object.keys(data)) {
  201. // @ts-expect-error -- testing deprecated options
  202. const error = new ValidationErrorItem('error!', k, 'foo', null);
  203. expect(error).to.have.property('origin', data[k]);
  204. expect(error).to.have.property('type', k);
  205. }
  206. });
  207. it('SequelizeValidationErrorItemOrigin is valid', () => {
  208. const ORIGINS = ValidationErrorItemOrigin;
  209. expect(ORIGINS).to.have.property('CORE', 'CORE');
  210. expect(ORIGINS).to.have.property('DB', 'DB');
  211. expect(ORIGINS).to.have.property('FUNCTION', 'FUNCTION');
  212. });
  213. it('SequelizeValidationErrorItem.Origins is valid', () => {
  214. const ORIGINS = ValidationErrorItem.Origins;
  215. expect(ORIGINS).to.have.property('CORE', 'CORE');
  216. expect(ORIGINS).to.have.property('DB', 'DB');
  217. expect(ORIGINS).to.have.property('FUNCTION', 'FUNCTION');
  218. });
  219. it('SequelizeDatabaseError should keep original message', () => {
  220. const orig = new Error('original database error message') as DatabaseErrorParent;
  221. const databaseError = new DatabaseError(orig);
  222. expect(databaseError).to.have.property('parent');
  223. expect(databaseError).to.have.property('original');
  224. expect(databaseError.name).to.equal('SequelizeDatabaseError');
  225. expect(databaseError.message).to.include('original database error message');
  226. });
  227. it('SequelizeDatabaseError should keep the original sql and the parameters', () => {
  228. const orig = new Error('original database error message') as DatabaseErrorParent;
  229. // @ts-expect-error -- this option is set by the database
  230. orig.sql = 'SELECT * FROM table WHERE id = $1';
  231. // @ts-expect-error -- this option is set by the database
  232. orig.parameters = ['1'];
  233. const databaseError = new DatabaseError(orig);
  234. expect(databaseError).to.have.property('sql');
  235. expect(databaseError).to.have.property('parameters');
  236. expect(databaseError.sql).to.equal(orig.sql);
  237. expect(databaseError.parameters).to.equal(orig.parameters);
  238. });
  239. it('ConnectionError should keep original message', () => {
  240. const orig = new Error('original connection error message');
  241. const connectionError = new ConnectionError(orig);
  242. expect(connectionError).to.have.property('parent');
  243. expect(connectionError).to.have.property('original');
  244. expect(connectionError.name).to.equal('SequelizeConnectionError');
  245. expect(connectionError.message).to.include('original connection error message');
  246. });
  247. it('ConnectionRefusedError should keep original message', () => {
  248. const orig = new Error('original connection error message');
  249. const connectionError = new ConnectionRefusedError(orig);
  250. expect(connectionError).to.have.property('parent');
  251. expect(connectionError).to.have.property('original');
  252. expect(connectionError.name).to.equal('SequelizeConnectionRefusedError');
  253. expect(connectionError.message).to.include('original connection error message');
  254. });
  255. it('AccessDeniedError should keep original message', () => {
  256. const orig = new Error('original connection error message');
  257. const connectionError = new AccessDeniedError(orig);
  258. expect(connectionError).to.have.property('parent');
  259. expect(connectionError).to.have.property('original');
  260. expect(connectionError.name).to.equal('SequelizeAccessDeniedError');
  261. expect(connectionError.message).to.include('original connection error message');
  262. });
  263. it('HostNotFoundError should keep original message', () => {
  264. const orig = new Error('original connection error message');
  265. const connectionError = new HostNotFoundError(orig);
  266. expect(connectionError).to.have.property('parent');
  267. expect(connectionError).to.have.property('original');
  268. expect(connectionError.name).to.equal('SequelizeHostNotFoundError');
  269. expect(connectionError.message).to.include('original connection error message');
  270. });
  271. it('HostNotReachableError should keep original message', () => {
  272. const orig = new Error('original connection error message');
  273. const connectionError = new HostNotReachableError(orig);
  274. expect(connectionError).to.have.property('parent');
  275. expect(connectionError).to.have.property('original');
  276. expect(connectionError.name).to.equal('SequelizeHostNotReachableError');
  277. expect(connectionError.message).to.include('original connection error message');
  278. });
  279. it('InvalidConnectionError should keep original message', () => {
  280. const orig = new Error('original connection error message');
  281. const connectionError = new InvalidConnectionError(orig);
  282. expect(connectionError).to.have.property('parent');
  283. expect(connectionError).to.have.property('original');
  284. expect(connectionError.name).to.equal('SequelizeInvalidConnectionError');
  285. expect(connectionError.message).to.include('original connection error message');
  286. });
  287. it('ConnectionTimedOutError should keep original message', () => {
  288. const orig = new Error('original connection error message');
  289. const connectionError = new ConnectionTimedOutError(orig);
  290. expect(connectionError).to.have.property('parent');
  291. expect(connectionError).to.have.property('original');
  292. expect(connectionError.name).to.equal('SequelizeConnectionTimedOutError');
  293. expect(connectionError.message).to.include('original connection error message');
  294. });
  295. });
  296. describe('OptimisticLockError', () => {
  297. it('got correct error type and message', async () => {
  298. class User extends Model {
  299. declare id: number;
  300. declare number: number;
  301. }
  302. const Account = sequelize.define<User>(
  303. 'Account',
  304. {
  305. id: {
  306. type: DataTypes.INTEGER,
  307. primaryKey: true,
  308. autoIncrement: true,
  309. },
  310. number: {
  311. type: DataTypes.INTEGER,
  312. },
  313. },
  314. {
  315. version: true,
  316. },
  317. );
  318. await Account.sync({ force: true });
  319. const result = (async () => {
  320. const accountA = await Account.create({ number: 1 });
  321. const accountB = await Account.findByPk(accountA.id);
  322. accountA.number += 1;
  323. await accountA.save();
  324. if (!accountB) {
  325. throw new Error('accountB is null');
  326. }
  327. accountB.number += 1;
  328. return accountB.save();
  329. })();
  330. await Promise.all([
  331. expect(result).to.eventually.be.rejectedWith(OptimisticLockError),
  332. expect(result).to.eventually.be.rejectedWith(
  333. 'Attempting to update a stale model instance: Account',
  334. ),
  335. ]);
  336. });
  337. });
  338. describe('ConstraintError', () => {
  339. allowDeprecationsInSuite(['SEQUELIZE0007']);
  340. for (const constraintTest of [
  341. {
  342. type: 'UniqueConstraintError',
  343. exception: UniqueConstraintError,
  344. },
  345. {
  346. type: 'ValidationError',
  347. exception: ValidationError,
  348. },
  349. ]) {
  350. it(`Can be intercepted as ${constraintTest.type} using .catch`, async () => {
  351. const userSpy = spy();
  352. const User = sequelize.define('user', {
  353. first_name: {
  354. type: DataTypes.STRING,
  355. unique: 'unique_name',
  356. },
  357. last_name: {
  358. type: DataTypes.STRING,
  359. unique: 'unique_name',
  360. },
  361. });
  362. const record = { first_name: 'jan', last_name: 'meier' };
  363. await sequelize.sync({ force: true });
  364. await User.create(record);
  365. try {
  366. await User.create(record);
  367. } catch (error) {
  368. if (!(error instanceof constraintTest.exception)) {
  369. throw error;
  370. }
  371. await userSpy(error);
  372. }
  373. expect(userSpy).to.have.been.calledOnce;
  374. });
  375. }
  376. // IBM i doesn't support newlines in identifiers
  377. if (dialect !== 'ibmi') {
  378. it('Supports newlines in keys', async () => {
  379. const userSpy = spy();
  380. const User = sequelize.define('user', {
  381. name: {
  382. type: DataTypes.STRING,
  383. unique: 'unique \n unique',
  384. },
  385. });
  386. await sequelize.sync({ force: true });
  387. await User.create({ name: 'jan' });
  388. try {
  389. await User.create({ name: 'jan' });
  390. } catch (error) {
  391. if (!(error instanceof UniqueConstraintError)) {
  392. throw error;
  393. }
  394. await userSpy(error);
  395. }
  396. expect(userSpy).to.have.been.calledOnce;
  397. });
  398. it('Works when unique keys are not defined in sequelize', async () => {
  399. let User = sequelize.define(
  400. 'user',
  401. {
  402. name: {
  403. type: DataTypes.STRING,
  404. unique: 'unique \n unique',
  405. },
  406. },
  407. { timestamps: false },
  408. );
  409. await sequelize.sync({ force: true });
  410. // Now let's pretend the index was created by someone else, and sequelize doesn't know about it
  411. User = sequelize.define(
  412. 'user',
  413. {
  414. name: DataTypes.STRING,
  415. },
  416. { timestamps: false },
  417. );
  418. await User.create({ name: 'jan' });
  419. // It should work even though the unique key is not defined in the model
  420. await expect(User.create({ name: 'jan' })).to.be.rejectedWith(UniqueConstraintError);
  421. // And when the model is not passed at all
  422. if (['db2', 'ibmi'].includes(dialect)) {
  423. await expect(
  424. sequelize.query('INSERT INTO "users" ("name") VALUES (\'jan\')'),
  425. ).to.be.rejectedWith(UniqueConstraintError);
  426. } else {
  427. await expect(
  428. sequelize.query("INSERT INTO users (name) VALUES ('jan')"),
  429. ).to.be.rejectedWith(UniqueConstraintError);
  430. }
  431. });
  432. }
  433. it('adds parent and sql properties', async () => {
  434. const User = sequelize.define(
  435. 'user',
  436. {
  437. name: {
  438. type: DataTypes.STRING,
  439. unique: 'unique',
  440. },
  441. },
  442. { timestamps: false },
  443. );
  444. await sequelize.sync({ force: true });
  445. await User.create({ name: 'jan' });
  446. // Unique key
  447. const error0 = await expect(User.create({ name: 'jan' })).to.be.rejected;
  448. expect(error0).to.be.instanceOf(UniqueConstraintError);
  449. expect(error0).to.have.property('parent');
  450. expect(error0).to.have.property('original');
  451. expect(error0).to.have.property('sql');
  452. await User.create({ id: 2, name: 'jon' });
  453. // Primary key
  454. const error = await expect(User.create({ id: 2, name: 'jon' })).to.be.rejected;
  455. expect(error).to.be.instanceOf(UniqueConstraintError);
  456. expect(error).to.have.property('parent');
  457. expect(error).to.have.property('original');
  458. expect(error).to.have.property('sql');
  459. });
  460. });
  461. describe('Query Errors', () => {
  462. it('should throw a unique constraint error for unique constraints', async () => {
  463. await queryInterface.createTable('Users', {
  464. username: {
  465. type: DataTypes.STRING,
  466. allowNull: false,
  467. unique: true,
  468. },
  469. });
  470. try {
  471. await queryInterface.bulkInsert('Users', [{ username: 'foo' }, { username: 'foo' }]);
  472. } catch (error) {
  473. expect(error).to.be.instanceOf(ValidationError);
  474. assert(error instanceof ValidationError);
  475. if (dialect === 'db2') {
  476. expect(error.errors).to.have.length(0);
  477. } else {
  478. expect(error.errors).to.have.length(1);
  479. expect(error.errors[0].type).to.equal('unique violation');
  480. if (dialect === 'sqlite3') {
  481. expect(error.errors[0].value).to.be.null;
  482. } else {
  483. expect(error.errors[0].value).to.equal('foo');
  484. }
  485. }
  486. assert(error.cause instanceof Error);
  487. switch (dialect) {
  488. case 'db2':
  489. expect(error.cause.message).to.contain(
  490. 'One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains table "DB2INST1.Users" from having duplicate values for the index key.',
  491. );
  492. break;
  493. case 'mssql':
  494. expect(error.cause.message).to.match(
  495. /Violation of UNIQUE KEY constraint 'UQ__Users__\w+'\. Cannot insert duplicate key in object 'dbo.Users'\. The duplicate key value is \(foo\)\./,
  496. );
  497. expect(error.errors[0].path).to.match(/UQ__Users__\w+/);
  498. expect(error.errors[0].message).to.match(/UQ__Users__\w+ must be unique/);
  499. break;
  500. case 'mysql':
  501. expect(error.cause.message).to.match(
  502. /Duplicate entry 'foo' for key '(?:Users.)?username'/,
  503. );
  504. expect(error.errors[0].path).to.equal('username');
  505. expect(error.errors[0].message).to.equal('username must be unique');
  506. break;
  507. case 'postgres':
  508. expect(error.cause.message).to.equal(
  509. 'duplicate key value violates unique constraint "Users_username_key"',
  510. );
  511. expect(error.errors[0].path).to.equal('username');
  512. expect(error.errors[0].message).to.equal('username must be unique');
  513. break;
  514. case 'sqlite3':
  515. expect(error.cause.message).to.equal(
  516. 'SQLITE_CONSTRAINT: UNIQUE constraint failed: Users.username',
  517. );
  518. expect(error.errors[0].path).to.equal('username');
  519. expect(error.errors[0].message).to.equal('username must be unique');
  520. break;
  521. default:
  522. expect(error.cause.message).to.contain("Duplicate entry 'foo' for key 'username'");
  523. expect(error.errors[0].path).to.equal('username');
  524. expect(error.errors[0].message).to.equal('username must be unique');
  525. }
  526. }
  527. });
  528. it('should throw a unique constraint error for unique indexes', async () => {
  529. await queryInterface.createTable('Users', {
  530. username: DataTypes.STRING,
  531. });
  532. await queryInterface.addIndex('Users', {
  533. fields: ['username'],
  534. unique: true,
  535. });
  536. try {
  537. await queryInterface.bulkInsert('Users', [{ username: 'foo' }, { username: 'foo' }]);
  538. } catch (error) {
  539. expect(error).to.be.instanceOf(ValidationError);
  540. assert(error instanceof ValidationError);
  541. if (dialect === 'db2') {
  542. expect(error.errors).to.have.length(0);
  543. } else {
  544. expect(error.errors).to.have.length(1);
  545. expect(error.errors[0].message).to.match(
  546. /(?:users_username_unique|username) must be unique/,
  547. );
  548. expect(error.errors[0].type).to.equal('unique violation');
  549. expect(error.errors[0].path).to.match(/(?:users_username_unique|username)/);
  550. if (dialect === 'sqlite3') {
  551. expect(error.errors[0].value).to.be.null;
  552. } else {
  553. expect(error.errors[0].value).to.equal('foo');
  554. }
  555. }
  556. assert(error.cause instanceof Error);
  557. switch (dialect) {
  558. case 'db2':
  559. expect(error.cause.message).to.contain(
  560. 'One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains table "DB2INST1.Users" from having duplicate values for the index key.',
  561. );
  562. break;
  563. case 'mssql':
  564. expect(error.cause.message).to.equal(
  565. "Cannot insert duplicate key row in object 'dbo.Users' with unique index 'users_username_unique'. The duplicate key value is (foo).",
  566. );
  567. break;
  568. case 'mysql':
  569. expect(error.cause.message).to.match(
  570. /Duplicate entry 'foo' for key '(?:Users.)?users_username_unique'/,
  571. );
  572. break;
  573. case 'postgres':
  574. expect(error.cause.message).to.equal(
  575. 'duplicate key value violates unique constraint "users_username_unique"',
  576. );
  577. break;
  578. case 'sqlite3':
  579. expect(error.cause.message).to.equal(
  580. 'SQLITE_CONSTRAINT: UNIQUE constraint failed: Users.username',
  581. );
  582. break;
  583. default:
  584. expect(error.cause.message).to.contain(
  585. "Duplicate entry 'foo' for key 'users_username_unique'",
  586. );
  587. }
  588. }
  589. });
  590. it('should throw a foreign key constraint error when deleting a parent row that has assocated child rows', async () => {
  591. await queryInterface.createTable('Users', {
  592. id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  593. username: DataTypes.STRING,
  594. });
  595. await queryInterface.createTable('Tasks', {
  596. title: DataTypes.STRING,
  597. userId: { type: DataTypes.INTEGER, allowNull: false },
  598. });
  599. await queryInterface.addConstraint('Tasks', {
  600. fields: ['userId'],
  601. type: 'FOREIGN KEY',
  602. name: 'Tasks_userId_Users_fk',
  603. references: {
  604. table: 'Users',
  605. field: 'id',
  606. },
  607. });
  608. await queryInterface.bulkInsert('Users', [{ username: 'foo' }]);
  609. await queryInterface.bulkInsert('Tasks', [{ title: 'task', userId: 1 }]);
  610. try {
  611. await queryInterface.bulkDelete('Users');
  612. } catch (error) {
  613. expect(error).to.be.instanceOf(ForeignKeyConstraintError);
  614. assert(error instanceof ForeignKeyConstraintError);
  615. if (dialect === 'sqlite3') {
  616. expect(error.index).to.be.undefined;
  617. } else {
  618. expect(error.index).to.equal('Tasks_userId_Users_fk');
  619. }
  620. switch (dialect) {
  621. case 'db2':
  622. expect(error.table).to.equal('Tasks');
  623. expect(error.fields).to.be.null;
  624. expect(error.cause.message).to.contain(
  625. 'A parent row cannot be deleted because the relationship "DB2INST1.Tasks.Tasks_userId_Users_fk" restricts the deletion.',
  626. );
  627. break;
  628. case 'mssql':
  629. expect(error.table).to.equal('dbo.Tasks');
  630. expect(error.fields).to.deep.equal(['userId']);
  631. expect(error.cause.message).to.equal(
  632. 'The DELETE statement conflicted with the REFERENCE constraint "Tasks_userId_Users_fk". The conflict occurred in database "sequelize_test", table "dbo.Tasks", column \'userId\'.',
  633. );
  634. break;
  635. case 'postgres':
  636. expect(error.table).to.equal('Users');
  637. expect(error.fields).to.be.null;
  638. expect(error.cause.message).to.equal(
  639. 'update or delete on table "Users" violates foreign key constraint "Tasks_userId_Users_fk" on table "Tasks"',
  640. );
  641. break;
  642. case 'sqlite3':
  643. expect(error.table).to.be.undefined;
  644. expect(error.fields).to.be.undefined;
  645. expect(error.cause.message).to.equal(
  646. 'SQLITE_CONSTRAINT: FOREIGN KEY constraint failed',
  647. );
  648. break;
  649. default:
  650. expect(error.table).to.equal('Users');
  651. expect(error.fields).to.deep.equal(['userId']);
  652. expect(error.cause.message).to.contain(
  653. 'Cannot delete or update a parent row: a foreign key constraint fails (`sequelize_test`.`Tasks`, CONSTRAINT `Tasks_userId_Users_fk` FOREIGN KEY (`userId`) REFERENCES `Users` (`id`))',
  654. );
  655. }
  656. }
  657. });
  658. it('should throw a foreign key constraint error when inserting a child row that has invalid parent row', async () => {
  659. await queryInterface.createTable('Users', {
  660. id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  661. username: DataTypes.STRING,
  662. });
  663. await queryInterface.createTable('Tasks', {
  664. title: DataTypes.STRING,
  665. userId: { type: DataTypes.INTEGER, allowNull: false },
  666. });
  667. await queryInterface.addConstraint('Tasks', {
  668. fields: ['userId'],
  669. type: 'FOREIGN KEY',
  670. name: 'Tasks_userId_Users_fk',
  671. references: {
  672. table: 'Users',
  673. field: 'id',
  674. },
  675. });
  676. try {
  677. await queryInterface.bulkInsert('Tasks', [{ title: 'task', userId: 1 }]);
  678. } catch (error) {
  679. expect(error).to.be.instanceOf(ForeignKeyConstraintError);
  680. assert(error instanceof ForeignKeyConstraintError);
  681. if (dialect === 'sqlite3') {
  682. expect(error.index).to.be.undefined;
  683. } else {
  684. expect(error.index).to.equal('Tasks_userId_Users_fk');
  685. }
  686. switch (dialect) {
  687. case 'db2':
  688. expect(error.table).to.equal('Tasks');
  689. expect(error.fields).to.be.null;
  690. expect(error.cause.message).to.contain(
  691. 'The insert or update value of the FOREIGN KEY "DB2INST1.Tasks.Tasks_userId_Users_fk" is not equal to any value of the parent key of the parent table.',
  692. );
  693. break;
  694. case 'mssql':
  695. expect(error.table).to.equal('dbo.Users');
  696. expect(error.fields).to.deep.equal(['id']);
  697. expect(error.cause.message).to.equal(
  698. 'The INSERT statement conflicted with the FOREIGN KEY constraint "Tasks_userId_Users_fk". The conflict occurred in database "sequelize_test", table "dbo.Users", column \'id\'.',
  699. );
  700. break;
  701. case 'postgres':
  702. expect(error.table).to.equal('Tasks');
  703. expect(error.fields).to.be.null;
  704. expect(error.cause.message).to.equal(
  705. 'insert or update on table "Tasks" violates foreign key constraint "Tasks_userId_Users_fk"',
  706. );
  707. break;
  708. case 'sqlite3':
  709. expect(error.table).to.be.undefined;
  710. expect(error.fields).to.be.undefined;
  711. expect(error.cause.message).to.equal(
  712. 'SQLITE_CONSTRAINT: FOREIGN KEY constraint failed',
  713. );
  714. break;
  715. default:
  716. expect(error.table).to.equal('Users');
  717. expect(error.fields).to.deep.equal(['userId']);
  718. expect(error.cause.message).to.contain(
  719. 'Cannot add or update a child row: a foreign key constraint fails (`sequelize_test`.`Tasks`, CONSTRAINT `Tasks_userId_Users_fk` FOREIGN KEY (`userId`) REFERENCES `Users` (`id`))',
  720. );
  721. }
  722. }
  723. });
  724. it('should throw an unknown constranit error for duplicate constraint names', async () => {
  725. await queryInterface.createTable('Users', {
  726. id: { type: DataTypes.INTEGER, allowNull: false },
  727. username: DataTypes.STRING,
  728. });
  729. await queryInterface.addConstraint('Users', {
  730. type: 'PRIMARY KEY',
  731. fields: ['id'],
  732. name: 'unique_constraint',
  733. });
  734. try {
  735. await queryInterface.addConstraint('Users', {
  736. type: 'UNIQUE',
  737. fields: ['username'],
  738. name: 'unique_constraint',
  739. });
  740. } catch (error) {
  741. if (['mariadb', 'mssql', 'mysql', 'sqlite3'].includes(dialect)) {
  742. expect(error).to.be.instanceOf(AggregateError);
  743. assert(error instanceof AggregateError);
  744. expect(error.errors).to.have.length(3);
  745. expect(error.errors[0].message).to.equal(
  746. "There is already an object named 'unique_constraint' in the database.",
  747. );
  748. expect(error.errors[1].message).to.equal(
  749. 'Could not create constraint or index. See previous errors.',
  750. );
  751. assert(error.errors[2] instanceof UnknownConstraintError);
  752. expect(error.errors[2].constraint).to.equal('unique_constraint');
  753. expect(error.errors[2].table).to.equal('Users');
  754. } else {
  755. expect(error).to.be.instanceOf(DatabaseError);
  756. assert(error instanceof DatabaseError);
  757. expect(error.sql).to.match(/.+(?:Users).+(?:unique_constraint)/);
  758. }
  759. }
  760. });
  761. });
  762. });