transaction-test-plugin.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
  2. import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
  3. import {
  4. Administrator,
  5. Ctx,
  6. EventBus,
  7. InternalServerError,
  8. NativeAuthenticationMethod,
  9. PluginCommonModule,
  10. RequestContext,
  11. Transaction,
  12. TransactionalConnection,
  13. User,
  14. VendureEvent,
  15. VendurePlugin,
  16. } from '@vendure/core';
  17. import gql from 'graphql-tag';
  18. import { ReplaySubject, Subscription } from 'rxjs';
  19. export class TestEvent extends VendureEvent {
  20. constructor(public ctx: RequestContext, public administrator: Administrator) {
  21. super();
  22. }
  23. }
  24. export const TRIGGER_EMAIL = 'trigger-email';
  25. @Injectable()
  26. class TestUserService {
  27. constructor(private connection: TransactionalConnection) {}
  28. async createUser(ctx: RequestContext, identifier: string) {
  29. const authMethod = await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(
  30. new NativeAuthenticationMethod({
  31. identifier,
  32. passwordHash: 'abc',
  33. }),
  34. );
  35. const user = await this.connection.getRepository(ctx, User).save(
  36. new User({
  37. authenticationMethods: [authMethod],
  38. identifier,
  39. roles: [],
  40. verified: true,
  41. }),
  42. );
  43. return user;
  44. }
  45. }
  46. @Injectable()
  47. class TestAdminService {
  48. constructor(private connection: TransactionalConnection, private userService: TestUserService) {}
  49. async createAdministrator(ctx: RequestContext, emailAddress: string, fail: boolean) {
  50. const user = await this.userService.createUser(ctx, emailAddress);
  51. if (fail) {
  52. throw new InternalServerError('Failed!');
  53. }
  54. const admin = await this.connection.getRepository(ctx, Administrator).save(
  55. new Administrator({
  56. emailAddress,
  57. user,
  58. firstName: 'jim',
  59. lastName: 'jiminy',
  60. }),
  61. );
  62. return admin;
  63. }
  64. }
  65. @Resolver()
  66. class TestResolver {
  67. constructor(
  68. private testAdminService: TestAdminService,
  69. private connection: TransactionalConnection,
  70. private eventBus: EventBus,
  71. ) {}
  72. @Mutation()
  73. @Transaction()
  74. async createTestAdministrator(@Ctx() ctx: RequestContext, @Args() args: any) {
  75. const admin = await this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  76. this.eventBus.publish(new TestEvent(ctx, admin));
  77. return admin;
  78. }
  79. @Mutation()
  80. @Transaction('manual')
  81. async createTestAdministrator2(@Ctx() ctx: RequestContext, @Args() args: any) {
  82. await this.connection.startTransaction(ctx);
  83. return this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  84. }
  85. @Mutation()
  86. @Transaction('manual')
  87. async createTestAdministrator3(@Ctx() ctx: RequestContext, @Args() args: any) {
  88. // no transaction started
  89. return this.testAdminService.createAdministrator(ctx, args.emailAddress, args.fail);
  90. }
  91. @Query()
  92. async verify() {
  93. const admins = await this.connection.getRepository(Administrator).find();
  94. const users = await this.connection.getRepository(User).find();
  95. return {
  96. admins,
  97. users,
  98. };
  99. }
  100. }
  101. @VendurePlugin({
  102. imports: [PluginCommonModule],
  103. providers: [TestAdminService, TestUserService],
  104. adminApiExtensions: {
  105. schema: gql`
  106. extend type Mutation {
  107. createTestAdministrator(emailAddress: String!, fail: Boolean!): Administrator
  108. createTestAdministrator2(emailAddress: String!, fail: Boolean!): Administrator
  109. createTestAdministrator3(emailAddress: String!, fail: Boolean!): Administrator
  110. }
  111. type VerifyResult {
  112. admins: [Administrator!]!
  113. users: [User!]!
  114. }
  115. extend type Query {
  116. verify: VerifyResult!
  117. }
  118. `,
  119. resolvers: [TestResolver],
  120. },
  121. })
  122. export class TransactionTestPlugin implements OnApplicationBootstrap {
  123. private subscription: Subscription;
  124. static errorHandler = jest.fn();
  125. static eventHandlerComplete$ = new ReplaySubject(1);
  126. constructor(private eventBus: EventBus, private connection: TransactionalConnection) {}
  127. onApplicationBootstrap(): any {
  128. // This part is used to test how RequestContext with transactions behave
  129. // when used in an Event subscription
  130. this.subscription = this.eventBus.ofType(TestEvent).subscribe(async event => {
  131. const { ctx, administrator } = event;
  132. if (administrator.emailAddress === TRIGGER_EMAIL) {
  133. administrator.lastName = 'modified';
  134. try {
  135. await new Promise(resolve => setTimeout(resolve, 50));
  136. await this.connection.getRepository(ctx, Administrator).save(administrator);
  137. } catch (e) {
  138. TransactionTestPlugin.errorHandler(e);
  139. } finally {
  140. TransactionTestPlugin.eventHandlerComplete$.complete();
  141. }
  142. }
  143. });
  144. }
  145. }