@ikatec/digisac-api-sdk 4.5.0 → 4.6.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 (123) hide show
  1. package/README.md +5 -3
  2. package/dist/apis/answers/AnswersApi.cjs +38 -0
  3. package/dist/apis/answers/AnswersApi.cjs.map +1 -1
  4. package/dist/apis/answers/AnswersApi.d.ts +33 -2
  5. package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
  6. package/dist/apis/answers/AnswersApi.mjs +38 -0
  7. package/dist/apis/answers/AnswersApi.mjs.map +1 -1
  8. package/dist/apis/answers/types.d.ts +64 -1
  9. package/dist/apis/answers/types.d.ts.map +1 -1
  10. package/dist/apis/botSimulators/BotSimulatorsApi.cjs +42 -0
  11. package/dist/apis/botSimulators/BotSimulatorsApi.cjs.map +1 -0
  12. package/dist/apis/botSimulators/BotSimulatorsApi.d.ts +31 -0
  13. package/dist/apis/botSimulators/BotSimulatorsApi.d.ts.map +1 -0
  14. package/dist/apis/botSimulators/BotSimulatorsApi.mjs +41 -0
  15. package/dist/apis/botSimulators/BotSimulatorsApi.mjs.map +1 -0
  16. package/dist/apis/botSimulators/index.cjs +4 -0
  17. package/dist/apis/botSimulators/index.d.ts +3 -0
  18. package/dist/apis/botSimulators/index.d.ts.map +1 -0
  19. package/dist/apis/botSimulators/index.mjs +3 -0
  20. package/dist/apis/botSimulators/types.cjs +0 -0
  21. package/dist/apis/botSimulators/types.d.ts +73 -0
  22. package/dist/apis/botSimulators/types.d.ts.map +1 -0
  23. package/dist/apis/botSimulators/types.mjs +0 -0
  24. package/dist/apis/botVersions/BotVersionsApi.cjs +25 -0
  25. package/dist/apis/botVersions/BotVersionsApi.cjs.map +1 -1
  26. package/dist/apis/botVersions/BotVersionsApi.d.ts +18 -0
  27. package/dist/apis/botVersions/BotVersionsApi.d.ts.map +1 -1
  28. package/dist/apis/botVersions/BotVersionsApi.mjs +25 -0
  29. package/dist/apis/botVersions/BotVersionsApi.mjs.map +1 -1
  30. package/dist/apis/botVersions/types.d.ts +4 -0
  31. package/dist/apis/botVersions/types.d.ts.map +1 -1
  32. package/dist/apis/bots/BotsApi.cjs +99 -4
  33. package/dist/apis/bots/BotsApi.cjs.map +1 -1
  34. package/dist/apis/bots/BotsApi.d.ts +78 -5
  35. package/dist/apis/bots/BotsApi.d.ts.map +1 -1
  36. package/dist/apis/bots/BotsApi.mjs +99 -4
  37. package/dist/apis/bots/BotsApi.mjs.map +1 -1
  38. package/dist/apis/bots/types.d.ts +266 -16
  39. package/dist/apis/bots/types.d.ts.map +1 -1
  40. package/dist/apis/index.cjs +3 -0
  41. package/dist/apis/index.d.ts +1 -0
  42. package/dist/apis/index.d.ts.map +1 -1
  43. package/dist/apis/index.mjs +3 -1
  44. package/dist/apis/permissions/types.d.ts +1 -1
  45. package/dist/apis/permissions/types.d.ts.map +1 -1
  46. package/dist/apis/questions/QuestionsApi.cjs +15 -0
  47. package/dist/apis/questions/QuestionsApi.cjs.map +1 -1
  48. package/dist/apis/questions/QuestionsApi.d.ts +15 -1
  49. package/dist/apis/questions/QuestionsApi.d.ts.map +1 -1
  50. package/dist/apis/questions/QuestionsApi.mjs +15 -0
  51. package/dist/apis/questions/QuestionsApi.mjs.map +1 -1
  52. package/dist/apis/questions/types.d.ts +25 -5
  53. package/dist/apis/questions/types.d.ts.map +1 -1
  54. package/dist/apis/quickReplies/QuickRepliesApi.cjs +16 -0
  55. package/dist/apis/quickReplies/QuickRepliesApi.cjs.map +1 -1
  56. package/dist/apis/quickReplies/QuickRepliesApi.d.ts +15 -2
  57. package/dist/apis/quickReplies/QuickRepliesApi.d.ts.map +1 -1
  58. package/dist/apis/quickReplies/QuickRepliesApi.mjs +16 -0
  59. package/dist/apis/quickReplies/QuickRepliesApi.mjs.map +1 -1
  60. package/dist/apis/quickReplies/types.d.ts +30 -2
  61. package/dist/apis/quickReplies/types.d.ts.map +1 -1
  62. package/dist/apis/schedule/ScheduleApi.cjs +37 -2
  63. package/dist/apis/schedule/ScheduleApi.cjs.map +1 -1
  64. package/dist/apis/schedule/ScheduleApi.d.ts +30 -3
  65. package/dist/apis/schedule/ScheduleApi.d.ts.map +1 -1
  66. package/dist/apis/schedule/ScheduleApi.mjs +37 -2
  67. package/dist/apis/schedule/ScheduleApi.mjs.map +1 -1
  68. package/dist/apis/schedule/types.d.ts +58 -11
  69. package/dist/apis/schedule/types.d.ts.map +1 -1
  70. package/dist/apis/ticketTopics/TicketTopicsApi.cjs +25 -2
  71. package/dist/apis/ticketTopics/TicketTopicsApi.cjs.map +1 -1
  72. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts +18 -4
  73. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts.map +1 -1
  74. package/dist/apis/ticketTopics/TicketTopicsApi.mjs +25 -2
  75. package/dist/apis/ticketTopics/TicketTopicsApi.mjs.map +1 -1
  76. package/dist/apis/ticketTopics/types.d.ts +22 -1
  77. package/dist/apis/ticketTopics/types.d.ts.map +1 -1
  78. package/dist/apis/tickets/types.d.ts +37 -1
  79. package/dist/apis/tickets/types.d.ts.map +1 -1
  80. package/dist/core/BaseCrudApi.cjs +5 -2
  81. package/dist/core/BaseCrudApi.cjs.map +1 -1
  82. package/dist/core/BaseCrudApi.d.ts +7 -4
  83. package/dist/core/BaseCrudApi.d.ts.map +1 -1
  84. package/dist/core/BaseCrudApi.mjs +5 -2
  85. package/dist/core/BaseCrudApi.mjs.map +1 -1
  86. package/dist/core/types.d.ts +1 -1
  87. package/dist/core/types.d.ts.map +1 -1
  88. package/dist/incommingWebhooks/index.d.ts +7 -7
  89. package/dist/incommingWebhooks/index.d.ts.map +1 -1
  90. package/package.json +1 -1
  91. package/src/apis/answers/AnswersApi.test.ts +67 -0
  92. package/src/apis/answers/AnswersApi.ts +66 -2
  93. package/src/apis/answers/types.ts +64 -2
  94. package/src/apis/botSimulators/BotSimulatorsApi.test.ts +58 -0
  95. package/src/apis/botSimulators/BotSimulatorsApi.ts +57 -0
  96. package/src/apis/botSimulators/index.ts +2 -0
  97. package/src/apis/botSimulators/types.ts +72 -0
  98. package/src/apis/botVersions/BotVersionsApi.test.ts +51 -0
  99. package/src/apis/botVersions/BotVersionsApi.ts +41 -0
  100. package/src/apis/botVersions/types.ts +4 -0
  101. package/src/apis/bots/BotsApi.test.ts +177 -0
  102. package/src/apis/bots/BotsApi.ts +162 -6
  103. package/src/apis/bots/types.ts +267 -15
  104. package/src/apis/index.ts +1 -0
  105. package/src/apis/payloads.test-d.ts +171 -1
  106. package/src/apis/permissions/types.ts +10 -4
  107. package/src/apis/questions/QuestionsApi.test.ts +49 -0
  108. package/src/apis/questions/QuestionsApi.ts +20 -1
  109. package/src/apis/questions/types.ts +25 -6
  110. package/src/apis/quickReplies/QuickRepliesApi.test.ts +69 -0
  111. package/src/apis/quickReplies/QuickRepliesApi.ts +35 -2
  112. package/src/apis/quickReplies/types.ts +34 -2
  113. package/src/apis/schedule/ScheduleApi.test.ts +80 -0
  114. package/src/apis/schedule/ScheduleApi.ts +59 -6
  115. package/src/apis/schedule/types.ts +66 -11
  116. package/src/apis/ticketTopics/TicketTopicsApi.test.ts +40 -0
  117. package/src/apis/ticketTopics/TicketTopicsApi.ts +35 -4
  118. package/src/apis/ticketTopics/types.ts +26 -1
  119. package/src/apis/tickets/types.ts +40 -1
  120. package/src/core/BaseCrudApi.ts +9 -5
  121. package/src/core/types.ts +2 -1
  122. package/src/incommingWebhooks/index.test-d.ts +9 -0
  123. package/src/incommingWebhooks/index.ts +7 -7
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { BotSimulatorsApi } from './BotSimulatorsApi'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ const headers = { 'X-Custom': 'value' }
20
+
21
+ // ── Tests ────────────────────────────────────────────────────────────
22
+
23
+ describe('BotSimulatorsApi', () => {
24
+ let client: ReturnType<typeof createMockClient>
25
+ let api: BotSimulatorsApi
26
+
27
+ beforeEach(() => {
28
+ client = createMockClient()
29
+ api = new BotSimulatorsApi(client)
30
+ })
31
+
32
+ it('getContactMessages calls client.get with /bot-simulators/contact-messages/:botId', async () => {
33
+ const state = { contact: { id: 'c1', name: 'QA', createdAt: 'x', session: '' }, messages: [] }
34
+ vi.mocked(client.get).mockResolvedValue(state)
35
+ expect(await api.getContactMessages('b1', headers)).toEqual(state)
36
+ expect(client.get).toHaveBeenCalledWith('/bot-simulators/contact-messages/b1', headers)
37
+ })
38
+
39
+ it('sendEvent posts the event to /bot-simulators/send-event/:botId', async () => {
40
+ vi.mocked(client.post).mockResolvedValue('OK')
41
+ const body = { type: 'select_trigger', id: 'm1', selectedValue: 'TICKET_OPENED' } as const
42
+ expect(await api.sendEvent('b1', body)).toBe('OK')
43
+ expect(client.post).toHaveBeenCalledWith('/bot-simulators/send-event/b1', body, undefined)
44
+ })
45
+
46
+ it('sendEvent resolves false when the simulator refuses the event', async () => {
47
+ vi.mocked(client.post).mockResolvedValue(false)
48
+ const body = { type: 'select_condition', id: 'm1', selectedValue: true } as const
49
+ expect(await api.sendEvent('b1', body, headers)).toBe(false)
50
+ expect(client.post).toHaveBeenCalledWith('/bot-simulators/send-event/b1', body, headers)
51
+ })
52
+
53
+ it('restart posts an empty body to /bot-simulators/restart-simulator/:botId', async () => {
54
+ vi.mocked(client.post).mockResolvedValue({ restartDate: '2026-10-01T00:00:00.000Z' })
55
+ expect(await api.restart('b1')).toEqual({ restartDate: '2026-10-01T00:00:00.000Z' })
56
+ expect(client.post).toHaveBeenCalledWith('/bot-simulators/restart-simulator/b1', {}, undefined)
57
+ })
58
+ })
@@ -0,0 +1,57 @@
1
+ import { BaseApi } from '../../core/BaseApi'
2
+ import type {
3
+ BotSimulatorEventPayload,
4
+ BotSimulatorEventResult,
5
+ BotSimulatorRestartResult,
6
+ BotSimulatorState,
7
+ } from './types'
8
+
9
+ /**
10
+ * The bot simulator of the bot builder. It runs the latest version of the bot (draft or
11
+ * published) against a virtual contact kept in the cache, one per user and bot — no contact,
12
+ * connection or ticket is created. The bot answers asynchronously (~0.5–1 s per bot message plus
13
+ * the queue): poll `getContactMessages` for the answers. A bot saved without `flowJson.edges`
14
+ * never answers (SD-2323).
15
+ */
16
+ export class BotSimulatorsApi extends BaseApi {
17
+ public readonly basePath = '/bot-simulators'
18
+
19
+ /**
20
+ * Returns the virtual contact and its messages. The first call starts the simulator: it creates
21
+ * the contact and pushes the `select_trigger_bot` message with the `@INIT` triggers.
22
+ * @permissions bots.update
23
+ */
24
+ getContactMessages(botId: string, headers?: Record<string, string>): Promise<BotSimulatorState> {
25
+ return this.client.get<BotSimulatorState>(`${this.basePath}/contact-messages/${botId}`, headers)
26
+ }
27
+
28
+ /**
29
+ * Sends an event to the simulator: a message of the contact, or the pick of a trigger or of a
30
+ * condition result.
31
+ * @permissions bots.update
32
+ */
33
+ sendEvent(
34
+ botId: string,
35
+ body: BotSimulatorEventPayload,
36
+ headers?: Record<string, string>,
37
+ ): Promise<BotSimulatorEventResult> {
38
+ return this.client.post<BotSimulatorEventResult, BotSimulatorEventPayload>(
39
+ `${this.basePath}/send-event/${botId}`,
40
+ body,
41
+ headers,
42
+ )
43
+ }
44
+
45
+ /**
46
+ * Clears the messages and starts over with a new virtual contact (new id, empty session) and a
47
+ * new `select_trigger_bot` message. Runs already queued in the worker are not cancelled.
48
+ * @permissions bots.update
49
+ */
50
+ restart(botId: string, headers?: Record<string, string>): Promise<BotSimulatorRestartResult> {
51
+ return this.client.post<BotSimulatorRestartResult>(
52
+ `${this.basePath}/restart-simulator/${botId}`,
53
+ {},
54
+ headers,
55
+ )
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ export { BotSimulatorsApi } from './BotSimulatorsApi'
2
+ export type * from './types'
@@ -0,0 +1,72 @@
1
+ import type { BotEvent } from '../bots/types'
2
+
3
+ export type BotSimulatorMessageType =
4
+ | 'text_bot'
5
+ | 'text_interactive_bot'
6
+ | 'select_trigger_bot'
7
+ | 'select_condition_bot'
8
+ | 'text_contact'
9
+ | 'chat_event'
10
+
11
+ export type BotSimulatorMessage = {
12
+ id: string
13
+ type: BotSimulatorMessageType
14
+ /** Empty for the selection messages. `chat_event` carries the block name (e.g. `ADD_TAG_NODE`). */
15
+ text: string
16
+ data: {
17
+ /** In `select_trigger_bot`: the triggers of `@INIT` to pick from. */
18
+ trigger?: { events: BotEvent[] }
19
+ /** In `select_condition_bot`. */
20
+ conditionNodeId?: string
21
+ /** What `sendEvent` picked for this selection message. */
22
+ selectedValue?: string | boolean
23
+ interactive?: unknown
24
+ customFieldData?: { type: string; name: string; id: string; settings: unknown }
25
+ }
26
+ createdAt: string
27
+ file?: { id: string; url: string; publicFilename: string }
28
+ }
29
+
30
+ /** The virtual contact of the simulator: it lives in the cache, one per user and bot. */
31
+ export type BotSimulatorContact = {
32
+ id: string
33
+ /** The name of the user running the simulator. */
34
+ name: string
35
+ createdAt: string
36
+ /** `null` in the call that starts the simulator, `''` after it, until the bot runs. */
37
+ session: { store: { context?: string; contextPassCount?: number } } | '' | null
38
+ }
39
+
40
+ export type BotSimulatorState = {
41
+ contact: BotSimulatorContact
42
+ /** Sorted by `createdAt`. */
43
+ messages: BotSimulatorMessage[]
44
+ }
45
+
46
+ export type BotSimulatorEventPayload =
47
+ | {
48
+ /** A message of the contact: pushed at once, the bot answers asynchronously. */
49
+ type: 'text_contact'
50
+ text: string
51
+ }
52
+ | {
53
+ /** Picks the trigger of a `select_trigger_bot` message. Only while the contact is in `@INIT`. */
54
+ type: 'select_trigger'
55
+ /** The id of the `select_trigger_bot` message. */
56
+ id: string
57
+ selectedValue: BotEvent
58
+ }
59
+ | {
60
+ /** Forces the result of a condition block. Only while the contact is in a `CONDITION_NODE_*` context. */
61
+ type: 'select_condition'
62
+ /** The id of the `select_condition_bot` message. */
63
+ id: string
64
+ selectedValue: boolean
65
+ }
66
+
67
+ /** `'OK'` when the event was taken; `false` when refused (wrong context, missing id). */
68
+ export type BotSimulatorEventResult = 'OK' | false
69
+
70
+ export type BotSimulatorRestartResult = {
71
+ restartDate: string
72
+ }
@@ -0,0 +1,51 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { BotVersionsApi } from './BotVersionsApi'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ // ── Tests ────────────────────────────────────────────────────────────
20
+
21
+ describe('BotVersionsApi', () => {
22
+ let client: ReturnType<typeof createMockClient>
23
+ let api: BotVersionsApi
24
+
25
+ beforeEach(() => {
26
+ client = createMockClient()
27
+ api = new BotVersionsApi(client)
28
+ })
29
+
30
+ it('getMany lists the versions of a bot', async () => {
31
+ const query = { where: { botId: 'b1' }, paginate: false } as const
32
+ vi.mocked(client.get).mockResolvedValue([{ id: 'v1' }])
33
+ expect(await api.getMany(query)).toEqual([{ id: 'v1' }])
34
+ expect(client.get).toHaveBeenCalledWith(
35
+ `/bot-versions?query=${encodeURIComponent(JSON.stringify(query))}`,
36
+ undefined,
37
+ )
38
+ })
39
+
40
+ it('getOne filters by id, the replacement of the deprecated getById', async () => {
41
+ vi.mocked(client.get).mockResolvedValue([{ id: 'v1' }])
42
+ expect(await api.getOne({ where: { id: 'v1' } })).toEqual({ id: 'v1' })
43
+ })
44
+
45
+ it('keeps the deprecated write methods calling their routes', async () => {
46
+ await api.create({ botId: 'b1' })
47
+ expect(client.post).toHaveBeenCalledWith('/bot-versions', { botId: 'b1' }, undefined)
48
+ await api.deleteById('v1')
49
+ expect(client.delete).toHaveBeenCalledWith('/bot-versions/v1', undefined)
50
+ })
51
+ })
@@ -1,7 +1,14 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
+ import type { GetByIdQuery } from '../../core/types'
3
4
  import type { BotVersion, CreateBotVersionPayload, UpdateBotVersionPayload } from './types'
4
5
 
6
+ /**
7
+ * Versions of the bots, with the contexts in the stored map form. The backend only serves the
8
+ * list (`getMany`/`getOne`, filter by `where.botId`; `paginate: false` is honored), under the
9
+ * `bots.view` permission: the versions are created and published by `BotsApi.create`/`updateById`.
10
+ * `include` is dropped (empty whitelist), so the relationships never come.
11
+ */
5
12
  export class BotVersionsApi extends BaseCrudApi<
6
13
  BotVersion,
7
14
  CreateBotVersionPayload,
@@ -10,4 +17,38 @@ export class BotVersionsApi extends BaseCrudApi<
10
17
  constructor(client: ApiClient) {
11
18
  super(client, '/bot-versions')
12
19
  }
20
+
21
+ /**
22
+ * @deprecated Not served: the backend only serves GET /bot-versions (list): this route answers
23
+ * 404. Use `getOne({ where: { id } })`.
24
+ */
25
+ override getById(
26
+ id: string,
27
+ query?: GetByIdQuery<BotVersion>,
28
+ headers?: Record<string, string>,
29
+ ): Promise<BotVersion> {
30
+ return super.getById(id, query, headers)
31
+ }
32
+
33
+ /** @deprecated Not served: the backend only serves GET /bot-versions (list): this route answers 404. Save the bot through `BotsApi`. */
34
+ override create(
35
+ body: CreateBotVersionPayload,
36
+ headers?: Record<string, string>,
37
+ ): Promise<BotVersion> {
38
+ return super.create(body, headers)
39
+ }
40
+
41
+ /** @deprecated Not served: the backend only serves GET /bot-versions (list): this route answers 404. Save the bot through `BotsApi`. */
42
+ override updateById(
43
+ id: string,
44
+ body: UpdateBotVersionPayload,
45
+ headers?: Record<string, string>,
46
+ ): Promise<BotVersion> {
47
+ return super.updateById(id, body, headers)
48
+ }
49
+
50
+ /** @deprecated Not served: the backend only serves GET /bot-versions (list): this route answers 404. */
51
+ override deleteById(id: string, headers?: Record<string, string>): Promise<BotVersion> {
52
+ return super.deleteById(id, headers)
53
+ }
13
54
  }
@@ -19,11 +19,15 @@ export type BotVersion = {
19
19
  updatedAt: string
20
20
  deletedAt: string | null
21
21
  // relationships
22
+ /** @deprecated Never comes: the route drops every `include` (empty whitelist). */
22
23
  bot?: Bot
24
+ /** @deprecated Never comes: the route drops every `include` (empty whitelist). */
23
25
  createdBy?: User | null
26
+ /** @deprecated Never comes: the route drops every `include` (empty whitelist). */
24
27
  publishedBy?: User | null
25
28
  }
26
29
 
30
+ /** @deprecated The route drops every `include` (empty whitelist). */
27
31
  export type BotVersionRelationships = 'bot' | 'createdBy' | 'publishedBy'
28
32
 
29
33
  export type CreateBotVersionPayload = {
@@ -0,0 +1,177 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { BotsApi } from './BotsApi'
4
+ import type { BotContextInput } from './types'
5
+
6
+ // ── Mock client factory ──────────────────────────────────────────────
7
+
8
+ function createMockClient(): ApiClient {
9
+ return {
10
+ setAccessToken: vi.fn().mockReturnThis(),
11
+ request: vi.fn(),
12
+ get: vi.fn(),
13
+ post: vi.fn(),
14
+ put: vi.fn(),
15
+ patch: vi.fn(),
16
+ delete: vi.fn(),
17
+ }
18
+ }
19
+
20
+ const headers = { 'X-Custom': 'value' }
21
+
22
+ const contexts: BotContextInput[] = [
23
+ {
24
+ id: '@INIT',
25
+ name: 'Contexto inicial',
26
+ triggers: [
27
+ {
28
+ trigger: { type: 'TICKET_OPENED' },
29
+ rules: [
30
+ {
31
+ title: 'abertura',
32
+ conditions: [],
33
+ actions: [{ type: { value: 'SEND_MESSAGE' }, data: { text: 'Olá' } }],
34
+ },
35
+ ],
36
+ },
37
+ ],
38
+ },
39
+ ]
40
+
41
+ // ── Tests ────────────────────────────────────────────────────────────
42
+
43
+ describe('BotsApi', () => {
44
+ let client: ReturnType<typeof createMockClient>
45
+ let api: BotsApi
46
+
47
+ beforeEach(() => {
48
+ client = createMockClient()
49
+ api = new BotsApi(client)
50
+ })
51
+
52
+ describe('getMany', () => {
53
+ it('calls client.get with /bots and the query', async () => {
54
+ await api.getMany({ where: { name: 'x' } }, headers)
55
+ expect(client.get).toHaveBeenCalledWith(
56
+ `/bots?query=${encodeURIComponent(JSON.stringify({ where: { name: 'x' } }))}`,
57
+ headers,
58
+ )
59
+ })
60
+ })
61
+
62
+ describe('getOne', () => {
63
+ it('reads the first item of a one-item page', async () => {
64
+ vi.mocked(client.get).mockResolvedValue({ data: [{ id: 'b1' }], total: 1 })
65
+ const result = await api.getOne({ where: { name: 'x' } })
66
+ expect(client.get).toHaveBeenCalledWith(
67
+ `/bots?query=${encodeURIComponent(
68
+ JSON.stringify({ where: { name: 'x' }, perPage: 1, page: 1 }),
69
+ )}`,
70
+ undefined,
71
+ )
72
+ expect(result).toEqual({ id: 'b1' })
73
+ })
74
+
75
+ it('resolves null on an empty page', async () => {
76
+ vi.mocked(client.get).mockResolvedValue({ data: [], total: 0 })
77
+ expect(await api.getOne()).toBeNull()
78
+ })
79
+ })
80
+
81
+ describe('CRUD', () => {
82
+ it('getById calls client.get with /bots/:id', async () => {
83
+ await api.getById('b1', { include: ['currentBotVersion'] }, headers)
84
+ expect(client.get).toHaveBeenCalledWith(
85
+ `/bots/b1?query=${encodeURIComponent(JSON.stringify({ include: ['currentBotVersion'] }))}`,
86
+ headers,
87
+ )
88
+ })
89
+
90
+ it('create calls client.post with /bots and the contexts list', async () => {
91
+ const body = { name: 'Robô', contexts, isDraft: true }
92
+ await api.create(body, headers)
93
+ expect(client.post).toHaveBeenCalledWith('/bots', body, headers)
94
+ })
95
+
96
+ it('updateById saves the flow or only renames', async () => {
97
+ await api.updateById('b1', { name: 'Robô', contexts })
98
+ expect(client.put).toHaveBeenCalledWith('/bots/b1', { name: 'Robô', contexts }, undefined)
99
+ await api.updateById('b1', { name: 'Novo', onlyUpdateBotName: true })
100
+ expect(client.put).toHaveBeenCalledWith(
101
+ '/bots/b1',
102
+ { name: 'Novo', onlyUpdateBotName: true },
103
+ undefined,
104
+ )
105
+ })
106
+
107
+ it('deleteById calls client.delete with /bots/:id', async () => {
108
+ vi.mocked(client.delete).mockResolvedValue(false)
109
+ expect(await api.deleteById('b1', headers)).toBe(false)
110
+ expect(client.delete).toHaveBeenCalledWith('/bots/b1', headers)
111
+ })
112
+
113
+ it("deleteById passes the 'OK' of a fixed backend through", async () => {
114
+ vi.mocked(client.delete).mockResolvedValue('OK')
115
+ expect(await api.deleteById('b1')).toBe('OK')
116
+ })
117
+ })
118
+
119
+ describe('getErrors', () => {
120
+ it('calls client.get with /bots/:id/errors and the filters', async () => {
121
+ const query = { page: 2, perPage: 10, errorCode: 'TIMEOUT', sort: 'serviceName' }
122
+ await api.getErrors('b1', query, headers)
123
+ expect(client.get).toHaveBeenCalledWith(
124
+ `/bots/b1/errors?query=${encodeURIComponent(JSON.stringify(query))}`,
125
+ headers,
126
+ )
127
+ })
128
+
129
+ it('sends no query string without filters', async () => {
130
+ await api.getErrors('b1')
131
+ expect(client.get).toHaveBeenCalledWith('/bots/b1/errors', undefined)
132
+ })
133
+ })
134
+
135
+ describe('triggerSignal', () => {
136
+ it('sends the flag in the body', async () => {
137
+ await api.triggerSignal('b1', 'c1', 'vip', headers)
138
+ expect(client.post).toHaveBeenCalledWith(
139
+ '/bots/b1/trigger-signal/c1',
140
+ { flag: 'vip' },
141
+ headers,
142
+ )
143
+ })
144
+
145
+ it('sends no body without a flag', async () => {
146
+ await api.triggerSignal('b1', 'c1')
147
+ expect(client.post).toHaveBeenCalledWith('/bots/b1/trigger-signal/c1', undefined, undefined)
148
+ })
149
+ })
150
+
151
+ describe('requestCallback', () => {
152
+ it('posts the response data to /bots/:id/request-callback/:pendingRequestId', async () => {
153
+ await api.requestCallback('b1', 'h1', { status: 'paid' }, headers)
154
+ expect(client.post).toHaveBeenCalledWith(
155
+ '/bots/b1/request-callback/h1',
156
+ { status: 'paid' },
157
+ headers,
158
+ )
159
+ })
160
+ })
161
+
162
+ describe('suggestAgentPrompt', () => {
163
+ it('posts the agent config to /bots/suggest-agent-prompt', async () => {
164
+ vi.mocked(client.post).mockResolvedValue('Você é um agente...')
165
+ const body = {
166
+ voiceTone: 'formal',
167
+ languageType: 'simple',
168
+ function: 'support',
169
+ companySegment: 'Varejo',
170
+ companySubject: 'Loja',
171
+ companyServices: 'Vendas',
172
+ } as const
173
+ expect(await api.suggestAgentPrompt(body)).toBe('Você é um agente...')
174
+ expect(client.post).toHaveBeenCalledWith('/bots/suggest-agent-prompt', body, undefined)
175
+ })
176
+ })
177
+ })
@@ -1,9 +1,165 @@
1
- import type { ApiClient } from '../../core/ApiClient'
2
- import { BaseCrudApi } from '../../core/BaseCrudApi'
3
- import type { Bot, CreateBotPayload, UpdateBotPayload } from './types'
1
+ import { BaseApi } from '../../core/BaseApi'
2
+ import type {
3
+ GetByIdQuery,
4
+ HasCreate,
5
+ HasDeleteById,
6
+ HasGetById,
7
+ HasUpdateById,
8
+ ListQuery,
9
+ Paginated,
10
+ } from '../../core/types'
11
+ import type {
12
+ Bot,
13
+ BotErrorsQuery,
14
+ BotHistory,
15
+ CreateBotPayload,
16
+ SuggestAgentPromptPayload,
17
+ UpdateBotPayload,
18
+ } from './types'
4
19
 
5
- export class BotsApi extends BaseCrudApi<Bot, CreateBotPayload, UpdateBotPayload> {
6
- constructor(client: ApiClient) {
7
- super(client, '/bots')
20
+ /**
21
+ * Bots. The list route always paginates (`paginate: false` is ignored), so this class does not
22
+ * extend `BaseCrudApi`. The contexts travel as a list (`BotContextInput` / `BotParsedContext`),
23
+ * not as the stored map that `BotVersionsApi` serves.
24
+ *
25
+ * `GET /bots/:id/errors/export` is not exposed: it answers a binary xlsx, and `ApiClient` only
26
+ * handles JSON/text bodies.
27
+ */
28
+ export class BotsApi
29
+ extends BaseApi
30
+ implements
31
+ HasGetById<Bot>,
32
+ HasCreate<Bot, CreateBotPayload>,
33
+ HasUpdateById<Bot, UpdateBotPayload>,
34
+ HasDeleteById<'OK' | false>
35
+ {
36
+ public readonly basePath = '/bots'
37
+
38
+ /**
39
+ * Lists the bots of the account. Always paginated, even with `paginate: false`.
40
+ * @permissions bots.view
41
+ */
42
+ getMany(query?: ListQuery<Bot>, headers?: Record<string, string>): Promise<Paginated<Bot>> {
43
+ return this.client.get<Paginated<Bot>>(`${this.basePath}${this.toQueryString(query)}`, headers)
44
+ }
45
+
46
+ /**
47
+ * Returns the first bot matching the query, or `null` when none match.
48
+ * @permissions bots.view
49
+ */
50
+ async getOne(query?: ListQuery<Bot>, headers?: Record<string, string>): Promise<Bot | null> {
51
+ const page = await this.getMany({ ...query, perPage: 1, page: 1 }, headers)
52
+ return page?.data?.[0] ?? null
53
+ }
54
+
55
+ /**
56
+ * Returns a bot by ID, with `defaultContexts` (the stored map, as a JSON string). An unknown id
57
+ * answers 500, not an empty body.
58
+ * @permissions bots.view
59
+ */
60
+ getById(id: string, query?: GetByIdQuery<Bot>, headers?: Record<string, string>): Promise<Bot> {
61
+ return this.client.get<Bot>(`${this.basePath}/${id}${this.toQueryString(query)}`, headers)
62
+ }
63
+
64
+ /**
65
+ * Creates a bot and its first version (published, or a draft with `isDraft`). A duplicate name
66
+ * answers 409, unless the account allows duplicate names. Send `flowJson` with `edges`, or the
67
+ * bot never sends a message (SD-2323).
68
+ * @permissions bots.create
69
+ */
70
+ create(body: CreateBotPayload, headers?: Record<string, string>): Promise<Bot> {
71
+ return this.client.post<Bot, CreateBotPayload>(this.basePath, body, headers)
72
+ }
73
+
74
+ /**
75
+ * Saves the flow (publishing a version, or into the draft with `isDraft`), or only renames the
76
+ * bot with `onlyUpdateBotName`. Resolves to the bot with `defaultContexts` as an object.
77
+ * @permissions bots.view OR bots.update
78
+ */
79
+ updateById(id: string, body: UpdateBotPayload, headers?: Record<string, string>): Promise<Bot> {
80
+ return this.client.put<Bot, UpdateBotPayload>(`${this.basePath}/${id}`, body, headers)
81
+ }
82
+
83
+ /**
84
+ * Deletes a bot and its versions (hard delete). Today resolves to `false` even when the bot was
85
+ * deleted (the backend destroy returns nothing); it becomes `'OK'` once the destroy returns the
86
+ * bot, as in the other routes. Do not read the result as success or failure. An unknown id
87
+ * answers 500.
88
+ * @permissions bots.view OR bots.destroy
89
+ */
90
+ deleteById(id: string, headers?: Record<string, string>): Promise<'OK' | false> {
91
+ return this.client.delete<'OK' | false>(`${this.basePath}/${id}`, headers)
92
+ }
93
+
94
+ /**
95
+ * Lists the failed steps of the bot runs, newest first by default. An unknown bot answers an
96
+ * empty page.
97
+ * @permissions bots.view
98
+ */
99
+ getErrors(
100
+ id: string,
101
+ query?: BotErrorsQuery,
102
+ headers?: Record<string, string>,
103
+ ): Promise<Paginated<BotHistory>> {
104
+ return this.client.get<Paginated<BotHistory>>(
105
+ `${this.basePath}/${id}/errors${this.toQueryString(query)}`,
106
+ headers,
107
+ )
108
+ }
109
+
110
+ /**
111
+ * Fires the `API_SIGNAL` trigger of the bot for the contact, with an optional `flag` (read by
112
+ * the `api_signal_flag` condition). Asynchronous: always resolves with no content, and the
113
+ * worker only runs it when the contact has an open ticket.
114
+ * @permissions bots.view
115
+ */
116
+ triggerSignal(
117
+ botId: string,
118
+ contactId: string,
119
+ flag?: string,
120
+ headers?: Record<string, string>,
121
+ ): Promise<void> {
122
+ return this.client.post<void, { flag: string } | undefined>(
123
+ `${this.basePath}/${botId}/trigger-signal/${contactId}`,
124
+ flag === undefined ? undefined : { flag },
125
+ headers,
126
+ )
127
+ }
128
+
129
+ /**
130
+ * Answers a pending asynchronous `HTTP_REQUEST` action: `body` becomes the response the bot
131
+ * reads. Resolves with no content. An unknown history answers 404; a history that is not
132
+ * `PENDING`, or of another bot, answers 400. JSON bodies only (the route also takes text, XML
133
+ * and CSV), up to 250 KB.
134
+ * @permissions bots.view
135
+ */
136
+ requestCallback(
137
+ id: string,
138
+ pendingRequestId: string,
139
+ body: unknown,
140
+ headers?: Record<string, string>,
141
+ ): Promise<void> {
142
+ return this.client.post<void, unknown>(
143
+ `${this.basePath}/${id}/request-callback/${pendingRequestId}`,
144
+ body,
145
+ headers,
146
+ )
147
+ }
148
+
149
+ /**
150
+ * Generates a prompt for an AI agent of the bot. COSTS one AI agent credit of the account and
151
+ * calls OpenAI. No permission check: any authenticated user. A missing field answers 400; an
152
+ * option outside the lists, or the AI agent limit of the account reached
153
+ * (`Agent limit reached`), answers 500.
154
+ */
155
+ suggestAgentPrompt(
156
+ body: SuggestAgentPromptPayload,
157
+ headers?: Record<string, string>,
158
+ ): Promise<string> {
159
+ return this.client.post<string, SuggestAgentPromptPayload>(
160
+ `${this.basePath}/suggest-agent-prompt`,
161
+ body,
162
+ headers,
163
+ )
8
164
  }
9
165
  }