@hed-hog/core 0.0.373 → 0.0.374

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 (107) hide show
  1. package/README.md +83 -1
  2. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  3. package/dist/integration-profile/integration-profile.secrets.js +4 -0
  4. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  5. package/dist/integration-profile/integration-profile.utils.d.ts +6 -0
  6. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  7. package/dist/integration-profile/integration-profile.utils.js +1 -1
  8. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  9. package/dist/oauth/oauth.controller.d.ts +11 -3
  10. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  11. package/dist/oauth/oauth.controller.js +52 -14
  12. package/dist/oauth/oauth.controller.js.map +1 -1
  13. package/dist/oauth/oauth.module.d.ts.map +1 -1
  14. package/dist/oauth/oauth.module.js +4 -0
  15. package/dist/oauth/oauth.module.js.map +1 -1
  16. package/dist/oauth/oauth.service.d.ts +38 -2
  17. package/dist/oauth/oauth.service.d.ts.map +1 -1
  18. package/dist/oauth/oauth.service.js +122 -5
  19. package/dist/oauth/oauth.service.js.map +1 -1
  20. package/dist/oauth/providers/apple.provider.d.ts +34 -0
  21. package/dist/oauth/providers/apple.provider.d.ts.map +1 -0
  22. package/dist/oauth/providers/apple.provider.js +197 -0
  23. package/dist/oauth/providers/apple.provider.js.map +1 -0
  24. package/dist/oauth/providers/facebook.provider.d.ts +1 -1
  25. package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
  26. package/dist/oauth/providers/facebook.provider.js +3 -2
  27. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  28. package/dist/oauth/providers/google.provider.d.ts +1 -1
  29. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  30. package/dist/oauth/providers/google.provider.js +3 -2
  31. package/dist/oauth/providers/google.provider.js.map +1 -1
  32. package/dist/oauth/providers/linkedin.provider.d.ts +25 -0
  33. package/dist/oauth/providers/linkedin.provider.d.ts.map +1 -0
  34. package/dist/oauth/providers/linkedin.provider.js +99 -0
  35. package/dist/oauth/providers/linkedin.provider.js.map +1 -0
  36. package/dist/oauth/providers/microsoft-entra-id.provider.js +1 -1
  37. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  38. package/dist/oauth/providers/microsoft.provider.d.ts +1 -1
  39. package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
  40. package/dist/oauth/providers/microsoft.provider.js +3 -2
  41. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  42. package/dist/pdf/pdf.service.d.ts.map +1 -1
  43. package/dist/pdf/pdf.service.js +19 -2
  44. package/dist/pdf/pdf.service.js.map +1 -1
  45. package/dist/setting/setting.service.d.ts.map +1 -1
  46. package/dist/setting/setting.service.js +4 -1
  47. package/dist/setting/setting.service.js.map +1 -1
  48. package/hedhog/data/integration_provider.yaml +16 -0
  49. package/hedhog/data/setting_group.yaml +98 -0
  50. package/hedhog/data/setting_subgroup.yaml +16 -0
  51. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +2 -0
  52. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +11 -11
  53. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +42 -0
  54. package/hedhog/frontend/app/menu/page.tsx.ejs +1 -1
  55. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +8 -0
  56. package/hedhog/frontend/messages/en.json +35 -0
  57. package/hedhog/frontend/messages/pt.json +35 -0
  58. package/hedhog/table/user_account.yaml +1 -0
  59. package/package.json +3 -3
  60. package/src/access-log/access-log.service.spec.ts +235 -0
  61. package/src/ai-instruction/ai-prompt-builder.service.spec.ts +250 -0
  62. package/src/auth/auth.service.spec.ts +891 -0
  63. package/src/auth/guards/auth.guard.spec.ts +177 -0
  64. package/src/challenge/challenge.service.spec.ts +334 -0
  65. package/src/dashboard/dashboard-core/dashboard-core.service.spec.ts +193 -0
  66. package/src/file/image-optimization.service.spec.ts +283 -0
  67. package/src/file/provider/provider.factory.spec.ts +111 -0
  68. package/src/install/install.service.spec.ts +139 -0
  69. package/src/integration/services/event-subscriber.registry.spec.ts +143 -0
  70. package/src/integration/services/inbox.service.spec.ts +183 -0
  71. package/src/integration/services/integration-link.service.spec.ts +217 -0
  72. package/src/integration/services/outbox.service.spec.ts +290 -0
  73. package/src/integration-profile/integration-credential-crypto.service.spec.ts +289 -0
  74. package/src/integration-profile/integration-profile.secrets.ts +4 -0
  75. package/src/integration-profile/integration-profile.utils.spec.ts +332 -0
  76. package/src/integration-profile/integration-profile.utils.ts +14 -1
  77. package/src/locale/locale.service.spec.ts +78 -0
  78. package/src/mail/mail.service.spec.ts +257 -0
  79. package/src/mcp/guards/mcp-auth.guard.spec.ts +87 -0
  80. package/src/mcp/guards/mcp-permission.guard.spec.ts +64 -0
  81. package/src/mcp/mcp-api-key.service.spec.ts +125 -0
  82. package/src/mcp/mcp-registry.service.spec.ts +96 -0
  83. package/src/mcp/mcp-server.service.spec.ts +187 -0
  84. package/src/menu/menu.service.spec.ts +76 -0
  85. package/src/notification/notification.service.spec.ts +297 -0
  86. package/src/oauth/oauth.controller.ts +53 -10
  87. package/src/oauth/oauth.module.ts +4 -0
  88. package/src/oauth/oauth.service.spec.ts +332 -0
  89. package/src/oauth/oauth.service.ts +156 -1
  90. package/src/oauth/providers/apple.provider.ts +175 -0
  91. package/src/oauth/providers/facebook.provider.ts +3 -2
  92. package/src/oauth/providers/google.provider.ts +3 -2
  93. package/src/oauth/providers/linkedin.provider.ts +96 -0
  94. package/src/oauth/providers/microsoft-entra-id.provider.ts +1 -1
  95. package/src/oauth/providers/microsoft.provider.ts +3 -2
  96. package/src/pdf/pdf.service.ts +20 -2
  97. package/src/role/guards/role.guard.spec.ts +157 -0
  98. package/src/role/role.service.spec.ts +179 -0
  99. package/src/session/session.service.spec.ts +376 -0
  100. package/src/setting/setting.service.spec.ts +316 -0
  101. package/src/setting/setting.service.ts +4 -1
  102. package/src/task/task.service.spec.ts +47 -0
  103. package/src/token/token.service.spec.ts +351 -0
  104. package/src/user/user.service.spec.ts +256 -0
  105. package/src/utils/locale-context.spec.ts +22 -0
  106. package/src/utils/webauthn-origin.spec.ts +73 -0
  107. package/src/webhook-integration/integration-event-catalog.service.spec.ts +149 -0
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
2
+ import {
3
+ ForbiddenException,
4
+ RequestMethod,
5
+ UnauthorizedException,
6
+ } from '@nestjs/common';
7
+ import { METHOD_METADATA } from '@nestjs/common/constants';
8
+ import { IS_PUBLIC_KEY, WITH_NO_ROLE, WITH_ROLE } from '@hed-hog/api';
9
+ import { RoleGuard } from './role.guard';
10
+
11
+ const CTRL = 'CTRL';
12
+ const HANDLER = 'HANDLER';
13
+
14
+ const makeContext = (request: any) => ({
15
+ getHandler: () => HANDLER,
16
+ getClass: () => CTRL,
17
+ switchToHttp: () => ({ getRequest: () => request }),
18
+ });
19
+
20
+ const makeDeps = () => {
21
+ const reflector = { getAllAndOverride: jest.fn(), get: jest.fn() };
22
+ const prisma = {
23
+ route: { count: jest.fn() },
24
+ user_credential: { findFirst: jest.fn() },
25
+ };
26
+ return { reflector, prisma };
27
+ };
28
+
29
+ const makeService = (deps = makeDeps()) => ({
30
+ guard: new RoleGuard(deps.reflector as any, deps.prisma as any),
31
+ deps,
32
+ });
33
+
34
+ const setMeta = (
35
+ reflector: any,
36
+ meta: { public?: boolean; withRole?: boolean; withNoRole?: boolean },
37
+ ) => {
38
+ reflector.getAllAndOverride.mockImplementation((key: any) => {
39
+ if (key === IS_PUBLIC_KEY) return meta.public;
40
+ if (key === WITH_ROLE) return meta.withRole;
41
+ if (key === WITH_NO_ROLE) return meta.withNoRole;
42
+ return undefined;
43
+ });
44
+ };
45
+
46
+ const setPath = (
47
+ reflector: any,
48
+ controllerPath: string,
49
+ methodPath: string,
50
+ method: RequestMethod,
51
+ ) => {
52
+ reflector.get.mockImplementation((key: any, target: any) => {
53
+ if (key === 'path') return target === CTRL ? controllerPath : methodPath;
54
+ if (key === METHOD_METADATA) return method;
55
+ return undefined;
56
+ });
57
+ };
58
+
59
+ describe('RoleGuard', () => {
60
+ afterEach(() => jest.clearAllMocks());
61
+
62
+ it('permite rota pública', async () => {
63
+ const { guard, deps } = makeService();
64
+ setMeta(deps.reflector, { public: true });
65
+ await expect(
66
+ guard.canActivate(makeContext({ headers: {} }) as any),
67
+ ).resolves.toBe(true);
68
+ });
69
+
70
+ it('permite withNoRole', async () => {
71
+ const { guard, deps } = makeService();
72
+ setMeta(deps.reflector, { withNoRole: true });
73
+ await expect(
74
+ guard.canActivate(makeContext({ headers: {} }) as any),
75
+ ).resolves.toBe(true);
76
+ });
77
+
78
+ it('permite quando não há withRole', async () => {
79
+ const { guard, deps } = makeService();
80
+ setMeta(deps.reflector, {});
81
+ await expect(
82
+ guard.canActivate(makeContext({ headers: {} }) as any),
83
+ ).resolves.toBe(true);
84
+ });
85
+
86
+ it('nega sem token quando withRole', async () => {
87
+ const { guard, deps } = makeService();
88
+ setMeta(deps.reflector, { withRole: true });
89
+ setPath(deps.reflector, 'user', 'list', RequestMethod.GET);
90
+ const request = { headers: {} };
91
+ await expect(guard.canActivate(makeContext(request) as any)).rejects.toThrow(
92
+ UnauthorizedException,
93
+ );
94
+ });
95
+
96
+ it('permite quando rota autorizada e sem reset pendente', async () => {
97
+ const { guard, deps } = makeService();
98
+ setMeta(deps.reflector, { withRole: true });
99
+ setPath(deps.reflector, 'user', '', RequestMethod.GET);
100
+ deps.prisma.route.count.mockResolvedValue(1);
101
+ deps.prisma.user_credential.findFirst.mockResolvedValue(null);
102
+ const request = {
103
+ headers: { authorization: 'Bearer tok' },
104
+ auth: { sub: 3 },
105
+ };
106
+ await expect(guard.canActivate(makeContext(request) as any)).resolves.toBe(
107
+ true,
108
+ );
109
+ expect(deps.prisma.route.count).toHaveBeenCalledWith(
110
+ expect.objectContaining({ where: expect.objectContaining({ url: '/user' }) }),
111
+ );
112
+ });
113
+
114
+ it('nega (Forbidden) quando rota não autorizada', async () => {
115
+ const { guard, deps } = makeService();
116
+ setMeta(deps.reflector, { withRole: true });
117
+ setPath(deps.reflector, 'user', 'secret', RequestMethod.POST);
118
+ deps.prisma.route.count.mockResolvedValue(0);
119
+ const request = {
120
+ headers: { authorization: 'Bearer tok' },
121
+ auth: { sub: 3 },
122
+ };
123
+ await expect(guard.canActivate(makeContext(request) as any)).rejects.toThrow(
124
+ ForbiddenException,
125
+ );
126
+ });
127
+
128
+ it('nega quando há reset de senha pendente em rota não permitida', async () => {
129
+ const { guard, deps } = makeService();
130
+ setMeta(deps.reflector, { withRole: true });
131
+ setPath(deps.reflector, 'user', 'list', RequestMethod.GET);
132
+ deps.prisma.route.count.mockResolvedValue(1);
133
+ deps.prisma.user_credential.findFirst.mockResolvedValue({ id: 1 });
134
+ const request = {
135
+ headers: { authorization: 'Bearer tok' },
136
+ auth: { sub: 3 },
137
+ };
138
+ await expect(guard.canActivate(makeContext(request) as any)).rejects.toThrow(
139
+ UnauthorizedException,
140
+ );
141
+ });
142
+
143
+ it('permite reset pendente em rota liberada (PUT /profile/change-password)', async () => {
144
+ const { guard, deps } = makeService();
145
+ setMeta(deps.reflector, { withRole: true });
146
+ setPath(deps.reflector, 'profile', 'change-password', RequestMethod.PUT);
147
+ deps.prisma.route.count.mockResolvedValue(1);
148
+ deps.prisma.user_credential.findFirst.mockResolvedValue({ id: 1 });
149
+ const request = {
150
+ headers: { authorization: 'Bearer tok' },
151
+ auth: { sub: 3 },
152
+ };
153
+ await expect(guard.canActivate(makeContext(request) as any)).resolves.toBe(
154
+ true,
155
+ );
156
+ });
157
+ });
@@ -0,0 +1,179 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
2
+ import { BadRequestException, NotFoundException } from '@nestjs/common';
3
+ import { RoleService } from './role.service';
4
+
5
+ const makeDeps = () => {
6
+ const prismaService = {
7
+ role: { findUnique: jest.fn(), findMany: jest.fn(), deleteMany: jest.fn(), count: jest.fn() },
8
+ role_user: { deleteMany: jest.fn(), createMany: jest.fn() },
9
+ role_screen: { deleteMany: jest.fn(), createMany: jest.fn() },
10
+ role_route: { deleteMany: jest.fn(), createMany: jest.fn() },
11
+ role_menu: { deleteMany: jest.fn(), createMany: jest.fn() },
12
+ $queryRaw: jest.fn(),
13
+ };
14
+ const paginationService = { paginate: jest.fn().mockResolvedValue({ data: [] }) };
15
+ const localeService = { getByCode: jest.fn() };
16
+ const integrationApi = { publishEvent: jest.fn().mockResolvedValue(undefined) };
17
+ return { prismaService, paginationService, localeService, integrationApi };
18
+ };
19
+
20
+ const makeService = (deps = makeDeps()) => ({
21
+ service: new RoleService(
22
+ deps.prismaService as any,
23
+ deps.paginationService as any,
24
+ deps.localeService as any,
25
+ deps.integrationApi as any,
26
+ ),
27
+ deps,
28
+ });
29
+
30
+ describe('RoleService', () => {
31
+ afterEach(() => jest.clearAllMocks());
32
+
33
+ describe('updateUsers', () => {
34
+ it('recria vínculos de usuários', async () => {
35
+ const { service, deps } = makeService();
36
+ deps.prismaService.role_user.createMany.mockResolvedValue({ count: 2 });
37
+ const result = await service.updateUsers(3, { ids: [1, 2] } as any);
38
+ expect(deps.prismaService.role_user.deleteMany).toHaveBeenCalledWith({
39
+ where: { role_id: 3 },
40
+ });
41
+ expect(deps.prismaService.role_user.createMany).toHaveBeenCalledWith({
42
+ data: [
43
+ { role_id: 3, user_id: 1 },
44
+ { role_id: 3, user_id: 2 },
45
+ ],
46
+ skipDuplicates: true,
47
+ });
48
+ expect(result).toEqual({ count: 2 });
49
+ });
50
+ });
51
+
52
+ describe('listRoutes', () => {
53
+ it('busca contains por padrão', async () => {
54
+ const { service, deps } = makeService();
55
+ await service.listRoutes(1, {} as any, 'abc');
56
+ const opts = deps.paginationService.paginate.mock.calls[0][2];
57
+ expect(opts.where.url).toEqual({ contains: 'abc' });
58
+ });
59
+
60
+ it('busca startsWith / endsWith', async () => {
61
+ const { service, deps } = makeService();
62
+ await service.listRoutes(1, {} as any, 'abc', 'startsWith');
63
+ expect(
64
+ deps.paginationService.paginate.mock.calls[0][2].where.url,
65
+ ).toEqual({ startsWith: 'abc' });
66
+
67
+ await service.listRoutes(1, {} as any, 'abc', 'endsWith');
68
+ expect(
69
+ deps.paginationService.paginate.mock.calls[1][2].where.url,
70
+ ).toEqual({ endsWith: 'abc' });
71
+ });
72
+
73
+ it('filtra método específico mas ignora "all"', async () => {
74
+ const { service, deps } = makeService();
75
+ await service.listRoutes(1, {} as any, undefined, undefined, 'POST');
76
+ expect(deps.paginationService.paginate.mock.calls[0][2].where.method).toBe(
77
+ 'POST',
78
+ );
79
+
80
+ await service.listRoutes(1, {} as any, undefined, undefined, 'all');
81
+ expect(
82
+ deps.paginationService.paginate.mock.calls[1][2].where,
83
+ ).not.toHaveProperty('method');
84
+ });
85
+ });
86
+
87
+ describe('get', () => {
88
+ it('lança NotFound quando role ausente', async () => {
89
+ const { service, deps } = makeService();
90
+ deps.prismaService.role.findUnique.mockResolvedValue(null);
91
+ await expect(service.get(9)).rejects.toThrow(NotFoundException);
92
+ });
93
+
94
+ it('reduz role_locale em mapa por código', async () => {
95
+ const { service, deps } = makeService();
96
+ deps.prismaService.role.findUnique.mockResolvedValue({
97
+ id: 1,
98
+ slug: 'admin',
99
+ role_locale: [
100
+ { name: 'Admin', description: 'd', locale: { code: 'en' } },
101
+ { name: 'Adm', description: 'dp', locale: { code: 'pt' } },
102
+ ],
103
+ });
104
+ const result = await service.get(1);
105
+ expect(result.locale).toEqual({
106
+ en: { name: 'Admin', description: 'd' },
107
+ pt: { name: 'Adm', description: 'dp' },
108
+ });
109
+ });
110
+ });
111
+
112
+ describe('list', () => {
113
+ it('lança quando locale inexistente', async () => {
114
+ const { service, deps } = makeService();
115
+ deps.localeService.getByCode.mockResolvedValue(null);
116
+ await expect(
117
+ service.list({ search: '', take: 10, skip: 0 }, 'zz'),
118
+ ).rejects.toThrow('not found');
119
+ });
120
+
121
+ it('calcula paginação e normaliza available_locales', async () => {
122
+ const { service, deps } = makeService();
123
+ deps.localeService.getByCode.mockResolvedValue({ id: 1 });
124
+ deps.prismaService.$queryRaw.mockResolvedValue([
125
+ { role_id: 1, available_locales: null },
126
+ ]);
127
+ deps.prismaService.role.count.mockResolvedValue(25);
128
+
129
+ const result = await service.list({ search: '', take: 10, skip: 0 }, 'en');
130
+
131
+ expect(result.total).toBe(25);
132
+ expect(result.page).toBe(1);
133
+ expect(result.lastPage).toBe(3);
134
+ expect(result.prev).toBeNull();
135
+ expect(result.next).toBe(2);
136
+ expect(result.data[0].available_locales).toEqual([]);
137
+ });
138
+
139
+ it('usa branch de busca quando search preenchido', async () => {
140
+ const { service, deps } = makeService();
141
+ deps.localeService.getByCode.mockResolvedValue({ id: 1 });
142
+ deps.prismaService.$queryRaw.mockResolvedValue([]);
143
+ deps.prismaService.role.count.mockResolvedValue(0);
144
+
145
+ const result = await service.list({ search: 'adm', take: 10, skip: 10 }, 'en');
146
+ expect(result.page).toBe(2);
147
+ expect(result.lastPage).toBe(1);
148
+ expect(result.next).toBeNull();
149
+ });
150
+ });
151
+
152
+ describe('delete', () => {
153
+ it('lança BadRequest sem ids', async () => {
154
+ const { service } = makeService();
155
+ await expect(service.delete({ ids: null } as any)).rejects.toThrow(
156
+ BadRequestException,
157
+ );
158
+ });
159
+
160
+ it('lança NotFound quando algum id não existe', async () => {
161
+ const { service, deps } = makeService();
162
+ deps.prismaService.role.findMany.mockResolvedValue([{ id: 1 }]);
163
+ await expect(service.delete({ ids: [1, 2] } as any)).rejects.toThrow(
164
+ NotFoundException,
165
+ );
166
+ });
167
+
168
+ it('remove roles e publica evento', async () => {
169
+ const { service, deps } = makeService();
170
+ deps.prismaService.role.findMany.mockResolvedValue([{ id: 1 }, { id: 2 }]);
171
+ deps.prismaService.role.deleteMany.mockResolvedValue({ count: 2 });
172
+ const result = await service.delete({ ids: [1, 2] } as any);
173
+ expect(result).toEqual({ count: 2 });
174
+ expect(deps.integrationApi.publishEvent).toHaveBeenCalledWith(
175
+ expect.objectContaining({ eventName: 'core.role.deleted' }),
176
+ );
177
+ });
178
+ });
179
+ });