database-transactions.e2e-spec.ts 12 KB

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