database-transactions.e2e-spec.ts 12 KB

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