@flusys/nestjs-form-builder 7.1.0 → 8.0.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.
Files changed (118) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/form-result.controller.d.ts +3 -3
  3. package/controllers/form.controller.d.ts +3 -3
  4. package/controllers/index.d.ts +2 -2
  5. package/docs/form-builder-swagger.config.d.ts +2 -2
  6. package/docs/index.d.ts +1 -1
  7. package/dtos/form.dto.d.ts +1 -1
  8. package/dtos/index.d.ts +2 -2
  9. package/entities/form-with-company.entity.d.ts +1 -1
  10. package/entities/form.entity.d.ts +1 -1
  11. package/entities/index.d.ts +7 -7
  12. package/enums/index.d.ts +1 -1
  13. package/fesm/262.js +1841 -0
  14. package/fesm/98.js +807 -0
  15. package/fesm/config/index.js +98 -3
  16. package/fesm/controllers/index.js +14 -2
  17. package/fesm/docs/index.js +171 -1
  18. package/fesm/dtos/index.js +571 -2
  19. package/fesm/entities/index.js +342 -10
  20. package/fesm/enums/index.js +38 -1
  21. package/fesm/index.js +482 -10
  22. package/fesm/interfaces/index.js +17 -3
  23. package/fesm/modules/index.js +178 -1
  24. package/fesm/runtime.js +85 -0
  25. package/fesm/services/index.js +35 -4
  26. package/index.d.ts +10 -10
  27. package/interfaces/form-builder-module.interface.d.ts +3 -3
  28. package/interfaces/form-result.interface.d.ts +1 -1
  29. package/interfaces/form.interface.d.ts +2 -2
  30. package/interfaces/index.d.ts +3 -3
  31. package/modules/form-builder.module.d.ts +2 -2
  32. package/modules/index.d.ts +1 -1
  33. package/package.json +26 -36
  34. package/services/form-builder-config.service.d.ts +4 -4
  35. package/services/form-builder-datasource.provider.d.ts +3 -3
  36. package/services/form-result.service.d.ts +6 -6
  37. package/services/form.service.d.ts +7 -7
  38. package/services/index.d.ts +4 -4
  39. package/utils/index.d.ts +2 -2
  40. package/utils/permission.utils.d.ts +1 -1
  41. package/cjs/config/event-actions.js +0 -33
  42. package/cjs/config/form-builder.constants.js +0 -13
  43. package/cjs/config/index.js +0 -20
  44. package/cjs/config/message-keys.js +0 -38
  45. package/cjs/controllers/form-result.controller.js +0 -281
  46. package/cjs/controllers/form-result.controller.spec.js +0 -257
  47. package/cjs/controllers/form.controller.js +0 -231
  48. package/cjs/controllers/form.controller.spec.js +0 -212
  49. package/cjs/controllers/index.js +0 -19
  50. package/cjs/docs/form-builder-swagger.config.js +0 -150
  51. package/cjs/docs/form-builder-swagger.config.spec.js +0 -75
  52. package/cjs/docs/index.js +0 -11
  53. package/cjs/dtos/form-result.dto.js +0 -248
  54. package/cjs/dtos/form.dto.js +0 -270
  55. package/cjs/dtos/index.js +0 -19
  56. package/cjs/entities/form-result.entity.js +0 -100
  57. package/cjs/entities/form-with-company.entity.js +0 -65
  58. package/cjs/entities/form.entity.js +0 -127
  59. package/cjs/entities/index.js +0 -52
  60. package/cjs/entities/index.spec.js +0 -18
  61. package/cjs/enums/form-access-type.enum.js +0 -19
  62. package/cjs/enums/index.js +0 -18
  63. package/cjs/index.js +0 -28
  64. package/cjs/interfaces/form-builder-module.interface.js +0 -4
  65. package/cjs/interfaces/form-result.interface.js +0 -4
  66. package/cjs/interfaces/form.interface.js +0 -4
  67. package/cjs/interfaces/index.js +0 -20
  68. package/cjs/modules/form-builder.module.js +0 -136
  69. package/cjs/modules/form-builder.module.spec.js +0 -133
  70. package/cjs/modules/index.js +0 -18
  71. package/cjs/services/form-builder-config.service.js +0 -69
  72. package/cjs/services/form-builder-config.service.spec.js +0 -121
  73. package/cjs/services/form-builder-datasource.provider.js +0 -125
  74. package/cjs/services/form-builder-datasource.provider.spec.js +0 -147
  75. package/cjs/services/form-result.service.js +0 -366
  76. package/cjs/services/form-result.service.spec.js +0 -823
  77. package/cjs/services/form.service.js +0 -341
  78. package/cjs/services/form.service.spec.js +0 -746
  79. package/cjs/services/index.js +0 -21
  80. package/cjs/utils/computed-field.utils.js +0 -199
  81. package/cjs/utils/computed-field.utils.spec.js +0 -1205
  82. package/cjs/utils/index.js +0 -19
  83. package/cjs/utils/permission.utils.js +0 -44
  84. package/cjs/utils/permission.utils.spec.js +0 -118
  85. package/fesm/config/event-actions.js +0 -12
  86. package/fesm/config/form-builder.constants.js +0 -3
  87. package/fesm/config/message-keys.js +0 -20
  88. package/fesm/controllers/form-result.controller.js +0 -271
  89. package/fesm/controllers/form-result.controller.spec.js +0 -253
  90. package/fesm/controllers/form.controller.js +0 -221
  91. package/fesm/controllers/form.controller.spec.js +0 -208
  92. package/fesm/docs/form-builder-swagger.config.js +0 -146
  93. package/fesm/docs/form-builder-swagger.config.spec.js +0 -71
  94. package/fesm/dtos/form-result.dto.js +0 -215
  95. package/fesm/dtos/form.dto.js +0 -243
  96. package/fesm/entities/form-result.entity.js +0 -90
  97. package/fesm/entities/form-with-company.entity.js +0 -55
  98. package/fesm/entities/form.entity.js +0 -117
  99. package/fesm/entities/index.spec.js +0 -14
  100. package/fesm/enums/form-access-type.enum.js +0 -9
  101. package/fesm/interfaces/form-builder-module.interface.js +0 -3
  102. package/fesm/interfaces/form-result.interface.js +0 -6
  103. package/fesm/interfaces/form.interface.js +0 -3
  104. package/fesm/modules/form-builder.module.js +0 -126
  105. package/fesm/modules/form-builder.module.spec.js +0 -129
  106. package/fesm/services/form-builder-config.service.js +0 -59
  107. package/fesm/services/form-builder-config.service.spec.js +0 -117
  108. package/fesm/services/form-builder-datasource.provider.js +0 -74
  109. package/fesm/services/form-builder-datasource.provider.spec.js +0 -143
  110. package/fesm/services/form-result.service.js +0 -356
  111. package/fesm/services/form-result.service.spec.js +0 -778
  112. package/fesm/services/form.service.js +0 -331
  113. package/fesm/services/form.service.spec.js +0 -701
  114. package/fesm/utils/computed-field.utils.js +0 -195
  115. package/fesm/utils/computed-field.utils.spec.js +0 -1201
  116. package/fesm/utils/index.js +0 -2
  117. package/fesm/utils/permission.utils.js +0 -42
  118. package/fesm/utils/permission.utils.spec.js +0 -114
