add-command.e2e-spec.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /*
  2. * E2E tests for the add command
  3. *
  4. * To run these tests:
  5. * npm run vitest -- --config e2e/vitest.e2e.config.mts
  6. */
  7. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  8. import { addCommand } from '../src/commands/add/add';
  9. import * as apiExtensionModule from '../src/commands/add/api-extension/add-api-extension';
  10. import * as codegenModule from '../src/commands/add/codegen/add-codegen';
  11. import * as entityModule from '../src/commands/add/entity/add-entity';
  12. import * as jobQueueModule from '../src/commands/add/job-queue/add-job-queue';
  13. import * as pluginModule from '../src/commands/add/plugin/create-new-plugin';
  14. import * as serviceModule from '../src/commands/add/service/add-service';
  15. import * as uiExtensionsModule from '../src/commands/add/ui-extensions/add-ui-extensions';
  16. // Mock clack prompts to prevent interactive prompts during tests
  17. vi.mock('@clack/prompts', () => ({
  18. intro: vi.fn(),
  19. outro: vi.fn(),
  20. cancel: vi.fn(),
  21. isCancel: vi.fn(() => false),
  22. select: vi.fn(() => Promise.resolve('no-selection')),
  23. spinner: vi.fn(() => ({
  24. start: vi.fn(),
  25. stop: vi.fn(),
  26. })),
  27. log: {
  28. success: vi.fn(),
  29. error: vi.fn(),
  30. info: vi.fn(),
  31. },
  32. }));
  33. type Spy = ReturnType<typeof vi.spyOn>;
  34. let pluginRunSpy: Spy;
  35. let entityRunSpy: Spy;
  36. let serviceRunSpy: Spy;
  37. let jobQueueRunSpy: Spy;
  38. let codegenRunSpy: Spy;
  39. let apiExtRunSpy: Spy;
  40. let uiExtRunSpy: Spy;
  41. beforeEach(() => {
  42. // Stub all core functions before every test
  43. const defaultReturnValue = { project: undefined as any, modifiedSourceFiles: [] };
  44. pluginRunSpy = vi.spyOn(pluginModule, 'createNewPlugin').mockResolvedValue(defaultReturnValue as any);
  45. entityRunSpy = vi.spyOn(entityModule, 'addEntity').mockResolvedValue(defaultReturnValue as any);
  46. serviceRunSpy = vi.spyOn(serviceModule, 'addService').mockResolvedValue(defaultReturnValue as any);
  47. jobQueueRunSpy = vi.spyOn(jobQueueModule, 'addJobQueue').mockResolvedValue(defaultReturnValue as any);
  48. codegenRunSpy = vi.spyOn(codegenModule, 'addCodegen').mockResolvedValue(defaultReturnValue as any);
  49. apiExtRunSpy = vi
  50. .spyOn(apiExtensionModule, 'addApiExtension')
  51. .mockResolvedValue(defaultReturnValue as any);
  52. uiExtRunSpy = vi
  53. .spyOn(uiExtensionsModule, 'addUiExtensions')
  54. .mockResolvedValue(defaultReturnValue as any);
  55. });
  56. afterEach(() => {
  57. vi.restoreAllMocks();
  58. });
  59. describe('Add Command E2E', () => {
  60. describe('addCommand non-interactive mode', () => {
  61. it('creates a plugin when the "plugin" option is provided', async () => {
  62. await addCommand({ plugin: 'test-plugin' });
  63. expect(pluginRunSpy).toHaveBeenCalledOnce();
  64. expect(pluginRunSpy).toHaveBeenCalledWith({ name: 'test-plugin', config: undefined });
  65. });
  66. it('throws when the plugin name is empty', async () => {
  67. const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
  68. await addCommand({ plugin: ' ' });
  69. expect(pluginRunSpy).not.toHaveBeenCalled();
  70. expect(exitSpy).toHaveBeenCalledWith(1);
  71. exitSpy.mockRestore();
  72. });
  73. it('adds an entity to the specified plugin', async () => {
  74. await addCommand({
  75. entity: 'MyEntity',
  76. selectedPlugin: 'MyPlugin',
  77. });
  78. expect(entityRunSpy).toHaveBeenCalledOnce();
  79. expect(entityRunSpy).toHaveBeenCalledWith({
  80. className: 'MyEntity',
  81. isNonInteractive: true,
  82. config: undefined,
  83. pluginName: 'MyPlugin',
  84. customFields: undefined,
  85. translatable: undefined,
  86. });
  87. });
  88. it('fails when adding an entity without specifying a plugin in non-interactive mode', async () => {
  89. const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
  90. await addCommand({ entity: 'MyEntity' });
  91. expect(entityRunSpy).not.toHaveBeenCalled();
  92. expect(exitSpy).toHaveBeenCalledWith(1);
  93. exitSpy.mockRestore();
  94. });
  95. it('adds a service to the specified plugin', async () => {
  96. await addCommand({
  97. service: 'MyService',
  98. selectedPlugin: 'MyPlugin',
  99. });
  100. expect(serviceRunSpy).toHaveBeenCalledOnce();
  101. expect(serviceRunSpy.mock.calls[0][0]).toMatchObject({
  102. serviceName: 'MyService',
  103. pluginName: 'MyPlugin',
  104. });
  105. });
  106. it('adds a job-queue when required parameters are provided', async () => {
  107. const options = {
  108. jobQueue: 'MyPlugin',
  109. name: 'ReindexJob',
  110. selectedService: 'SearchService',
  111. } as const;
  112. await addCommand(options);
  113. expect(jobQueueRunSpy).toHaveBeenCalledOnce();
  114. expect(jobQueueRunSpy.mock.calls[0][0]).toMatchObject({
  115. pluginName: 'MyPlugin',
  116. name: 'ReindexJob',
  117. selectedService: 'SearchService',
  118. });
  119. });
  120. it('fails when job-queue parameters are incomplete', async () => {
  121. const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
  122. await addCommand({ jobQueue: true, name: 'JobWithoutService' } as any);
  123. expect(jobQueueRunSpy).not.toHaveBeenCalled();
  124. expect(exitSpy).toHaveBeenCalledWith(1);
  125. exitSpy.mockRestore();
  126. });
  127. it('adds codegen configuration with boolean flag (interactive plugin selection)', async () => {
  128. await addCommand({ codegen: true });
  129. expect(codegenRunSpy).toHaveBeenCalledOnce();
  130. expect(codegenRunSpy.mock.calls[0][0]).toMatchObject({ pluginName: undefined });
  131. });
  132. it('adds codegen configuration to a specific plugin when plugin name is supplied', async () => {
  133. await addCommand({ codegen: 'MyPlugin' });
  134. expect(codegenRunSpy).toHaveBeenCalledOnce();
  135. expect(codegenRunSpy.mock.calls[0][0]).toMatchObject({ pluginName: 'MyPlugin' });
  136. });
  137. it('adds an API extension scaffold when queryName is provided', async () => {
  138. await addCommand({
  139. apiExtension: 'MyPlugin',
  140. queryName: 'myQuery',
  141. });
  142. expect(apiExtRunSpy).toHaveBeenCalledOnce();
  143. expect(apiExtRunSpy.mock.calls[0][0]).toMatchObject({
  144. pluginName: 'MyPlugin',
  145. queryName: 'myQuery',
  146. mutationName: undefined,
  147. });
  148. });
  149. it('fails when neither queryName nor mutationName is provided for API extension', async () => {
  150. const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
  151. await addCommand({ apiExtension: true } as any);
  152. expect(apiExtRunSpy).not.toHaveBeenCalled();
  153. expect(exitSpy).toHaveBeenCalledWith(1);
  154. exitSpy.mockRestore();
  155. });
  156. it('adds UI extensions when the uiExtensions flag is used', async () => {
  157. await addCommand({ uiExtensions: 'MyPlugin' });
  158. expect(uiExtRunSpy).toHaveBeenCalledOnce();
  159. expect(uiExtRunSpy.mock.calls[0][0]).toMatchObject({ pluginName: 'MyPlugin' });
  160. });
  161. it('exits with error when no valid operation is specified', async () => {
  162. const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
  163. await addCommand({});
  164. expect(exitSpy).not.toHaveBeenCalled(); // Empty options triggers interactive mode
  165. expect(pluginRunSpy).not.toHaveBeenCalled();
  166. expect(entityRunSpy).not.toHaveBeenCalled();
  167. expect(serviceRunSpy).not.toHaveBeenCalled();
  168. exitSpy.mockRestore();
  169. });
  170. });
  171. });