@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
|
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NestCrudService = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@nestjs/common");
|
|
6
|
-
const
|
|
6
|
+
const audit_log_entity_1 = require("../entities/audit-log.entity");
|
|
7
7
|
const filter_helper_1 = require("../helpers/filter.helper");
|
|
8
8
|
const pagination_helper_1 = require("../helpers/pagination.helper");
|
|
9
|
+
const cursor_pagination_helper_1 = require("../helpers/cursor-pagination.helper");
|
|
9
10
|
let NestCrudService = class NestCrudService {
|
|
10
11
|
constructor(options) {
|
|
11
12
|
this.repo = options.repository;
|
|
@@ -17,6 +18,12 @@ let NestCrudService = class NestCrudService {
|
|
|
17
18
|
this.createDtoClass = options.createDtoClass;
|
|
18
19
|
this.updateDtoClass = options.updateDtoClass;
|
|
19
20
|
this.disabledEndpoints = options.disabledEndpoints ?? [];
|
|
21
|
+
this.cursorStrategy =
|
|
22
|
+
options.cursorStrategy ?? (0, cursor_pagination_helper_1.detectCursorStrategy)(this.repo);
|
|
23
|
+
this.hooks = options.hooks ?? {};
|
|
24
|
+
this.transactionConfig = options.transactionConfig ?? {};
|
|
25
|
+
this.userOwnershipField = options.userOwnershipField;
|
|
26
|
+
this.findMineQuery = options.findMineQuery;
|
|
20
27
|
}
|
|
21
28
|
async resolveRelations(payload) {
|
|
22
29
|
if (!this.relations.length)
|
|
@@ -39,6 +46,32 @@ let NestCrudService = class NestCrudService {
|
|
|
39
46
|
}
|
|
40
47
|
return payload;
|
|
41
48
|
}
|
|
49
|
+
async executeInTransaction(fn, isolationLevel) {
|
|
50
|
+
const queryRunner = this.repo.manager.connection.createQueryRunner();
|
|
51
|
+
await queryRunner.startTransaction(isolationLevel);
|
|
52
|
+
try {
|
|
53
|
+
const result = await fn();
|
|
54
|
+
await queryRunner.commitTransaction();
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
await queryRunner.rollbackTransaction();
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await queryRunner.release();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async executeHook(hook, context) {
|
|
66
|
+
if (!hook)
|
|
67
|
+
return;
|
|
68
|
+
if (hook.transaction) {
|
|
69
|
+
await this.executeInTransaction(() => hook.handler(context), this.transactionConfig?.isolationLevel);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await hook.handler(context);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
42
75
|
async findAll(query) {
|
|
43
76
|
const qb = this.repo.createQueryBuilder('e');
|
|
44
77
|
if (this.include.length > 0) {
|
|
@@ -72,7 +105,115 @@ let NestCrudService = class NestCrudService {
|
|
|
72
105
|
? { data, meta: { ...paginationMeta, total } }
|
|
73
106
|
: { data };
|
|
74
107
|
}
|
|
108
|
+
async findMine(userId, query) {
|
|
109
|
+
if (!this.userOwnershipField && !this.findMineQuery) {
|
|
110
|
+
throw new common_1.BadRequestException('findMine not configured');
|
|
111
|
+
}
|
|
112
|
+
const qb = this.repo.createQueryBuilder('e');
|
|
113
|
+
if (this.findMineQuery) {
|
|
114
|
+
this.findMineQuery(qb, userId);
|
|
115
|
+
}
|
|
116
|
+
else if (this.userOwnershipField) {
|
|
117
|
+
qb.where(`e.${String(this.userOwnershipField)} = :userId`, { userId });
|
|
118
|
+
}
|
|
119
|
+
if (this.include.length > 0) {
|
|
120
|
+
this.include.forEach((relation) => {
|
|
121
|
+
const parts = relation.split('.');
|
|
122
|
+
if (parts.length === 1) {
|
|
123
|
+
qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const parentAlias = parts.slice(0, -1).join('_');
|
|
127
|
+
const field = parts[parts.length - 1];
|
|
128
|
+
const alias = parts.join('_');
|
|
129
|
+
qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
(0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
|
|
134
|
+
const paginationMeta = (0, pagination_helper_1.applyPagination)(qb, query);
|
|
135
|
+
if (query.orderBy) {
|
|
136
|
+
const orderDirection = query.orderDirection === 'ASC' ? 'ASC' : 'DESC';
|
|
137
|
+
if (this.allowedSortFields.length === 0 ||
|
|
138
|
+
this.allowedSortFields.includes(query.orderBy)) {
|
|
139
|
+
qb.orderBy(`e.${query.orderBy}`, orderDirection);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const [entities, total] = await qb.getManyAndCount();
|
|
143
|
+
const data = this.toResponseDto
|
|
144
|
+
? this.toResponseDto(entities)
|
|
145
|
+
: entities;
|
|
146
|
+
return paginationMeta
|
|
147
|
+
? { data, meta: { ...paginationMeta, total } }
|
|
148
|
+
: { data };
|
|
149
|
+
}
|
|
150
|
+
async findAllWithCursor(query) {
|
|
151
|
+
const limit = query.limit ?? 10;
|
|
152
|
+
const strategy = this.cursorStrategy;
|
|
153
|
+
const orderDirection = 'DESC';
|
|
154
|
+
const qb = this.repo.createQueryBuilder('e');
|
|
155
|
+
// Join relations
|
|
156
|
+
if (this.include.length > 0) {
|
|
157
|
+
this.include.forEach((relation) => {
|
|
158
|
+
const parts = relation.split('.');
|
|
159
|
+
if (parts.length === 1) {
|
|
160
|
+
qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const parentAlias = parts.slice(0, -1).join('_');
|
|
164
|
+
const field = parts[parts.length - 1];
|
|
165
|
+
const alias = parts.join('_');
|
|
166
|
+
qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Apply filters
|
|
171
|
+
(0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
|
|
172
|
+
// Apply cursor filter
|
|
173
|
+
if (query.cursor) {
|
|
174
|
+
const decoded = (0, cursor_pagination_helper_1.decodeCursor)(query.cursor, strategy);
|
|
175
|
+
(0, cursor_pagination_helper_1.applyCursorFilter)(qb, decoded, strategy, orderDirection);
|
|
176
|
+
}
|
|
177
|
+
// Default ordering by id
|
|
178
|
+
qb.orderBy(`e.id`, orderDirection);
|
|
179
|
+
// Fetch limit + 1 to detect hasMore
|
|
180
|
+
const take = limit + 1;
|
|
181
|
+
const entities = await qb.take(take).getMany();
|
|
182
|
+
const hasMore = entities.length > limit;
|
|
183
|
+
const data = hasMore ? entities.slice(0, limit) : entities;
|
|
184
|
+
// Build next cursor from last entity
|
|
185
|
+
const nextCursor = hasMore ? (0, cursor_pagination_helper_1.buildNextCursor)(data, strategy) : null;
|
|
186
|
+
// Optionally compute total count
|
|
187
|
+
let total;
|
|
188
|
+
if (query.includeTotal) {
|
|
189
|
+
// Build a clean count query (reuse same filters but no cursor/order/take)
|
|
190
|
+
const countQb = this.repo.createQueryBuilder('e');
|
|
191
|
+
if (this.include.length > 0) {
|
|
192
|
+
this.include.forEach((relation) => {
|
|
193
|
+
const parts = relation.split('.');
|
|
194
|
+
if (parts.length === 1) {
|
|
195
|
+
countQb.leftJoin(`e.${parts[0]}`, parts[0]);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
(0, filter_helper_1.applyFilters)(countQb, query.filter, this.allowedFilters);
|
|
200
|
+
total = await countQb.getCount();
|
|
201
|
+
}
|
|
202
|
+
const response = this.toResponseDto
|
|
203
|
+
? this.toResponseDto(data)
|
|
204
|
+
: data;
|
|
205
|
+
return {
|
|
206
|
+
data: response,
|
|
207
|
+
meta: {
|
|
208
|
+
limit,
|
|
209
|
+
hasMore,
|
|
210
|
+
nextCursor,
|
|
211
|
+
...(total !== undefined ? { total } : {}),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
75
215
|
async findOne(id) {
|
|
216
|
+
await this.executeHook(this.hooks.beforeFindOne, { id });
|
|
76
217
|
const entity = await this.repo.findOne({
|
|
77
218
|
where: { id },
|
|
78
219
|
relations: this.include,
|
|
@@ -80,16 +221,21 @@ let NestCrudService = class NestCrudService {
|
|
|
80
221
|
if (!entity) {
|
|
81
222
|
throw new common_1.NotFoundException('Resource not found');
|
|
82
223
|
}
|
|
83
|
-
|
|
224
|
+
const result = this.toResponseDto
|
|
84
225
|
? this.toResponseDto(entity)
|
|
85
226
|
: entity;
|
|
227
|
+
await this.executeHook(this.hooks.afterFindOne, { entity, id });
|
|
228
|
+
return result;
|
|
86
229
|
}
|
|
87
230
|
async create(payload) {
|
|
231
|
+
await this.executeHook(this.hooks.beforeCreate, { payload });
|
|
88
232
|
const resolved = await this.resolveRelations(payload);
|
|
89
233
|
const entity = await this.repo.save(resolved);
|
|
90
|
-
|
|
234
|
+
const result = this.toResponseDto
|
|
91
235
|
? this.toResponseDto(entity)
|
|
92
236
|
: entity;
|
|
237
|
+
await this.executeHook(this.hooks.afterCreate, { entity, payload });
|
|
238
|
+
return result;
|
|
93
239
|
}
|
|
94
240
|
async update(id, payload) {
|
|
95
241
|
const existing = await this.repo.findOneBy({
|
|
@@ -98,23 +244,32 @@ let NestCrudService = class NestCrudService {
|
|
|
98
244
|
if (!existing) {
|
|
99
245
|
throw new common_1.NotFoundException('Resource not found');
|
|
100
246
|
}
|
|
247
|
+
await this.executeHook(this.hooks.beforeUpdate, { payload, entity: existing, id });
|
|
101
248
|
const resolved = await this.resolveRelations(payload);
|
|
102
249
|
this.repo.merge(existing, resolved);
|
|
103
250
|
await this.repo.save(existing);
|
|
104
|
-
|
|
251
|
+
const result = await this.findOne(id);
|
|
252
|
+
await this.executeHook(this.hooks.afterUpdate, { entity: result, payload, id });
|
|
253
|
+
return result;
|
|
105
254
|
}
|
|
106
255
|
async remove(id) {
|
|
107
|
-
const
|
|
108
|
-
|
|
256
|
+
const existing = await this.repo.findOneBy({
|
|
257
|
+
id,
|
|
258
|
+
});
|
|
259
|
+
if (!existing) {
|
|
109
260
|
throw new common_1.NotFoundException('Resource not found');
|
|
110
261
|
}
|
|
111
|
-
|
|
262
|
+
await this.executeHook(this.hooks.beforeRemove, { entity: existing, id });
|
|
263
|
+
const result = await this.repo.delete(id);
|
|
264
|
+
const deleted = result.affected !== 0;
|
|
265
|
+
await this.executeHook(this.hooks.afterRemove, { id, deleted });
|
|
266
|
+
return deleted;
|
|
112
267
|
}
|
|
113
268
|
async findAuditLogs(query) {
|
|
114
269
|
const page = query.page ?? 1;
|
|
115
270
|
const limit = query.limit ?? 10;
|
|
116
271
|
const qb = this.repo.manager
|
|
117
|
-
.getRepository(
|
|
272
|
+
.getRepository(audit_log_entity_1.AuditLogEntity)
|
|
118
273
|
.createQueryBuilder('auditLog')
|
|
119
274
|
.where('auditLog.entity = :entity', {
|
|
120
275
|
entity: this.repo.metadata.name,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ObjectLiteral } from 'typeorm';
|
|
2
|
+
import { CrudControllerTestConfig, CrudTestContext } from './testing.interface';
|
|
3
|
+
export declare function crudControllerTests<TEntity extends ObjectLiteral, TCreateDto, TUpdateDto, TResponseDto>(config: CrudControllerTestConfig<TEntity, TCreateDto, TUpdateDto, TResponseDto>): CrudTestContext;
|
|
4
|
+
//# sourceMappingURL=crud-controller.test-suites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-controller.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-controller.test-suites.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAMxC,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGhF,wBAAgB,mBAAmB,CACjC,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EAEZ,MAAM,EAAE,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAC9E,eAAe,CAoNjB"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.crudControllerTests = crudControllerTests;
|
|
4
|
+
const testing_1 = require("@nestjs/testing");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const nest_crud_controller_1 = require("../controllers/nest-crud.controller");
|
|
7
|
+
const mock_repository_1 = require("./mock-repository");
|
|
8
|
+
function crudControllerTests(config) {
|
|
9
|
+
let controller;
|
|
10
|
+
let service;
|
|
11
|
+
let ControllerBase;
|
|
12
|
+
let moduleRef;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
service = {
|
|
15
|
+
disabledEndpoints: [],
|
|
16
|
+
findAll: jest.fn(),
|
|
17
|
+
findOne: jest.fn(),
|
|
18
|
+
create: jest.fn(),
|
|
19
|
+
update: jest.fn(),
|
|
20
|
+
remove: jest.fn(),
|
|
21
|
+
findAuditLogs: jest.fn(),
|
|
22
|
+
findMine: jest.fn(),
|
|
23
|
+
};
|
|
24
|
+
ControllerBase = config.controllerFactory();
|
|
25
|
+
class TestController extends ControllerBase {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(service);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
moduleRef = await testing_1.Test.createTestingModule({
|
|
31
|
+
controllers: [TestController],
|
|
32
|
+
}).compile();
|
|
33
|
+
controller = moduleRef.get(TestController);
|
|
34
|
+
});
|
|
35
|
+
describe('findAll', () => {
|
|
36
|
+
it('should call service.findAll with query params', async () => {
|
|
37
|
+
const query = { page: 1, limit: 10 };
|
|
38
|
+
const expected = { data: [], meta: { total: 0 } };
|
|
39
|
+
service.findAll.mockResolvedValue(expected);
|
|
40
|
+
const result = await controller.findAll(query);
|
|
41
|
+
expect(service.findAll).toHaveBeenCalledWith(query);
|
|
42
|
+
expect(result).toEqual(expected);
|
|
43
|
+
});
|
|
44
|
+
it('should handle pagination parameters', async () => {
|
|
45
|
+
const query = { page: 5, limit: 100 };
|
|
46
|
+
service.findAll.mockResolvedValue({ data: [] });
|
|
47
|
+
await controller.findAll(query);
|
|
48
|
+
expect(service.findAll).toHaveBeenCalledWith(expect.objectContaining({ page: 5, limit: 100 }));
|
|
49
|
+
});
|
|
50
|
+
it('should handle complex filter objects', async () => {
|
|
51
|
+
const query = {
|
|
52
|
+
filter: { name_cont: 'Alice', isActive_eq: 'true' },
|
|
53
|
+
};
|
|
54
|
+
service.findAll.mockResolvedValue({ data: [] });
|
|
55
|
+
await controller.findAll(query);
|
|
56
|
+
expect(service.findAll).toHaveBeenCalledWith(expect.objectContaining({
|
|
57
|
+
filter: { name_cont: 'Alice', isActive_eq: 'true' },
|
|
58
|
+
}));
|
|
59
|
+
});
|
|
60
|
+
it('should work with no query parameters', async () => {
|
|
61
|
+
service.findAll.mockResolvedValue({ data: [] });
|
|
62
|
+
await controller.findAll({});
|
|
63
|
+
expect(service.findAll).toHaveBeenCalledWith({});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('findOne', () => {
|
|
67
|
+
it('should call service.findOne with correct id', async () => {
|
|
68
|
+
const expectedResult = { id: 1, name: 'Test' };
|
|
69
|
+
service.findOne.mockResolvedValue(expectedResult);
|
|
70
|
+
const result = await controller.findOne(1);
|
|
71
|
+
expect(service.findOne).toHaveBeenCalledWith(1);
|
|
72
|
+
expect(result).toEqual(expectedResult);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('create', () => {
|
|
76
|
+
it('should call service.create with dto', async () => {
|
|
77
|
+
const dto = config.test.createPayload;
|
|
78
|
+
const expectedResult = { id: 1 };
|
|
79
|
+
service.create.mockResolvedValue(expectedResult);
|
|
80
|
+
const result = await controller.create(dto);
|
|
81
|
+
expect(service.create).toHaveBeenCalledWith(dto);
|
|
82
|
+
expect(result).toEqual(expectedResult);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('update', () => {
|
|
86
|
+
it('should call service.update with id and dto', async () => {
|
|
87
|
+
const dto = config.test.updatePayload;
|
|
88
|
+
const expectedResult = { id: 1 };
|
|
89
|
+
service.update.mockResolvedValue(expectedResult);
|
|
90
|
+
const result = await controller.update(1, dto);
|
|
91
|
+
expect(service.update).toHaveBeenCalledWith(1, dto);
|
|
92
|
+
expect(result).toEqual(expectedResult);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('remove', () => {
|
|
96
|
+
it('should call service.remove', async () => {
|
|
97
|
+
service.remove.mockResolvedValue(true);
|
|
98
|
+
const result = await controller.remove(1);
|
|
99
|
+
expect(service.remove).toHaveBeenCalledWith(1);
|
|
100
|
+
expect(result).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('findMine', () => {
|
|
104
|
+
it('should call service.findMine with user id', async () => {
|
|
105
|
+
const query = { page: 1, limit: 10 };
|
|
106
|
+
const expected = { data: [] };
|
|
107
|
+
service.findMine.mockResolvedValue(expected);
|
|
108
|
+
const mockUser = { id: 42 };
|
|
109
|
+
const result = await controller.findMine(mockUser, query);
|
|
110
|
+
expect(service.findMine).toHaveBeenCalledWith(42, query);
|
|
111
|
+
expect(result).toEqual(expected);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('disabledEndpoints', () => {
|
|
115
|
+
it('should block disabled endpoints', async () => {
|
|
116
|
+
service.disabledEndpoints = ['create'];
|
|
117
|
+
expect(() => controller.create({})).toThrow(common_1.NotFoundException);
|
|
118
|
+
expect(service.create).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('findAuditLogs', () => {
|
|
122
|
+
it('should call service.findAuditLogs with query params', async () => {
|
|
123
|
+
const query = { page: 1, limit: 5, user_id: '1' };
|
|
124
|
+
const expected = { data: [], meta: { total: 0, page: 1, limit: 5 } };
|
|
125
|
+
service.findAuditLogs.mockResolvedValue(expected);
|
|
126
|
+
const result = await controller.findAuditLogs?.(query);
|
|
127
|
+
expect(service.findAuditLogs).toHaveBeenCalledWith(query);
|
|
128
|
+
expect(result).toEqual(expected);
|
|
129
|
+
});
|
|
130
|
+
it('should throw when audit logs endpoint is not available', () => {
|
|
131
|
+
service.findAuditLogs = undefined;
|
|
132
|
+
expect(() => controller.findAuditLogs?.({})).toThrow(common_1.NotFoundException);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('permissions', () => {
|
|
136
|
+
it('should attach auth permission metadata to configured handlers', () => {
|
|
137
|
+
if (!config.permissions)
|
|
138
|
+
return;
|
|
139
|
+
const PermissionedBase = config.controllerFactory();
|
|
140
|
+
class PermissionedCtrl extends PermissionedBase {
|
|
141
|
+
constructor() {
|
|
142
|
+
super(service);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [endpoint, permission] of Object.entries(config.permissions)) {
|
|
146
|
+
const method = PermissionedCtrl.prototype[endpoint];
|
|
147
|
+
if (!method)
|
|
148
|
+
continue;
|
|
149
|
+
const metadata = Reflect.getMetadata(nest_crud_controller_1.AUTH_PERMISSIONS_METADATA_KEY, method);
|
|
150
|
+
const expected = Array.isArray(permission)
|
|
151
|
+
? permission
|
|
152
|
+
: [permission];
|
|
153
|
+
expect(metadata).toEqual(expected);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
const ctx = {
|
|
158
|
+
get module() {
|
|
159
|
+
return moduleRef;
|
|
160
|
+
},
|
|
161
|
+
repository: null,
|
|
162
|
+
createMockQb: mock_repository_1.createMockQb,
|
|
163
|
+
};
|
|
164
|
+
return ctx;
|
|
165
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ObjectLiteral } from 'typeorm';
|
|
2
|
+
import { CrudTestConfig, CrudTestContext } from './testing.interface';
|
|
3
|
+
export declare function crudServiceTests<TEntity extends ObjectLiteral, TCreateDto, TUpdateDto, TResponseDto>(config: CrudTestConfig<TEntity, TCreateDto, TUpdateDto, TResponseDto>): CrudTestContext;
|
|
4
|
+
//# sourceMappingURL=crud-service.test-suites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-service.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-service.test-suites.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtE,wBAAgB,gBAAgB,CAC9B,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EACZ,MAAM,EAAE,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,eAAe,CAqVxF"}
|