database-transactions.e2e-spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import { mergeConfig } from '@vendure/core';
  2. import { createTestEnvironment } from '@vendure/testing';
  3. import { fail } from 'assert';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  7. import { initialData } from '../../../e2e-common/e2e-initial-data';
  8. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  9. import {
  10. TransactionTestPlugin,
  11. TRIGGER_ATTEMPTED_READ_EMAIL,
  12. TRIGGER_ATTEMPTED_UPDATE_EMAIL,
  13. TRIGGER_NO_OPERATION,
  14. } from './fixtures/test-plugins/transaction-test-plugin';
  15. type DBType = 'mysql' | 'postgres' | 'sqlite' | 'sqljs';
  16. const itIfDb = (dbs: DBType[]) => {
  17. return dbs.includes((process.env.DB as DBType) || 'sqljs') ? it : it.skip;
  18. };
  19. describe('Transaction infrastructure', () => {
  20. const { server, adminClient } = createTestEnvironment(
  21. mergeConfig(testConfig(), {
  22. plugins: [TransactionTestPlugin],
  23. }),
  24. );
  25. beforeAll(async () => {
  26. await server.init({
  27. initialData,
  28. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  29. customerCount: 0,
  30. });
  31. await adminClient.asSuperAdmin();
  32. }, TEST_SETUP_TIMEOUT_MS);
  33. afterAll(async () => {
  34. await server.destroy();
  35. });
  36. it('non-failing mutation', async () => {
  37. const { createTestAdministrator } = await adminClient.query(CREATE_ADMIN, {
  38. emailAddress: 'test1',
  39. fail: false,
  40. });
  41. expect(createTestAdministrator.emailAddress).toBe('test1');
  42. expect(createTestAdministrator.user.identifier).toBe('test1');
  43. const { verify } = await adminClient.query(VERIFY_TEST);
  44. expect(verify.admins.length).toBe(2);
  45. expect(verify.users.length).toBe(2);
  46. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test1')).toBe(true);
  47. expect(!!verify.users.find((u: any) => u.identifier === 'test1')).toBe(true);
  48. });
  49. it('failing mutation', async () => {
  50. try {
  51. await adminClient.query(CREATE_ADMIN, {
  52. emailAddress: 'test2',
  53. fail: true,
  54. });
  55. fail('Should have thrown');
  56. } catch (e: any) {
  57. expect(e.message).toContain('Failed!');
  58. }
  59. const { verify } = await adminClient.query(VERIFY_TEST);
  60. expect(verify.admins.length).toBe(2);
  61. expect(verify.users.length).toBe(2);
  62. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test2')).toBe(false);
  63. expect(!!verify.users.find((u: any) => u.identifier === 'test2')).toBe(false);
  64. });
  65. it('failing mutation with promise concurrent execution', async () => {
  66. try {
  67. await adminClient.query(CREATE_N_ADMINS, {
  68. emailAddress: 'testN-',
  69. failFactor: 0.4,
  70. n: 10,
  71. });
  72. fail('Should have thrown');
  73. } catch (e) {
  74. expect(e.message).toContain('Failed!');
  75. }
  76. const { verify } = await adminClient.query(VERIFY_TEST);
  77. expect(verify.admins.length).toBe(2);
  78. expect(verify.users.length).toBe(2);
  79. expect(!!verify.admins.find((a: any) => a.emailAddress.includes('testN'))).toBe(false);
  80. expect(!!verify.users.find((u: any) => u.identifier.includes('testN'))).toBe(false);
  81. });
  82. it('failing manual mutation', async () => {
  83. try {
  84. await adminClient.query(CREATE_ADMIN2, {
  85. emailAddress: 'test3',
  86. fail: true,
  87. });
  88. fail('Should have thrown');
  89. } catch (e: any) {
  90. expect(e.message).toContain('Failed!');
  91. }
  92. const { verify } = await adminClient.query(VERIFY_TEST);
  93. expect(verify.admins.length).toBe(2);
  94. expect(verify.users.length).toBe(2);
  95. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test3')).toBe(false);
  96. expect(!!verify.users.find((u: any) => u.identifier === 'test3')).toBe(false);
  97. });
  98. it('failing manual mutation without transaction', async () => {
  99. try {
  100. await adminClient.query(CREATE_ADMIN3, {
  101. emailAddress: 'test4',
  102. fail: true,
  103. });
  104. fail('Should have thrown');
  105. } catch (e: any) {
  106. expect(e.message).toContain('Failed!');
  107. }
  108. const { verify } = await adminClient.query(VERIFY_TEST);
  109. expect(verify.admins.length).toBe(2);
  110. expect(verify.users.length).toBe(3);
  111. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test4')).toBe(false);
  112. expect(!!verify.users.find((u: any) => u.identifier === 'test4')).toBe(true);
  113. });
  114. it('failing mutation inside connection.withTransaction() wrapper with request context', async () => {
  115. try {
  116. await adminClient.query(CREATE_ADMIN5, {
  117. emailAddress: 'test5',
  118. fail: true,
  119. noContext: false,
  120. });
  121. fail('Should have thrown');
  122. } catch (e: any) {
  123. expect(e.message).toContain('Failed!');
  124. }
  125. const { verify } = await adminClient.query(VERIFY_TEST);
  126. expect(verify.admins.length).toBe(2);
  127. expect(verify.users.length).toBe(3);
  128. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test5')).toBe(false);
  129. expect(!!verify.users.find((u: any) => u.identifier === 'test5')).toBe(false);
  130. });
  131. itIfDb(['postgres', 'mysql'])(
  132. 'failing mutation inside connection.withTransaction() wrapper with context and promise concurrent execution',
  133. async () => {
  134. try {
  135. await adminClient.query(CREATE_N_ADMINS2, {
  136. emailAddress: 'testN-',
  137. failFactor: 0.4,
  138. n: 10,
  139. });
  140. fail('Should have thrown');
  141. } catch (e) {
  142. expect(e.message).toMatch(
  143. /^Failed!|Query runner already released. Cannot run queries anymore.$/,
  144. );
  145. }
  146. const { verify } = await adminClient.query(VERIFY_TEST);
  147. expect(verify.admins.length).toBe(2);
  148. expect(verify.users.length).toBe(3);
  149. expect(!!verify.admins.find((a: any) => a.emailAddress.includes('testN'))).toBe(false);
  150. expect(!!verify.users.find((u: any) => u.identifier.includes('testN'))).toBe(false);
  151. },
  152. );
  153. it('failing mutation inside connection.withTransaction() wrapper without request context', async () => {
  154. try {
  155. await adminClient.query(CREATE_ADMIN5, {
  156. emailAddress: 'test5',
  157. fail: true,
  158. noContext: true,
  159. });
  160. fail('Should have thrown');
  161. } catch (e: any) {
  162. expect(e.message).toContain('Failed!');
  163. }
  164. const { verify } = await adminClient.query(VERIFY_TEST);
  165. expect(verify.admins.length).toBe(2);
  166. expect(verify.users.length).toBe(3);
  167. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test5')).toBe(false);
  168. expect(!!verify.users.find((u: any) => u.identifier === 'test5')).toBe(false);
  169. });
  170. it('non-failing mutation inside connection.withTransaction() wrapper with failing nested transactions and request context', async () => {
  171. await adminClient.query(CREATE_N_ADMINS3, {
  172. emailAddress: 'testNestedTransactionsN-',
  173. failFactor: 0.5,
  174. n: 2,
  175. });
  176. const { verify } = await adminClient.query(VERIFY_TEST);
  177. expect(verify.admins.length).toBe(3);
  178. expect(verify.users.length).toBe(4);
  179. expect(
  180. verify.admins.filter((a: any) => a.emailAddress.includes('testNestedTransactionsN')),
  181. ).toHaveLength(1);
  182. expect(
  183. verify.users.filter((u: any) => u.identifier.includes('testNestedTransactionsN')),
  184. ).toHaveLength(1);
  185. });
  186. it('event do not publish after transaction rollback', async () => {
  187. TransactionTestPlugin.reset();
  188. try {
  189. await adminClient.query(CREATE_N_ADMINS, {
  190. emailAddress: TRIGGER_NO_OPERATION,
  191. failFactor: 0.5,
  192. n: 2,
  193. });
  194. fail('Should have thrown');
  195. } catch (e) {
  196. expect(e.message).toContain('Failed!');
  197. }
  198. // Wait a bit to see an events in handler
  199. await new Promise(resolve => setTimeout(resolve, 100));
  200. expect(TransactionTestPlugin.callHandler).not.toHaveBeenCalled();
  201. expect(TransactionTestPlugin.errorHandler).not.toHaveBeenCalled();
  202. });
  203. // Testing https://github.com/vendurehq/vendure/issues/520
  204. it('passing transaction via EventBus', async () => {
  205. TransactionTestPlugin.reset();
  206. const { createTestAdministrator } = await adminClient.query(CREATE_ADMIN, {
  207. emailAddress: TRIGGER_ATTEMPTED_UPDATE_EMAIL,
  208. fail: false,
  209. });
  210. await TransactionTestPlugin.eventHandlerComplete$.toPromise();
  211. expect(createTestAdministrator.emailAddress).toBe(TRIGGER_ATTEMPTED_UPDATE_EMAIL);
  212. expect(TransactionTestPlugin.errorHandler).not.toHaveBeenCalled();
  213. });
  214. // Testing https://github.com/vendurehq/vendure/issues/1107
  215. it('passing transaction via EventBus with delay in committing transaction', async () => {
  216. TransactionTestPlugin.reset();
  217. const { createTestAdministrator4 } = await adminClient.query(CREATE_ADMIN4, {
  218. emailAddress: TRIGGER_ATTEMPTED_READ_EMAIL,
  219. fail: false,
  220. });
  221. await TransactionTestPlugin.eventHandlerComplete$.toPromise();
  222. expect(createTestAdministrator4.emailAddress).toBe(TRIGGER_ATTEMPTED_READ_EMAIL);
  223. expect(TransactionTestPlugin.errorHandler).not.toHaveBeenCalled();
  224. });
  225. });
  226. const ADMIN_FRAGMENT = gql`
  227. fragment CreatedAdmin on Administrator {
  228. id
  229. emailAddress
  230. user {
  231. id
  232. identifier
  233. }
  234. }
  235. `;
  236. const CREATE_ADMIN = gql`
  237. mutation CreateTestAdmin($emailAddress: String!, $fail: Boolean!) {
  238. createTestAdministrator(emailAddress: $emailAddress, fail: $fail) {
  239. ...CreatedAdmin
  240. }
  241. }
  242. ${ADMIN_FRAGMENT}
  243. `;
  244. const CREATE_ADMIN2 = gql`
  245. mutation CreateTestAdmin2($emailAddress: String!, $fail: Boolean!) {
  246. createTestAdministrator2(emailAddress: $emailAddress, fail: $fail) {
  247. ...CreatedAdmin
  248. }
  249. }
  250. ${ADMIN_FRAGMENT}
  251. `;
  252. const CREATE_ADMIN3 = gql`
  253. mutation CreateTestAdmin3($emailAddress: String!, $fail: Boolean!) {
  254. createTestAdministrator3(emailAddress: $emailAddress, fail: $fail) {
  255. ...CreatedAdmin
  256. }
  257. }
  258. ${ADMIN_FRAGMENT}
  259. `;
  260. const CREATE_ADMIN4 = gql`
  261. mutation CreateTestAdmin4($emailAddress: String!, $fail: Boolean!) {
  262. createTestAdministrator4(emailAddress: $emailAddress, fail: $fail) {
  263. ...CreatedAdmin
  264. }
  265. }
  266. ${ADMIN_FRAGMENT}
  267. `;
  268. const CREATE_ADMIN5 = gql`
  269. mutation CreateTestAdmin5($emailAddress: String!, $fail: Boolean!, $noContext: Boolean!) {
  270. createTestAdministrator5(emailAddress: $emailAddress, fail: $fail, noContext: $noContext) {
  271. ...CreatedAdmin
  272. }
  273. }
  274. ${ADMIN_FRAGMENT}
  275. `;
  276. const CREATE_N_ADMINS = gql`
  277. mutation CreateNTestAdmins($emailAddress: String!, $failFactor: Float!, $n: Int!) {
  278. createNTestAdministrators(emailAddress: $emailAddress, failFactor: $failFactor, n: $n)
  279. }
  280. `;
  281. const CREATE_N_ADMINS2 = gql`
  282. mutation CreateNTestAdmins2($emailAddress: String!, $failFactor: Float!, $n: Int!) {
  283. createNTestAdministrators2(emailAddress: $emailAddress, failFactor: $failFactor, n: $n)
  284. }
  285. `;
  286. const CREATE_N_ADMINS3 = gql`
  287. mutation CreateNTestAdmins3($emailAddress: String!, $failFactor: Float!, $n: Int!) {
  288. createNTestAdministrators3(emailAddress: $emailAddress, failFactor: $failFactor, n: $n)
  289. }
  290. `;
  291. const VERIFY_TEST = gql`
  292. query VerifyTest {
  293. verify {
  294. admins {
  295. id
  296. emailAddress
  297. }
  298. users {
  299. id
  300. identifier
  301. }
  302. }
  303. }
  304. `;