@nest-util/nest-crud 0.1.1 → 1.0.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.
- package/README.md +12 -4
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/lib/controllers/nest-crud.controller.d.ts +2 -1
- package/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -1
- package/dist/lib/controllers/nest-crud.controller.js +41 -5
- package/dist/lib/decorators/audit-log.decorator.d.ts +7 -0
- package/dist/lib/decorators/audit-log.decorator.d.ts.map +1 -0
- package/dist/lib/decorators/audit-log.decorator.js +7 -0
- package/dist/lib/dtos/cursor-pagination.dto.d.ts +6 -0
- package/dist/lib/dtos/cursor-pagination.dto.d.ts.map +1 -0
- package/dist/lib/dtos/cursor-pagination.dto.js +47 -0
- package/dist/lib/dtos/list-audit-logs.dto.d.ts +8 -0
- package/dist/lib/dtos/list-audit-logs.dto.d.ts.map +1 -0
- package/dist/lib/dtos/list-audit-logs.dto.js +65 -0
- package/dist/lib/entities/audit-log.entity.d.ts +13 -0
- package/dist/lib/entities/audit-log.entity.d.ts.map +1 -0
- package/dist/lib/entities/audit-log.entity.js +58 -0
- package/dist/lib/helpers/cursor-pagination.helper.d.ts +9 -0
- package/dist/lib/helpers/cursor-pagination.helper.d.ts.map +1 -0
- package/dist/lib/helpers/cursor-pagination.helper.js +93 -0
- package/dist/lib/interceptors/audit-log.interceptor.d.ts +12 -0
- package/dist/lib/interceptors/audit-log.interceptor.d.ts.map +1 -0
- package/dist/lib/interceptors/audit-log.interceptor.js +72 -0
- package/dist/lib/interfaces/audit-log.interface.d.ts +11 -0
- package/dist/lib/interfaces/audit-log.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/audit-log.interface.js +2 -0
- package/dist/lib/interfaces/crud.interface.d.ts +16 -1
- package/dist/lib/interfaces/crud.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/cursor-strategy.interface.d.ts +30 -0
- package/dist/lib/interfaces/cursor-strategy.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/cursor-strategy.interface.js +2 -0
- package/dist/lib/interfaces/find-mine.interface.d.ts +6 -0
- package/dist/lib/interfaces/find-mine.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/find-mine.interface.js +2 -0
- package/dist/lib/interfaces/hooks.interface.d.ts +52 -0
- package/dist/lib/interfaces/hooks.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/hooks.interface.js +2 -0
- package/dist/lib/nest-crud.module.d.ts.map +1 -1
- package/dist/lib/nest-crud.module.js +6 -3
- package/dist/lib/services/audit-log.service.d.ts +43 -0
- package/dist/lib/services/audit-log.service.d.ts.map +1 -0
- package/dist/lib/services/audit-log.service.js +154 -0
- package/dist/lib/services/nest-crud.service.d.ts +23 -4
- package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.js +163 -8
- package/dist/lib/testing/crud-controller.test-suites.d.ts +4 -0
- package/dist/lib/testing/crud-controller.test-suites.d.ts.map +1 -0
- package/dist/lib/testing/crud-controller.test-suites.js +165 -0
- package/dist/lib/testing/crud-service.test-suites.d.ts +4 -0
- package/dist/lib/testing/crud-service.test-suites.d.ts.map +1 -0
- package/dist/lib/testing/crud-service.test-suites.js +279 -0
- package/dist/lib/testing/index.d.ts +5 -0
- package/dist/lib/testing/index.d.ts.map +1 -0
- package/dist/lib/testing/index.js +7 -0
- package/dist/lib/testing/mock-repository.d.ts +7 -0
- package/dist/lib/testing/mock-repository.d.ts.map +1 -0
- package/dist/lib/testing/mock-repository.js +97 -0
- package/dist/lib/testing/testing.interface.d.ts +52 -0
- package/dist/lib/testing/testing.interface.d.ts.map +1 -0
- package/dist/lib/testing/testing.interface.js +2 -0
- package/package.json +21 -5
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.crudServiceTests = crudServiceTests;
|
|
4
|
+
const testing_1 = require("@nestjs/testing");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const typeorm_1 = require("@nestjs/typeorm");
|
|
7
|
+
const nest_crud_service_1 = require("../services/nest-crud.service");
|
|
8
|
+
const mock_repository_1 = require("./mock-repository");
|
|
9
|
+
function crudServiceTests(config) {
|
|
10
|
+
let service;
|
|
11
|
+
let repository;
|
|
12
|
+
let moduleRef;
|
|
13
|
+
let lastQb;
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
repository = (0, mock_repository_1.createMockRepository)(config.entity, config.test.mockRepoOverrides);
|
|
16
|
+
const serviceOptions = {
|
|
17
|
+
repository,
|
|
18
|
+
allowedFilters: config.allowedFilters ?? [],
|
|
19
|
+
allowedSortFields: config.allowedSortFields ?? [],
|
|
20
|
+
include: config.include ?? [],
|
|
21
|
+
relations: config.relations ?? [],
|
|
22
|
+
toResponseDto: config.toResponseDto,
|
|
23
|
+
disabledEndpoints: config.disabledEndpoints ?? [],
|
|
24
|
+
hooks: config.hooks,
|
|
25
|
+
userOwnershipField: config.userOwnershipField,
|
|
26
|
+
findMineQuery: config.findMineQuery,
|
|
27
|
+
};
|
|
28
|
+
service = new nest_crud_service_1.NestCrudService(serviceOptions);
|
|
29
|
+
moduleRef = await testing_1.Test.createTestingModule({
|
|
30
|
+
providers: [
|
|
31
|
+
{ provide: config.serviceClass, useValue: service },
|
|
32
|
+
{ provide: (0, typeorm_1.getRepositoryToken)(config.entity), useValue: repository },
|
|
33
|
+
],
|
|
34
|
+
}).compile();
|
|
35
|
+
});
|
|
36
|
+
describe('findAll', () => {
|
|
37
|
+
it('should return paginated data and meta', async () => {
|
|
38
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
39
|
+
const mockEntity = createMockEntity(config);
|
|
40
|
+
lastQb.getManyAndCount.mockResolvedValue([[mockEntity], 1]);
|
|
41
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
42
|
+
const result = await service.findAll({ page: 1, limit: 10 });
|
|
43
|
+
expect(repository.createQueryBuilder).toHaveBeenCalledWith('e');
|
|
44
|
+
expect(lastQb.getManyAndCount).toHaveBeenCalled();
|
|
45
|
+
expect(result).toHaveProperty('data');
|
|
46
|
+
expect(result).toHaveProperty('meta');
|
|
47
|
+
});
|
|
48
|
+
it('should apply filters via andWhen allowedFilters is configured', async () => {
|
|
49
|
+
if (!config.allowedFilters || config.allowedFilters.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
52
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
53
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
54
|
+
const filterKey = config.allowedFilters[0];
|
|
55
|
+
await service.findAll({
|
|
56
|
+
page: 1,
|
|
57
|
+
limit: 10,
|
|
58
|
+
filter: { [`${String(filterKey)}_cont`]: 'test' },
|
|
59
|
+
});
|
|
60
|
+
expect(lastQb.andWhere).toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
it('should join include relations via leftJoinAndSelect', async () => {
|
|
63
|
+
if (!config.include || config.include.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
66
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
67
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
68
|
+
await service.findAll({ page: 1, limit: 10 });
|
|
69
|
+
expect(lastQb.leftJoinAndSelect).toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
it('should apply orderBy when specified', async () => {
|
|
72
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
73
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
74
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
75
|
+
await service.findAll({
|
|
76
|
+
page: 1,
|
|
77
|
+
limit: 10,
|
|
78
|
+
orderBy: 'id',
|
|
79
|
+
orderDirection: 'ASC',
|
|
80
|
+
});
|
|
81
|
+
expect(lastQb.orderBy).toHaveBeenCalledWith('e.id', 'ASC');
|
|
82
|
+
});
|
|
83
|
+
it('should default orderBy to DESC', async () => {
|
|
84
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
85
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
86
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
87
|
+
await service.findAll({
|
|
88
|
+
page: 1,
|
|
89
|
+
limit: 10,
|
|
90
|
+
orderBy: 'id',
|
|
91
|
+
});
|
|
92
|
+
expect(lastQb.orderBy).toHaveBeenCalledWith('e.id', 'DESC');
|
|
93
|
+
});
|
|
94
|
+
it('should return empty data when no results', async () => {
|
|
95
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
96
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
97
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
98
|
+
const result = await service.findAll({ page: 1, limit: 10 });
|
|
99
|
+
expect(result).toHaveProperty('data');
|
|
100
|
+
expect(result.data).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('findOne', () => {
|
|
104
|
+
it('should return the entity', async () => {
|
|
105
|
+
const mockEntity = createMockEntity(config);
|
|
106
|
+
repository.findOne.mockResolvedValue(mockEntity);
|
|
107
|
+
const result = await service.findOne(1);
|
|
108
|
+
expect(repository.findOne).toHaveBeenCalledWith({
|
|
109
|
+
where: { id: 1 },
|
|
110
|
+
relations: config.include ?? [],
|
|
111
|
+
});
|
|
112
|
+
expect(result).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
it('should throw NotFoundException when not found', async () => {
|
|
115
|
+
repository.findOne.mockResolvedValue(null);
|
|
116
|
+
await expect(service.findOne(999)).rejects.toThrow(common_1.NotFoundException);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe('create', () => {
|
|
120
|
+
it('should save and return the entity', async () => {
|
|
121
|
+
const mockEntity = createMockEntity(config);
|
|
122
|
+
repository.save.mockResolvedValue(mockEntity);
|
|
123
|
+
const result = await service.create(config.test.createPayload);
|
|
124
|
+
expect(repository.save).toHaveBeenCalled();
|
|
125
|
+
expect(result).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('update', () => {
|
|
129
|
+
it('should merge, save, and return the updated entity', async () => {
|
|
130
|
+
const mockEntity = createMockEntity(config);
|
|
131
|
+
repository.findOneBy.mockResolvedValue(mockEntity);
|
|
132
|
+
repository.save.mockResolvedValue(mockEntity);
|
|
133
|
+
repository.findOne.mockResolvedValue(mockEntity);
|
|
134
|
+
const result = await service.update(1, config.test.updatePayload);
|
|
135
|
+
expect(repository.findOneBy).toHaveBeenCalled();
|
|
136
|
+
expect(repository.merge).toHaveBeenCalled();
|
|
137
|
+
expect(repository.save).toHaveBeenCalled();
|
|
138
|
+
expect(result).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
it('should throw NotFoundException when entity not found', async () => {
|
|
141
|
+
repository.findOneBy.mockResolvedValue(null);
|
|
142
|
+
await expect(service.update(999, config.test.updatePayload)).rejects.toThrow(common_1.NotFoundException);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('remove', () => {
|
|
146
|
+
it('should delete and return true', async () => {
|
|
147
|
+
const mockEntity = createMockEntity(config);
|
|
148
|
+
repository.findOneBy.mockResolvedValue(mockEntity);
|
|
149
|
+
repository.delete.mockResolvedValue({ affected: 1 });
|
|
150
|
+
const result = await service.remove(1);
|
|
151
|
+
expect(repository.findOneBy).toHaveBeenCalled();
|
|
152
|
+
expect(repository.delete).toHaveBeenCalledWith(1);
|
|
153
|
+
expect(result).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
it('should throw NotFoundException when not found', async () => {
|
|
156
|
+
repository.findOneBy.mockResolvedValue(null);
|
|
157
|
+
await expect(service.remove(999)).rejects.toThrow(common_1.NotFoundException);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('findMine', () => {
|
|
161
|
+
it('should filter by ownership field', async () => {
|
|
162
|
+
if (!config.userOwnershipField && !config.findMineQuery)
|
|
163
|
+
return;
|
|
164
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
165
|
+
const mockEntity = createMockEntity(config);
|
|
166
|
+
lastQb.getManyAndCount.mockResolvedValue([[mockEntity], 1]);
|
|
167
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
168
|
+
const result = await service.findMine(123, { page: 1, limit: 10 });
|
|
169
|
+
expect(repository.createQueryBuilder).toHaveBeenCalled();
|
|
170
|
+
expect(result).toHaveProperty('data');
|
|
171
|
+
});
|
|
172
|
+
it('should throw BadRequestException when not configured', async () => {
|
|
173
|
+
if (config.userOwnershipField || config.findMineQuery)
|
|
174
|
+
return;
|
|
175
|
+
await expect(service.findMine(123, {})).rejects.toThrow(common_1.BadRequestException);
|
|
176
|
+
});
|
|
177
|
+
it('should apply filters via andWhere', async () => {
|
|
178
|
+
if (!config.userOwnershipField && !config.findMineQuery)
|
|
179
|
+
return;
|
|
180
|
+
if (!config.allowedFilters || config.allowedFilters.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
183
|
+
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
184
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
185
|
+
const filterKey = config.allowedFilters[0];
|
|
186
|
+
await service.findMine(123, {
|
|
187
|
+
filter: { [`${String(filterKey)}_cont`]: 'test' },
|
|
188
|
+
});
|
|
189
|
+
expect(lastQb.andWhere).toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
describe('findAllWithCursor', () => {
|
|
193
|
+
it('should return cursor-based results', async () => {
|
|
194
|
+
if (config.disabledEndpoints?.includes('findMine'))
|
|
195
|
+
return;
|
|
196
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
197
|
+
const mockEntity = createMockEntity(config);
|
|
198
|
+
lastQb.getMany.mockResolvedValue([mockEntity]);
|
|
199
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
200
|
+
const result = await service.findAllWithCursor({ limit: 10 });
|
|
201
|
+
expect(result).toHaveProperty('data');
|
|
202
|
+
expect(result).toHaveProperty('meta');
|
|
203
|
+
expect(result.meta).toHaveProperty('limit', 10);
|
|
204
|
+
expect(result.meta).toHaveProperty('hasMore');
|
|
205
|
+
expect(result.meta).toHaveProperty('nextCursor');
|
|
206
|
+
});
|
|
207
|
+
it('should detect hasMore when entities exceed limit', async () => {
|
|
208
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
209
|
+
const entities = Array.from({ length: 11 }, () => createMockEntity(config));
|
|
210
|
+
lastQb.getMany.mockResolvedValue(entities);
|
|
211
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
212
|
+
const result = await service.findAllWithCursor({ limit: 10 });
|
|
213
|
+
expect(result.meta.hasMore).toBe(true);
|
|
214
|
+
expect(result.meta.nextCursor).toBeDefined();
|
|
215
|
+
});
|
|
216
|
+
it('should set hasMore false when entities <= limit', async () => {
|
|
217
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
218
|
+
const mockEntity = createMockEntity(config);
|
|
219
|
+
lastQb.getMany.mockResolvedValue([mockEntity]);
|
|
220
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
221
|
+
const result = await service.findAllWithCursor({ limit: 10 });
|
|
222
|
+
expect(result.meta.hasMore).toBe(false);
|
|
223
|
+
expect(result.meta.nextCursor).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
it('should include total when includeTotal is true', async () => {
|
|
226
|
+
lastQb = (0, mock_repository_1.createMockQb)();
|
|
227
|
+
const mockEntity = createMockEntity(config);
|
|
228
|
+
lastQb.getMany.mockResolvedValue([mockEntity]);
|
|
229
|
+
lastQb.getCount.mockResolvedValue(1);
|
|
230
|
+
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
231
|
+
const countQb = (0, mock_repository_1.createMockQb)();
|
|
232
|
+
countQb.getCount.mockResolvedValue(1);
|
|
233
|
+
repository.createQueryBuilder.mockReturnValueOnce(lastQb);
|
|
234
|
+
repository.createQueryBuilder.mockReturnValueOnce(countQb);
|
|
235
|
+
const result = await service.findAllWithCursor({
|
|
236
|
+
limit: 10,
|
|
237
|
+
includeTotal: true,
|
|
238
|
+
});
|
|
239
|
+
expect(result.meta).toHaveProperty('total');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('findAuditLogs', () => {
|
|
243
|
+
it('should query audit logs with pagination', async () => {
|
|
244
|
+
const auditQb = (0, mock_repository_1.createMockQb)();
|
|
245
|
+
auditQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
246
|
+
repository.manager.getRepository.mockReturnValue({
|
|
247
|
+
createQueryBuilder: jest.fn().mockReturnValue(auditQb),
|
|
248
|
+
});
|
|
249
|
+
const result = await service.findAuditLogs({ page: 1, limit: 10 });
|
|
250
|
+
expect(repository.manager.getRepository).toHaveBeenCalled();
|
|
251
|
+
expect(result).toHaveProperty('data');
|
|
252
|
+
expect(result).toHaveProperty('meta');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('disabledEndpoints', () => {
|
|
256
|
+
it('should block disabled endpoints', async () => {
|
|
257
|
+
if (!config.disabledEndpoints || config.disabledEndpoints.length === 0)
|
|
258
|
+
return;
|
|
259
|
+
const disabled = config.disabledEndpoints[0];
|
|
260
|
+
service.disabledEndpoints = config.disabledEndpoints;
|
|
261
|
+
expect(service.disabledEndpoints).toContain(disabled);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
const ctx = {
|
|
265
|
+
get module() {
|
|
266
|
+
return moduleRef;
|
|
267
|
+
},
|
|
268
|
+
get repository() {
|
|
269
|
+
return repository;
|
|
270
|
+
},
|
|
271
|
+
createMockQb: mock_repository_1.createMockQb,
|
|
272
|
+
};
|
|
273
|
+
return ctx;
|
|
274
|
+
}
|
|
275
|
+
function createMockEntity(config) {
|
|
276
|
+
const { createDefaultMockEntity } = require('./mock-repository');
|
|
277
|
+
const defaults = createDefaultMockEntity(config.entity);
|
|
278
|
+
return { ...defaults, ...(config.test.mockEntity ?? {}) };
|
|
279
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./testing.interface"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./mock-repository"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./crud-service.test-suites"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./crud-controller.test-suites"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common';
|
|
2
|
+
import { ObjectLiteral, Repository } from 'typeorm';
|
|
3
|
+
import { MockQueryBuilder } from './testing.interface';
|
|
4
|
+
export declare function createMockQb(): MockQueryBuilder;
|
|
5
|
+
export declare function createMockRepository<TEntity extends ObjectLiteral>(entity: Type<TEntity>, overrides?: Partial<jest.Mocked<Repository<TEntity>>>): jest.Mocked<Repository<TEntity>>;
|
|
6
|
+
export declare function createDefaultMockEntity<TEntity extends ObjectLiteral>(entity: Type<TEntity>): TEntity;
|
|
7
|
+
//# sourceMappingURL=mock-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-repository.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/mock-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,wBAAgB,YAAY,IAAI,gBAAgB,CAgB/C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,aAAa,EAChE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,EACrB,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GACpD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAiClC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,aAAa,EACnE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GACpB,OAAO,CAuCT"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMockQb = createMockQb;
|
|
4
|
+
exports.createMockRepository = createMockRepository;
|
|
5
|
+
exports.createDefaultMockEntity = createDefaultMockEntity;
|
|
6
|
+
function createMockQb() {
|
|
7
|
+
const qb = {
|
|
8
|
+
where: jest.fn().mockReturnThis(),
|
|
9
|
+
andWhere: jest.fn().mockReturnThis(),
|
|
10
|
+
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
11
|
+
leftJoin: jest.fn().mockReturnThis(),
|
|
12
|
+
orderBy: jest.fn().mockReturnThis(),
|
|
13
|
+
skip: jest.fn().mockReturnThis(),
|
|
14
|
+
take: jest.fn().mockReturnThis(),
|
|
15
|
+
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
|
|
16
|
+
getMany: jest.fn().mockResolvedValue([]),
|
|
17
|
+
getOne: jest.fn().mockResolvedValue(null),
|
|
18
|
+
getCount: jest.fn().mockResolvedValue(0),
|
|
19
|
+
getRawOne: jest.fn().mockResolvedValue({}),
|
|
20
|
+
};
|
|
21
|
+
return qb;
|
|
22
|
+
}
|
|
23
|
+
function createMockRepository(entity, overrides) {
|
|
24
|
+
const mockEntity = createDefaultMockEntity(entity);
|
|
25
|
+
const auditQb = createMockQb();
|
|
26
|
+
const repo = {
|
|
27
|
+
createQueryBuilder: jest.fn().mockReturnValue(createMockQb()),
|
|
28
|
+
findOne: jest.fn().mockResolvedValue(mockEntity),
|
|
29
|
+
findOneBy: jest.fn().mockResolvedValue(mockEntity),
|
|
30
|
+
merge: jest.fn((e, p) => Object.assign(e, p)),
|
|
31
|
+
save: jest.fn().mockImplementation((e) => Promise.resolve(e)),
|
|
32
|
+
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
33
|
+
find: jest.fn().mockResolvedValue([mockEntity]),
|
|
34
|
+
metadata: {
|
|
35
|
+
name: entity.name,
|
|
36
|
+
primaryColumns: [{ propertyPath: 'id', type: () => Number }],
|
|
37
|
+
},
|
|
38
|
+
manager: {
|
|
39
|
+
getRepository: jest.fn().mockReturnValue({
|
|
40
|
+
createQueryBuilder: jest.fn().mockReturnValue(auditQb),
|
|
41
|
+
}),
|
|
42
|
+
connection: {
|
|
43
|
+
createQueryRunner: jest.fn().mockReturnValue({
|
|
44
|
+
startTransaction: jest.fn(),
|
|
45
|
+
commitTransaction: jest.fn(),
|
|
46
|
+
rollbackTransaction: jest.fn(),
|
|
47
|
+
release: jest.fn(),
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
return repo;
|
|
54
|
+
}
|
|
55
|
+
function createDefaultMockEntity(entity) {
|
|
56
|
+
try {
|
|
57
|
+
const { getMetadata } = require('typeorm');
|
|
58
|
+
const metadata = getMetadata(entity);
|
|
59
|
+
const instance = {};
|
|
60
|
+
for (const col of metadata.columns) {
|
|
61
|
+
const type = col.type;
|
|
62
|
+
const name = col.propertyPath;
|
|
63
|
+
if (col.isPrimary) {
|
|
64
|
+
if (type === 'uuid') {
|
|
65
|
+
instance[name] = '00000000-0000-0000-0000-000000000001';
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
instance[name] = 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (type === String || type === 'varchar' || type === 'text') {
|
|
72
|
+
instance[name] = `mock_${name}`;
|
|
73
|
+
}
|
|
74
|
+
else if (type === Number || type === 'int' || type === 'integer') {
|
|
75
|
+
instance[name] = 0;
|
|
76
|
+
}
|
|
77
|
+
else if (type === Boolean || type === 'boolean') {
|
|
78
|
+
instance[name] = true;
|
|
79
|
+
}
|
|
80
|
+
else if (type === Date ||
|
|
81
|
+
type === 'timestamp' ||
|
|
82
|
+
type === 'datetime') {
|
|
83
|
+
instance[name] = new Date('2024-01-01T00:00:00.000Z');
|
|
84
|
+
}
|
|
85
|
+
else if (type === 'json' || type === 'jsonb') {
|
|
86
|
+
instance[name] = {};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
instance[name] = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return instance;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return { id: 1 };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common';
|
|
2
|
+
import { ObjectLiteral, Repository } from 'typeorm';
|
|
3
|
+
import { CrudServiceOptions } from '../services/nest-crud.service';
|
|
4
|
+
import { CrudEndpoint } from '../interfaces/crud.interface';
|
|
5
|
+
export interface CrudTestConfig<TEntity extends ObjectLiteral, TCreateDto, TUpdateDto, TResponseDto> {
|
|
6
|
+
entity: Type<TEntity>;
|
|
7
|
+
serviceClass: Type<any>;
|
|
8
|
+
createDto?: Type<TCreateDto>;
|
|
9
|
+
updateDto?: Type<TUpdateDto>;
|
|
10
|
+
responseDto?: Type<TResponseDto>;
|
|
11
|
+
allowedFilters?: readonly (keyof TEntity)[];
|
|
12
|
+
allowedSortFields?: readonly (keyof TEntity)[];
|
|
13
|
+
include?: readonly string[];
|
|
14
|
+
userOwnershipField?: keyof TEntity;
|
|
15
|
+
findMineQuery?: (qb: any, userId: string | number) => void;
|
|
16
|
+
disabledEndpoints?: readonly CrudEndpoint[];
|
|
17
|
+
hooks?: CrudServiceOptions<TEntity, TResponseDto>['hooks'];
|
|
18
|
+
toResponseDto?: (entity: TEntity | TEntity[]) => TResponseDto | TResponseDto[];
|
|
19
|
+
relations?: CrudServiceOptions<TEntity, TResponseDto>['relations'];
|
|
20
|
+
test: {
|
|
21
|
+
createPayload: Partial<TCreateDto>;
|
|
22
|
+
updatePayload: Partial<TUpdateDto>;
|
|
23
|
+
mockEntity?: Partial<TEntity>;
|
|
24
|
+
mockEntities?: TEntity[];
|
|
25
|
+
mockRepoOverrides?: Partial<jest.Mocked<Repository<TEntity>>>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface CrudControllerTestConfig<TEntity extends ObjectLiteral, TCreateDto, TUpdateDto, TResponseDto> extends Omit<CrudTestConfig<TEntity, TCreateDto, TUpdateDto, TResponseDto>, 'serviceClass'> {
|
|
29
|
+
controllerFactory: () => Type<any>;
|
|
30
|
+
serviceClass: Type<any>;
|
|
31
|
+
permissions?: Record<CrudEndpoint, string | string[]>;
|
|
32
|
+
}
|
|
33
|
+
export interface CrudTestContext {
|
|
34
|
+
module: any;
|
|
35
|
+
repository: jest.Mocked<any>;
|
|
36
|
+
createMockQb: () => MockQueryBuilder;
|
|
37
|
+
}
|
|
38
|
+
export interface MockQueryBuilder {
|
|
39
|
+
where: jest.Mock;
|
|
40
|
+
andWhere: jest.Mock;
|
|
41
|
+
leftJoinAndSelect: jest.Mock;
|
|
42
|
+
leftJoin: jest.Mock;
|
|
43
|
+
orderBy: jest.Mock;
|
|
44
|
+
skip: jest.Mock;
|
|
45
|
+
take: jest.Mock;
|
|
46
|
+
getManyAndCount: jest.Mock;
|
|
47
|
+
getMany: jest.Mock;
|
|
48
|
+
getOne: jest.Mock;
|
|
49
|
+
getCount: jest.Mock;
|
|
50
|
+
getRawOne: jest.Mock;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=testing.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/testing.interface.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-util/nest-crud",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./testing": {
|
|
16
|
+
"@nest-util/source": "./src/lib/testing/index.ts",
|
|
17
|
+
"types": "./dist/lib/testing/index.d.ts",
|
|
18
|
+
"import": "./dist/lib/testing/index.js",
|
|
19
|
+
"default": "./dist/lib/testing/index.js"
|
|
14
20
|
}
|
|
15
21
|
},
|
|
16
22
|
"files": [
|
|
@@ -18,17 +24,27 @@
|
|
|
18
24
|
"!**/*.tsbuildinfo"
|
|
19
25
|
],
|
|
20
26
|
"dependencies": {
|
|
21
|
-
"
|
|
27
|
+
"@nestjs/testing": "^11.0.0",
|
|
28
|
+
"tslib": "^2.8.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@nest-util/nest-auth": "workspace:*"
|
|
22
32
|
},
|
|
23
33
|
"peerDependencies": {
|
|
24
|
-
"@nest-util/nest-
|
|
34
|
+
"@nest-util/nest-auth": "workspace:*",
|
|
25
35
|
"@nestjs/common": "^11.0.0",
|
|
26
36
|
"@nestjs/core": "^11.0.0",
|
|
27
37
|
"@nestjs/swagger": "^11.2.6",
|
|
38
|
+
"@nestjs/typeorm": "^11.0.1",
|
|
28
39
|
"class-transformer": "^0.5.1",
|
|
29
40
|
"class-validator": "^0.14.3",
|
|
30
41
|
"express": "^5.2.1",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
42
|
+
"rxjs": "^7.8.0",
|
|
43
|
+
"typeorm": "^1.1.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"@nest-util/nest-auth": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
}
|