@@ -1,253 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { UtilsService } from '@flusys/nestjs-shared/modules';
3
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
4
- import { FormResultController } from './form-result.controller';
5
- import { FormResultService } from '../services/form-result.service';
6
- function buildResultDto(overrides = {}) {
7
- return {
8
- id: 'result-1',
9
- formId: 'form-1',
10
- schemaVersionSnapshot: {
11
- sections: []
12
- },
13
- schemaVersion: 1,
14
- data: {
15
- field1: 'value1'
16
- },
17
- submittedById: 'user-uuid-1',
18
- submittedAt: new Date(),
19
- isDraft: false,
20
- createdAt: new Date(),
21
- updatedAt: new Date(),
22
- deletedAt: null,
23
- createdById: null,
24
- updatedById: null,
25
- deletedById: null,
26
- ...overrides
27
- };
28
- }
29
- describe('FormResultController', ()=>{
30
- let controller;
31
- let mockService;
32
- beforeEach(async ()=>{
33
- mockService = {
34
- insert: jest.fn(),
35
- insertMany: jest.fn(),
36
- findById: jest.fn(),
37
- findByIds: jest.fn(),
38
- getAll: jest.fn(),
39
- update: jest.fn(),
40
- updateMany: jest.fn(),
41
- delete: jest.fn(),
42
- submitForm: jest.fn(),
43
- getMyDraft: jest.fn(),
44
- updateDraft: jest.fn(),
45
- getByFormId: jest.fn(),
46
- hasUserSubmitted: jest.fn()
47
- };
48
- const module = await Test.createTestingModule({
49
- controllers: [
50
- FormResultController
51
- ],
52
- // UtilsService is a real (dependency-free) provider — required to satisfy the
53
- // Slug interceptor applied by the inherited insert/update endpoints.
54
- providers: [
55
- {
56
- provide: FormResultService,
57
- useValue: mockService
58
- },
59
- UtilsService
60
- ]
61
- }).compile();
62
- controller = module.get(FormResultController);
63
- });
64
- afterEach(()=>{
65
- jest.clearAllMocks();
66
- });
67
- describe('submitForm', ()=>{
68
- it('submits on behalf of the authenticated user (isPublic=false)', async ()=>{
69
- const user = buildMockUser();
70
- mockService.submitForm.mockResolvedValue(buildResultDto());
71
- const result = await controller.submitForm({
72
- formId: 'form-1',
73
- data: {
74
- field1: 'value1'
75
- }
76
- }, user);
77
- expect(mockService.submitForm).toHaveBeenCalledWith({
78
- formId: 'form-1',
79
- data: {
80
- field1: 'value1'
81
- }
82
- }, user, false);
83
- expect(result).toEqual({
84
- success: true,
85
- message: 'Form submitted successfully',
86
- messageKey: 'form.result.submit.success',
87
- data: expect.objectContaining({
88
- id: 'result-1'
89
- })
90
- });
91
- });
92
- it('propagates errors from the service (e.g. permission denied)', async ()=>{
93
- mockService.submitForm.mockRejectedValue(Object.assign(new Error('denied'), {
94
- messageKey: 'form.access.denied'
95
- }));
96
- await expect(controller.submitForm({
97
- formId: 'form-1',
98
- data: {}
99
- }, buildMockUser())).rejects.toThrow();
100
- });
101
- });
102
- describe('submitPublicForm', ()=>{
103
- it('submits without a user and marks the submission as public', async ()=>{
104
- mockService.submitForm.mockResolvedValue(buildResultDto({
105
- submittedById: null
106
- }));
107
- const result = await controller.submitPublicForm({
108
- formId: 'form-1',
109
- data: {}
110
- });
111
- expect(mockService.submitForm).toHaveBeenCalledWith({
112
- formId: 'form-1',
113
- data: {}
114
- }, null, true);
115
- expect(result.data.submittedById).toBeNull();
116
- });
117
- });
118
- describe('getMyDraft', ()=>{
119
- it('returns the current draft for the user', async ()=>{
120
- const user = buildMockUser();
121
- mockService.getMyDraft.mockResolvedValue(buildResultDto({
122
- isDraft: true
123
- }));
124
- const result = await controller.getMyDraft({
125
- formId: 'form-1'
126
- }, user);
127
- expect(mockService.getMyDraft).toHaveBeenCalledWith('form-1', user);
128
- expect(result.data?.isDraft).toBe(true);
129
- });
130
- it('returns null data when no draft exists', async ()=>{
131
- mockService.getMyDraft.mockResolvedValue(null);
132
- const result = await controller.getMyDraft({
133
- formId: 'form-1'
134
- }, buildMockUser());
135
- expect(result.data).toBeNull();
136
- });
137
- });
138
- describe('updateDraft', ()=>{
139
- it('updates the identified draft', async ()=>{
140
- const user = buildMockUser();
141
- mockService.updateDraft.mockResolvedValue(buildResultDto({
142
- isDraft: true
143
- }));
144
- const result = await controller.updateDraft({
145
- draftId: 'draft-1',
146
- formId: 'form-1',
147
- data: {
148
- field1: 'new'
149
- }
150
- }, user);
151
- expect(mockService.updateDraft).toHaveBeenCalledWith('draft-1', {
152
- draftId: 'draft-1',
153
- formId: 'form-1',
154
- data: {
155
- field1: 'new'
156
- }
157
- }, user);
158
- expect(result.success).toBe(true);
159
- });
160
- });
161
- describe('getByFormId', ()=>{
162
- it('returns paginated results with the requested page/pageSize', async ()=>{
163
- mockService.getByFormId.mockResolvedValue({
164
- data: [
165
- buildResultDto()
166
- ],
167
- total: 1
168
- });
169
- const result = await controller.getByFormId({
170
- formId: 'form-1',
171
- page: 1,
172
- pageSize: 5
173
- }, buildMockUser());
174
- expect(mockService.getByFormId).toHaveBeenCalledWith('form-1', expect.any(Object), {
175
- page: 1,
176
- pageSize: 5
177
- });
178
- expect(result.meta).toEqual({
179
- total: 1,
180
- page: 1,
181
- pageSize: 5,
182
- count: 1
183
- });
184
- });
185
- it('defaults to page 0 and pageSize 10 when not provided', async ()=>{
186
- mockService.getByFormId.mockResolvedValue({
187
- data: [],
188
- total: 0
189
- });
190
- const result = await controller.getByFormId({
191
- formId: 'form-1'
192
- }, buildMockUser());
193
- expect(mockService.getByFormId).toHaveBeenCalledWith('form-1', expect.any(Object), {
194
- page: 0,
195
- pageSize: 10
196
- });
197
- expect(result.meta).toEqual({
198
- total: 0,
199
- page: 0,
200
- pageSize: 10,
201
- count: 0
202
- });
203
- });
204
- });
205
- describe('hasUserSubmitted', ()=>{
206
- it('returns a truthy submitted message when the user has submitted', async ()=>{
207
- mockService.hasUserSubmitted.mockResolvedValue(true);
208
- const result = await controller.hasUserSubmitted({
209
- formId: 'form-1'
210
- }, buildMockUser());
211
- expect(result).toEqual({
212
- success: true,
213
- message: 'User has submitted this form',
214
- messageKey: 'form.result.has.submitted.success',
215
- data: true
216
- });
217
- });
218
- it('returns a not-submitted message when the user has not submitted', async ()=>{
219
- mockService.hasUserSubmitted.mockResolvedValue(false);
220
- const result = await controller.hasUserSubmitted({
221
- formId: 'form-1'
222
- }, buildMockUser());
223
- expect(result).toEqual({
224
- success: true,
225
- message: 'User has not submitted this form',
226
- messageKey: 'form.result.has.not.submitted.success',
227
- data: false
228
- });
229
- });
230
- });
231
- // ==========================================================================
232
- // Inherited base CRUD wiring (createApiController)
233
- // ==========================================================================
234
- describe('inherited CRUD endpoints', ()=>{
235
- it('delete delegates to FormResultService.delete', async ()=>{
236
- mockService.delete.mockResolvedValue(null);
237
- const user = buildMockUser();
238
- const result = await controller.delete({
239
- id: [
240
- 'result-1'
241
- ],
242
- type: 'delete'
243
- }, user);
244
- expect(mockService.delete).toHaveBeenCalledWith({
245
- id: [
246
- 'result-1'
247
- ],
248
- type: 'delete'
249
- }, user);
250
- expect(result.success).toBe(true);
251
- });
252
- });
253
- });
@@ -1,221 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { createApiController, FORM_PERMISSIONS } from '@flusys/nestjs-shared/classes';
29
- import { CurrentUser, Public, RequirePermission } from '@flusys/nestjs-shared/decorators';
30
- import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
31
- import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
32
- import { Controller, HttpCode, HttpStatus, Inject, Param, Post, UseGuards } from '@nestjs/common';
33
- import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
34
- import { FORM_MESSAGES } from '../config';
35
- import { CreateFormDto, FormResponseDto, UpdateFormDto } from '../dtos';
36
- import { FormService } from '../services/form.service';
37
- export class FormController extends createApiController(CreateFormDto, UpdateFormDto, FormResponseDto, {
38
- entityName: 'form',
39
- security: {
40
- insert: {
41
- level: 'permission',
42
- permissions: [
43
- FORM_PERMISSIONS.CREATE
44
- ]
45
- },
46
- insertMany: {
47
- level: 'permission',
48
- permissions: [
49
- FORM_PERMISSIONS.CREATE
50
- ]
51
- },
52
- getById: {
53
- level: 'permission',
54
- permissions: [
55
- FORM_PERMISSIONS.READ
56
- ]
57
- },
58
- getAll: {
59
- level: 'permission',
60
- permissions: [
61
- FORM_PERMISSIONS.READ
62
- ]
63
- },
64
- update: {
65
- level: 'permission',
66
- permissions: [
67
- FORM_PERMISSIONS.UPDATE
68
- ]
69
- },
70
- updateMany: {
71
- level: 'permission',
72
- permissions: [
73
- FORM_PERMISSIONS.UPDATE
74
- ]
75
- },
76
- delete: {
77
- level: 'permission',
78
- permissions: [
79
- FORM_PERMISSIONS.DELETE
80
- ]
81
- }
82
- }
83
- }) {
84
- // Public Endpoints
85
- /**
86
- * Get form access info (no authentication required)
87
- */ async getFormAccessInfo(id) {
88
- const data = await this.formService.getFormAccessInfo(id);
89
- return {
90
- success: true,
91
- message: 'Form access info retrieved',
92
- messageKey: FORM_MESSAGES.GET_ACCESS_INFO_SUCCESS,
93
- data
94
- };
95
- }
96
- async getPublicForm(id) {
97
- const data = await this.formService.getPublicForm(id);
98
- return {
99
- success: true,
100
- message: 'Form retrieved',
101
- messageKey: FORM_MESSAGES.GET_SUCCESS,
102
- data
103
- };
104
- }
105
- async getAuthenticatedForm(id, user) {
106
- const data = await this.formService.getAuthenticatedForm(id, user);
107
- return {
108
- success: true,
109
- message: 'Form retrieved',
110
- messageKey: FORM_MESSAGES.GET_SUCCESS,
111
- data
112
- };
113
- }
114
- async getBySlug(slug, _user) {
115
- const data = await this.formService.getBySlug(slug);
116
- return {
117
- success: true,
118
- message: 'Form retrieved',
119
- messageKey: FORM_MESSAGES.GET_SUCCESS,
120
- data
121
- };
122
- }
123
- async getPublicFormBySlug(slug) {
124
- const data = await this.formService.getPublicFormBySlug(slug);
125
- return {
126
- success: true,
127
- message: 'Form retrieved',
128
- messageKey: FORM_MESSAGES.GET_SUCCESS,
129
- data
130
- };
131
- }
132
- constructor(formService){
133
- super(formService), _define_property(this, "formService", void 0), this.formService = formService;
134
- }
135
- }
136
- _ts_decorate([
137
- Post('access-info/:id'),
138
- Public(),
139
- HttpCode(HttpStatus.OK),
140
- ApiOperation({
141
- summary: 'Get form access info (no auth required)'
142
- }),
143
- _ts_param(0, Param('id')),
144
- _ts_metadata("design:type", Function),
145
- _ts_metadata("design:paramtypes", [
146
- String
147
- ]),
148
- _ts_metadata("design:returntype", Promise)
149
- ], FormController.prototype, "getFormAccessInfo", null);
150
- _ts_decorate([
151
- Post('public/:id'),
152
- Public(),
153
- HttpCode(HttpStatus.OK),
154
- ApiOperation({
155
- summary: 'Get public form for submission (no auth required)'
156
- }),
157
- _ts_param(0, Param('id')),
158
- _ts_metadata("design:type", Function),
159
- _ts_metadata("design:paramtypes", [
160
- String
161
- ]),
162
- _ts_metadata("design:returntype", Promise)
163
- ], FormController.prototype, "getPublicForm", null);
164
- _ts_decorate([
165
- Post('authenticated/:id'),
166
- UseGuards(JwtAuthGuard),
167
- ApiBearerAuth(),
168
- HttpCode(HttpStatus.OK),
169
- ApiOperation({
170
- summary: 'Get form for authenticated submission'
171
- }),
172
- _ts_param(0, Param('id')),
173
- _ts_param(1, CurrentUser()),
174
- _ts_metadata("design:type", Function),
175
- _ts_metadata("design:paramtypes", [
176
- String,
177
- typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
178
- ]),
179
- _ts_metadata("design:returntype", Promise)
180
- ], FormController.prototype, "getAuthenticatedForm", null);
181
- _ts_decorate([
182
- Post('by-slug/:slug'),
183
- UseGuards(JwtAuthGuard, PermissionGuard),
184
- ApiBearerAuth(),
185
- RequirePermission(FORM_PERMISSIONS.READ),
186
- HttpCode(HttpStatus.OK),
187
- ApiOperation({
188
- summary: 'Get form by slug'
189
- }),
190
- _ts_param(0, Param('slug')),
191
- _ts_param(1, CurrentUser()),
192
- _ts_metadata("design:type", Function),
193
- _ts_metadata("design:paramtypes", [
194
- String,
195
- typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
196
- ]),
197
- _ts_metadata("design:returntype", Promise)
198
- ], FormController.prototype, "getBySlug", null);
199
- _ts_decorate([
200
- Post('public/by-slug/:slug'),
201
- Public(),
202
- HttpCode(HttpStatus.OK),
203
- ApiOperation({
204
- summary: 'Get public form by slug (no auth required)'
205
- }),
206
- _ts_param(0, Param('slug')),
207
- _ts_metadata("design:type", Function),
208
- _ts_metadata("design:paramtypes", [
209
- String
210
- ]),
211
- _ts_metadata("design:returntype", Promise)
212
- ], FormController.prototype, "getPublicFormBySlug", null);
213
- FormController = _ts_decorate([
214
- ApiTags('Form Builder - Forms'),
215
- Controller('form-builder/form'),
216
- _ts_param(0, Inject(FormService)),
217
- _ts_metadata("design:type", Function),
218
- _ts_metadata("design:paramtypes", [
219
- typeof FormService === "undefined" ? Object : FormService
220
- ])
221
- ], FormController);
@@ -1,208 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { UtilsService } from '@flusys/nestjs-shared/modules';
3
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
4
- import { FormAccessType } from '../enums/form-access-type.enum';
5
- import { FormController } from './form.controller';
6
- import { FormService } from '../services/form.service';
7
- describe('FormController', ()=>{
8
- let controller;
9
- let mockService;
10
- beforeEach(async ()=>{
11
- mockService = {
12
- insert: jest.fn(),
13
- insertMany: jest.fn(),
14
- findById: jest.fn(),
15
- findByIds: jest.fn(),
16
- getAll: jest.fn(),
17
- update: jest.fn(),
18
- updateMany: jest.fn(),
19
- delete: jest.fn(),
20
- getFormAccessInfo: jest.fn(),
21
- getPublicForm: jest.fn(),
22
- getAuthenticatedForm: jest.fn(),
23
- getBySlug: jest.fn(),
24
- getPublicFormBySlug: jest.fn()
25
- };
26
- const module = await Test.createTestingModule({
27
- controllers: [
28
- FormController
29
- ],
30
- // UtilsService is a real (dependency-free) provider — required to satisfy the
31
- // Slug interceptor applied by the inherited insert/update endpoints.
32
- providers: [
33
- {
34
- provide: FormService,
35
- useValue: mockService
36
- },
37
- UtilsService
38
- ]
39
- }).compile();
40
- controller = module.get(FormController);
41
- });
42
- afterEach(()=>{
43
- jest.clearAllMocks();
44
- });
45
- describe('getFormAccessInfo', ()=>{
46
- it('returns access info without requiring authentication', async ()=>{
47
- mockService.getFormAccessInfo.mockResolvedValue({
48
- id: 'form-1',
49
- name: 'Survey',
50
- description: null,
51
- accessType: FormAccessType.PUBLIC,
52
- actionGroups: null,
53
- isActive: true
54
- });
55
- const result = await controller.getFormAccessInfo('form-1');
56
- expect(result).toEqual({
57
- success: true,
58
- message: 'Form access info retrieved',
59
- messageKey: 'form.get.access.info.success',
60
- data: expect.objectContaining({
61
- id: 'form-1',
62
- accessType: FormAccessType.PUBLIC
63
- })
64
- });
65
- expect(mockService.getFormAccessInfo).toHaveBeenCalledWith('form-1');
66
- });
67
- it('propagates NotFoundException raised by the service', async ()=>{
68
- mockService.getFormAccessInfo.mockRejectedValue(Object.assign(new Error('not found'), {
69
- messageKey: 'form.not.found'
70
- }));
71
- await expect(controller.getFormAccessInfo('missing')).rejects.toThrow();
72
- });
73
- });
74
- describe('getPublicForm', ()=>{
75
- it('returns the public form projection', async ()=>{
76
- mockService.getPublicForm.mockResolvedValue({
77
- id: 'form-1',
78
- name: 'Survey',
79
- description: null,
80
- schema: {
81
- sections: []
82
- },
83
- schemaVersion: 1
84
- });
85
- const result = await controller.getPublicForm('form-1');
86
- expect(result.success).toBe(true);
87
- expect(result.messageKey).toBe('form.get.success');
88
- expect(result.data.id).toBe('form-1');
89
- });
90
- });
91
- describe('getAuthenticatedForm', ()=>{
92
- it('forwards the current user to the service and returns the form', async ()=>{
93
- const user = buildMockUser();
94
- mockService.getAuthenticatedForm.mockResolvedValue({
95
- id: 'form-1',
96
- name: 'Survey',
97
- description: null,
98
- schema: {},
99
- schemaVersion: 1
100
- });
101
- const result = await controller.getAuthenticatedForm('form-1', user);
102
- expect(mockService.getAuthenticatedForm).toHaveBeenCalledWith('form-1', user);
103
- expect(result.data.id).toBe('form-1');
104
- });
105
- });
106
- describe('getBySlug', ()=>{
107
- it('returns the form when found by slug', async ()=>{
108
- mockService.getBySlug.mockResolvedValue({
109
- id: 'form-1',
110
- name: 'Survey',
111
- description: null,
112
- slug: 'survey',
113
- schema: {},
114
- schemaVersion: 1,
115
- accessType: FormAccessType.AUTHENTICATED,
116
- actionGroups: null,
117
- tags: null,
118
- companyId: null,
119
- isActive: true,
120
- createdAt: new Date(),
121
- updatedAt: new Date(),
122
- deletedAt: null,
123
- createdById: null,
124
- updatedById: null,
125
- deletedById: null
126
- });
127
- const result = await controller.getBySlug('survey', buildMockUser());
128
- expect(result.data?.slug).toBe('survey');
129
- });
130
- it('returns null data when no form matches the slug', async ()=>{
131
- mockService.getBySlug.mockResolvedValue(null);
132
- const result = await controller.getBySlug('missing', buildMockUser());
133
- expect(result.data).toBeNull();
134
- });
135
- });
136
- describe('getPublicFormBySlug', ()=>{
137
- it('returns the public form projection when found', async ()=>{
138
- mockService.getPublicFormBySlug.mockResolvedValue({
139
- id: 'form-1',
140
- name: 'Survey',
141
- description: null,
142
- schema: {},
143
- schemaVersion: 1
144
- });
145
- const result = await controller.getPublicFormBySlug('survey');
146
- expect(result.data?.id).toBe('form-1');
147
- });
148
- it('returns null data when no public form matches the slug', async ()=>{
149
- mockService.getPublicFormBySlug.mockResolvedValue(null);
150
- const result = await controller.getPublicFormBySlug('missing');
151
- expect(result.data).toBeNull();
152
- });
153
- });
154
- // ==========================================================================
155
- // Inherited base CRUD wiring (createApiController)
156
- // ==========================================================================
157
- describe('inherited CRUD endpoints', ()=>{
158
- it('insert delegates to FormService.insert and wraps the result', async ()=>{
159
- mockService.insert.mockResolvedValue({
160
- id: 'form-1',
161
- name: 'Survey',
162
- description: null,
163
- slug: null,
164
- schema: {},
165
- schemaVersion: 1,
166
- accessType: FormAccessType.AUTHENTICATED,
167
- actionGroups: null,
168
- tags: null,
169
- companyId: null,
170
- isActive: true,
171
- createdAt: new Date(),
172
- updatedAt: new Date(),
173
- deletedAt: null,
174
- createdById: null,
175
- updatedById: null,
176
- deletedById: null
177
- });
178
- const user = buildMockUser();
179
- const result = await controller.insert({
180
- name: 'Survey',
181
- schema: {}
182
- }, user);
183
- expect(mockService.insert).toHaveBeenCalledWith({
184
- name: 'Survey',
185
- schema: {}
186
- }, user);
187
- expect(result.success).toBe(true);
188
- expect(result.data.name).toBe('Survey');
189
- });
190
- it('delete delegates to FormService.delete', async ()=>{
191
- mockService.delete.mockResolvedValue(null);
192
- const user = buildMockUser();
193
- const result = await controller.delete({
194
- id: [
195
- 'form-1'
196
- ],
197
- type: 'delete'
198
- }, user);
199
- expect(mockService.delete).toHaveBeenCalledWith({
200
- id: [
201
- 'form-1'
202
- ],
203
- type: 'delete'
204
- }, user);
205
- expect(result.success).toBe(true);
206
- });
207
- });
208
- });