nodejs-quickstart-structure 1.19.0 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +309 -301
  2. package/LICENSE +15 -15
  3. package/lib/generator.js +139 -139
  4. package/lib/modules/app-setup.js +401 -401
  5. package/lib/modules/config-files.js +151 -151
  6. package/lib/modules/database-setup.js +116 -116
  7. package/lib/modules/project-setup.js +32 -32
  8. package/lib/prompts.js +100 -100
  9. package/package.json +78 -78
  10. package/templates/clean-architecture/js/src/domain/models/User.js +9 -9
  11. package/templates/clean-architecture/js/src/errors/ApiError.js +14 -14
  12. package/templates/clean-architecture/js/src/index.js.ejs +55 -55
  13. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -47
  14. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +36 -36
  15. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -63
  16. package/templates/clean-architecture/js/src/infrastructure/webserver/middleware/errorMiddleware.js +30 -30
  17. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +89 -89
  18. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js.ejs +6 -6
  19. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -13
  20. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -31
  21. package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -5
  22. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -6
  23. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -6
  24. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -38
  25. package/templates/clean-architecture/js/src/usecases/CreateUser.js +14 -14
  26. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -51
  27. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -12
  28. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -61
  29. package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -9
  30. package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -46
  31. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +6 -6
  32. package/templates/clean-architecture/ts/src/domain/user.ts +7 -7
  33. package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -15
  34. package/templates/clean-architecture/ts/src/index.ts.ejs +139 -139
  35. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +63 -63
  36. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +36 -36
  37. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -32
  38. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -17
  39. package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -3
  40. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -4
  41. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -4
  42. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -40
  43. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -51
  44. package/templates/clean-architecture/ts/src/usecases/createUser.ts +13 -13
  45. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -63
  46. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -10
  47. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +27 -27
  48. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -7
  49. package/templates/common/.cursorrules.ejs +60 -60
  50. package/templates/common/.dockerignore +12 -12
  51. package/templates/common/.env.example.ejs +41 -41
  52. package/templates/common/.gitlab-ci.yml.ejs +86 -86
  53. package/templates/common/.lintstagedrc +6 -6
  54. package/templates/common/.prettierrc +7 -7
  55. package/templates/common/Dockerfile +73 -73
  56. package/templates/common/Jenkinsfile.ejs +87 -87
  57. package/templates/common/SECURITY.md +20 -20
  58. package/templates/common/_github/workflows/ci.yml.ejs +46 -46
  59. package/templates/common/_github/workflows/security.yml.ejs +36 -36
  60. package/templates/common/_gitignore +5 -5
  61. package/templates/common/_husky/pre-commit +4 -4
  62. package/templates/common/caching/clean/js/CreateUser.js.ejs +29 -29
  63. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +37 -37
  64. package/templates/common/caching/clean/ts/createUser.ts.ejs +27 -27
  65. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +34 -34
  66. package/templates/common/caching/js/memoryCache.js.ejs +60 -60
  67. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -101
  68. package/templates/common/caching/js/redisClient.js.ejs +75 -75
  69. package/templates/common/caching/js/redisClient.spec.js.ejs +147 -147
  70. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -102
  71. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -157
  72. package/templates/common/database/js/database.js.ejs +19 -19
  73. package/templates/common/database/js/database.spec.js.ejs +56 -56
  74. package/templates/common/database/js/mongoose.js.ejs +33 -33
  75. package/templates/common/database/js/mongoose.spec.js.ejs +43 -43
  76. package/templates/common/database/ts/database.spec.ts.ejs +56 -56
  77. package/templates/common/database/ts/database.ts.ejs +21 -21
  78. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -42
  79. package/templates/common/database/ts/mongoose.ts.ejs +28 -28
  80. package/templates/common/docker-compose.yml.ejs +159 -159
  81. package/templates/common/ecosystem.config.js.ejs +40 -40
  82. package/templates/common/eslint.config.mjs.ejs +77 -77
  83. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -70
  84. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -76
  85. package/templates/common/jest.config.js.ejs +32 -32
  86. package/templates/common/kafka/js/config/kafka.js +9 -9
  87. package/templates/common/kafka/js/config/kafka.spec.js.ejs +27 -27
  88. package/templates/common/kafka/js/messaging/baseConsumer.spec.js.ejs +58 -58
  89. package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -27
  90. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +106 -106
  91. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +27 -27
  92. package/templates/common/kafka/ts/config/kafka.ts +7 -7
  93. package/templates/common/kafka/ts/messaging/baseConsumer.spec.ts.ejs +50 -50
  94. package/templates/common/kafka/ts/messaging/baseConsumer.ts.ejs +27 -27
  95. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +81 -81
  96. package/templates/common/migrate-mongo-config.js.ejs +31 -31
  97. package/templates/common/migrations/init.js.ejs +23 -23
  98. package/templates/common/package.json.ejs +119 -118
  99. package/templates/common/prompts/add-feature.md.ejs +26 -26
  100. package/templates/common/prompts/project-context.md.ejs +43 -43
  101. package/templates/common/prompts/troubleshoot.md.ejs +28 -28
  102. package/templates/common/public/css/style.css +147 -147
  103. package/templates/common/scripts/run-e2e.js.ejs +63 -63
  104. package/templates/common/sonar-project.properties.ejs +27 -27
  105. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -79
  106. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -94
  107. package/templates/common/tsconfig.json +22 -22
  108. package/templates/common/views/ejs/index.ejs +55 -55
  109. package/templates/common/views/pug/index.pug +40 -40
  110. package/templates/mvc/js/src/config/env.js.ejs +46 -46
  111. package/templates/mvc/js/src/config/swagger.js.ejs +6 -6
  112. package/templates/mvc/js/src/errors/ApiError.js +14 -14
  113. package/templates/mvc/js/src/graphql/context.js.ejs +7 -7
  114. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -29
  115. package/templates/mvc/js/src/graphql/index.js.ejs +5 -5
  116. package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -6
  117. package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -6
  118. package/templates/mvc/js/src/index.js.ejs +136 -136
  119. package/templates/mvc/js/src/utils/errorMiddleware.js +29 -29
  120. package/templates/mvc/js/src/utils/httpCodes.js +9 -9
  121. package/templates/mvc/js/src/utils/logger.js +40 -40
  122. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -63
  123. package/templates/mvc/ts/src/config/env.ts.ejs +45 -45
  124. package/templates/mvc/ts/src/config/swagger.ts.ejs +6 -6
  125. package/templates/mvc/ts/src/errors/ApiError.ts +15 -15
  126. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -30
  127. package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -12
  128. package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -3
  129. package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -4
  130. package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -4
  131. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +27 -27
  132. package/templates/mvc/ts/src/utils/httpCodes.ts +7 -7
  133. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +63 -63
  134. package/templates/mvc/ts/src/utils/logger.ts +36 -36
