@ikatec/digisac-api-sdk 4.2.0 → 4.4.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.
- package/dist/apis/contacts/ContactsApi.cjs +70 -0
- package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.d.ts +51 -1
- package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
- package/dist/apis/contacts/ContactsApi.mjs +70 -0
- package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
- package/dist/apis/contacts/types.d.ts +141 -1
- package/dist/apis/contacts/types.d.ts.map +1 -1
- package/dist/apis/dashboard/DashboardApi.cjs +36 -1
- package/dist/apis/dashboard/DashboardApi.cjs.map +1 -1
- package/dist/apis/dashboard/DashboardApi.d.ts +55 -2
- package/dist/apis/dashboard/DashboardApi.d.ts.map +1 -1
- package/dist/apis/dashboard/DashboardApi.mjs +36 -1
- package/dist/apis/dashboard/DashboardApi.mjs.map +1 -1
- package/dist/apis/dashboard/types.d.ts +71 -0
- package/dist/apis/dashboard/types.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.cjs +8 -0
- package/dist/apis/services/ServicesApi.cjs.map +1 -1
- package/dist/apis/services/ServicesApi.d.ts +7 -1
- package/dist/apis/services/ServicesApi.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.mjs +8 -0
- package/dist/apis/services/ServicesApi.mjs.map +1 -1
- package/dist/apis/services/types.d.ts +7 -0
- package/dist/apis/services/types.d.ts.map +1 -1
- package/dist/apis/tags/TagsApi.cjs +23 -0
- package/dist/apis/tags/TagsApi.cjs.map +1 -1
- package/dist/apis/tags/TagsApi.d.ts +19 -1
- package/dist/apis/tags/TagsApi.d.ts.map +1 -1
- package/dist/apis/tags/TagsApi.mjs +23 -0
- package/dist/apis/tags/TagsApi.mjs.map +1 -1
- package/dist/apis/tags/types.d.ts +8 -0
- package/dist/apis/tags/types.d.ts.map +1 -1
- package/dist/apis/users/UsersApi.cjs +77 -0
- package/dist/apis/users/UsersApi.cjs.map +1 -1
- package/dist/apis/users/UsersApi.d.ts +59 -1
- package/dist/apis/users/UsersApi.d.ts.map +1 -1
- package/dist/apis/users/UsersApi.mjs +77 -0
- package/dist/apis/users/UsersApi.mjs.map +1 -1
- package/dist/apis/users/types.d.ts +102 -0
- package/dist/apis/users/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/apis/acceptanceTerms/AcceptanceTermsApi.test.ts +1 -1
- package/src/apis/authHistory/AuthHistoryApi.test.ts +1 -1
- package/src/apis/campaigns/CampaignsApi.test.ts +1 -1
- package/src/apis/contacts/ContactsApi.test.ts +182 -5
- package/src/apis/contacts/ContactsApi.ts +128 -0
- package/src/apis/contacts/types.ts +154 -1
- package/src/apis/dashboard/DashboardApi.test-d.ts +49 -0
- package/src/apis/dashboard/DashboardApi.test.ts +197 -9
- package/src/apis/dashboard/DashboardApi.ts +130 -2
- package/src/apis/dashboard/types.ts +78 -0
- package/src/apis/messages/MessagesApi.test.ts +3 -3
- package/src/apis/payloads.test-d.ts +26 -0
- package/src/apis/queryFormat.test.ts +91 -3
- package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.test.ts +2 -2
- package/src/apis/services/ServicesApi.test.ts +49 -0
- package/src/apis/services/ServicesApi.ts +10 -1
- package/src/apis/services/types.ts +9 -0
- package/src/apis/stickerUsers/StickerUsersApi.test.ts +1 -1
- package/src/apis/tags/TagsApi.test.ts +79 -0
- package/src/apis/tags/TagsApi.ts +57 -1
- package/src/apis/tags/types.ts +9 -0
- package/src/apis/terms/TermsApi.test.ts +1 -1
- package/src/apis/users/UsersApi.test.ts +228 -0
- package/src/apis/users/UsersApi.ts +152 -1
- package/src/apis/users/types.ts +121 -0
|
@@ -85,7 +85,7 @@ describe('MessagesApi', () => {
|
|
|
85
85
|
it('serializes query parameters', async () => {
|
|
86
86
|
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
87
87
|
await api.getMany({ page: 2, perPage: 5 })
|
|
88
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
88
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
89
89
|
expect(url).toContain('/messages?')
|
|
90
90
|
expect(decodeURIComponent(url)).toContain('"page":2')
|
|
91
91
|
expect(decodeURIComponent(url)).toContain('"perPage":5')
|
|
@@ -382,7 +382,7 @@ describe('MessagesApi', () => {
|
|
|
382
382
|
it('appends limit query parameter when provided', async () => {
|
|
383
383
|
vi.mocked(client.get).mockResolvedValue({ outOfRange: true })
|
|
384
384
|
await api.outOfRange('msg-1', 50)
|
|
385
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
385
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
386
386
|
expect(url).toContain('/messages/msg-1/out-of-range?')
|
|
387
387
|
expect(url).toContain('limit=50')
|
|
388
388
|
})
|
|
@@ -426,7 +426,7 @@ describe('MessagesApi', () => {
|
|
|
426
426
|
it('appends limit query parameter when provided', async () => {
|
|
427
427
|
vi.mocked(client.get).mockResolvedValue([])
|
|
428
428
|
await api.around('msg-1', { limit: 10 })
|
|
429
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
429
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
430
430
|
expect(url).toContain('/messages/msg-1/around?')
|
|
431
431
|
expect(url).toContain('limit=10')
|
|
432
432
|
})
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { expectError } from 'tsd'
|
|
2
2
|
import type { ApiClient } from '../core/ApiClient'
|
|
3
|
+
import { ContactsApi } from './contacts/ContactsApi'
|
|
3
4
|
import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
|
|
4
5
|
import { RolesApi } from './roles/RolesApi'
|
|
6
|
+
import { TagsApi } from './tags/TagsApi'
|
|
5
7
|
import { TimetableApi } from './timetable/TimetableApi'
|
|
8
|
+
import { UsersApi } from './users/UsersApi'
|
|
6
9
|
|
|
7
10
|
declare const client: ApiClient
|
|
8
11
|
|
|
@@ -37,3 +40,26 @@ tokens.create({ name: 'CI', expiresIn: '90d' })
|
|
|
37
40
|
expectError(tokens.create({ name: 'CI', accessTokenExpiresAt: '2030-01-01T00:00:00Z' }))
|
|
38
41
|
expectError(tokens.create({ name: 'CI', expiresIn: '7d' }))
|
|
39
42
|
expectError(tokens.updateById('t-1', { scope: '*' }))
|
|
43
|
+
|
|
44
|
+
// ── users: the dedicated archive route takes a boolean, not a date ──
|
|
45
|
+
|
|
46
|
+
const users = new UsersApi(client)
|
|
47
|
+
users.archiveWithValidation('u-1', { archive: true })
|
|
48
|
+
expectError(users.archiveWithValidation('u-1', { archivedAt: '2030-01-01T00:00:00Z' }))
|
|
49
|
+
expectError(users.exportCsv({ filter: 'deleted' }))
|
|
50
|
+
|
|
51
|
+
// ── contacts: /many takes an array, the bulk delete a selection ─────
|
|
52
|
+
|
|
53
|
+
const contacts = new ContactsApi(client)
|
|
54
|
+
contacts.createMany([{ serviceId: 's-1', name: 'Alice', number: '5511999990001' }])
|
|
55
|
+
expectError(contacts.createMany({ serviceId: 's-1', name: 'Alice', number: '5511999990001' }))
|
|
56
|
+
expectError(contacts.getMedia('c-1', { where: { type: 'audio' } }))
|
|
57
|
+
|
|
58
|
+
// ── contact selections: an empty filter would reach every contact of the account ─
|
|
59
|
+
|
|
60
|
+
contacts.deleteMany({ where: { id: 'c-1' } })
|
|
61
|
+
expectError(contacts.deleteMany({}))
|
|
62
|
+
const tags = new TagsApi(client)
|
|
63
|
+
tags.attachContacts('t-1', { where: { id: { $in: ['c-1', 'c-2'] } } })
|
|
64
|
+
expectError(tags.attachContacts('t-1', {}))
|
|
65
|
+
expectError(tags.detachContacts('t-1', { include: ['tags'] }))
|
|
@@ -32,6 +32,8 @@ function createMockClient(queryFormat?: QueryFormat): ApiClient {
|
|
|
32
32
|
|
|
33
33
|
const listQuery = { where: { name: 'x' }, page: 2, perPage: 5 }
|
|
34
34
|
const getQuery = { attributes: ['id'] }
|
|
35
|
+
/** `Permission.name` is a union of the catalog names, so the permissions case filters by a real one. */
|
|
36
|
+
const permissionsQuery = { where: { name: 'chat' as const }, page: 2, perPage: 5 }
|
|
35
37
|
|
|
36
38
|
type Case = {
|
|
37
39
|
name: string
|
|
@@ -69,6 +71,27 @@ const cases: Case[] = [
|
|
|
69
71
|
return (f ? api.withQueryFormat(f) : api).count(listQuery)
|
|
70
72
|
},
|
|
71
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: 'ContactsApi.getMedia',
|
|
76
|
+
path: '/contacts/c1/media',
|
|
77
|
+
query: { where: { type: 'document' }, perPage: 10 },
|
|
78
|
+
call: (client, f) => {
|
|
79
|
+
const api = new ContactsApi(client)
|
|
80
|
+
return (f ? api.withQueryFormat(f) : api).getMedia('c1', {
|
|
81
|
+
where: { type: 'document' },
|
|
82
|
+
perPage: 10,
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'ContactsApi.createMany',
|
|
88
|
+
path: '/contacts/many',
|
|
89
|
+
query: { include: ['tags'] },
|
|
90
|
+
call: (client, f) => {
|
|
91
|
+
const api = new ContactsApi(client)
|
|
92
|
+
return (f ? api.withQueryFormat(f) : api).createMany([], { include: ['tags'] })
|
|
93
|
+
},
|
|
94
|
+
},
|
|
72
95
|
{
|
|
73
96
|
name: 'ContactsApi.forward',
|
|
74
97
|
path: '/contacts/forward',
|
|
@@ -99,6 +122,70 @@ const cases: Case[] = [
|
|
|
99
122
|
})
|
|
100
123
|
},
|
|
101
124
|
},
|
|
125
|
+
{
|
|
126
|
+
name: 'DashboardApi.getByPeriod',
|
|
127
|
+
path: '/dashboard/by-period',
|
|
128
|
+
query: { status: 'closed', departmentId: ['d1'], grouping: 'weekly', withTotals: true },
|
|
129
|
+
call: (client, f) => {
|
|
130
|
+
const api = new DashboardApi(client)
|
|
131
|
+
return (f ? api.withQueryFormat(f) : api).getByPeriod({
|
|
132
|
+
status: 'closed',
|
|
133
|
+
departmentId: ['d1'],
|
|
134
|
+
grouping: 'weekly',
|
|
135
|
+
withTotals: true,
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'DashboardApi.getByUser',
|
|
141
|
+
path: '/dashboard/by-user',
|
|
142
|
+
query: { status: 'closed', departmentId: ['d1'], withTotals: true },
|
|
143
|
+
call: (client, f) => {
|
|
144
|
+
const api = new DashboardApi(client)
|
|
145
|
+
return (f ? api.withQueryFormat(f) : api).getByUser({
|
|
146
|
+
status: 'closed',
|
|
147
|
+
departmentId: ['d1'],
|
|
148
|
+
withTotals: true,
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'DashboardApi.getByDepartment',
|
|
154
|
+
path: '/dashboard/by-department',
|
|
155
|
+
query: { status: 'closed', departmentId: ['d1'], withTotals: true },
|
|
156
|
+
call: (client, f) => {
|
|
157
|
+
const api = new DashboardApi(client)
|
|
158
|
+
return (f ? api.withQueryFormat(f) : api).getByDepartment({
|
|
159
|
+
status: 'closed',
|
|
160
|
+
departmentId: ['d1'],
|
|
161
|
+
withTotals: true,
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'DashboardApi.getByTopic',
|
|
167
|
+
path: '/dashboard/by-topic',
|
|
168
|
+
query: { status: 'closed', departmentId: ['d1'] },
|
|
169
|
+
call: (client, f) => {
|
|
170
|
+
const api = new DashboardApi(client)
|
|
171
|
+
return (f ? api.withQueryFormat(f) : api).getByTopic({
|
|
172
|
+
status: 'closed',
|
|
173
|
+
departmentId: ['d1'],
|
|
174
|
+
})
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'DashboardApi.getByService',
|
|
179
|
+
path: '/dashboard/by-service',
|
|
180
|
+
query: { status: 'closed', departmentId: ['d1'] },
|
|
181
|
+
call: (client, f) => {
|
|
182
|
+
const api = new DashboardApi(client)
|
|
183
|
+
return (f ? api.withQueryFormat(f) : api).getByService({
|
|
184
|
+
status: 'closed',
|
|
185
|
+
departmentId: ['d1'],
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
},
|
|
102
189
|
{
|
|
103
190
|
name: 'NowApi.getResume',
|
|
104
191
|
path: '/now/resume',
|
|
@@ -135,10 +222,10 @@ const cases: Case[] = [
|
|
|
135
222
|
{
|
|
136
223
|
name: 'PermissionsApi.getMany',
|
|
137
224
|
path: '/permissions',
|
|
138
|
-
query:
|
|
225
|
+
query: permissionsQuery,
|
|
139
226
|
call: (client, f) => {
|
|
140
227
|
const api = new PermissionsApi(client)
|
|
141
|
-
return (f ? api.withQueryFormat(f) : api).getMany(
|
|
228
|
+
return (f ? api.withQueryFormat(f) : api).getMany(permissionsQuery)
|
|
142
229
|
},
|
|
143
230
|
},
|
|
144
231
|
{
|
|
@@ -191,7 +278,8 @@ const cases: Case[] = [
|
|
|
191
278
|
const jsonUrl = (path: string, query: object) =>
|
|
192
279
|
`${path}?query=${encodeURIComponent(JSON.stringify(query))}`
|
|
193
280
|
const qsUrl = (path: string, query: object) => `${path}?${qs.stringify(query)}`
|
|
194
|
-
const calledUrl = (client: ApiClient) =>
|
|
281
|
+
const calledUrl = (client: ApiClient) =>
|
|
282
|
+
vi.mocked(client.get).mock.calls.at(0)?.at(0) ?? vi.mocked(client.post).mock.calls.at(0)?.at(0)
|
|
195
283
|
|
|
196
284
|
describe.each(cases)('$name', ({ path, query, call }) => {
|
|
197
285
|
it("sends JSON with the default client format ('json')", async () => {
|
|
@@ -61,7 +61,7 @@ describe('ServiceAccessManagementApi', () => {
|
|
|
61
61
|
it('serializes query parameters', async () => {
|
|
62
62
|
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
63
63
|
await api.getMany({ page: 2, perPage: 5 })
|
|
64
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
64
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
65
65
|
expect(url).toContain('/service-access-management?')
|
|
66
66
|
expect(decodeURIComponent(url)).toContain('"page":2')
|
|
67
67
|
})
|
|
@@ -76,7 +76,7 @@ describe('ServiceAccessManagementApi', () => {
|
|
|
76
76
|
it('returns unpaginated array when paginate: false', async () => {
|
|
77
77
|
vi.mocked(client.get).mockResolvedValue([record])
|
|
78
78
|
const result = await api.getMany({ paginate: false })
|
|
79
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
79
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
80
80
|
expect(decodeURIComponent(url)).toContain('"paginate":false')
|
|
81
81
|
expect(result).toEqual([record])
|
|
82
82
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { ServicesApi } from './ServicesApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type { ServiceLimits } 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
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
describe('ServicesApi', () => {
|
|
23
|
+
let client: ReturnType<typeof createMockClient>
|
|
24
|
+
let api: ServicesApi
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
client = createMockClient()
|
|
28
|
+
api = new ServicesApi(client)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('getLimits', () => {
|
|
32
|
+
it('calls client.get with /services/limits', async () => {
|
|
33
|
+
const limits: ServiceLimits = {
|
|
34
|
+
whatsapp: { used: 1, total: 80, available: 79 },
|
|
35
|
+
telegram: { used: 0, total: 0, available: 0 },
|
|
36
|
+
}
|
|
37
|
+
vi.mocked(client.get).mockResolvedValue(limits)
|
|
38
|
+
const result = await api.getLimits()
|
|
39
|
+
expect(client.get).toHaveBeenCalledWith('/services/limits', undefined)
|
|
40
|
+
expect(result).toEqual(limits)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('forwards custom headers', async () => {
|
|
44
|
+
const headers = { 'X-Custom': 'value' }
|
|
45
|
+
await api.getLimits(headers)
|
|
46
|
+
expect(client.get).toHaveBeenCalledWith('/services/limits', headers)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
|
|
3
|
-
import type { Service, CreateServicePayload, UpdateServicePayload } from './types'
|
|
3
|
+
import type { Service, CreateServicePayload, ServiceLimits, UpdateServicePayload } from './types'
|
|
4
4
|
|
|
5
5
|
export class ServicesApi extends ArchivableCrudApi<
|
|
6
6
|
Service,
|
|
@@ -10,4 +10,13 @@ export class ServicesApi extends ArchivableCrudApi<
|
|
|
10
10
|
constructor(client: ApiClient) {
|
|
11
11
|
super(client, '/services')
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns, for each connection type of the account plan, how many connections are used,
|
|
16
|
+
* allowed and still available.
|
|
17
|
+
* @permissions services.view
|
|
18
|
+
*/
|
|
19
|
+
getLimits(headers?: Record<string, string>): Promise<ServiceLimits> {
|
|
20
|
+
return this.client.get<ServiceLimits>(`${this.basePath}/limits`, headers)
|
|
21
|
+
}
|
|
13
22
|
}
|
|
@@ -221,3 +221,12 @@ export type UpdateServicePayload = {
|
|
|
221
221
|
botId?: string | null
|
|
222
222
|
archivedAt?: string | null
|
|
223
223
|
}
|
|
224
|
+
|
|
225
|
+
export type ServiceLimit = {
|
|
226
|
+
used: number
|
|
227
|
+
total: number
|
|
228
|
+
available: number
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Connections used and allowed by the account plan, by connection type. */
|
|
232
|
+
export type ServiceLimits = Partial<Record<ServiceType, ServiceLimit>>
|
|
@@ -64,7 +64,7 @@ describe('StickerUsersApi', () => {
|
|
|
64
64
|
it('serializes query parameters', async () => {
|
|
65
65
|
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
66
66
|
await api.getMany({ page: 2, perPage: 5 })
|
|
67
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
67
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
68
68
|
expect(url).toContain('/sticker-users?')
|
|
69
69
|
expect(decodeURIComponent(url)).toContain('"page":2')
|
|
70
70
|
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { TagsApi } from './TagsApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
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('TagsApi', () => {
|
|
24
|
+
let client: ReturnType<typeof createMockClient>
|
|
25
|
+
let api: TagsApi
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
client = createMockClient()
|
|
29
|
+
api = new TagsApi(client)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('attachContacts', () => {
|
|
33
|
+
it('calls client.post with /tags/:id/contacts and the selection', async () => {
|
|
34
|
+
vi.mocked(client.post).mockResolvedValue('OK')
|
|
35
|
+
const body = { where: { id: { $in: ['c1', 'c2'] } } }
|
|
36
|
+
const result = await api.attachContacts('t1', body)
|
|
37
|
+
expect(client.post).toHaveBeenCalledWith('/tags/t1/contacts', body, undefined)
|
|
38
|
+
expect(result).toBe('OK')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('forwards custom headers', async () => {
|
|
42
|
+
await api.attachContacts('t1', { where: { id: 'c1' } }, headers)
|
|
43
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
44
|
+
'/tags/t1/contacts',
|
|
45
|
+
{ where: { id: 'c1' } },
|
|
46
|
+
headers,
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('detachContacts', () => {
|
|
52
|
+
it('calls client.request with DELETE /tags/:id/contacts and the selection', async () => {
|
|
53
|
+
vi.mocked(client.request).mockResolvedValue('OK')
|
|
54
|
+
const body = { where: { id: 'c1' } }
|
|
55
|
+
const result = await api.detachContacts('t1', body, headers)
|
|
56
|
+
expect(client.request).toHaveBeenCalledWith('DELETE', '/tags/t1/contacts', body, headers)
|
|
57
|
+
expect(result).toBe('OK')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('attachManyToDepartments', () => {
|
|
62
|
+
it('calls client.post with /tags/attach-many-departments and body', async () => {
|
|
63
|
+
vi.mocked(client.post).mockResolvedValue('OK')
|
|
64
|
+
const body = { ids: ['t1', 't2'], departmentIds: ['d1'] }
|
|
65
|
+
const result = await api.attachManyToDepartments(body)
|
|
66
|
+
expect(client.post).toHaveBeenCalledWith('/tags/attach-many-departments', body, undefined)
|
|
67
|
+
expect(result).toBe('OK')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('error propagation', () => {
|
|
72
|
+
it('propagates errors from attachManyToDepartments', async () => {
|
|
73
|
+
vi.mocked(client.post).mockRejectedValue(new Error('Invalid tag ids'))
|
|
74
|
+
await expect(api.attachManyToDepartments({ ids: [], departmentIds: [] })).rejects.toThrow(
|
|
75
|
+
'Invalid tag ids',
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
})
|
package/src/apis/tags/TagsApi.ts
CHANGED
|
@@ -1,9 +1,65 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type { ContactsSelection } from '../contacts/types'
|
|
4
|
+
import type {
|
|
5
|
+
AttachTagsToDepartmentsPayload,
|
|
6
|
+
CreateTagPayload,
|
|
7
|
+
Tag,
|
|
8
|
+
UpdateTagPayload,
|
|
9
|
+
} from './types'
|
|
4
10
|
|
|
5
11
|
export class TagsApi extends BaseCrudApi<Tag, CreateTagPayload, UpdateTagPayload> {
|
|
6
12
|
constructor(client: ApiClient) {
|
|
7
13
|
super(client, '/tags')
|
|
8
14
|
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds the tag to every contact of the account that matches the selection. Resolves to `'OK'`.
|
|
18
|
+
* @permissions tags.view
|
|
19
|
+
*/
|
|
20
|
+
attachContacts(
|
|
21
|
+
id: string,
|
|
22
|
+
body: ContactsSelection,
|
|
23
|
+
headers?: Record<string, string>,
|
|
24
|
+
): Promise<string> {
|
|
25
|
+
return this.client.post<string, ContactsSelection>(
|
|
26
|
+
`${this.basePath}/${id}/contacts`,
|
|
27
|
+
body,
|
|
28
|
+
headers,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Removes the tag from every contact of the account that matches the selection. Resolves to
|
|
34
|
+
* `'OK'`.
|
|
35
|
+
* @permissions tags.view OR tags.destroy
|
|
36
|
+
*/
|
|
37
|
+
detachContacts(
|
|
38
|
+
id: string,
|
|
39
|
+
body: ContactsSelection,
|
|
40
|
+
headers?: Record<string, string>,
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
return this.client.request<string, ContactsSelection>(
|
|
43
|
+
'DELETE',
|
|
44
|
+
`${this.basePath}/${id}/contacts`,
|
|
45
|
+
body,
|
|
46
|
+
headers,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Links several tags to the given departments. Resolves to `'OK'`. A tag without departments
|
|
52
|
+
* is already available to all of them and is left unchanged.
|
|
53
|
+
* @permissions tags.view
|
|
54
|
+
*/
|
|
55
|
+
attachManyToDepartments(
|
|
56
|
+
body: AttachTagsToDepartmentsPayload,
|
|
57
|
+
headers?: Record<string, string>,
|
|
58
|
+
): Promise<string> {
|
|
59
|
+
return this.client.post<string, AttachTagsToDepartmentsPayload>(
|
|
60
|
+
`${this.basePath}/attach-many-departments`,
|
|
61
|
+
body,
|
|
62
|
+
headers,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
9
65
|
}
|
package/src/apis/tags/types.ts
CHANGED
|
@@ -27,3 +27,12 @@ export type UpdateTagPayload = {
|
|
|
27
27
|
label?: string
|
|
28
28
|
backgroundColor?: string | null
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Body for `POST /tags/attach-many-departments`. An empty `departmentIds` makes the tags
|
|
33
|
+
* available to every department.
|
|
34
|
+
*/
|
|
35
|
+
export type AttachTagsToDepartmentsPayload = {
|
|
36
|
+
ids: string[]
|
|
37
|
+
departmentIds: string[]
|
|
38
|
+
}
|
|
@@ -66,7 +66,7 @@ describe('TermsApi', () => {
|
|
|
66
66
|
it('serializes the query', async () => {
|
|
67
67
|
vi.mocked(client.get).mockResolvedValue(term)
|
|
68
68
|
await api.getById('term-1', { attributes: ['id', 'version'] })
|
|
69
|
-
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
69
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
70
70
|
expect(url).toContain('/terms/term-1?')
|
|
71
71
|
expect(url).toContain('attributes')
|
|
72
72
|
})
|