database-transactions.e2e-spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. } from './fixtures/test-plugins/transaction-test-plugin';
  13. type DBType = 'mysql' | 'postgres' | 'sqlite' | 'sqljs';
  14. const itIfDb = (dbs: DBType[]) => {
  15. return dbs.includes(process.env.DB as DBType || 'sqljs')
  16. ? it
  17. : 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) {
  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) {
  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) {
  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) {
  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'])('failing mutation inside connection.withTransaction() wrapper with context and promise concurrent execution', async () => {
  132. try {
  133. await adminClient.query(CREATE_N_ADMINS2, {
  134. emailAddress: 'testN-',
  135. failFactor: 0.4,
  136. n: 10
  137. })
  138. fail('Should have thrown');
  139. } catch (e) {
  140. expect(e.message)
  141. .toMatch(/^Failed!|Query runner already released. Cannot run queries anymore.$/);
  142. }
  143. const { verify } = await adminClient.query(VERIFY_TEST);
  144. expect(verify.admins.length).toBe(2);
  145. expect(verify.users.length).toBe(3);
  146. expect(!!verify.admins.find((a: any) => a.emailAddress.includes('testN'))).toBe(false);
  147. expect(!!verify.users.find((u: any) => u.identifier.includes('testN'))).toBe(false);
  148. });
  149. it('failing mutation inside connection.withTransaction() wrapper without request context', async () => {
  150. try {
  151. await adminClient.query(CREATE_ADMIN5, {
  152. emailAddress: 'test5',
  153. fail: true,
  154. noContext: true,
  155. });
  156. fail('Should have thrown');
  157. } catch (e) {
  158. expect(e.message).toContain('Failed!');
  159. }
  160. const { verify } = await adminClient.query(VERIFY_TEST);
  161. expect(verify.admins.length).toBe(2);
  162. expect(verify.users.length).toBe(3);
  163. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test5')).toBe(false);
  164. expect(!!verify.users.find((u: any) => u.identifier === 'test5')).toBe(false);
  165. });
  166. // Testing https://github.com/vendure-ecommerce/vendure/issues/520
  167. it('passing transaction via EventBus', async () => {
  168. TransactionTestPlugin.reset();
  169. const { createTestAdministrator } = await adminClient.query(CREATE_ADMIN, {
  170. emailAddress: TRIGGER_ATTEMPTED_UPDATE_EMAIL,
  171. fail: false,
  172. });
  173. await TransactionTestPlugin.eventHandlerComplete$.toPromise();
  174. expect(createTestAdministrator.emailAddress).toBe(TRIGGER_ATTEMPTED_UPDATE_EMAIL);
  175. expect(TransactionTestPlugin.errorHandler).not.toHaveBeenCalled();
  176. });
  177. // Testing https://github.com/vendure-ecommerce/vendure/issues/1107
  178. it('passing transaction via EventBus with delay in committing transaction', async () => {
  179. TransactionTestPlugin.reset();
  180. const { createTestAdministrator4 } = await adminClient.query(CREATE_ADMIN4, {
  181. emailAddress: TRIGGER_ATTEMPTED_READ_EMAIL,
  182. fail: false,
  183. });
  184. await TransactionTestPlugin.eventHandlerComplete$.toPromise();
  185. expect(createTestAdministrator4.emailAddress).toBe(TRIGGER_ATTEMPTED_READ_EMAIL);
  186. expect(TransactionTestPlugin.errorHandler).not.toHaveBeenCalled();
  187. });
  188. });
  189. const ADMIN_FRAGMENT = gql`
  190. fragment CreatedAdmin on Administrator {
  191. id
  192. emailAddress
  193. user {
  194. id
  195. identifier
  196. }
  197. }
  198. `;
  199. const CREATE_ADMIN = gql`
  200. mutation CreateTestAdmin($emailAddress: String!, $fail: Boolean!) {
  201. createTestAdministrator(emailAddress: $emailAddress, fail: $fail) {
  202. ...CreatedAdmin
  203. }
  204. }
  205. ${ADMIN_FRAGMENT}
  206. `;
  207. const CREATE_ADMIN2 = gql`
  208. mutation CreateTestAdmin2($emailAddress: String!, $fail: Boolean!) {
  209. createTestAdministrator2(emailAddress: $emailAddress, fail: $fail) {
  210. ...CreatedAdmin
  211. }
  212. }
  213. ${ADMIN_FRAGMENT}
  214. `;
  215. const CREATE_ADMIN3 = gql`
  216. mutation CreateTestAdmin3($emailAddress: String!, $fail: Boolean!) {
  217. createTestAdministrator3(emailAddress: $emailAddress, fail: $fail) {
  218. ...CreatedAdmin
  219. }
  220. }
  221. ${ADMIN_FRAGMENT}
  222. `;
  223. const CREATE_ADMIN4 = gql`
  224. mutation CreateTestAdmin4($emailAddress: String!, $fail: Boolean!) {
  225. createTestAdministrator4(emailAddress: $emailAddress, fail: $fail) {
  226. ...CreatedAdmin
  227. }
  228. }
  229. ${ADMIN_FRAGMENT}
  230. `;
  231. const CREATE_ADMIN5 = gql`
  232. mutation CreateTestAdmin5($emailAddress: String!, $fail: Boolean!, $noContext: Boolean!) {
  233. createTestAdministrator5(emailAddress: $emailAddress, fail: $fail, noContext: $noContext) {
  234. ...CreatedAdmin
  235. }
  236. }
  237. ${ADMIN_FRAGMENT}
  238. `;
  239. const CREATE_N_ADMINS = gql`
  240. mutation CreateNTestAdmins($emailAddress: String!, $failFactor: Float!, $n: Int!) {
  241. createNTestAdministrators(emailAddress: $emailAddress, failFactor: $failFactor, n: $n)
  242. }
  243. `;
  244. const CREATE_N_ADMINS2 = gql`
  245. mutation CreateNTestAdmins2($emailAddress: String!, $failFactor: Float!, $n: Int!) {
  246. createNTestAdministrators2(emailAddress: $emailAddress, failFactor: $failFactor, n: $n)
  247. }
  248. `;
  249. const VERIFY_TEST = gql`
  250. query VerifyTest {
  251. verify {
  252. admins {
  253. id
  254. emailAddress
  255. }
  256. users {
  257. id
  258. identifier
  259. }
  260. }
  261. }
  262. `;