database-transactions.e2e-spec.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { Injectable } from '@nestjs/common';
  2. import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
  3. import {
  4. Administrator,
  5. Ctx,
  6. InternalServerError,
  7. mergeConfig,
  8. NativeAuthenticationMethod,
  9. PluginCommonModule,
  10. RequestContext,
  11. Transaction,
  12. TransactionalConnection,
  13. User,
  14. VendurePlugin,
  15. } from '@vendure/core';
  16. import { createTestEnvironment } from '@vendure/testing';
  17. import gql from 'graphql-tag';
  18. import path from 'path';
  19. import { initialData } from '../../../e2e-common/e2e-initial-data';
  20. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  21. @Injectable()
  22. class TestUserService {
  23. constructor(private connection: TransactionalConnection) {}
  24. async createUser(ctx: RequestContext, identifier: string) {
  25. const authMethod = await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(
  26. new NativeAuthenticationMethod({
  27. identifier,
  28. passwordHash: 'abc',
  29. }),
  30. );
  31. const user = await this.connection.getRepository(ctx, User).save(
  32. new User({
  33. authenticationMethods: [authMethod],
  34. identifier,
  35. roles: [],
  36. verified: true,
  37. }),
  38. );
  39. return user;
  40. }
  41. }
  42. @Injectable()
  43. class TestAdminService {
  44. constructor(private connection: TransactionalConnection, private userService: TestUserService) {}
  45. async createAdministrator(ctx: RequestContext, emailAddress: string, fail: boolean) {
  46. const user = await this.userService.createUser(ctx, emailAddress);
  47. if (fail) {
  48. throw new InternalServerError('Failed!');
  49. }
  50. const admin = await this.connection.getRepository(ctx, Administrator).save(
  51. new Administrator({
  52. emailAddress,
  53. user,
  54. firstName: 'jim',
  55. lastName: 'jiminy',
  56. }),
  57. );
  58. return admin;
  59. }
  60. }
  61. @Resolver()
  62. class TestResolver {
  63. constructor(private testAdminService: TestAdminService, private connection: TransactionalConnection) {}
  64. @Mutation()
  65. @Transaction()
  66. createTestAdministrator(@Ctx() ctx: RequestContext, @Args() args: any) {
  67. return this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  68. }
  69. @Mutation()
  70. @Transaction('manual')
  71. async createTestAdministrator2(@Ctx() ctx: RequestContext, @Args() args: any) {
  72. await this.connection.startTransaction(ctx);
  73. return this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  74. }
  75. @Mutation()
  76. @Transaction('manual')
  77. async createTestAdministrator3(@Ctx() ctx: RequestContext, @Args() args: any) {
  78. // no transaction started
  79. return this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  80. }
  81. @Query()
  82. async verify() {
  83. const admins = await this.connection.getRepository(Administrator).find();
  84. const users = await this.connection.getRepository(User).find();
  85. return {
  86. admins,
  87. users,
  88. };
  89. }
  90. }
  91. @VendurePlugin({
  92. imports: [PluginCommonModule],
  93. providers: [TestAdminService, TestUserService],
  94. adminApiExtensions: {
  95. schema: gql`
  96. extend type Mutation {
  97. createTestAdministrator(emailAddress: String!, fail: Boolean!): Administrator
  98. createTestAdministrator2(emailAddress: String!, fail: Boolean!): Administrator
  99. createTestAdministrator3(emailAddress: String!, fail: Boolean!): Administrator
  100. }
  101. type VerifyResult {
  102. admins: [Administrator!]!
  103. users: [User!]!
  104. }
  105. extend type Query {
  106. verify: VerifyResult!
  107. }
  108. `,
  109. resolvers: [TestResolver],
  110. },
  111. })
  112. class TransactionTestPlugin {}
  113. describe('Transaction infrastructure', () => {
  114. const { server, adminClient } = createTestEnvironment(
  115. mergeConfig(testConfig, {
  116. plugins: [TransactionTestPlugin],
  117. }),
  118. );
  119. beforeAll(async () => {
  120. await server.init({
  121. initialData,
  122. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  123. customerCount: 0,
  124. });
  125. await adminClient.asSuperAdmin();
  126. }, TEST_SETUP_TIMEOUT_MS);
  127. afterAll(async () => {
  128. await server.destroy();
  129. });
  130. it('non-failing mutation', async () => {
  131. const { createTestAdministrator } = await adminClient.query(CREATE_ADMIN, {
  132. emailAddress: 'test1',
  133. fail: false,
  134. });
  135. expect(createTestAdministrator.emailAddress).toBe('test1');
  136. expect(createTestAdministrator.user.identifier).toBe('test1');
  137. const { verify } = await adminClient.query(VERIFY_TEST);
  138. expect(verify.admins.length).toBe(2);
  139. expect(verify.users.length).toBe(2);
  140. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test1')).toBe(true);
  141. expect(!!verify.users.find((u: any) => u.identifier === 'test1')).toBe(true);
  142. });
  143. it('failing mutation', async () => {
  144. try {
  145. await adminClient.query(CREATE_ADMIN, {
  146. emailAddress: 'test2',
  147. fail: true,
  148. });
  149. fail('Should have thrown');
  150. } catch (e) {
  151. expect(e.message).toContain('Failed!');
  152. }
  153. const { verify } = await adminClient.query(VERIFY_TEST);
  154. expect(verify.admins.length).toBe(2);
  155. expect(verify.users.length).toBe(2);
  156. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test2')).toBe(false);
  157. expect(!!verify.users.find((u: any) => u.identifier === 'test2')).toBe(false);
  158. });
  159. it('failing manual mutation', async () => {
  160. try {
  161. await adminClient.query(CREATE_ADMIN2, {
  162. emailAddress: 'test3',
  163. fail: true,
  164. });
  165. fail('Should have thrown');
  166. } catch (e) {
  167. expect(e.message).toContain('Failed!');
  168. }
  169. const { verify } = await adminClient.query(VERIFY_TEST);
  170. expect(verify.admins.length).toBe(2);
  171. expect(verify.users.length).toBe(2);
  172. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test3')).toBe(false);
  173. expect(!!verify.users.find((u: any) => u.identifier === 'test3')).toBe(false);
  174. });
  175. it('failing manual mutation without transaction', async () => {
  176. try {
  177. await adminClient.query(CREATE_ADMIN3, {
  178. emailAddress: 'test4',
  179. fail: true,
  180. });
  181. fail('Should have thrown');
  182. } catch (e) {
  183. expect(e.message).toContain('Failed!');
  184. }
  185. const { verify } = await adminClient.query(VERIFY_TEST);
  186. expect(verify.admins.length).toBe(2);
  187. expect(verify.users.length).toBe(3);
  188. expect(!!verify.admins.find((a: any) => a.emailAddress === 'test4')).toBe(false);
  189. expect(!!verify.users.find((u: any) => u.identifier === 'test4')).toBe(true);
  190. });
  191. });
  192. const ADMIN_FRAGMENT = gql`
  193. fragment CreatedAdmin on Administrator {
  194. id
  195. emailAddress
  196. user {
  197. id
  198. identifier
  199. }
  200. }
  201. `;
  202. const CREATE_ADMIN = gql`
  203. mutation CreateTestAdmin($emailAddress: String!, $fail: Boolean!) {
  204. createTestAdministrator(emailAddress: $emailAddress, fail: $fail) {
  205. ...CreatedAdmin
  206. }
  207. }
  208. ${ADMIN_FRAGMENT}
  209. `;
  210. const CREATE_ADMIN2 = gql`
  211. mutation CreateTestAdmin2($emailAddress: String!, $fail: Boolean!) {
  212. createTestAdministrator2(emailAddress: $emailAddress, fail: $fail) {
  213. ...CreatedAdmin
  214. }
  215. }
  216. ${ADMIN_FRAGMENT}
  217. `;
  218. const CREATE_ADMIN3 = gql`
  219. mutation CreateTestAdmin2($emailAddress: String!, $fail: Boolean!) {
  220. createTestAdministrator3(emailAddress: $emailAddress, fail: $fail) {
  221. ...CreatedAdmin
  222. }
  223. }
  224. ${ADMIN_FRAGMENT}
  225. `;
  226. const VERIFY_TEST = gql`
  227. query VerifyTest {
  228. verify {
  229. admins {
  230. id
  231. emailAddress
  232. }
  233. users {
  234. id
  235. identifier
  236. }
  237. }
  238. }
  239. `;