@ikatec/digisac-api-sdk 2.0.0 → 2.1.1
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/campaigns/types.d.ts +22 -5
- package/dist/apis/campaigns/types.d.ts.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/types.d.ts +20 -3
- package/dist/apis/whatsappBusinessTemplates/types.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +2 -2
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs.map +1 -1
- package/package.json +1 -1
- package/src/apis/authHistory/AuthHistoryApi.test.ts +110 -0
- package/src/apis/campaigns/CampaignsApi.test.ts +344 -0
- package/src/apis/campaigns/types.ts +26 -5
- package/src/apis/clientFeedback/ClientFeedbackApi.test.ts +76 -0
- package/src/apis/contacts/ContactsApi.test.ts +364 -0
- package/src/apis/me/MeApi.test.ts +372 -0
- package/src/apis/messages/MessagesApi.test.ts +460 -0
- package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.test.ts +89 -0
- package/src/apis/stickerUsers/StickerUsersApi.test.ts +84 -0
- package/src/apis/terms/TermsApi.test.ts +196 -0
- package/src/apis/transcripts/TranscriptsApi.test.ts +63 -0
- package/src/apis/whatsappBusinessTemplates/types.ts +24 -3
- package/src/core/BaseCrudApi.ts +2 -2
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { CampaignsApi } from './CampaignsApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type {
|
|
5
|
+
Campaign,
|
|
6
|
+
CreateCampaignPayload,
|
|
7
|
+
CampaignStats,
|
|
8
|
+
SetIntervalPayload,
|
|
9
|
+
AutoPauseModePayload,
|
|
10
|
+
ExportCampaignResultPayload,
|
|
11
|
+
ExportTemplateCsvPayload,
|
|
12
|
+
HsmLimit,
|
|
13
|
+
} from './types'
|
|
14
|
+
import type { Paginated } from '../../core/types'
|
|
15
|
+
|
|
16
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function createMockClient(): ApiClient {
|
|
19
|
+
return {
|
|
20
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
21
|
+
request: vi.fn(),
|
|
22
|
+
get: vi.fn(),
|
|
23
|
+
post: vi.fn(),
|
|
24
|
+
put: vi.fn(),
|
|
25
|
+
patch: vi.fn(),
|
|
26
|
+
delete: vi.fn(),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Fixtures ─────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const campaign: Campaign = {
|
|
33
|
+
id: 'cmp-1',
|
|
34
|
+
title: 'Test Campaign',
|
|
35
|
+
status: 'ready',
|
|
36
|
+
config: { minInterval: 1000, maxInterval: 5000 },
|
|
37
|
+
sendsAt: null,
|
|
38
|
+
isScheduled: false,
|
|
39
|
+
startedAt: null,
|
|
40
|
+
finishedAt: null,
|
|
41
|
+
totalMessagesCount: null,
|
|
42
|
+
sentMessagesCount: null,
|
|
43
|
+
totalContacts: null,
|
|
44
|
+
totalContactsImported: null,
|
|
45
|
+
totalValidContacts: null,
|
|
46
|
+
sentContacts: null,
|
|
47
|
+
viewedContacts: null,
|
|
48
|
+
contactCount: null,
|
|
49
|
+
mustOpenTicket: false,
|
|
50
|
+
marketingIntegration: false,
|
|
51
|
+
accountId: 'acc-1',
|
|
52
|
+
serviceId: 'svc-1',
|
|
53
|
+
defaultDepartmentId: null,
|
|
54
|
+
defaultUserId: null,
|
|
55
|
+
createdById: null,
|
|
56
|
+
sentById: null,
|
|
57
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
58
|
+
updatedAt: '2024-01-01T00:00:00Z',
|
|
59
|
+
deletedAt: null,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const paginated: Paginated<Campaign> = {
|
|
63
|
+
data: [campaign],
|
|
64
|
+
total: 1,
|
|
65
|
+
limit: 10,
|
|
66
|
+
skip: 0,
|
|
67
|
+
currentPage: 1,
|
|
68
|
+
lastPage: 1,
|
|
69
|
+
from: 1,
|
|
70
|
+
to: 1,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
describe('CampaignsApi', () => {
|
|
76
|
+
let client: ReturnType<typeof createMockClient>
|
|
77
|
+
let api: CampaignsApi
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
client = createMockClient()
|
|
81
|
+
api = new CampaignsApi(client)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// ── getMany ──────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe('getMany', () => {
|
|
87
|
+
it('calls client.get with /campaigns when no query is provided', async () => {
|
|
88
|
+
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
89
|
+
const result = await api.getMany()
|
|
90
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns', undefined)
|
|
91
|
+
expect(result).toEqual(paginated)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('serializes query parameters', async () => {
|
|
95
|
+
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
96
|
+
await api.getMany({ page: 2, perPage: 5 })
|
|
97
|
+
const url = vi.mocked(client.get).mock.calls[0][0] as string
|
|
98
|
+
expect(url).toContain('/campaigns?')
|
|
99
|
+
expect(url).toContain('page=2')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('forwards custom headers', async () => {
|
|
103
|
+
vi.mocked(client.get).mockResolvedValue(paginated)
|
|
104
|
+
const headers = { 'X-Custom': 'value' }
|
|
105
|
+
await api.getMany(undefined, headers)
|
|
106
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns', headers)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// ── getById ──────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
describe('getById', () => {
|
|
113
|
+
it('calls client.get with /campaigns/:id', async () => {
|
|
114
|
+
vi.mocked(client.get).mockResolvedValue(campaign)
|
|
115
|
+
const result = await api.getById('cmp-1')
|
|
116
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns/cmp-1', undefined)
|
|
117
|
+
expect(result).toEqual(campaign)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('forwards custom headers', async () => {
|
|
121
|
+
vi.mocked(client.get).mockResolvedValue(campaign)
|
|
122
|
+
const headers = { 'X-Tenant': 'abc' }
|
|
123
|
+
await api.getById('cmp-1', undefined, headers)
|
|
124
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns/cmp-1', headers)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// ── create ───────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
describe('create', () => {
|
|
131
|
+
it('calls client.post with /campaigns and body', async () => {
|
|
132
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
133
|
+
const body: CreateCampaignPayload = { title: 'New Campaign', serviceId: 'svc-1' }
|
|
134
|
+
const result = await api.create(body)
|
|
135
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns', body, undefined)
|
|
136
|
+
expect(result).toEqual(campaign)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('forwards custom headers', async () => {
|
|
140
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
141
|
+
const headers = { 'X-Req': 'r-1' }
|
|
142
|
+
await api.create({ title: 'x', serviceId: 'svc-1' }, headers)
|
|
143
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
144
|
+
'/campaigns',
|
|
145
|
+
{ title: 'x', serviceId: 'svc-1' },
|
|
146
|
+
headers,
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// ── updateById ───────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
describe('updateById', () => {
|
|
154
|
+
it('calls client.put with /campaigns/:id and body', async () => {
|
|
155
|
+
vi.mocked(client.put).mockResolvedValue(campaign)
|
|
156
|
+
const result = await api.updateById('cmp-1', { title: 'Updated' })
|
|
157
|
+
expect(client.put).toHaveBeenCalledWith('/campaigns/cmp-1', { title: 'Updated' }, undefined)
|
|
158
|
+
expect(result).toEqual(campaign)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('forwards custom headers', async () => {
|
|
162
|
+
vi.mocked(client.put).mockResolvedValue(campaign)
|
|
163
|
+
const headers = { 'If-Match': 'etag' }
|
|
164
|
+
await api.updateById('cmp-1', { title: 'x' }, headers)
|
|
165
|
+
expect(client.put).toHaveBeenCalledWith('/campaigns/cmp-1', { title: 'x' }, headers)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// ── deleteById ───────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
describe('deleteById', () => {
|
|
172
|
+
it('calls client.delete with /campaigns/:id', async () => {
|
|
173
|
+
vi.mocked(client.delete).mockResolvedValue(campaign)
|
|
174
|
+
const result = await api.deleteById('cmp-1')
|
|
175
|
+
expect(client.delete).toHaveBeenCalledWith('/campaigns/cmp-1', undefined)
|
|
176
|
+
expect(result).toEqual(campaign)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('forwards custom headers', async () => {
|
|
180
|
+
vi.mocked(client.delete).mockResolvedValue(campaign)
|
|
181
|
+
const headers = { 'X-Reason': 'cleanup' }
|
|
182
|
+
await api.deleteById('cmp-1', headers)
|
|
183
|
+
expect(client.delete).toHaveBeenCalledWith('/campaigns/cmp-1', headers)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// ── sendById ─────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
describe('sendById', () => {
|
|
190
|
+
it('calls client.post with /campaigns/:id/send', async () => {
|
|
191
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
192
|
+
const result = await api.sendById('cmp-1')
|
|
193
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/cmp-1/send')
|
|
194
|
+
expect(result).toEqual(campaign)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// ── pauseById ────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
describe('pauseById', () => {
|
|
201
|
+
it('calls client.post with /campaigns/:id/pause', async () => {
|
|
202
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
203
|
+
const result = await api.pauseById('cmp-1')
|
|
204
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/cmp-1/pause')
|
|
205
|
+
expect(result).toEqual(campaign)
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// ── resumeById ───────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
describe('resumeById', () => {
|
|
212
|
+
it('calls client.post with /campaigns/:id/resume', async () => {
|
|
213
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
214
|
+
const result = await api.resumeById('cmp-1')
|
|
215
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/cmp-1/resume')
|
|
216
|
+
expect(result).toEqual(campaign)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// ── duplicateById ────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
describe('duplicateById', () => {
|
|
223
|
+
it('calls client.post with /campaigns/:id/duplicate', async () => {
|
|
224
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
225
|
+
const result = await api.duplicateById('cmp-1')
|
|
226
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/cmp-1/duplicate')
|
|
227
|
+
expect(result).toEqual(campaign)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// ── getStatsById ─────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
describe('getStatsById', () => {
|
|
234
|
+
it('calls client.get with /campaigns/:id/stats', async () => {
|
|
235
|
+
const stats: CampaignStats = {
|
|
236
|
+
campaignInfo: {
|
|
237
|
+
totalValidContacts: 100,
|
|
238
|
+
totalContacts: 110,
|
|
239
|
+
totalContactsImported: 120,
|
|
240
|
+
firedSuccessCount: 90,
|
|
241
|
+
firedFailedCount: 10,
|
|
242
|
+
totalMessagesCount: 100,
|
|
243
|
+
totalFiredCount: 100,
|
|
244
|
+
firedBlockedByMessageRuleCount: 0,
|
|
245
|
+
},
|
|
246
|
+
messageInfo: { error: 5, received: 85, viewed: 70, answered: 30 },
|
|
247
|
+
}
|
|
248
|
+
vi.mocked(client.get).mockResolvedValue(stats)
|
|
249
|
+
const result = await api.getStatsById('cmp-1')
|
|
250
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns/cmp-1/stats')
|
|
251
|
+
expect(result).toEqual(stats)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// ── setIntervalById ──────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
describe('setIntervalById', () => {
|
|
258
|
+
it('calls client.post with /campaigns/:id/interval and body', async () => {
|
|
259
|
+
vi.mocked(client.post).mockResolvedValue(campaign)
|
|
260
|
+
const data: SetIntervalPayload = { minInterval: '1000', maxInterval: '5000' }
|
|
261
|
+
const result = await api.setIntervalById('cmp-1', data)
|
|
262
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/cmp-1/interval', data)
|
|
263
|
+
expect(result).toEqual(campaign)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// ── getContactCount ──────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
describe('getContactCount', () => {
|
|
270
|
+
it('calls client.get with /campaigns/:id/contactCount', async () => {
|
|
271
|
+
vi.mocked(client.get).mockResolvedValue(42)
|
|
272
|
+
const result = await api.getContactCount('cmp-1')
|
|
273
|
+
expect(client.get).toHaveBeenCalledWith('/campaigns/cmp-1/contactCount')
|
|
274
|
+
expect(result).toBe(42)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// ── setAutoPauseMode ─────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
describe('setAutoPauseMode', () => {
|
|
281
|
+
it('calls client.put with /campaigns/autoPauseMode and body', async () => {
|
|
282
|
+
vi.mocked(client.put).mockResolvedValue({ ok: true })
|
|
283
|
+
const data: AutoPauseModePayload = { mode: 'auto' }
|
|
284
|
+
const result = await api.setAutoPauseMode(data)
|
|
285
|
+
expect(client.put).toHaveBeenCalledWith('/campaigns/autoPauseMode', data)
|
|
286
|
+
expect(result).toEqual({ ok: true })
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// ── exportCampaignResult ─────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
describe('exportCampaignResult', () => {
|
|
293
|
+
it('calls client.post with /campaigns/export/csv and body', async () => {
|
|
294
|
+
vi.mocked(client.post).mockResolvedValue({ ok: true })
|
|
295
|
+
const data: ExportCampaignResultPayload = { campaignId: 'cmp-1' }
|
|
296
|
+
const result = await api.exportCampaignResult(data)
|
|
297
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/export/csv', data)
|
|
298
|
+
expect(result).toEqual({ ok: true })
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// ── exportTemplateCsv ────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
describe('exportTemplateCsv', () => {
|
|
305
|
+
it('calls client.post with /campaigns/exportTemplate and body', async () => {
|
|
306
|
+
vi.mocked(client.post).mockResolvedValue({ ok: true })
|
|
307
|
+
const data: ExportTemplateCsvPayload = { hsmId: 'hsm-1' }
|
|
308
|
+
const result = await api.exportTemplateCsv(data)
|
|
309
|
+
expect(client.post).toHaveBeenCalledWith('/campaigns/exportTemplate', data)
|
|
310
|
+
expect(result).toEqual({ ok: true })
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// ── getHsmLimit ──────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
describe('getHsmLimit', () => {
|
|
317
|
+
it('calls client.get with /whatsapp-business-templates/:accountId/hsmLimit', async () => {
|
|
318
|
+
const limit: HsmLimit = { hsmLimit: 1000, hsmUsedLimit: 150 }
|
|
319
|
+
vi.mocked(client.get).mockResolvedValue(limit)
|
|
320
|
+
const result = await api.getHsmLimit('acc-1')
|
|
321
|
+
expect(client.get).toHaveBeenCalledWith('/whatsapp-business-templates/acc-1/hsmLimit')
|
|
322
|
+
expect(result).toEqual(limit)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// ── Error propagation ────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
describe('error propagation', () => {
|
|
329
|
+
it('propagates errors from getMany', async () => {
|
|
330
|
+
vi.mocked(client.get).mockRejectedValue(new Error('Network error'))
|
|
331
|
+
await expect(api.getMany()).rejects.toThrow('Network error')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('propagates errors from sendById', async () => {
|
|
335
|
+
vi.mocked(client.post).mockRejectedValue(new Error('Conflict'))
|
|
336
|
+
await expect(api.sendById('cmp-1')).rejects.toThrow('Conflict')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('propagates errors from getHsmLimit', async () => {
|
|
340
|
+
vi.mocked(client.get).mockRejectedValue(new Error('Forbidden'))
|
|
341
|
+
await expect(api.getHsmLimit('acc-1')).rejects.toThrow('Forbidden')
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
})
|
|
@@ -4,6 +4,7 @@ import type { Message } from '../messages'
|
|
|
4
4
|
import type { Service } from '../services'
|
|
5
5
|
import type { Tag } from '../tags'
|
|
6
6
|
import type { User } from '../users'
|
|
7
|
+
import type { WhatsappBusinessTemplate } from '../whatsappBusinessTemplates'
|
|
7
8
|
|
|
8
9
|
export type CampaignStatus =
|
|
9
10
|
| 'ready'
|
|
@@ -84,6 +85,25 @@ export type CampaignRelationships =
|
|
|
84
85
|
| 'messages'
|
|
85
86
|
| 'importFile'
|
|
86
87
|
|
|
88
|
+
export type CampaignTemplateParameterItem = {
|
|
89
|
+
type: string
|
|
90
|
+
text?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type CampaignTemplateParameter = {
|
|
94
|
+
type: string
|
|
95
|
+
parameters: CampaignTemplateParameterItem[]
|
|
96
|
+
sub_type?: string
|
|
97
|
+
index?: number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type CampaignFileTemplate = {
|
|
101
|
+
blob?: undefined
|
|
102
|
+
base64Url?: string
|
|
103
|
+
mimetype?: string
|
|
104
|
+
name?: string
|
|
105
|
+
}
|
|
106
|
+
|
|
87
107
|
export type CreateCampaignPayload = {
|
|
88
108
|
title: string
|
|
89
109
|
serviceId: string
|
|
@@ -95,15 +115,16 @@ export type CreateCampaignPayload = {
|
|
|
95
115
|
mustOpenTicket?: boolean
|
|
96
116
|
defaultDepartmentId?: string | null
|
|
97
117
|
defaultUserId?: string | null
|
|
98
|
-
|
|
99
|
-
parameters?: unknown
|
|
100
|
-
fileTemplate?: unknown
|
|
101
|
-
contacts?: unknown[]
|
|
118
|
+
contacts?: Record<string, string>[]
|
|
102
119
|
useTags?: boolean
|
|
103
|
-
marketingIntegration?: boolean
|
|
104
120
|
fileId?: string
|
|
105
121
|
nameField?: string
|
|
106
122
|
numberField?: string
|
|
123
|
+
// whatsapp-business specific
|
|
124
|
+
hsm?: WhatsappBusinessTemplate
|
|
125
|
+
parameters?: CampaignTemplateParameter[]
|
|
126
|
+
fileTemplate?: CampaignFileTemplate
|
|
127
|
+
marketingIntegration?: boolean
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
export type UpdateCampaignPayload = Partial<CreateCampaignPayload>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { ClientFeedbackApi } from './ClientFeedbackApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type { ClientFeedback, CreateClientFeedbackPayload } 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
|
+
// ── Fixtures ─────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const feedback: ClientFeedback = {
|
|
23
|
+
id: 'cf-1',
|
|
24
|
+
accountId: 'acc-1',
|
|
25
|
+
ticketId: 'ticket-1',
|
|
26
|
+
feedback: 'Great service!',
|
|
27
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
28
|
+
updatedAt: '2024-01-01T00:00:00Z',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
describe('ClientFeedbackApi', () => {
|
|
34
|
+
let client: ReturnType<typeof createMockClient>
|
|
35
|
+
let api: ClientFeedbackApi
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
client = createMockClient()
|
|
39
|
+
api = new ClientFeedbackApi(client)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// ── create ───────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
describe('create', () => {
|
|
45
|
+
it('calls client.post with /client-feedback and body', async () => {
|
|
46
|
+
vi.mocked(client.post).mockResolvedValue(feedback)
|
|
47
|
+
const body: CreateClientFeedbackPayload = {
|
|
48
|
+
accountId: 'acc-1',
|
|
49
|
+
ticketId: 'ticket-1',
|
|
50
|
+
feedback: 'Great service!',
|
|
51
|
+
}
|
|
52
|
+
const result = await api.create(body)
|
|
53
|
+
expect(client.post).toHaveBeenCalledWith('/client-feedback', body, undefined)
|
|
54
|
+
expect(result).toEqual(feedback)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('forwards custom headers', async () => {
|
|
58
|
+
vi.mocked(client.post).mockResolvedValue(feedback)
|
|
59
|
+
const headers = { 'X-Custom': 'h' }
|
|
60
|
+
const body: CreateClientFeedbackPayload = {
|
|
61
|
+
accountId: 'acc-1',
|
|
62
|
+
ticketId: 'ticket-1',
|
|
63
|
+
feedback: 'Good',
|
|
64
|
+
}
|
|
65
|
+
await api.create(body, headers)
|
|
66
|
+
expect(client.post).toHaveBeenCalledWith('/client-feedback', body, headers)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('propagates errors from client.post', async () => {
|
|
70
|
+
vi.mocked(client.post).mockRejectedValue(new Error('Bad request'))
|
|
71
|
+
await expect(
|
|
72
|
+
api.create({ accountId: 'acc-1', ticketId: 't-1', feedback: 'x' }),
|
|
73
|
+
).rejects.toThrow('Bad request')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|