@hed-hog/core 0.0.387 → 0.0.390
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 -11
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +4 -2
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/ai/ai-attachment.fragments.d.ts +23 -0
- package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
- package/dist/ai/ai-attachment.fragments.js +34 -0
- package/dist/ai/ai-attachment.fragments.js.map +1 -0
- package/dist/ai/ai.controller.d.ts +7 -16
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.prompts.d.ts +16 -0
- package/dist/ai/ai.prompts.d.ts.map +1 -0
- package/dist/ai/ai.prompts.js +37 -0
- package/dist/ai/ai.prompts.js.map +1 -0
- package/dist/ai/ai.service.d.ts +63 -12
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +215 -88
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai/dto/chat.dto.d.ts +4 -1
- package/dist/ai/dto/chat.dto.d.ts.map +1 -1
- package/dist/ai/dto/chat.dto.js +23 -1
- package/dist/ai/dto/chat.dto.js.map +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/create-agent.dto.js +1 -1
- package/dist/ai/dto/create-agent.dto.js.map +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/update-agent.dto.js +1 -1
- package/dist/ai/dto/update-agent.dto.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
- package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.js +63 -20
- package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.js +13 -2
- package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
- package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.js +533 -60
- package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
- package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-registry.js +49 -0
- package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
- package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
- package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
- package/dist/auth/auth.controller.d.ts +24 -9
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +49 -21
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +33 -8
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +78 -29
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/captcha/captcha.module.d.ts +3 -0
- package/dist/captcha/captcha.module.d.ts.map +1 -0
- package/dist/captcha/captcha.module.js +23 -0
- package/dist/captcha/captcha.module.js.map +1 -0
- package/dist/captcha/captcha.service.d.ts +38 -0
- package/dist/captcha/captcha.service.d.ts.map +1 -0
- package/dist/captcha/captcha.service.js +144 -0
- package/dist/captcha/captcha.service.js.map +1 -0
- package/dist/challenge/challenge.service.d.ts +16 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +64 -16
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core.module.d.ts +3 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +29 -2
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
- package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
- package/dist/file/file.service.d.ts +6 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +44 -4
- package/dist/file/file.service.js.map +1 -1
- package/dist/incident/incident-notifier.service.d.ts +36 -0
- package/dist/incident/incident-notifier.service.d.ts.map +1 -0
- package/dist/incident/incident-notifier.service.js +208 -0
- package/dist/incident/incident-notifier.service.js.map +1 -0
- package/dist/incident/incident.controller.d.ts +23 -0
- package/dist/incident/incident.controller.d.ts.map +1 -0
- package/dist/incident/incident.controller.js +75 -0
- package/dist/incident/incident.controller.js.map +1 -0
- package/dist/incident/incident.module.d.ts +7 -0
- package/dist/incident/incident.module.d.ts.map +1 -0
- package/dist/incident/incident.module.js +32 -0
- package/dist/incident/incident.module.js.map +1 -0
- package/dist/incident/incident.service.d.ts +72 -0
- package/dist/incident/incident.service.d.ts.map +1 -0
- package/dist/incident/incident.service.js +184 -0
- package/dist/incident/incident.service.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/dto/test.dto.d.ts +13 -0
- package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/test.dto.js +26 -0
- package/dist/integration-profile/dto/test.dto.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +9 -1
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.module.d.ts +6 -1
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +26 -3
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +9 -1
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +26 -7
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +94 -91
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +20 -5
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
- package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/ai.testers.js +125 -0
- package/dist/integration-profile/testers/ai.testers.js.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.js +121 -0
- package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
- package/dist/integration-profile/testers/email.testers.d.ts +3 -0
- package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/email.testers.js +128 -0
- package/dist/integration-profile/testers/email.testers.js.map +1 -0
- package/dist/integration-profile/testers/http-probe.d.ts +57 -0
- package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
- package/dist/integration-profile/testers/http-probe.js +140 -0
- package/dist/integration-profile/testers/http-probe.js.map +1 -0
- package/dist/integration-profile/testers/index.d.ts +12 -0
- package/dist/integration-profile/testers/index.d.ts.map +1 -0
- package/dist/integration-profile/testers/index.js +45 -0
- package/dist/integration-profile/testers/index.js.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
- package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
- package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.js +16 -0
- package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.js +70 -0
- package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.js +285 -0
- package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
- package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
- package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/payment.testers.js +169 -0
- package/dist/integration-profile/testers/payment.testers.js.map +1 -0
- package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
- package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/storage.testers.js +208 -0
- package/dist/integration-profile/testers/storage.testers.js.map +1 -0
- package/dist/language/en.json +4 -2
- package/dist/language/pt.json +4 -2
- package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
- package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.controller.js +78 -0
- package/dist/leaked-password/leaked-password.controller.js.map +1 -0
- package/dist/leaked-password/leaked-password.module.d.ts +8 -0
- package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.module.js +31 -0
- package/dist/leaked-password/leaked-password.module.js.map +1 -0
- package/dist/leaked-password/leaked-password.service.d.ts +64 -0
- package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.service.js +218 -0
- package/dist/leaked-password/leaked-password.service.js.map +1 -0
- package/dist/locale/locale.controller.d.ts +1 -1
- package/dist/locale/locale.service.d.ts +1 -1
- package/dist/mail/dto/preview.dto.d.ts +18 -0
- package/dist/mail/dto/preview.dto.d.ts.map +1 -0
- package/dist/mail/dto/preview.dto.js +66 -0
- package/dist/mail/dto/preview.dto.js.map +1 -0
- package/dist/mail/dto/send.dto.d.ts +7 -0
- package/dist/mail/dto/send.dto.d.ts.map +1 -1
- package/dist/mail/dto/send.dto.js +10 -0
- package/dist/mail/dto/send.dto.js.map +1 -1
- package/dist/mail/mail.controller.d.ts +4 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +16 -2
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.service.d.ts +23 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +102 -13
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-layout/dto/update.dto.d.ts +17 -0
- package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
- package/dist/mail-layout/dto/update.dto.js +27 -0
- package/dist/mail-layout/dto/update.dto.js.map +1 -0
- package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
- package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.controller.js +71 -0
- package/dist/mail-layout/mail-layout.controller.js.map +1 -0
- package/dist/mail-layout/mail-layout.module.d.ts +3 -0
- package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.module.js +35 -0
- package/dist/mail-layout/mail-layout.module.js.map +1 -0
- package/dist/mail-layout/mail-layout.service.d.ts +27 -0
- package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.service.js +80 -0
- package/dist/mail-layout/mail-layout.service.js.map +1 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +80 -24
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/menu/menu.controller.d.ts +3 -3
- package/dist/menu/menu.service.d.ts +3 -3
- package/dist/notification/notification.controller.d.ts +12 -12
- package/dist/notification/notification.service.d.ts +12 -12
- package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
- package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
- package/dist/oauth/oauth-token-broker.service.js +258 -0
- package/dist/oauth/oauth-token-broker.service.js.map +1 -0
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +3 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +2 -0
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +6 -3
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +8 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +52 -0
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/presence/presence.controller.d.ts +16 -0
- package/dist/presence/presence.controller.d.ts.map +1 -0
- package/dist/presence/presence.controller.js +60 -0
- package/dist/presence/presence.controller.js.map +1 -0
- package/dist/presence/presence.listener.d.ts +29 -0
- package/dist/presence/presence.listener.d.ts.map +1 -0
- package/dist/presence/presence.listener.js +82 -0
- package/dist/presence/presence.listener.js.map +1 -0
- package/dist/presence/presence.module.d.ts +3 -0
- package/dist/presence/presence.module.d.ts.map +1 -0
- package/dist/presence/presence.module.js +32 -0
- package/dist/presence/presence.module.js.map +1 -0
- package/dist/presence/presence.service.d.ts +42 -0
- package/dist/presence/presence.service.d.ts.map +1 -0
- package/dist/presence/presence.service.js +147 -0
- package/dist/presence/presence.service.js.map +1 -0
- package/dist/profile/dto/delete-account.dto.d.ts +8 -0
- package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
- package/dist/profile/dto/delete-account.dto.js +22 -0
- package/dist/profile/dto/delete-account.dto.js.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.js +17 -0
- package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
- package/dist/profile/profile.controller.d.ts +21 -15
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +75 -46
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +30 -18
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +113 -37
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/realtime/realtime.gateway.d.ts +48 -0
- package/dist/realtime/realtime.gateway.d.ts.map +1 -0
- package/dist/realtime/realtime.gateway.js +156 -0
- package/dist/realtime/realtime.gateway.js.map +1 -0
- package/dist/realtime/realtime.module.d.ts +8 -0
- package/dist/realtime/realtime.module.d.ts.map +1 -0
- package/dist/realtime/realtime.module.js +29 -0
- package/dist/realtime/realtime.module.js.map +1 -0
- package/dist/realtime/realtime.service.d.ts +51 -0
- package/dist/realtime/realtime.service.d.ts.map +1 -0
- package/dist/realtime/realtime.service.js +177 -0
- package/dist/realtime/realtime.service.js.map +1 -0
- package/dist/redis/redis-pubsub.d.ts +27 -0
- package/dist/redis/redis-pubsub.d.ts.map +1 -0
- package/dist/redis/redis-pubsub.js +140 -0
- package/dist/redis/redis-pubsub.js.map +1 -0
- package/dist/role/role.controller.d.ts +3 -3
- package/dist/role/role.service.d.ts +3 -3
- package/dist/setting/dto/create.dto.d.ts.map +1 -1
- package/dist/setting/dto/create.dto.js +9 -2
- package/dist/setting/dto/create.dto.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +5 -5
- package/dist/setting/setting.service.d.ts +8 -5
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +25 -1
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
- package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
- package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
- package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
- package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
- package/dist/user/dto/query-user-mail-list.dto.js +25 -0
- package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
- package/dist/user/user-credential.util.d.ts +19 -0
- package/dist/user/user-credential.util.d.ts.map +1 -0
- package/dist/user/user-credential.util.js +32 -0
- package/dist/user/user-credential.util.js.map +1 -0
- package/dist/user/user.controller.d.ts +17 -5
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +18 -3
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +28 -6
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +204 -13
- package/dist/user/user.service.js.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
- package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
- package/dist/validators/service-locator.d.ts +9 -0
- package/dist/validators/service-locator.d.ts.map +1 -1
- package/dist/validators/service-locator.js +12 -0
- package/dist/validators/service-locator.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
- package/hedhog/data/integration_provider.yaml +16 -0
- package/hedhog/data/mail.yaml +88 -61
- package/hedhog/data/mail_layout.yaml +188 -0
- package/hedhog/data/menu.yaml +67 -3
- package/hedhog/data/route.yaml +135 -0
- package/hedhog/data/setting_group.yaml +139 -1
- package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
- package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
- package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
- package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
- package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
- package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
- package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
- package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
- package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
- package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
- package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
- package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
- package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
- package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
- package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
- package/hedhog/frontend/messages/en.json +499 -3
- package/hedhog/frontend/messages/pt.json +499 -3
- package/hedhog/table/ai_agent.yaml +1 -0
- package/hedhog/table/ai_instruction.yaml +15 -0
- package/hedhog/table/leaked_password.yaml +18 -0
- package/hedhog/table/mail_layout.yaml +35 -0
- package/hedhog/table/mail_var.yaml +4 -0
- package/hedhog/table/setting.yaml +1 -0
- package/hedhog/table/system_incident.yaml +59 -0
- package/hedhog/table/user_presence.yaml +36 -0
- package/package.json +8 -4
- package/src/access-log/access-log.interceptor.ts +4 -2
- package/src/ai/ai-attachment.fragments.spec.ts +319 -0
- package/src/ai/ai-attachment.fragments.ts +35 -0
- package/src/ai/ai.prompts.ts +38 -0
- package/src/ai/ai.service.spec.ts +280 -0
- package/src/ai/ai.service.ts +338 -82
- package/src/ai/dto/chat.dto.ts +33 -3
- package/src/ai/dto/create-agent.dto.ts +2 -2
- package/src/ai/dto/update-agent.dto.ts +2 -2
- package/src/ai-instruction/ai-instruction.controller.ts +40 -9
- package/src/ai-instruction/ai-instruction.module.ts +13 -2
- package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
- package/src/ai-instruction/ai-instruction.service.ts +661 -62
- package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
- package/src/ai-instruction/ai-prompt-registry.ts +87 -0
- package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
- package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
- package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
- package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
- package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
- package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
- package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
- package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
- package/src/auth/auth.controller.ts +31 -5
- package/src/auth/auth.service.spec.ts +3 -1
- package/src/auth/auth.service.ts +101 -36
- package/src/captcha/captcha.module.ts +10 -0
- package/src/captcha/captcha.service.spec.ts +210 -0
- package/src/captcha/captcha.service.ts +191 -0
- package/src/challenge/challenge.service.spec.ts +39 -4
- package/src/challenge/challenge.service.ts +72 -17
- package/src/core.module.ts +27 -1
- package/src/file/file.service.spec.ts +105 -0
- package/src/file/file.service.ts +60 -3
- package/src/incident/incident-notifier.service.spec.ts +205 -0
- package/src/incident/incident-notifier.service.ts +241 -0
- package/src/incident/incident.controller.ts +38 -0
- package/src/incident/incident.module.ts +19 -0
- package/src/incident/incident.service.spec.ts +150 -0
- package/src/incident/incident.service.ts +209 -0
- package/src/index.ts +37 -0
- package/src/integration-profile/dto/test.dto.ts +19 -0
- package/src/integration-profile/integration-profile.controller.ts +10 -1
- package/src/integration-profile/integration-profile.module.spec.ts +51 -0
- package/src/integration-profile/integration-profile.module.ts +23 -4
- package/src/integration-profile/integration-profile.secrets.ts +9 -1
- package/src/integration-profile/integration-profile.service.spec.ts +279 -0
- package/src/integration-profile/integration-profile.service.ts +120 -124
- package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
- package/src/integration-profile/integration-profile.utils.ts +40 -3
- package/src/integration-profile/testers/ai.testers.ts +165 -0
- package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
- package/src/integration-profile/testers/captcha.testers.ts +139 -0
- package/src/integration-profile/testers/email.testers.ts +161 -0
- package/src/integration-profile/testers/http-probe.spec.ts +153 -0
- package/src/integration-profile/testers/http-probe.ts +171 -0
- package/src/integration-profile/testers/index.ts +30 -0
- package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
- package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
- package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
- package/src/integration-profile/testers/integration-tester.types.ts +61 -0
- package/src/integration-profile/testers/messaging.testers.ts +102 -0
- package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
- package/src/integration-profile/testers/oauth.testers.ts +305 -0
- package/src/integration-profile/testers/payment.testers.ts +185 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
- package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
- package/src/integration-profile/testers/storage.testers.ts +226 -0
- package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
- package/src/language/en.json +4 -2
- package/src/language/pt.json +4 -2
- package/src/leaked-password/README.md +83 -0
- package/src/leaked-password/leaked-password.controller.ts +52 -0
- package/src/leaked-password/leaked-password.module.ts +18 -0
- package/src/leaked-password/leaked-password.service.ts +242 -0
- package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
- package/src/leaked-password/seed/common-passwords.txt +335 -0
- package/src/mail/dto/preview.dto.ts +50 -0
- package/src/mail/dto/send.dto.ts +14 -1
- package/src/mail/mail-template.spec.ts +197 -0
- package/src/mail/mail.controller.ts +9 -2
- package/src/mail/mail.service.spec.ts +116 -1
- package/src/mail/mail.service.ts +118 -12
- package/src/mail-layout/dto/update.dto.ts +19 -0
- package/src/mail-layout/mail-layout.controller.ts +47 -0
- package/src/mail-layout/mail-layout.module.ts +22 -0
- package/src/mail-layout/mail-layout.service.ts +94 -0
- package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
- package/src/mcp-chat/mcp-chat.service.ts +122 -26
- package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
- package/src/oauth/oauth-token-broker.service.ts +363 -0
- package/src/oauth/oauth.module.ts +3 -1
- package/src/oauth/oauth.service.ts +8 -2
- package/src/pdf/pdf.service.ts +75 -0
- package/src/presence/presence.controller.ts +29 -0
- package/src/presence/presence.listener.ts +53 -0
- package/src/presence/presence.module.ts +19 -0
- package/src/presence/presence.service.spec.ts +97 -0
- package/src/presence/presence.service.ts +160 -0
- package/src/profile/dto/delete-account.dto.ts +11 -0
- package/src/profile/events/profile-account-deleted.event.ts +13 -0
- package/src/profile/profile.controller.ts +46 -34
- package/src/profile/profile.service.ts +138 -38
- package/src/realtime/realtime.gateway.ts +138 -0
- package/src/realtime/realtime.module.ts +16 -0
- package/src/realtime/realtime.service.spec.ts +50 -0
- package/src/realtime/realtime.service.ts +187 -0
- package/src/redis/redis-pubsub.ts +179 -0
- package/src/setting/dto/create.dto.ts +13 -2
- package/src/setting/setting.service.spec.ts +56 -0
- package/src/setting/setting.service.ts +29 -1
- package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
- package/src/user/dto/query-user-mail-list.dto.ts +10 -0
- package/src/user/user-credential.util.ts +35 -0
- package/src/user/user.controller.ts +17 -2
- package/src/user/user.service.spec.ts +153 -0
- package/src/user/user.service.ts +255 -12
- package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
- package/src/validators/service-locator.ts +15 -0
- package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { McpChatService } from './mcp-chat.service';
|
|
3
|
+
|
|
4
|
+
jest.mock('axios');
|
|
5
|
+
|
|
6
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
7
|
+
|
|
8
|
+
type ProfileConfig = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* O serviço é instanciado à mão em vez de por `Test.createTestingModule` para
|
|
12
|
+
* não arrastar os `forwardRef` do módulo — o que está sob teste é a resolução
|
|
13
|
+
* do modelo, não a injeção.
|
|
14
|
+
*/
|
|
15
|
+
function buildService(params: {
|
|
16
|
+
providerSlug: string;
|
|
17
|
+
config: ProfileConfig;
|
|
18
|
+
clarification?: string;
|
|
19
|
+
}) {
|
|
20
|
+
const recordStep = jest.fn();
|
|
21
|
+
const finish = jest.fn();
|
|
22
|
+
|
|
23
|
+
const prisma = {
|
|
24
|
+
mcp_conversation: {
|
|
25
|
+
findUnique: jest.fn().mockResolvedValue({ user_id: 1, title: 'conversa' }),
|
|
26
|
+
update: jest.fn().mockResolvedValue({}),
|
|
27
|
+
},
|
|
28
|
+
mcp_conversation_message: {
|
|
29
|
+
create: jest.fn().mockResolvedValue({}),
|
|
30
|
+
findMany: jest.fn().mockResolvedValue([]),
|
|
31
|
+
},
|
|
32
|
+
integration_profile: {
|
|
33
|
+
findUnique: jest.fn().mockResolvedValue({
|
|
34
|
+
config: { api_key: 'chave-secreta', ...params.config },
|
|
35
|
+
integration_provider: { slug: params.providerSlug },
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
route: { findMany: jest.fn().mockResolvedValue([]) },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const setting = {
|
|
42
|
+
getSettingValues: jest.fn(async (keys: string[]) => {
|
|
43
|
+
const values: Record<string, unknown> = {
|
|
44
|
+
'mcp-enabled': true,
|
|
45
|
+
'mcp-ai-profile-id': 'perfil-mcp',
|
|
46
|
+
};
|
|
47
|
+
return Object.fromEntries(keys.map((key) => [key, values[key]]));
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Sem ferramentas registradas o loop encerra na primeira resposta, e
|
|
52
|
+
// `tools`/`tool_choice` continuam `undefined` no corpo enviado.
|
|
53
|
+
const registry = { getAll: jest.fn().mockReturnValue([]) };
|
|
54
|
+
|
|
55
|
+
const promptBuilder = {
|
|
56
|
+
build: jest.fn().mockResolvedValue({
|
|
57
|
+
systemPrompt: 'prompt de sistema',
|
|
58
|
+
layers: [],
|
|
59
|
+
layerNames: [],
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const aiExecution = {
|
|
64
|
+
start: jest.fn().mockResolvedValue(7),
|
|
65
|
+
recordStep,
|
|
66
|
+
finish,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const service = new McpChatService(
|
|
70
|
+
prisma as any,
|
|
71
|
+
setting as any,
|
|
72
|
+
registry as any,
|
|
73
|
+
promptBuilder as any,
|
|
74
|
+
aiExecution as any,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (params.clarification) {
|
|
78
|
+
jest
|
|
79
|
+
.spyOn(service as any, 'selectOpenAiTools')
|
|
80
|
+
.mockResolvedValue({ tools: [], clarification: params.clarification });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { service, recordStep, finish, prisma };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mockOpenAiReply() {
|
|
87
|
+
mockedAxios.post.mockResolvedValue({
|
|
88
|
+
data: {
|
|
89
|
+
choices: [{ message: { content: 'resposta' } }],
|
|
90
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
91
|
+
},
|
|
92
|
+
} as any);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mockGeminiReply() {
|
|
96
|
+
mockedAxios.post.mockResolvedValue({
|
|
97
|
+
data: {
|
|
98
|
+
candidates: [{ content: { parts: [{ text: 'resposta' }] } }],
|
|
99
|
+
usageMetadata: {
|
|
100
|
+
promptTokenCount: 10,
|
|
101
|
+
candidatesTokenCount: 5,
|
|
102
|
+
totalTokenCount: 15,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
} as any);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const send = (service: McpChatService) =>
|
|
109
|
+
service.sendMessage(1, 1, 'pt-BR', 'olá');
|
|
110
|
+
|
|
111
|
+
describe('McpChatService — modelo vindo do integration_profile', () => {
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
jest.clearAllMocks();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('OpenAI', () => {
|
|
117
|
+
it('sem model no perfil, envia o corpo histórico intacto', async () => {
|
|
118
|
+
const { service, recordStep } = buildService({
|
|
119
|
+
providerSlug: 'openai',
|
|
120
|
+
config: {},
|
|
121
|
+
});
|
|
122
|
+
mockOpenAiReply();
|
|
123
|
+
|
|
124
|
+
await send(service);
|
|
125
|
+
|
|
126
|
+
const [url, body, options] = mockedAxios.post.mock.calls[0];
|
|
127
|
+
expect(url).toBe('https://api.openai.com/v1/chat/completions');
|
|
128
|
+
expect(body).toEqual({
|
|
129
|
+
model: 'gpt-4o-mini',
|
|
130
|
+
messages: [
|
|
131
|
+
{ role: 'system', content: 'prompt de sistema' },
|
|
132
|
+
],
|
|
133
|
+
tools: undefined,
|
|
134
|
+
tool_choice: undefined,
|
|
135
|
+
});
|
|
136
|
+
expect(options).toEqual({
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: 'Bearer chave-secreta',
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
expect(recordStep).toHaveBeenCalledWith(
|
|
143
|
+
expect.objectContaining({ provider: 'openai', model: 'gpt-4o-mini' }),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('usa o model do perfil no corpo e no registro de custo', async () => {
|
|
148
|
+
const { service, recordStep } = buildService({
|
|
149
|
+
providerSlug: 'openai',
|
|
150
|
+
config: { model: 'gpt-4o' },
|
|
151
|
+
});
|
|
152
|
+
mockOpenAiReply();
|
|
153
|
+
|
|
154
|
+
await send(service);
|
|
155
|
+
|
|
156
|
+
expect(mockedAxios.post.mock.calls[0][1]).toEqual(
|
|
157
|
+
expect.objectContaining({ model: 'gpt-4o' }),
|
|
158
|
+
);
|
|
159
|
+
expect(recordStep).toHaveBeenCalledWith(
|
|
160
|
+
expect.objectContaining({ provider: 'openai', model: 'gpt-4o' }),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Gemini', () => {
|
|
166
|
+
it('sem model no perfil, mantém a URL e o custo históricos', async () => {
|
|
167
|
+
const { service, recordStep } = buildService({
|
|
168
|
+
providerSlug: 'gemini',
|
|
169
|
+
config: {},
|
|
170
|
+
});
|
|
171
|
+
mockGeminiReply();
|
|
172
|
+
|
|
173
|
+
await send(service);
|
|
174
|
+
|
|
175
|
+
expect(mockedAxios.post.mock.calls[0][0]).toBe(
|
|
176
|
+
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=chave-secreta',
|
|
177
|
+
);
|
|
178
|
+
expect(recordStep).toHaveBeenCalledWith(
|
|
179
|
+
expect.objectContaining({
|
|
180
|
+
provider: 'gemini',
|
|
181
|
+
model: 'gemini-2.0-flash',
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('usa o model do perfil na URL e no registro de custo', async () => {
|
|
187
|
+
const { service, recordStep } = buildService({
|
|
188
|
+
providerSlug: 'gemini',
|
|
189
|
+
config: { model: 'gemini-2.5-flash' },
|
|
190
|
+
});
|
|
191
|
+
mockGeminiReply();
|
|
192
|
+
|
|
193
|
+
await send(service);
|
|
194
|
+
|
|
195
|
+
expect(mockedAxios.post.mock.calls[0][0]).toContain(
|
|
196
|
+
'/models/gemini-2.5-flash:generateContent',
|
|
197
|
+
);
|
|
198
|
+
expect(recordStep).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({
|
|
200
|
+
provider: 'gemini',
|
|
201
|
+
model: 'gemini-2.5-flash',
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('aceita o model no formato "models/<id>" sem duplicar o segmento', async () => {
|
|
207
|
+
const { service, recordStep } = buildService({
|
|
208
|
+
providerSlug: 'gemini',
|
|
209
|
+
config: { model: 'models/gemini-2.5-flash' },
|
|
210
|
+
});
|
|
211
|
+
mockGeminiReply();
|
|
212
|
+
|
|
213
|
+
await send(service);
|
|
214
|
+
|
|
215
|
+
const url = mockedAxios.post.mock.calls[0][0];
|
|
216
|
+
expect(url).toContain('/v1beta/models/gemini-2.5-flash:generateContent');
|
|
217
|
+
expect(url).not.toContain('models/models/');
|
|
218
|
+
expect(recordStep).toHaveBeenCalledWith(
|
|
219
|
+
expect.objectContaining({ model: 'gemini-2.5-flash' }),
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('fecha a execução quando o caminho OpenAI devolve pedido de esclarecimento', async () => {
|
|
225
|
+
const { service, finish } = buildService({
|
|
226
|
+
providerSlug: 'openai',
|
|
227
|
+
config: {},
|
|
228
|
+
clarification: 'Qual conta você quer consultar?',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await send(service);
|
|
232
|
+
|
|
233
|
+
expect(mockedAxios.post).not.toHaveBeenCalled();
|
|
234
|
+
expect(finish).toHaveBeenCalledWith(7, 'completed');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -12,13 +12,27 @@ import {
|
|
|
12
12
|
} from '@nestjs/common';
|
|
13
13
|
import axios from 'axios';
|
|
14
14
|
import { AiExecutionService } from '../ai-instruction/ai-execution.service';
|
|
15
|
+
import { FALLBACK_SYSTEM_PROMPT } from '../ai-instruction/ai-instruction.service';
|
|
15
16
|
import { AiPromptBuilderService } from '../ai-instruction/ai-prompt-builder.service';
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
buildAiConfigFromIntegration,
|
|
19
|
+
DEEPSEEK_API_BASE_URL,
|
|
20
|
+
DEEPSEEK_DEFAULT_MODEL,
|
|
21
|
+
} from '../integration-profile/integration-profile.utils';
|
|
17
22
|
import { McpRegistryService, McpToolEntry } from '../mcp/mcp-registry.service';
|
|
18
23
|
import { McpContext } from '../mcp/types/mcp-context.type';
|
|
19
24
|
import { SettingService } from '../setting/setting.service';
|
|
20
25
|
import { CreateConversationDTO } from './dto/create-conversation.dto';
|
|
21
26
|
|
|
27
|
+
const OPENAI_API_BASE_URL = 'https://api.openai.com/v1';
|
|
28
|
+
/**
|
|
29
|
+
* Modelos usados quando o perfil de integração não declara `config.model`.
|
|
30
|
+
* São o comportamento histórico do chat MCP, de quando o modelo era literal:
|
|
31
|
+
* mudar aqui muda o padrão de toda instalação que ainda não preencheu o campo.
|
|
32
|
+
*/
|
|
33
|
+
const OPENAI_CHAT_MODEL = 'gpt-4o-mini';
|
|
34
|
+
const GEMINI_CHAT_MODEL = 'gemini-2.0-flash';
|
|
35
|
+
|
|
22
36
|
type OpenAiMessage =
|
|
23
37
|
| { role: 'system'; content: string }
|
|
24
38
|
| { role: 'user'; content: string }
|
|
@@ -90,13 +104,6 @@ const FORCE_FINISH_THRESHOLD = 2;
|
|
|
90
104
|
const MIN_DOMAIN_CONFIDENCE = 2;
|
|
91
105
|
const AMBIGUOUS_DOMAIN_DELTA = 1;
|
|
92
106
|
|
|
93
|
-
// SYSTEM_PROMPT is now managed via AiInstructionService (slug: system.global).
|
|
94
|
-
// This fallback is used only if the DB is unreachable during startup.
|
|
95
|
-
const SYSTEM_PROMPT_FALLBACK =
|
|
96
|
-
'You are a helpful assistant with access to tools from the HedHog system. ' +
|
|
97
|
-
"Use the available tools to answer the user's questions accurately. " +
|
|
98
|
-
'Always respond in the same language the user writes in.';
|
|
99
|
-
|
|
100
107
|
const sanitize = (n: string) => n.replace(/\./g, '__');
|
|
101
108
|
const restore = (n: string) => n.replace(/__/g, '.');
|
|
102
109
|
|
|
@@ -300,7 +307,7 @@ export class McpChatService {
|
|
|
300
307
|
|
|
301
308
|
await this.assertOwnership(conversationId, userId);
|
|
302
309
|
|
|
303
|
-
const { provider, apiKey } = await this.getProviderKey();
|
|
310
|
+
const { provider, apiKey, baseUrl, model } = await this.getProviderKey();
|
|
304
311
|
|
|
305
312
|
// Save user message
|
|
306
313
|
await this.prisma.mcp_conversation_message.create({
|
|
@@ -331,28 +338,65 @@ export class McpChatService {
|
|
|
331
338
|
|
|
332
339
|
try {
|
|
333
340
|
if (provider === 'gemini') {
|
|
334
|
-
return await this.runGeminiLoop(
|
|
341
|
+
return await this.runGeminiLoop(
|
|
342
|
+
conversationId,
|
|
343
|
+
userId,
|
|
344
|
+
locale,
|
|
345
|
+
message,
|
|
346
|
+
apiKey,
|
|
347
|
+
executionId,
|
|
348
|
+
model,
|
|
349
|
+
);
|
|
335
350
|
}
|
|
336
|
-
return await this.runOpenAiLoop(
|
|
351
|
+
return await this.runOpenAiLoop(
|
|
352
|
+
conversationId,
|
|
353
|
+
userId,
|
|
354
|
+
locale,
|
|
355
|
+
message,
|
|
356
|
+
apiKey,
|
|
357
|
+
executionId,
|
|
358
|
+
provider,
|
|
359
|
+
baseUrl,
|
|
360
|
+
model,
|
|
361
|
+
);
|
|
337
362
|
} catch (err) {
|
|
338
363
|
await this.aiExecution.finish(executionId, 'failed', String(err));
|
|
339
364
|
throw err;
|
|
340
365
|
}
|
|
341
366
|
}
|
|
342
367
|
|
|
343
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Composes the system prompt from the stored layers.
|
|
370
|
+
*
|
|
371
|
+
* `moduleSlug`/`agentSlug` activate the L3/L4 layers; without them the seeded
|
|
372
|
+
* `module.*` and `agent.*` instructions are never read. `SYSTEM_PROMPT_FALLBACK`
|
|
373
|
+
* used to be duplicated here — it now comes from `AiInstructionService`, which
|
|
374
|
+
* already falls back internally, so this only guards against the whole build
|
|
375
|
+
* throwing.
|
|
376
|
+
*/
|
|
377
|
+
private async getSystemPrompt(
|
|
378
|
+
locale?: string,
|
|
379
|
+
context?: { moduleSlug?: string; agentSlug?: string },
|
|
380
|
+
): Promise<string> {
|
|
344
381
|
try {
|
|
345
382
|
const built = await this.promptBuilder.build({
|
|
346
383
|
locale,
|
|
384
|
+
moduleSlug: context?.moduleSlug,
|
|
385
|
+
agentSlug: context?.agentSlug,
|
|
347
386
|
});
|
|
348
|
-
return built.systemPrompt ||
|
|
387
|
+
return built.systemPrompt || FALLBACK_SYSTEM_PROMPT;
|
|
349
388
|
} catch {
|
|
350
|
-
return
|
|
389
|
+
return FALLBACK_SYSTEM_PROMPT;
|
|
351
390
|
}
|
|
352
391
|
}
|
|
353
392
|
|
|
354
393
|
// ── OpenAI loop ──────────────────────────────────────────────────────────
|
|
355
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Loop de tool calling no formato OpenAI. Serve tanto para a OpenAI quanto
|
|
397
|
+
* para o DeepSeek, que expõe a mesma API de `/chat/completions` (inclusive
|
|
398
|
+
* `tools`/`tool_calls`) — muda só a base URL, o modelo e a chave.
|
|
399
|
+
*/
|
|
356
400
|
private async runOpenAiLoop(
|
|
357
401
|
conversationId: number,
|
|
358
402
|
userId: number,
|
|
@@ -360,6 +404,9 @@ export class McpChatService {
|
|
|
360
404
|
message: string,
|
|
361
405
|
apiKey: string,
|
|
362
406
|
executionId: number,
|
|
407
|
+
provider: 'openai' | 'deepseek' = 'openai',
|
|
408
|
+
baseUrl = OPENAI_API_BASE_URL,
|
|
409
|
+
model = OPENAI_CHAT_MODEL,
|
|
363
410
|
) {
|
|
364
411
|
const systemPrompt = await this.getSystemPrompt(locale);
|
|
365
412
|
|
|
@@ -385,6 +432,9 @@ export class McpChatService {
|
|
|
385
432
|
history,
|
|
386
433
|
);
|
|
387
434
|
if (toolSelection.clarification) {
|
|
435
|
+
// Sem o finish a execução fica presa em `running` para sempre — o caminho
|
|
436
|
+
// Gemini já fechava aqui.
|
|
437
|
+
await this.aiExecution.finish(executionId, 'completed');
|
|
388
438
|
return this.recordAssistantAndReturnHistory(
|
|
389
439
|
conversationId,
|
|
390
440
|
toolSelection.clarification,
|
|
@@ -401,9 +451,9 @@ export class McpChatService {
|
|
|
401
451
|
const stepStart = Date.now();
|
|
402
452
|
try {
|
|
403
453
|
response = await axios.post(
|
|
404
|
-
|
|
454
|
+
`${baseUrl}/chat/completions`,
|
|
405
455
|
{
|
|
406
|
-
model
|
|
456
|
+
model,
|
|
407
457
|
messages: openAiMessages,
|
|
408
458
|
tools: !forceFinish && tools.length > 0 ? tools : undefined,
|
|
409
459
|
tool_choice: !forceFinish && tools.length > 0 ? 'auto' : undefined,
|
|
@@ -418,17 +468,19 @@ export class McpChatService {
|
|
|
418
468
|
} catch (err: any) {
|
|
419
469
|
const status = err?.response?.status;
|
|
420
470
|
const errBody = err?.response?.data?.error;
|
|
471
|
+
const providerName = provider === 'deepseek' ? 'DeepSeek' : 'OpenAI';
|
|
421
472
|
if (status === 429) {
|
|
422
473
|
throw new HttpException(
|
|
423
|
-
errBody?.message ??
|
|
474
|
+
errBody?.message ??
|
|
475
|
+
`${providerName} rate limit exceeded. Please try again later.`,
|
|
424
476
|
HttpStatus.TOO_MANY_REQUESTS,
|
|
425
477
|
);
|
|
426
478
|
}
|
|
427
479
|
if (status === 401) {
|
|
428
|
-
throw new BadRequestException(
|
|
480
|
+
throw new BadRequestException(`Invalid ${providerName} API key.`);
|
|
429
481
|
}
|
|
430
482
|
throw new HttpException(
|
|
431
|
-
errBody?.message ??
|
|
483
|
+
errBody?.message ?? `${providerName} API error.`,
|
|
432
484
|
status ?? HttpStatus.BAD_GATEWAY,
|
|
433
485
|
);
|
|
434
486
|
}
|
|
@@ -438,8 +490,8 @@ export class McpChatService {
|
|
|
438
490
|
executionId,
|
|
439
491
|
stepOrder: stepOrder++,
|
|
440
492
|
type: 'prompt',
|
|
441
|
-
provider
|
|
442
|
-
model
|
|
493
|
+
provider,
|
|
494
|
+
model,
|
|
443
495
|
durationMs: Date.now() - stepStart,
|
|
444
496
|
usage: usage
|
|
445
497
|
? { input: usage.prompt_tokens ?? 0, output: usage.completion_tokens ?? 0, total: usage.total_tokens ?? 0 }
|
|
@@ -571,7 +623,11 @@ export class McpChatService {
|
|
|
571
623
|
message: string,
|
|
572
624
|
apiKey: string,
|
|
573
625
|
executionId: number,
|
|
626
|
+
model = GEMINI_CHAT_MODEL,
|
|
574
627
|
) {
|
|
628
|
+
// O perfil pode trazer o nome no formato da API ("models/gemini-2.0-flash");
|
|
629
|
+
// a URL abaixo já inclui o segmento `/models/`.
|
|
630
|
+
const modelId = model.replace(/^models\//, '');
|
|
575
631
|
const systemPrompt = await this.getSystemPrompt(locale);
|
|
576
632
|
|
|
577
633
|
const allHistory = await this.prisma.mcp_conversation_message.findMany({
|
|
@@ -608,7 +664,7 @@ export class McpChatService {
|
|
|
608
664
|
const stepStart = Date.now();
|
|
609
665
|
try {
|
|
610
666
|
response = await axios.post(
|
|
611
|
-
`https://generativelanguage.googleapis.com/v1beta/models
|
|
667
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelId)}:generateContent?key=${apiKey}`,
|
|
612
668
|
{
|
|
613
669
|
systemInstruction: { parts: [{ text: systemPrompt }] },
|
|
614
670
|
contents: geminiContents,
|
|
@@ -648,7 +704,7 @@ export class McpChatService {
|
|
|
648
704
|
stepOrder: stepOrder++,
|
|
649
705
|
type: 'prompt',
|
|
650
706
|
provider: 'gemini',
|
|
651
|
-
model:
|
|
707
|
+
model: modelId,
|
|
652
708
|
durationMs: Date.now() - stepStart,
|
|
653
709
|
usage: geminiUsage
|
|
654
710
|
? { input: geminiUsage.promptTokenCount ?? 0, output: geminiUsage.candidatesTokenCount ?? 0, total: geminiUsage.totalTokenCount ?? 0 }
|
|
@@ -858,7 +914,21 @@ export class McpChatService {
|
|
|
858
914
|
}
|
|
859
915
|
}
|
|
860
916
|
|
|
861
|
-
|
|
917
|
+
/**
|
|
918
|
+
* Resolve credenciais e modelo do chat MCP a partir do perfil de integração
|
|
919
|
+
* apontado pela setting `mcp-ai-profile-id`.
|
|
920
|
+
*
|
|
921
|
+
* O `model` segue a mesma precedência do resto do codebase — aqui sem o degrau
|
|
922
|
+
* de `ai_instruction`, porque este fluxo não tem prompt de task próprio:
|
|
923
|
+
* `integration_profile.config.model` > constante de default do provider.
|
|
924
|
+
*/
|
|
925
|
+
private async getProviderKey(): Promise<{
|
|
926
|
+
provider: 'openai' | 'gemini' | 'deepseek';
|
|
927
|
+
apiKey: string;
|
|
928
|
+
/** Base URL só se aplica aos providers no formato OpenAI; o Gemini leva o modelo na URL. */
|
|
929
|
+
baseUrl: string;
|
|
930
|
+
model: string;
|
|
931
|
+
}> {
|
|
862
932
|
const settings = await this.setting.getSettingValues(['mcp-ai-profile-id']);
|
|
863
933
|
const profileSlug = String(settings['mcp-ai-profile-id'] ?? '').trim();
|
|
864
934
|
|
|
@@ -878,13 +948,39 @@ export class McpChatService {
|
|
|
878
948
|
}
|
|
879
949
|
|
|
880
950
|
const providerSlug = profile.integration_provider.slug;
|
|
881
|
-
const { apiKey } = buildAiConfigFromIntegration(
|
|
951
|
+
const { apiKey, model, baseUrl } = buildAiConfigFromIntegration(
|
|
952
|
+
providerSlug,
|
|
953
|
+
profile.config,
|
|
954
|
+
);
|
|
882
955
|
|
|
883
956
|
if (!apiKey) {
|
|
884
957
|
throw new BadRequestException(`MCP AI profile "${profileSlug}" has no API key configured.`);
|
|
885
958
|
}
|
|
886
959
|
|
|
887
|
-
|
|
960
|
+
if (providerSlug === 'gemini') {
|
|
961
|
+
return {
|
|
962
|
+
provider: 'gemini',
|
|
963
|
+
apiKey,
|
|
964
|
+
baseUrl: '',
|
|
965
|
+
model: model || GEMINI_CHAT_MODEL,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (providerSlug === 'deepseek') {
|
|
970
|
+
return {
|
|
971
|
+
provider: 'deepseek',
|
|
972
|
+
apiKey,
|
|
973
|
+
baseUrl: baseUrl || DEEPSEEK_API_BASE_URL,
|
|
974
|
+
model: model || DEEPSEEK_DEFAULT_MODEL,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return {
|
|
979
|
+
provider: 'openai',
|
|
980
|
+
apiKey,
|
|
981
|
+
baseUrl: OPENAI_API_BASE_URL,
|
|
982
|
+
model: model || OPENAI_CHAT_MODEL,
|
|
983
|
+
};
|
|
888
984
|
}
|
|
889
985
|
|
|
890
986
|
private async buildOpenAiTools(
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
|
2
|
+
import { of, throwError } from 'rxjs';
|
|
3
|
+
import { SecurityService } from '../security/security.service';
|
|
4
|
+
import {
|
|
5
|
+
OAuthTokenBrokerError,
|
|
6
|
+
OAuthTokenBrokerService,
|
|
7
|
+
} from './oauth-token-broker.service';
|
|
8
|
+
|
|
9
|
+
const ENCRYPTION_SECRET = 'test-encryption-secret-0123456789';
|
|
10
|
+
|
|
11
|
+
const makeSecurity = () =>
|
|
12
|
+
new SecurityService({
|
|
13
|
+
get: (key: string) =>
|
|
14
|
+
key === 'ENCRYPTION_SECRET' ? ENCRYPTION_SECRET : undefined,
|
|
15
|
+
} as any);
|
|
16
|
+
|
|
17
|
+
const makeDeps = () => {
|
|
18
|
+
const security = makeSecurity();
|
|
19
|
+
const http = { post: jest.fn() };
|
|
20
|
+
const prisma = {
|
|
21
|
+
integration_profile: { findUnique: jest.fn() },
|
|
22
|
+
user_account: { findFirst: jest.fn(), update: jest.fn() },
|
|
23
|
+
};
|
|
24
|
+
const setting = { getSettingValues: jest.fn() };
|
|
25
|
+
return { security, http, prisma, setting };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const makeService = (deps = makeDeps()) => ({
|
|
29
|
+
service: new OAuthTokenBrokerService(
|
|
30
|
+
deps.http as any,
|
|
31
|
+
deps.setting as any,
|
|
32
|
+
deps.prisma as any,
|
|
33
|
+
deps.security,
|
|
34
|
+
),
|
|
35
|
+
deps,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const PROVIDER = 'microsoft-entra-id';
|
|
39
|
+
|
|
40
|
+
const primeConfig = (deps: ReturnType<typeof makeDeps>) => {
|
|
41
|
+
deps.setting.getSettingValues.mockResolvedValue({
|
|
42
|
+
'oauth-microsoft-entra-id-profile-id': 'ms-profile',
|
|
43
|
+
} as any);
|
|
44
|
+
deps.prisma.integration_profile.findUnique.mockResolvedValue({
|
|
45
|
+
slug: 'ms-profile',
|
|
46
|
+
config: {
|
|
47
|
+
client_id: 'CID',
|
|
48
|
+
client_secret: 'SECRET',
|
|
49
|
+
tenant_id: 'TENANT',
|
|
50
|
+
},
|
|
51
|
+
integration_provider: { slug: 'microsoft-entra-id-oauth' },
|
|
52
|
+
} as any);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe('OAuthTokenBrokerService', () => {
|
|
56
|
+
let deps: ReturnType<typeof makeDeps>;
|
|
57
|
+
let service: OAuthTokenBrokerService;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
const built = makeService();
|
|
61
|
+
deps = built.deps;
|
|
62
|
+
service = built.service;
|
|
63
|
+
primeConfig(deps);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const storedRefreshTokenRow = (security: SecurityService, plain: string) => ({
|
|
67
|
+
id: 42,
|
|
68
|
+
refresh_token: Buffer.from(security.encrypt(plain)),
|
|
69
|
+
scopes: 'openid,offline_access,Calendars.Read',
|
|
70
|
+
token_expires_at: new Date('2020-01-01T00:00:00Z'),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('decrypts the stored refresh token, exchanges it, and returns an access token', async () => {
|
|
74
|
+
deps.prisma.user_account.findFirst.mockResolvedValue(
|
|
75
|
+
storedRefreshTokenRow(deps.security, 'refresh-original') as any,
|
|
76
|
+
);
|
|
77
|
+
deps.http.post.mockReturnValue(
|
|
78
|
+
of({
|
|
79
|
+
data: {
|
|
80
|
+
access_token: 'ACCESS-1',
|
|
81
|
+
refresh_token: 'refresh-rotated',
|
|
82
|
+
expires_in: 3600,
|
|
83
|
+
},
|
|
84
|
+
}) as any,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const result = await service.getAccessToken(1, PROVIDER);
|
|
88
|
+
|
|
89
|
+
expect(result.accessToken).toBe('ACCESS-1');
|
|
90
|
+
expect(result.expiresAt.getTime()).toBeGreaterThan(Date.now());
|
|
91
|
+
|
|
92
|
+
// The token endpoint received the DECRYPTED refresh token, not the ciphertext.
|
|
93
|
+
const [, body] = deps.http.post.mock.calls[0] as [string, string, unknown];
|
|
94
|
+
expect(body).toContain('grant_type=refresh_token');
|
|
95
|
+
expect(body).toContain('refresh_token=refresh-original');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('persists the rotated refresh token re-encrypted (never in plaintext)', async () => {
|
|
99
|
+
deps.prisma.user_account.findFirst.mockResolvedValue(
|
|
100
|
+
storedRefreshTokenRow(deps.security, 'refresh-original') as any,
|
|
101
|
+
);
|
|
102
|
+
deps.http.post.mockReturnValue(
|
|
103
|
+
of({
|
|
104
|
+
data: {
|
|
105
|
+
access_token: 'ACCESS-1',
|
|
106
|
+
refresh_token: 'refresh-rotated',
|
|
107
|
+
expires_in: 3600,
|
|
108
|
+
},
|
|
109
|
+
}) as any,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await service.getAccessToken(1, PROVIDER);
|
|
113
|
+
|
|
114
|
+
expect(deps.prisma.user_account.update).toHaveBeenCalledTimes(1);
|
|
115
|
+
const updateArg = deps.prisma.user_account.update.mock.calls[0][0] as any;
|
|
116
|
+
const storedBuffer = updateArg.data.refresh_token as Buffer;
|
|
117
|
+
// Round-trips back to the rotated token, and is not the plaintext itself.
|
|
118
|
+
expect(storedBuffer.toString()).not.toContain('refresh-rotated');
|
|
119
|
+
expect(deps.security.decrypt(storedBuffer.toString())).toBe('refresh-rotated');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('serves a cached access token without a second token exchange', async () => {
|
|
123
|
+
deps.prisma.user_account.findFirst.mockResolvedValue(
|
|
124
|
+
storedRefreshTokenRow(deps.security, 'refresh-original') as any,
|
|
125
|
+
);
|
|
126
|
+
deps.http.post.mockReturnValue(
|
|
127
|
+
of({
|
|
128
|
+
data: { access_token: 'ACCESS-1', expires_in: 3600 },
|
|
129
|
+
}) as any,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await service.getAccessToken(1, PROVIDER);
|
|
133
|
+
await service.getAccessToken(1, PROVIDER);
|
|
134
|
+
|
|
135
|
+
expect(deps.http.post).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('throws not_connected when the user has no linked account', async () => {
|
|
139
|
+
deps.prisma.user_account.findFirst.mockResolvedValue(null as any);
|
|
140
|
+
|
|
141
|
+
await expect(service.getAccessToken(1, PROVIDER)).rejects.toMatchObject({
|
|
142
|
+
reason: 'not_connected',
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('throws refresh_revoked on invalid_grant from the provider', async () => {
|
|
147
|
+
deps.prisma.user_account.findFirst.mockResolvedValue(
|
|
148
|
+
storedRefreshTokenRow(deps.security, 'refresh-original') as any,
|
|
149
|
+
);
|
|
150
|
+
deps.http.post.mockReturnValue(
|
|
151
|
+
throwError(() => ({
|
|
152
|
+
response: { data: { error: 'invalid_grant' }, status: 400 },
|
|
153
|
+
})) as any,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await expect(service.getAccessToken(1, PROVIDER)).rejects.toBeInstanceOf(
|
|
157
|
+
OAuthTokenBrokerError,
|
|
158
|
+
);
|
|
159
|
+
await expect(service.getAccessToken(1, PROVIDER)).rejects.toMatchObject({
|
|
160
|
+
reason: 'refresh_revoked',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('parses granted scopes from the stored account', async () => {
|
|
165
|
+
deps.prisma.user_account.findFirst.mockResolvedValue({
|
|
166
|
+
scopes: 'openid, Calendars.Read offline_access',
|
|
167
|
+
} as any);
|
|
168
|
+
|
|
169
|
+
const scopes = await service.getGrantedScopes(1, PROVIDER);
|
|
170
|
+
expect(scopes).toEqual(['openid', 'Calendars.Read', 'offline_access']);
|
|
171
|
+
});
|
|
172
|
+
});
|