nodejs-quickstart-structure 1.18.0 → 1.19.0
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.
- package/CHANGELOG.md +17 -4
- package/README.md +2 -1
- package/bin/index.js +93 -92
- package/lib/generator.js +1 -1
- package/lib/modules/caching-setup.js +76 -73
- package/lib/modules/config-files.js +4 -0
- package/lib/modules/kafka-setup.js +249 -191
- package/lib/modules/project-setup.js +1 -0
- package/package.json +13 -2
- package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -10
- package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -21
- package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -10
- package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -21
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +69 -39
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -81
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +1 -1
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +156 -75
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -138
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -21
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -49
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -17
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +12 -10
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js +11 -0
- package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +47 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js +11 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +48 -0
- package/templates/clean-architecture/js/src/utils/errorMessages.js +14 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
- package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -8
- package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -21
- package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -8
- package/templates/clean-architecture/ts/src/index.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -85
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +74 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -185
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +173 -84
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -21
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -15
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +13 -11
- package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +47 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +9 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +48 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts +9 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
- package/templates/common/.gitattributes +46 -0
- package/templates/common/.snyk.ejs +45 -0
- package/templates/common/Dockerfile +17 -9
- package/templates/common/README.md.ejs +295 -263
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
- package/templates/common/caching/ts/memoryCache.ts.ejs +73 -64
- package/templates/common/caching/ts/redisClient.ts.ejs +89 -80
- package/templates/common/database/js/models/User.js.ejs +79 -53
- package/templates/common/database/js/models/User.js.mongoose.ejs +23 -19
- package/templates/common/database/js/models/User.spec.js.ejs +94 -84
- package/templates/common/database/ts/models/User.spec.ts.ejs +100 -84
- package/templates/common/database/ts/models/User.ts.ejs +87 -61
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +30 -25
- package/templates/common/health/js/healthRoute.js.ejs +50 -47
- package/templates/common/health/ts/healthRoute.ts.ejs +49 -46
- package/templates/common/jest.e2e.config.js.ejs +8 -8
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -30
- package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -11
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -31
- package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -49
- package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -93
- package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
- package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -51
- package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -11
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -49
- package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -25
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -95
- package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
- package/templates/common/package.json.ejs +10 -2
- package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -61
- package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -160
- package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -158
- package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -55
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -49
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -49
- package/templates/common/swagger.yml.ejs +118 -66
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -9
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -9
- package/templates/mvc/js/src/controllers/userController.js.ejs +246 -105
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -209
- package/templates/mvc/js/src/errors/BadRequestError.js +11 -10
- package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -21
- package/templates/mvc/js/src/errors/NotFoundError.js +11 -10
- package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -21
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -19
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -47
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -17
- package/templates/mvc/js/src/index.js.ejs +1 -1
- package/templates/mvc/js/src/routes/api.js +10 -8
- package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -36
- package/templates/mvc/js/src/utils/errorMessages.js +14 -0
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -203
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +248 -107
- package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -21
- package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -8
- package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -21
- package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -8
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -51
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -21
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -15
- package/templates/mvc/ts/src/index.ts.ejs +156 -153
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -40
- package/templates/mvc/ts/src/routes/api.ts +12 -10
- package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +0 -37
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const DeleteUser = require('@/usecases/DeleteUser');
|
|
2
|
+
const UserRepository = require('@/infrastructure/repositories/UserRepository');
|
|
3
|
+
<%_ if (caching !== 'None') { -%>
|
|
4
|
+
const cacheService = require('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>');
|
|
5
|
+
<%_ } -%>
|
|
6
|
+
|
|
7
|
+
jest.mock('@/infrastructure/repositories/UserRepository');
|
|
8
|
+
<%_ if (caching !== 'None') { -%>
|
|
9
|
+
jest.mock('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>', () => ({
|
|
10
|
+
get: jest.fn(),
|
|
11
|
+
set: jest.fn(),
|
|
12
|
+
del: jest.fn()
|
|
13
|
+
}));
|
|
14
|
+
<%_ } -%>
|
|
15
|
+
|
|
16
|
+
describe('DeleteUser UseCase', () => {
|
|
17
|
+
let deleteUser;
|
|
18
|
+
let mockUserRepository;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockUserRepository = new UserRepository();
|
|
22
|
+
deleteUser = new DeleteUser(mockUserRepository);
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should delete and return the user', async () => {
|
|
27
|
+
const id = 1;
|
|
28
|
+
const expectedResult = { id, name: 'Deleted User', email: 'test@test.com' };
|
|
29
|
+
|
|
30
|
+
mockUserRepository.delete.mockResolvedValue(expectedResult);
|
|
31
|
+
|
|
32
|
+
const result = await deleteUser.execute(id);
|
|
33
|
+
|
|
34
|
+
expect(mockUserRepository.delete).toHaveBeenCalledWith(id);
|
|
35
|
+
expect(result).toEqual(expectedResult);
|
|
36
|
+
<%_ if (caching !== 'None') { -%>
|
|
37
|
+
expect(cacheService.del).toHaveBeenCalledWith('users:all');
|
|
38
|
+
<%_ } %>
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should throw an error if repository fails', async () => {
|
|
42
|
+
const error = new Error('Database error');
|
|
43
|
+
mockUserRepository.delete.mockRejectedValue(error);
|
|
44
|
+
|
|
45
|
+
await expect(deleteUser.execute(1)).rejects.toThrow(error);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const UpdateUser = require('@/usecases/UpdateUser');
|
|
2
|
+
const UserRepository = require('@/infrastructure/repositories/UserRepository');
|
|
3
|
+
<%_ if (caching !== 'None') { -%>
|
|
4
|
+
const cacheService = require('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>');
|
|
5
|
+
<%_ } -%>
|
|
6
|
+
|
|
7
|
+
jest.mock('@/infrastructure/repositories/UserRepository');
|
|
8
|
+
<%_ if (caching !== 'None') { -%>
|
|
9
|
+
jest.mock('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>', () => ({
|
|
10
|
+
get: jest.fn(),
|
|
11
|
+
set: jest.fn(),
|
|
12
|
+
del: jest.fn()
|
|
13
|
+
}));
|
|
14
|
+
<%_ } -%>
|
|
15
|
+
|
|
16
|
+
describe('UpdateUser UseCase', () => {
|
|
17
|
+
let updateUser;
|
|
18
|
+
let mockUserRepository;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockUserRepository = new UserRepository();
|
|
22
|
+
updateUser = new UpdateUser(mockUserRepository);
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should update and return the user', async () => {
|
|
27
|
+
const id = 1;
|
|
28
|
+
const data = { name: 'Updated Name' };
|
|
29
|
+
const expectedUser = { id, name: 'Updated Name', email: 'test@test.com' };
|
|
30
|
+
|
|
31
|
+
mockUserRepository.update.mockResolvedValue(expectedUser);
|
|
32
|
+
|
|
33
|
+
const result = await updateUser.execute(id, data);
|
|
34
|
+
|
|
35
|
+
expect(mockUserRepository.update).toHaveBeenCalledWith(id, data);
|
|
36
|
+
expect(result).toEqual(expectedUser);
|
|
37
|
+
<%_ if (caching !== 'None') { -%>
|
|
38
|
+
expect(cacheService.del).toHaveBeenCalledWith('users:all');
|
|
39
|
+
<%_ } %>
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should throw an error if repository fails', async () => {
|
|
43
|
+
const error = new Error('Database error');
|
|
44
|
+
mockUserRepository.update.mockRejectedValue(error);
|
|
45
|
+
|
|
46
|
+
await expect(updateUser.execute(1, { name: 'Test' })).rejects.toThrow(error);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const ERROR_MESSAGES = {
|
|
2
|
+
USER_NOT_FOUND: 'User not found',
|
|
3
|
+
RESOURCE_NOT_FOUND: 'Resource not found',
|
|
4
|
+
INVALID_USER_DATA: 'Invalid user event data',
|
|
5
|
+
INTERNAL_SERVER_ERROR: 'Internal Server Error',
|
|
6
|
+
BAD_REQUEST: 'Bad Request',
|
|
7
|
+
FETCH_USERS_ERROR: 'Error fetching users',
|
|
8
|
+
CREATE_USER_ERROR: 'Error creating user',
|
|
9
|
+
UPDATE_USER_ERROR: 'Error updating user',
|
|
10
|
+
DELETE_USER_ERROR: 'Error deleting user',
|
|
11
|
+
DATABASE_PING_FAILED: 'Health Check Database Ping Failed',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = ERROR_MESSAGES;
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { BadRequestError } from '@/errors/BadRequestError';
|
|
2
|
-
import { ApiError } from '@/errors/ApiError';
|
|
3
|
-
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
expect(error.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
1
|
+
import { BadRequestError } from '@/errors/BadRequestError';
|
|
2
|
+
import { ApiError } from '@/errors/ApiError';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
+
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
5
|
+
|
|
6
|
+
describe('BadRequestError', () => {
|
|
7
|
+
it('should extend ApiError', () => {
|
|
8
|
+
const error = new BadRequestError();
|
|
9
|
+
expect(error).toBeInstanceOf(ApiError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have default message "Bad Request"', () => {
|
|
13
|
+
const error = new BadRequestError();
|
|
14
|
+
expect(error.message).toBe(ERROR_MESSAGES.BAD_REQUEST);
|
|
15
|
+
expect(error.statusCode).toBe(HTTP_STATUS.BAD_REQUEST);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should accept a custom message', () => {
|
|
19
|
+
const error = new BadRequestError('Custom bad request');
|
|
20
|
+
expect(error.message).toBe('Custom bad request');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ApiError } from '@/errors/ApiError';
|
|
2
|
-
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
import { ApiError } from '@/errors/ApiError';
|
|
2
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
3
|
+
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
4
|
+
|
|
5
|
+
export class BadRequestError extends ApiError {
|
|
6
|
+
constructor(message: string = ERROR_MESSAGES.BAD_REQUEST) {
|
|
7
|
+
super(HTTP_STATUS.BAD_REQUEST, message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { NotFoundError } from '@/errors/NotFoundError';
|
|
2
|
-
import { ApiError } from '@/errors/ApiError';
|
|
3
|
-
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
expect(error.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
1
|
+
import { NotFoundError } from '@/errors/NotFoundError';
|
|
2
|
+
import { ApiError } from '@/errors/ApiError';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
+
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
5
|
+
|
|
6
|
+
describe('NotFoundError', () => {
|
|
7
|
+
it('should extend ApiError', () => {
|
|
8
|
+
const error = new NotFoundError();
|
|
9
|
+
expect(error).toBeInstanceOf(ApiError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have default message "Resource not found"', () => {
|
|
13
|
+
const error = new NotFoundError();
|
|
14
|
+
expect(error.message).toBe(ERROR_MESSAGES.RESOURCE_NOT_FOUND);
|
|
15
|
+
expect(error.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should accept a custom message', () => {
|
|
19
|
+
const error = new NotFoundError(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
20
|
+
expect(error.message).toBe(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ApiError } from '@/errors/ApiError';
|
|
2
|
-
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
import { ApiError } from '@/errors/ApiError';
|
|
2
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
3
|
+
import { ERROR_MESSAGES } from '@/utils/errorMessages';
|
|
4
|
+
|
|
5
|
+
export class NotFoundError extends ApiError {
|
|
6
|
+
constructor(message: string = ERROR_MESSAGES.RESOURCE_NOT_FOUND) {
|
|
7
|
+
super(HTTP_STATUS.NOT_FOUND, message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -16,7 +16,7 @@ import swaggerSpecs from '@/config/swagger';<% } %>
|
|
|
16
16
|
<%_ if (communication === 'Kafka') { -%>import { kafkaService } from '@/infrastructure/messaging/kafkaClient';<%_ } -%>
|
|
17
17
|
<%_ if (communication === 'GraphQL') { -%>
|
|
18
18
|
import { ApolloServer } from '@apollo/server';
|
|
19
|
-
import { expressMiddleware } from '@
|
|
19
|
+
import { expressMiddleware } from '@as-integrations/express4';
|
|
20
20
|
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
|
|
21
21
|
import { unwrapResolverError } from '@apollo/server/errors';
|
|
22
22
|
import { ApiError } from '@/errors/ApiError';
|
package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs
CHANGED
|
@@ -1,85 +1,175 @@
|
|
|
1
|
-
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
2
|
-
import UserModel from '@/infrastructure/database/models/User';
|
|
3
|
-
|
|
4
|
-
// Mock DB Model Database Layer
|
|
5
|
-
jest.mock('@/infrastructure/database/models/User');
|
|
6
|
-
|
|
7
|
-
describe('UserRepository', () => {
|
|
8
|
-
let userRepository: UserRepository;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
userRepository = new UserRepository();
|
|
12
|
-
jest.clearAllMocks();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('save', () => {
|
|
16
|
-
it('should save and return a newly created user (Happy Path)', async () => {
|
|
17
|
-
// Arrange
|
|
18
|
-
const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
|
|
19
|
-
<%_ if (database === 'MongoDB') { -%>
|
|
20
|
-
const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
|
|
21
|
-
(UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
22
|
-
<%_ } else if (database === 'None') { -%>
|
|
23
|
-
(UserModel.create as jest.Mock).mockResolvedValue(payload);
|
|
24
|
-
<%_ } else { -%>
|
|
25
|
-
const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
|
|
26
|
-
(UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
27
|
-
<%_ } -%>
|
|
28
|
-
|
|
29
|
-
// Act
|
|
30
|
-
const result = await userRepository.save(payload);
|
|
31
|
-
|
|
32
|
-
// Assert
|
|
33
|
-
<%_ if (database === 'None') { -%>
|
|
34
|
-
expect(result.name).toEqual(payload.name)
|
|
35
|
-
<%_ } else { -%>
|
|
36
|
-
expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
|
|
37
|
-
expect(UserModel.create).toHaveBeenCalledWith({ name: payload.name, email: payload.email });
|
|
38
|
-
<%_ } -%>
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should throw an error when DB fails explicitly (Edge Case)', async () => {
|
|
42
|
-
<%_ if (database === 'None') { -%>
|
|
43
|
-
// Mocks do not naturally fail
|
|
44
|
-
<%_ } else { -%>
|
|
45
|
-
// Arrange
|
|
46
|
-
const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
|
|
47
|
-
const error = new Error('DB Connection Refused');
|
|
48
|
-
(UserModel.create as jest.Mock).mockRejectedValue(error);
|
|
49
|
-
|
|
50
|
-
// Act & Assert
|
|
51
|
-
await expect(userRepository.save(payload)).rejects.toThrow(error);
|
|
52
|
-
<%_ } -%>
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('getUsers', () => {
|
|
57
|
-
it('should return a list of mapped UserEntities (Happy Path)', async () => {
|
|
58
|
-
// Arrange
|
|
59
|
-
<%_ if (database === 'MongoDB') { -%>
|
|
60
|
-
const mockDbRecords = [{ _id: { toString: () => '1' }, name: 'User1', email: 'user1@example.com' }];
|
|
61
|
-
(UserModel.find as jest.Mock).mockResolvedValue(mockDbRecords);
|
|
62
|
-
<%_ } else if (database === 'None') { -%>
|
|
63
|
-
const mockData = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
|
|
64
|
-
(UserModel.find as jest.Mock).mockResolvedValue(mockData);
|
|
65
|
-
<%_ } else { -%>
|
|
66
|
-
const mockDbRecords = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
|
|
67
|
-
(UserModel.findAll as jest.Mock).mockResolvedValue(mockDbRecords);
|
|
68
|
-
<%_ } -%>
|
|
69
|
-
|
|
70
|
-
// Act
|
|
71
|
-
const result = await userRepository.getUsers();
|
|
72
|
-
|
|
73
|
-
// Assert
|
|
74
|
-
expect(result).toHaveLength(1);
|
|
75
|
-
expect(result[0]).toEqual({ id: '1', name: 'User1', email: 'user1@example.com' });
|
|
76
|
-
<%_ if (database !== 'None') { -%>
|
|
77
|
-
<%_ if (database === 'MongoDB') { -%>
|
|
78
|
-
expect(UserModel.find).toHaveBeenCalled();
|
|
79
|
-
<%_ } else { -%>
|
|
80
|
-
expect(UserModel.findAll).toHaveBeenCalled();
|
|
81
|
-
<%_ } -%>
|
|
82
|
-
<%_ } -%>
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
1
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
2
|
+
import UserModel from '@/infrastructure/database/models/User';
|
|
3
|
+
|
|
4
|
+
// Mock DB Model Database Layer
|
|
5
|
+
jest.mock('@/infrastructure/database/models/User');
|
|
6
|
+
|
|
7
|
+
describe('UserRepository', () => {
|
|
8
|
+
let userRepository: UserRepository;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
userRepository = new UserRepository();
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('save', () => {
|
|
16
|
+
it('should save and return a newly created user (Happy Path)', async () => {
|
|
17
|
+
// Arrange
|
|
18
|
+
const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
|
|
19
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
20
|
+
const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
|
|
21
|
+
(UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
22
|
+
<%_ } else if (database === 'None') { -%>
|
|
23
|
+
(UserModel.create as jest.Mock).mockResolvedValue(payload);
|
|
24
|
+
<%_ } else { -%>
|
|
25
|
+
const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
|
|
26
|
+
(UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
27
|
+
<%_ } -%>
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
const result = await userRepository.save(payload);
|
|
31
|
+
|
|
32
|
+
// Assert
|
|
33
|
+
<%_ if (database === 'None') { -%>
|
|
34
|
+
expect(result.name).toEqual(payload.name)
|
|
35
|
+
<%_ } else { -%>
|
|
36
|
+
expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
|
|
37
|
+
expect(UserModel.create).toHaveBeenCalledWith({ name: payload.name, email: payload.email });
|
|
38
|
+
<%_ } -%>
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should throw an error when DB fails explicitly (Edge Case)', async () => {
|
|
42
|
+
<%_ if (database === 'None') { -%>
|
|
43
|
+
// Mocks do not naturally fail
|
|
44
|
+
<%_ } else { -%>
|
|
45
|
+
// Arrange
|
|
46
|
+
const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
|
|
47
|
+
const error = new Error('DB Connection Refused');
|
|
48
|
+
(UserModel.create as jest.Mock).mockRejectedValue(error);
|
|
49
|
+
|
|
50
|
+
// Act & Assert
|
|
51
|
+
await expect(userRepository.save(payload)).rejects.toThrow(error);
|
|
52
|
+
<%_ } -%>
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getUsers', () => {
|
|
57
|
+
it('should return a list of mapped UserEntities (Happy Path)', async () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
60
|
+
const mockDbRecords = [{ _id: { toString: () => '1' }, name: 'User1', email: 'user1@example.com' }];
|
|
61
|
+
(UserModel.find as jest.Mock).mockResolvedValue(mockDbRecords);
|
|
62
|
+
<%_ } else if (database === 'None') { -%>
|
|
63
|
+
const mockData = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
|
|
64
|
+
(UserModel.find as jest.Mock).mockResolvedValue(mockData);
|
|
65
|
+
<%_ } else { -%>
|
|
66
|
+
const mockDbRecords = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
|
|
67
|
+
(UserModel.findAll as jest.Mock).mockResolvedValue(mockDbRecords);
|
|
68
|
+
<%_ } -%>
|
|
69
|
+
|
|
70
|
+
// Act
|
|
71
|
+
const result = await userRepository.getUsers();
|
|
72
|
+
|
|
73
|
+
// Assert
|
|
74
|
+
expect(result).toHaveLength(1);
|
|
75
|
+
expect(result[0]).toEqual({ id: '1', name: 'User1', email: 'user1@example.com' });
|
|
76
|
+
<%_ if (database !== 'None') { -%>
|
|
77
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
78
|
+
expect(UserModel.find).toHaveBeenCalled();
|
|
79
|
+
<%_ } else { -%>
|
|
80
|
+
expect(UserModel.findAll).toHaveBeenCalled();
|
|
81
|
+
<%_ } -%>
|
|
82
|
+
<%_ } -%>
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('update', () => {
|
|
87
|
+
it('should update and return the user (Happy Path)', async () => {
|
|
88
|
+
// Arrange
|
|
89
|
+
const id = '1';
|
|
90
|
+
const data = { name: 'Updated' };
|
|
91
|
+
const expectedUser = { id: '1', name: 'Updated', email: 'test@example.com' };
|
|
92
|
+
|
|
93
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
94
|
+
const mockDbRecord = { _id: { toString: () => '1' }, name: 'Updated', email: 'test@example.com' };
|
|
95
|
+
(UserModel.findByIdAndUpdate as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
96
|
+
<%_ } else if (database === 'None') { -%>
|
|
97
|
+
(UserModel.update as jest.Mock).mockResolvedValue(expectedUser);
|
|
98
|
+
<%_ } else { -%>
|
|
99
|
+
const mockDbRecord = { id: '1', name: 'Updated', email: 'test@example.com', update: jest.fn().mockResolvedValue(true) };
|
|
100
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
101
|
+
<%_ } -%>
|
|
102
|
+
|
|
103
|
+
// Act
|
|
104
|
+
const result = await userRepository.update(id, data);
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result?.name).toEqual(data.name);
|
|
108
|
+
<%_ if (database !== 'None') { -%>
|
|
109
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
110
|
+
expect(UserModel.findByIdAndUpdate).toHaveBeenCalled();
|
|
111
|
+
<%_ } else { -%>
|
|
112
|
+
expect(UserModel.findByPk).toHaveBeenCalled();
|
|
113
|
+
<%_ } -%>
|
|
114
|
+
<%_ } -%>
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should return null when user not found (Error Handling)', async () => {
|
|
118
|
+
// Arrange
|
|
119
|
+
const id = '999';
|
|
120
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
121
|
+
(UserModel.findByIdAndUpdate as jest.Mock).mockResolvedValue(null);
|
|
122
|
+
<%_ } else if (database === 'None') { -%>
|
|
123
|
+
(UserModel.update as jest.Mock).mockResolvedValue(null);
|
|
124
|
+
<%_ } else { -%>
|
|
125
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(null);
|
|
126
|
+
<%_ } -%>
|
|
127
|
+
|
|
128
|
+
// Act
|
|
129
|
+
const result = await userRepository.update(id, { name: 'Fail' });
|
|
130
|
+
|
|
131
|
+
// Assert
|
|
132
|
+
expect(result).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('delete', () => {
|
|
137
|
+
it('should successfully delete a user (Happy Path)', async () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
const id = '1';
|
|
140
|
+
|
|
141
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
142
|
+
(UserModel.findByIdAndDelete as jest.Mock).mockResolvedValue(true);
|
|
143
|
+
<%_ } else if (database === 'None') { -%>
|
|
144
|
+
(UserModel.destroy as jest.Mock).mockResolvedValue(true);
|
|
145
|
+
<%_ } else { -%>
|
|
146
|
+
const mockDbRecord = { id: '1', destroy: jest.fn().mockResolvedValue(true) };
|
|
147
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(mockDbRecord);
|
|
148
|
+
<%_ } -%>
|
|
149
|
+
|
|
150
|
+
// Act
|
|
151
|
+
const result = await userRepository.delete(id);
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(result).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return false when user not found during deletion (Error Handling)', async () => {
|
|
158
|
+
// Arrange
|
|
159
|
+
const id = '999';
|
|
160
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
161
|
+
(UserModel.findByIdAndDelete as jest.Mock).mockResolvedValue(null);
|
|
162
|
+
<%_ } else if (database === 'None') { -%>
|
|
163
|
+
(UserModel.destroy as jest.Mock).mockResolvedValue(false);
|
|
164
|
+
<%_ } else { -%>
|
|
165
|
+
(UserModel.findByPk as jest.Mock).mockResolvedValue(null);
|
|
166
|
+
<%_ } -%>
|
|
167
|
+
|
|
168
|
+
// Act
|
|
169
|
+
const result = await userRepository.delete(id);
|
|
170
|
+
|
|
171
|
+
// Assert
|
|
172
|
+
expect(result).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { User as UserEntity } from '@/domain/user';
|
|
2
|
+
import UserModel from '@/infrastructure/database/models/User';
|
|
3
|
+
|
|
4
|
+
export class UserRepository {
|
|
5
|
+
async save(user: UserEntity): Promise<UserEntity> {
|
|
6
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
7
|
+
const newUser = await UserModel.create({ name: user.name, email: user.email });
|
|
8
|
+
return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
|
|
9
|
+
<%_ } else if (database === 'None') { -%>
|
|
10
|
+
const newUser = await UserModel.create({ name: user.name, email: user.email });
|
|
11
|
+
return { id: newUser.id, name: newUser.name, email: newUser.email };
|
|
12
|
+
<%_ } else { -%>
|
|
13
|
+
const newUser = await UserModel.create({ name: user.name, email: user.email });
|
|
14
|
+
return { id: newUser.id, name: newUser.name, email: newUser.email };
|
|
15
|
+
<%_ } -%>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getUsers(): Promise<UserEntity[]> {
|
|
19
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
20
|
+
const users = await UserModel.find();
|
|
21
|
+
return users.map(user => ({
|
|
22
|
+
id: user._id.toString(),
|
|
23
|
+
name: user.name,
|
|
24
|
+
email: user.email
|
|
25
|
+
}));
|
|
26
|
+
<%_ } else if (database === 'None') { -%>
|
|
27
|
+
const users = await UserModel.find();
|
|
28
|
+
return users.map(user => ({
|
|
29
|
+
id: user.id,
|
|
30
|
+
name: user.name,
|
|
31
|
+
email: user.email
|
|
32
|
+
}));
|
|
33
|
+
<%_ } else { -%>
|
|
34
|
+
const users = await UserModel.findAll();
|
|
35
|
+
return users.map(user => ({
|
|
36
|
+
id: user.id,
|
|
37
|
+
name: user.name,
|
|
38
|
+
email: user.email
|
|
39
|
+
}));
|
|
40
|
+
<%_ } -%>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async update(id: number | string, data: Partial<UserEntity>): Promise<UserEntity | null> {
|
|
44
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
45
|
+
const user = await UserModel.findByIdAndUpdate(id, data, { new: true });
|
|
46
|
+
if (!user) return null;
|
|
47
|
+
return { id: user._id.toString(), name: user.name, email: user.email };
|
|
48
|
+
<%_ } else if (database === 'None') { -%>
|
|
49
|
+
const { id: _, ...updateData } = data;
|
|
50
|
+
const user = await UserModel.update(id, updateData as Parameters<typeof UserModel.update>[1]);
|
|
51
|
+
if (!user) return null;
|
|
52
|
+
return { id: user.id, name: user.name, email: user.email };
|
|
53
|
+
<%_ } else { -%>
|
|
54
|
+
const user = await UserModel.findByPk(id);
|
|
55
|
+
if (!user) return null;
|
|
56
|
+
await user.update(data);
|
|
57
|
+
return { id: user.id || 0, name: user.name, email: user.email };
|
|
58
|
+
<%_ } -%>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async delete(id: number | string): Promise<boolean> {
|
|
62
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
63
|
+
const result = await UserModel.findByIdAndDelete(id);
|
|
64
|
+
return !!result;
|
|
65
|
+
<%_ } else if (database === 'None') { -%>
|
|
66
|
+
return await UserModel.destroy(id);
|
|
67
|
+
<%_ } else { -%>
|
|
68
|
+
const user = await UserModel.findByPk(id);
|
|
69
|
+
if (!user) return false;
|
|
70
|
+
await user.destroy();
|
|
71
|
+
return true;
|
|
72
|
+
<%_ } -%>
|
|
73
|
+
}
|
|
74
|
+
}
|