@@ -1,106 +1,106 @@
1
- let kafka;
2
- let connectKafka, sendMessage, disconnectKafka;
3
-
4
- jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>', () => ({
5
- kafka: {
6
- producer: jest.fn().mockReturnValue({
7
- connect: jest.fn().mockResolvedValue(undefined),
8
- send: jest.fn().mockResolvedValue(undefined),
9
- disconnect: jest.fn().mockResolvedValue(undefined),
10
- }),
11
- consumer: jest.fn().mockReturnValue({
12
- connect: jest.fn().mockResolvedValue(undefined),
13
- subscribe: jest.fn().mockResolvedValue(undefined),
14
- run: jest.fn().mockResolvedValue(undefined),
15
- disconnect: jest.fn().mockResolvedValue(undefined),
16
- }),
17
- },
18
- }));
19
-
20
- jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
21
-
22
- describe('Kafka Client', () => {
23
- beforeEach(async () => {
24
- jest.resetModules();
25
- jest.clearAllMocks();
26
- jest.useFakeTimers();
27
- jest.spyOn(global, 'setTimeout');
28
- kafka = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>').kafka;
29
- ({ connectKafka, sendMessage, disconnectKafka } = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>'));
30
- });
31
-
32
- it('should connect producer and consumer', async () => {
33
- await connectKafka();
34
- const producer = kafka.producer.mock.results[0].value;
35
- const consumer = kafka.consumer.mock.results[0].value;
36
-
37
- expect(producer.connect).toHaveBeenCalled();
38
- expect(consumer.connect).toHaveBeenCalled();
39
- expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ fromBeginning: true }));
40
- const subscribeCall = consumer.subscribe.mock.calls.find(call => call[0].topic === 'user-topic' || call[0].topic === 'test-topic');
41
- expect(subscribeCall).toBeDefined();
42
- });
43
-
44
- it('should send a message', async () => {
45
- await connectKafka();
46
- const topic = 'test-topic';
47
- const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
48
- await sendMessage(topic, message);
49
- const producer = kafka.producer.mock.results[0].value;
50
-
51
- expect(producer.send).toHaveBeenCalledWith({
52
- topic,
53
- messages: [{ value: message }],
54
- });
55
- });
56
-
57
- it('should retry connection on failure', async () => {
58
- // We need to re-require to get a fresh state
59
- jest.resetModules();
60
- const { connectKafka: retryConnectKafka } = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>');
61
- const kafkaConfig = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>');
62
-
63
- const newProducer = kafkaConfig.kafka.producer();
64
- // Mock the next producer creation to return our controlled producer
65
- kafkaConfig.kafka.producer.mockReturnValueOnce(newProducer);
66
-
67
- newProducer.connect
68
- .mockRejectedValueOnce(new Error('Connection failed'))
69
- .mockResolvedValueOnce(undefined);
70
-
71
- const connectPromise = retryConnectKafka(2);
72
-
73
- await jest.advanceTimersByTimeAsync(10000);
74
- await connectPromise;
75
- expect(newProducer.connect).toHaveBeenCalledTimes(2);
76
- });
77
-
78
- it('should throw error if producer not connected', async () => {
79
- await expect(sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
80
- });
81
-
82
- it('should log error when sendMessage fails', async () => {
83
- await connectKafka();
84
- const producer = kafka.producer.mock.results[0].value;
85
- producer.send.mockRejectedValue(new Error('Send failed'));
86
-
87
- await expect(sendMessage('test-topic', 'msg')).rejects.toThrow('Send failed');
88
- });
89
-
90
- it('should disconnect Kafka', async () => {
91
- await connectKafka();
92
- const producer = kafka.producer.mock.results[0].value;
93
- const consumer = kafka.consumer.mock.results[0].value;
94
-
95
- await disconnectKafka();
96
-
97
- expect(producer.disconnect).toHaveBeenCalled();
98
- expect(consumer.disconnect).toHaveBeenCalled();
99
- });
100
-
101
- it('should allow connecting with custom retries', async () => {
102
- await connectKafka(5);
103
- // This is mainly for coverage of the retries parameter
104
- expect(kafka.producer).toHaveBeenCalled();
105
- });
106
- });
1
+ let kafka;
2
+ let connectKafka, sendMessage, disconnectKafka;
3
+
4
+ jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>', () => ({
5
+ kafka: {
6
+ producer: jest.fn().mockReturnValue({
7
+ connect: jest.fn().mockResolvedValue(undefined),
8
+ send: jest.fn().mockResolvedValue(undefined),
9
+ disconnect: jest.fn().mockResolvedValue(undefined),
10
+ }),
11
+ consumer: jest.fn().mockReturnValue({
12
+ connect: jest.fn().mockResolvedValue(undefined),
13
+ subscribe: jest.fn().mockResolvedValue(undefined),
14
+ run: jest.fn().mockResolvedValue(undefined),
15
+ disconnect: jest.fn().mockResolvedValue(undefined),
16
+ }),
17
+ },
18
+ }));
19
+
20
+ jest.mock('<% if (architecture === "Clean Architecture") { %>@/infrastructure/log/logger<% } else { %>@/utils/logger<% } %>');
21
+
22
+ describe('Kafka Client', () => {
23
+ beforeEach(async () => {
24
+ jest.resetModules();
25
+ jest.clearAllMocks();
26
+ jest.useFakeTimers();
27
+ jest.spyOn(global, 'setTimeout');
28
+ kafka = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>').kafka;
29
+ ({ connectKafka, sendMessage, disconnectKafka } = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>'));
30
+ });
31
+
32
+ it('should connect producer and consumer', async () => {
33
+ await connectKafka();
34
+ const producer = kafka.producer.mock.results[0].value;
35
+ const consumer = kafka.consumer.mock.results[0].value;
36
+
37
+ expect(producer.connect).toHaveBeenCalled();
38
+ expect(consumer.connect).toHaveBeenCalled();
39
+ expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ fromBeginning: true }));
40
+ const subscribeCall = consumer.subscribe.mock.calls.find(call => call[0].topic === 'user-topic' || call[0].topic === 'test-topic');
41
+ expect(subscribeCall).toBeDefined();
42
+ });
43
+
44
+ it('should send a message', async () => {
45
+ await connectKafka();
46
+ const topic = 'test-topic';
47
+ const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
48
+ await sendMessage(topic, message);
49
+ const producer = kafka.producer.mock.results[0].value;
50
+
51
+ expect(producer.send).toHaveBeenCalledWith({
52
+ topic,
53
+ messages: [{ value: message }],
54
+ });
55
+ });
56
+
57
+ it('should retry connection on failure', async () => {
58
+ // We need to re-require to get a fresh state
59
+ jest.resetModules();
60
+ const { connectKafka: retryConnectKafka } = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>');
61
+ const kafkaConfig = require('<% if (architecture === "Clean Architecture") { %>@/infrastructure/config/kafka<% } else { %>@/config/kafka<% } %>');
62
+
63
+ const newProducer = kafkaConfig.kafka.producer();
64
+ // Mock the next producer creation to return our controlled producer
65
+ kafkaConfig.kafka.producer.mockReturnValueOnce(newProducer);
66
+
67
+ newProducer.connect
68
+ .mockRejectedValueOnce(new Error('Connection failed'))
69
+ .mockResolvedValueOnce(undefined);
70
+
71
+ const connectPromise = retryConnectKafka(2);
72
+
73
+ await jest.advanceTimersByTimeAsync(10000);
74
+ await connectPromise;
75
+ expect(newProducer.connect).toHaveBeenCalledTimes(2);
76
+ });
77
+
78
+ it('should throw error if producer not connected', async () => {
79
+ await expect(sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
80
+ });
81
+
82
+ it('should log error when sendMessage fails', async () => {
83
+ await connectKafka();
84
+ const producer = kafka.producer.mock.results[0].value;
85
+ producer.send.mockRejectedValue(new Error('Send failed'));
86
+
87
+ await expect(sendMessage('test-topic', 'msg')).rejects.toThrow('Send failed');
88
+ });
89
+
90
+ it('should disconnect Kafka', async () => {
91
+ await connectKafka();
92
+ const producer = kafka.producer.mock.results[0].value;
93
+ const consumer = kafka.consumer.mock.results[0].value;
94
+
95
+ await disconnectKafka();
96
+
97
+ expect(producer.disconnect).toHaveBeenCalled();
98
+ expect(consumer.disconnect).toHaveBeenCalled();
99
+ });
100
+
101
+ it('should allow connecting with custom retries', async () => {
102
+ await connectKafka(5);
103
+ // This is mainly for coverage of the retries parameter
104
+ expect(kafka.producer).toHaveBeenCalled();
105
+ });
106
+ });
@@ -1,27 +1,27 @@
1
- import { Kafka } from 'kafkajs';
2
-
3
- jest.mock('kafkajs', () => {
4
- return {
5
- Kafka: jest.fn().mockImplementation(() => ({
6
- producer: jest.fn(),
7
- consumer: jest.fn(),
8
- })),
9
- };
10
- });
11
-
12
- jest.mock('@/config/env', () => ({
13
- env: {
14
- KAFKA_BROKER: 'localhost:9092'
15
- }
16
- }));
17
-
18
- describe('Kafka Configuration', () => {
19
- beforeEach(() => {
20
- jest.clearAllMocks();
21
- });
22
-
23
- it('should initialize Kafka', () => {
24
- require('<% if (architecture === "MVC") { %>@/config/kafka<% } else { %>@/infrastructure/config/kafka<% } %>');
25
- expect(Kafka).toHaveBeenCalled();
26
- });
27
- });
1
+ import { Kafka } from 'kafkajs';
2
+
3
+ jest.mock('kafkajs', () => {
4
+ return {
5
+ Kafka: jest.fn().mockImplementation(() => ({
6
+ producer: jest.fn(),
7
+ consumer: jest.fn(),
8
+ })),
9
+ };
10
+ });
11
+
12
+ jest.mock('@/config/env', () => ({
13
+ env: {
14
+ KAFKA_BROKER: 'localhost:9092'
15
+ }
16
+ }));
17
+
18
+ describe('Kafka Configuration', () => {
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ it('should initialize Kafka', () => {
24
+ require('<% if (architecture === "MVC") { %>@/config/kafka<% } else { %>@/infrastructure/config/kafka<% } %>');
25
+ expect(Kafka).toHaveBeenCalled();
26
+ });
27
+ });
@@ -1,7 +1,7 @@
1
- import { Kafka } from 'kafkajs';
2
- import { env } from '@/config/env';
3
-
4
- export const kafka = new Kafka({
5
- clientId: 'nodejs-service',
6
- brokers: [env.KAFKA_BROKER]
7
- });
1
+ import { Kafka } from 'kafkajs';
2
+ import { env } from '@/config/env';
3
+
4
+ export const kafka = new Kafka({
5
+ clientId: 'nodejs-service',
6
+ brokers: [env.KAFKA_BROKER]
7
+ });
@@ -1,50 +1,50 @@
1
- import { BaseConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/baseConsumer<% } else { %>@/messaging/baseConsumer<% } %>';
2
- import logger from '<%= loggerPath %>';
3
-
4
- jest.mock('<%= loggerPath %>');
5
-
6
- class TestConsumer extends BaseConsumer {
7
- constructor() {
8
- super();
9
- }
10
- get topic() { return 'test-topic'; }
11
- get groupId() { return 'test-group'; }
12
- async handle(data: any) {
13
- logger.info('Handled', data);
14
- }
15
- }
16
-
17
- describe('BaseConsumer', () => {
18
- let consumer: TestConsumer;
19
-
20
- beforeEach(() => {
21
- jest.clearAllMocks();
22
- consumer = new TestConsumer();
23
- });
24
-
25
- it('should throw error if instantiated directly', () => {
26
- // @ts-expect-error: Abstract class cannot be instantiated
27
- expect(() => new BaseConsumer()).toThrow("Abstract class 'BaseConsumer' cannot be instantiated.");
28
- });
29
-
30
- it('should process a valid message', async () => {
31
- const message = { value: Buffer.from(JSON.stringify({ test: 'data' })) };
32
- await consumer.onMessage({ message } as any);
33
- expect(logger.info).toHaveBeenCalledWith('Handled', { test: 'data' });
34
- });
35
-
36
- it('should handle invalid JSON', async () => {
37
- const message = { value: Buffer.from('invalid-json') };
38
- await consumer.onMessage({ message } as any);
39
- expect(logger.error).toHaveBeenCalledWith(
40
- expect.stringContaining('[Kafka] Error processing message on topic test-topic:'),
41
- expect.anything()
42
- );
43
- });
44
-
45
- it('should skip empty messages', async () => {
46
- const message = { value: null };
47
- await consumer.onMessage({ message } as any);
48
- expect(logger.info).not.toHaveBeenCalled();
49
- });
50
- });
1
+ import { BaseConsumer } from '<% if (architecture === "Clean Architecture") { %>@/interfaces/messaging/baseConsumer<% } else { %>@/messaging/baseConsumer<% } %>';
2
+ import logger from '<%= loggerPath %>';
3
+
4
+ jest.mock('<%= loggerPath %>');
5
+
6
+ class TestConsumer extends BaseConsumer {
7
+ constructor() {
8
+ super();
9
+ }
10
+ get topic() { return 'test-topic'; }
11
+ get groupId() { return 'test-group'; }
12
+ async handle(data: any) {
13
+ logger.info('Handled', data);
14
+ }
15
+ }
16
+
17
+ describe('BaseConsumer', () => {
18
+ let consumer: TestConsumer;
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ consumer = new TestConsumer();
23
+ });
24
+
25
+ it('should throw error if instantiated directly', () => {
26
+ // @ts-expect-error: Abstract class cannot be instantiated
27
+ expect(() => new BaseConsumer()).toThrow("Abstract class 'BaseConsumer' cannot be instantiated.");
28
+ });
29
+
30
+ it('should process a valid message', async () => {
31
+ const message = { value: Buffer.from(JSON.stringify({ test: 'data' })) };
32
+ await consumer.onMessage({ message } as any);
33
+ expect(logger.info).toHaveBeenCalledWith('Handled', { test: 'data' });
34
+ });
35
+
36
+ it('should handle invalid JSON', async () => {
37
+ const message = { value: Buffer.from('invalid-json') };
38
+ await consumer.onMessage({ message } as any);
39
+ expect(logger.error).toHaveBeenCalledWith(
40
+ expect.stringContaining('[Kafka] Error processing message on topic test-topic:'),
41
+ expect.anything()
42
+ );
43
+ });
44
+
45
+ it('should skip empty messages', async () => {
46
+ const message = { value: null };
47
+ await consumer.onMessage({ message } as any);
48
+ expect(logger.info).not.toHaveBeenCalled();
49
+ });
50
+ });
@@ -1,27 +1,27 @@
1
- import { EachMessagePayload } from 'kafkajs';
2
- import logger from '<%= loggerPath %>';
3
-
4
- export abstract class BaseConsumer {
5
- abstract topic: string;
6
- abstract groupId: string;
7
-
8
- public constructor() {
9
- if (new.target === BaseConsumer) {
10
- throw new Error("Abstract class 'BaseConsumer' cannot be instantiated.");
11
- }
12
- }
13
-
14
- async onMessage({ message }: EachMessagePayload) {
15
- try {
16
- const rawValue = message.value?.toString();
17
- if (!rawValue) return;
18
-
19
- const data = JSON.parse(rawValue);
20
- await this.handle(data);
21
- } catch (error) {
22
- logger.error(`[Kafka] Error processing message on topic ${this.topic}:`, error);
23
- }
24
- }
25
-
26
- abstract handle(data: unknown): Promise<void>;
27
- }
1
+ import { EachMessagePayload } from 'kafkajs';
2
+ import logger from '<%= loggerPath %>';
3
+
4
+ export abstract class BaseConsumer {
5
+ abstract topic: string;
6
+ abstract groupId: string;
7
+
8
+ public constructor() {
9
+ if (new.target === BaseConsumer) {
10
+ throw new Error("Abstract class 'BaseConsumer' cannot be instantiated.");
11
+ }
12
+ }
13
+
14
+ async onMessage({ message }: EachMessagePayload) {
15
+ try {
16
+ const rawValue = message.value?.toString();
17
+ if (!rawValue) return;
18
+
19
+ const data = JSON.parse(rawValue);
20
+ await this.handle(data);
21
+ } catch (error) {
22
+ logger.error(`[Kafka] Error processing message on topic ${this.topic}:`, error);
23
+ }
24
+ }
25
+
26
+ abstract handle(data: unknown): Promise<void>;
27
+ }
@@ -1,81 +1,81 @@
1
- import { KafkaService } from '<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>';
2
- import { kafka } from '<%= configPath %>';
3
-
4
- jest.mock('<%= configPath %>', () => ({
5
- kafka: {
6
- producer: jest.fn().mockReturnValue({
7
- connect: jest.fn().mockResolvedValue(undefined),
8
- send: jest.fn().mockResolvedValue(undefined),
9
- disconnect: jest.fn().mockResolvedValue(undefined),
10
- }),
11
- consumer: jest.fn().mockReturnValue({
12
- connect: jest.fn().mockResolvedValue(undefined),
13
- subscribe: jest.fn().mockResolvedValue(undefined),
14
- run: jest.fn().mockResolvedValue(undefined),
15
- disconnect: jest.fn().mockResolvedValue(undefined),
16
- }),
17
- },
18
- }));
19
-
20
- jest.mock('<%= loggerPath %>');
21
-
22
- describe('KafkaService', () => {
23
- let kafkaService: KafkaService;
24
-
25
- beforeEach(() => {
26
- jest.clearAllMocks();
27
- kafkaService = new KafkaService();
28
- });
29
-
30
- it('should connect producer and consumer', async () => {
31
- await kafkaService.connect();
32
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
33
- const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
34
-
35
- expect(producer.connect).toHaveBeenCalled();
36
- expect(consumer.connect).toHaveBeenCalled();
37
- expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ topic: 'user-topic', fromBeginning: true }));
38
- expect(consumer.run).toHaveBeenCalled();
39
- });
40
-
41
- it('should send a message', async () => {
42
- await kafkaService.connect();
43
- const topic = 'test-topic';
44
- const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
45
- await kafkaService.sendMessage(topic, message);
46
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
47
-
48
- expect(producer.send).toHaveBeenCalledWith({
49
- topic,
50
- messages: [{ value: message }],
51
- });
52
- });
53
-
54
- it('should retry connection on failure', async () => {
55
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
56
- producer.connect
57
- .mockRejectedValueOnce(new Error('Connection failed'))
58
- .mockResolvedValueOnce(undefined);
59
-
60
- jest.useFakeTimers();
61
- const connectPromise = kafkaService.connect(2);
62
-
63
- await jest.advanceTimersByTimeAsync(10000);
64
- await connectPromise;
65
-
66
- expect(producer.connect).toHaveBeenCalledTimes(2);
67
- });
68
-
69
- it('should throw error if producer not connected', async () => {
70
- await expect(kafkaService.sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
71
- });
72
-
73
- it('should disconnect producer and consumer', async () => {
74
- await kafkaService.disconnect();
75
- const producer = (kafka.producer as jest.Mock).mock.results[0].value;
76
- const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
77
-
78
- expect(producer.disconnect).toHaveBeenCalled();
79
- expect(consumer.disconnect).toHaveBeenCalled();
80
- });
81
- });
1
+ import { KafkaService } from '<% if (architecture === "Clean Architecture") { %>@/infrastructure/messaging/kafkaClient<% } else { %>@/services/kafkaService<% } %>';
2
+ import { kafka } from '<%= configPath %>';
3
+
4
+ jest.mock('<%= configPath %>', () => ({
5
+ kafka: {
6
+ producer: jest.fn().mockReturnValue({
7
+ connect: jest.fn().mockResolvedValue(undefined),
8
+ send: jest.fn().mockResolvedValue(undefined),
9
+ disconnect: jest.fn().mockResolvedValue(undefined),
10
+ }),
11
+ consumer: jest.fn().mockReturnValue({
12
+ connect: jest.fn().mockResolvedValue(undefined),
13
+ subscribe: jest.fn().mockResolvedValue(undefined),
14
+ run: jest.fn().mockResolvedValue(undefined),
15
+ disconnect: jest.fn().mockResolvedValue(undefined),
16
+ }),
17
+ },
18
+ }));
19
+
20
+ jest.mock('<%= loggerPath %>');
21
+
22
+ describe('KafkaService', () => {
23
+ let kafkaService: KafkaService;
24
+
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ kafkaService = new KafkaService();
28
+ });
29
+
30
+ it('should connect producer and consumer', async () => {
31
+ await kafkaService.connect();
32
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
33
+ const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
34
+
35
+ expect(producer.connect).toHaveBeenCalled();
36
+ expect(consumer.connect).toHaveBeenCalled();
37
+ expect(consumer.subscribe).toHaveBeenCalledWith(expect.objectContaining({ topic: 'user-topic', fromBeginning: true }));
38
+ expect(consumer.run).toHaveBeenCalled();
39
+ });
40
+
41
+ it('should send a message', async () => {
42
+ await kafkaService.connect();
43
+ const topic = 'test-topic';
44
+ const message = JSON.stringify({ action: 'TEST', payload: { email: 'test@example.com' } });
45
+ await kafkaService.sendMessage(topic, message);
46
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
47
+
48
+ expect(producer.send).toHaveBeenCalledWith({
49
+ topic,
50
+ messages: [{ value: message }],
51
+ });
52
+ });
53
+
54
+ it('should retry connection on failure', async () => {
55
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
56
+ producer.connect
57
+ .mockRejectedValueOnce(new Error('Connection failed'))
58
+ .mockResolvedValueOnce(undefined);
59
+
60
+ jest.useFakeTimers();
61
+ const connectPromise = kafkaService.connect(2);
62
+
63
+ await jest.advanceTimersByTimeAsync(10000);
64
+ await connectPromise;
65
+
66
+ expect(producer.connect).toHaveBeenCalledTimes(2);
67
+ });
68
+
69
+ it('should throw error if producer not connected', async () => {
70
+ await expect(kafkaService.sendMessage('topic', 'msg')).rejects.toThrow('[Kafka] Producer not connected');
71
+ });
72
+
73
+ it('should disconnect producer and consumer', async () => {
74
+ await kafkaService.disconnect();
75
+ const producer = (kafka.producer as jest.Mock).mock.results[0].value;
76
+ const consumer = (kafka.consumer as jest.Mock).mock.results[0].value;
77
+
78
+ expect(producer.disconnect).toHaveBeenCalled();
79
+ expect(consumer.disconnect).toHaveBeenCalled();
80
+ });
81
+ });