@ikatec/digisac-api-sdk 4.0.0 → 4.2.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/README.md +34 -4
- package/dist/apis/authHistory/AuthHistoryApi.cjs +11 -17
- package/dist/apis/authHistory/AuthHistoryApi.cjs.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.d.ts +10 -18
- package/dist/apis/authHistory/AuthHistoryApi.d.ts.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.mjs +11 -15
- package/dist/apis/authHistory/AuthHistoryApi.mjs.map +1 -1
- package/dist/apis/authHistory/types.d.ts +4 -3
- package/dist/apis/authHistory/types.d.ts.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs +2 -5
- package/dist/apis/clientFeedback/ClientFeedbackApi.cjs.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts +2 -4
- package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts.map +1 -1
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs +3 -5
- package/dist/apis/clientFeedback/ClientFeedbackApi.mjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.cjs +2 -2
- package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.mjs +2 -2
- package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
- package/dist/apis/customFields/types.d.ts +21 -19
- package/dist/apis/customFields/types.d.ts.map +1 -1
- package/dist/apis/dashboard/DashboardApi.cjs +19 -0
- package/dist/apis/dashboard/DashboardApi.cjs.map +1 -0
- package/dist/apis/dashboard/DashboardApi.d.ts +12 -0
- package/dist/apis/dashboard/DashboardApi.d.ts.map +1 -0
- package/dist/apis/dashboard/DashboardApi.mjs +18 -0
- package/dist/apis/dashboard/DashboardApi.mjs.map +1 -0
- package/dist/apis/dashboard/index.cjs +4 -0
- package/dist/apis/dashboard/index.d.ts +3 -0
- package/dist/apis/dashboard/index.d.ts.map +1 -0
- package/dist/apis/dashboard/index.mjs +3 -0
- package/dist/apis/dashboard/types.cjs +0 -0
- package/dist/apis/dashboard/types.d.ts +53 -0
- package/dist/apis/dashboard/types.d.ts.map +1 -0
- package/dist/apis/dashboard/types.mjs +0 -0
- package/dist/apis/holiday/types.d.ts +34 -8
- package/dist/apis/holiday/types.d.ts.map +1 -1
- package/dist/apis/index.cjs +6 -0
- package/dist/apis/index.d.ts +2 -0
- package/dist/apis/index.d.ts.map +1 -1
- package/dist/apis/index.mjs +5 -1
- package/dist/apis/me/MeApi.cjs +2 -5
- package/dist/apis/me/MeApi.cjs.map +1 -1
- package/dist/apis/me/MeApi.d.ts +2 -4
- package/dist/apis/me/MeApi.d.ts.map +1 -1
- package/dist/apis/me/MeApi.mjs +3 -5
- package/dist/apis/me/MeApi.mjs.map +1 -1
- package/dist/apis/now/NowApi.cjs +37 -0
- package/dist/apis/now/NowApi.cjs.map +1 -0
- package/dist/apis/now/NowApi.d.ts +26 -0
- package/dist/apis/now/NowApi.d.ts.map +1 -0
- package/dist/apis/now/NowApi.mjs +36 -0
- package/dist/apis/now/NowApi.mjs.map +1 -0
- package/dist/apis/now/index.cjs +4 -0
- package/dist/apis/now/index.d.ts +3 -0
- package/dist/apis/now/index.d.ts.map +1 -0
- package/dist/apis/now/index.mjs +3 -0
- package/dist/apis/now/types.cjs +0 -0
- package/dist/apis/now/types.d.ts +45 -0
- package/dist/apis/now/types.d.ts.map +1 -0
- package/dist/apis/now/types.mjs +0 -0
- package/dist/apis/people/types.d.ts +7 -2
- package/dist/apis/people/types.d.ts.map +1 -1
- package/dist/apis/permissions/PermissionsApi.cjs +6 -2
- package/dist/apis/permissions/PermissionsApi.cjs.map +1 -1
- package/dist/apis/permissions/PermissionsApi.d.ts +7 -3
- package/dist/apis/permissions/PermissionsApi.d.ts.map +1 -1
- package/dist/apis/permissions/PermissionsApi.mjs +6 -2
- package/dist/apis/permissions/PermissionsApi.mjs.map +1 -1
- package/dist/apis/permissions/types.d.ts +4 -14
- package/dist/apis/permissions/types.d.ts.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.cjs +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.cjs.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.d.ts +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.d.ts.map +1 -1
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.mjs +5 -0
- package/dist/apis/personalAccessTokens/PersonalAccessTokensApi.mjs.map +1 -1
- package/dist/apis/personalAccessTokens/types.d.ts +23 -4
- package/dist/apis/personalAccessTokens/types.d.ts.map +1 -1
- package/dist/apis/roles/RolesApi.cjs.map +1 -1
- package/dist/apis/roles/RolesApi.d.ts +2 -2
- package/dist/apis/roles/RolesApi.d.ts.map +1 -1
- package/dist/apis/roles/RolesApi.mjs.map +1 -1
- package/dist/apis/roles/types.d.ts +27 -4
- package/dist/apis/roles/types.d.ts.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.cjs +3 -9
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.cjs.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.d.ts +3 -4
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.d.ts.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.mjs +3 -7
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.mjs.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.cjs +3 -9
- package/dist/apis/stickerUsers/StickerUsersApi.cjs.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.d.ts +3 -4
- package/dist/apis/stickerUsers/StickerUsersApi.d.ts.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.mjs +3 -7
- package/dist/apis/stickerUsers/StickerUsersApi.mjs.map +1 -1
- package/dist/apis/terms/TermsApi.cjs +7 -13
- package/dist/apis/terms/TermsApi.cjs.map +1 -1
- package/dist/apis/terms/TermsApi.d.ts +6 -5
- package/dist/apis/terms/TermsApi.d.ts.map +1 -1
- package/dist/apis/terms/TermsApi.mjs +7 -11
- package/dist/apis/terms/TermsApi.mjs.map +1 -1
- package/dist/apis/timetable/types.d.ts +12 -7
- package/dist/apis/timetable/types.d.ts.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.cjs +2 -5
- package/dist/apis/transcripts/TranscriptsApi.cjs.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.d.ts +2 -4
- package/dist/apis/transcripts/TranscriptsApi.d.ts.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.mjs +3 -5
- package/dist/apis/transcripts/TranscriptsApi.mjs.map +1 -1
- package/dist/core/ApiClient.d.ts +3 -0
- package/dist/core/ApiClient.d.ts.map +1 -1
- package/dist/core/ArchivableCrudApi.cjs +0 -2
- package/dist/core/ArchivableCrudApi.cjs.map +1 -1
- package/dist/core/ArchivableCrudApi.d.ts.map +1 -1
- package/dist/core/ArchivableCrudApi.mjs +0 -2
- package/dist/core/ArchivableCrudApi.mjs.map +1 -1
- package/dist/core/BaseApi.cjs +34 -0
- package/dist/core/BaseApi.cjs.map +1 -0
- package/dist/core/BaseApi.d.ts +20 -0
- package/dist/core/BaseApi.d.ts.map +1 -0
- package/dist/core/BaseApi.mjs +33 -0
- package/dist/core/BaseApi.mjs.map +1 -0
- package/dist/core/BaseApiClient.cjs +4 -1
- package/dist/core/BaseApiClient.cjs.map +1 -1
- package/dist/core/BaseApiClient.d.ts +7 -1
- package/dist/core/BaseApiClient.d.ts.map +1 -1
- package/dist/core/BaseApiClient.mjs +4 -1
- package/dist/core/BaseApiClient.mjs.map +1 -1
- package/dist/core/BaseCrudApi.cjs +3 -40
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +4 -23
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs +3 -38
- package/dist/core/BaseCrudApi.mjs.map +1 -1
- package/dist/core/BaseReadApi.cjs +44 -0
- package/dist/core/BaseReadApi.cjs.map +1 -0
- package/dist/core/BaseReadApi.d.ts +32 -0
- package/dist/core/BaseReadApi.d.ts.map +1 -0
- package/dist/core/BaseReadApi.mjs +43 -0
- package/dist/core/BaseReadApi.mjs.map +1 -0
- package/dist/core/bindMethods.cjs +24 -0
- package/dist/core/bindMethods.cjs.map +1 -0
- package/dist/core/bindMethods.d.ts +7 -0
- package/dist/core/bindMethods.d.ts.map +1 -0
- package/dist/core/bindMethods.mjs +22 -0
- package/dist/core/bindMethods.mjs.map +1 -0
- package/dist/core/index.cjs +4 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.mjs +3 -1
- package/dist/core/queryString.cjs +22 -0
- package/dist/core/queryString.cjs.map +1 -0
- package/dist/core/queryString.d.ts +14 -0
- package/dist/core/queryString.d.ts.map +1 -0
- package/dist/core/queryString.mjs +18 -0
- package/dist/core/queryString.mjs.map +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/withQueryFormat.cjs +42 -0
- package/dist/core/withQueryFormat.cjs.map +1 -0
- package/dist/core/withQueryFormat.d.ts +10 -0
- package/dist/core/withQueryFormat.d.ts.map +1 -0
- package/dist/core/withQueryFormat.mjs +40 -0
- package/dist/core/withQueryFormat.mjs.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.mjs +3 -1
- package/package.json +1 -1
- package/src/apis/authHistory/AuthHistoryApi.test.ts +11 -3
- package/src/apis/authHistory/AuthHistoryApi.ts +11 -37
- package/src/apis/authHistory/types.ts +4 -3
- package/src/apis/campaigns/CampaignsApi.test.ts +1 -1
- package/src/apis/clientFeedback/ClientFeedbackApi.ts +5 -7
- package/src/apis/contacts/ContactsApi.test.ts +1 -1
- package/src/apis/contacts/ContactsApi.ts +2 -2
- package/src/apis/customFields/types.ts +21 -25
- package/src/apis/dashboard/DashboardApi.test.ts +82 -0
- package/src/apis/dashboard/DashboardApi.ts +18 -0
- package/src/apis/dashboard/index.ts +2 -0
- package/src/apis/dashboard/types.ts +53 -0
- package/src/apis/holiday/types.ts +36 -8
- package/src/apis/index.ts +2 -0
- package/src/apis/me/MeApi.ts +2 -8
- package/src/apis/messages/MessagesApi.test.ts +2 -2
- package/src/apis/now/NowApi.test.ts +125 -0
- package/src/apis/now/NowApi.ts +59 -0
- package/src/apis/now/index.ts +2 -0
- package/src/apis/now/types.ts +47 -0
- package/src/apis/payloads.test-d.ts +39 -0
- package/src/apis/payloads.test.ts +81 -0
- package/src/apis/people/types.ts +7 -2
- package/src/apis/permissions/PermissionsApi.test.ts +93 -0
- package/src/apis/permissions/PermissionsApi.ts +7 -7
- package/src/apis/permissions/types.ts +4 -16
- package/src/apis/personalAccessTokens/PersonalAccessTokensApi.ts +5 -0
- package/src/apis/personalAccessTokens/types.ts +25 -4
- package/src/apis/queryFormat.test.ts +273 -0
- package/src/apis/roles/RolesApi.ts +7 -2
- package/src/apis/roles/types.ts +27 -4
- package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.test.ts +2 -2
- package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.ts +10 -10
- package/src/apis/stickerUsers/StickerUsersApi.test.ts +1 -1
- package/src/apis/stickerUsers/StickerUsersApi.ts +7 -10
- package/src/apis/terms/TermsApi.ts +7 -13
- package/src/apis/timetable/types.ts +13 -7
- package/src/apis/transcripts/TranscriptsApi.ts +2 -7
- package/src/core/ApiClient.ts +4 -0
- package/src/core/ArchivableCrudApi.ts +0 -2
- package/src/core/BaseApi.test.ts +164 -0
- package/src/core/BaseApi.ts +32 -0
- package/src/core/BaseApiClient.test.ts +10 -0
- package/src/core/BaseApiClient.ts +9 -1
- package/src/core/BaseCrudApi.test-d.ts +9 -0
- package/src/core/BaseCrudApi.test.ts +131 -60
- package/src/core/BaseCrudApi.ts +7 -63
- package/src/core/BaseReadApi.ts +70 -0
- package/src/core/bindMethods.ts +26 -0
- package/src/core/index.ts +4 -0
- package/src/core/queryString.test.ts +71 -0
- package/src/core/queryString.ts +30 -0
- package/src/core/types.ts +5 -0
- package/src/core/withQueryFormat.test.ts +136 -0
- package/src/core/withQueryFormat.ts +47 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { User } from '../users/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters of the "Agora" (now) overview. Each accepts one id, an array or a comma-separated list.
|
|
5
|
+
* A user with only `overview.now.view.departments` gets 403 for departments/users outside their
|
|
6
|
+
* departments.
|
|
7
|
+
*/
|
|
8
|
+
export type NowQuery = {
|
|
9
|
+
departmentId?: string | string[]
|
|
10
|
+
userId?: string | string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Every `/now` route answers its data under `totals`. */
|
|
14
|
+
export type NowResponse<T> = { totals: T }
|
|
15
|
+
|
|
16
|
+
export type NowResumeTotals = {
|
|
17
|
+
openTickets: number
|
|
18
|
+
queueTickets: number
|
|
19
|
+
usersWithAttendance: number
|
|
20
|
+
usersWithoutAttendance: number
|
|
21
|
+
/** Postgres `avg()` — a numeric string, or `0` when there is no data. */
|
|
22
|
+
averageOpenTicketsTime: number | string
|
|
23
|
+
/** Postgres `avg()` — a numeric string, or `0` when there is no data. */
|
|
24
|
+
averageOpenTicketsWaitTime: number | string
|
|
25
|
+
onlineUsersCount: number
|
|
26
|
+
offlineUsersCount: number
|
|
27
|
+
absentUsersCount: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One entry per department. */
|
|
31
|
+
export type NowDepartmentResume = {
|
|
32
|
+
name: string
|
|
33
|
+
onlineUserCount: number
|
|
34
|
+
offlineUserCount: number
|
|
35
|
+
absentUserCount: number
|
|
36
|
+
onQueueTicketCount: number
|
|
37
|
+
openTicketCount: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One entry per attendant (user). */
|
|
41
|
+
export type NowAttendanceResume = {
|
|
42
|
+
name?: string
|
|
43
|
+
/** Department names — `0` when the attendant has no department. */
|
|
44
|
+
departments: string[] | 0
|
|
45
|
+
status?: User['status']
|
|
46
|
+
ticketsOpenCount: number
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expectError } from 'tsd'
|
|
2
|
+
import type { ApiClient } from '../core/ApiClient'
|
|
3
|
+
import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
|
|
4
|
+
import { RolesApi } from './roles/RolesApi'
|
|
5
|
+
import { TimetableApi } from './timetable/TimetableApi'
|
|
6
|
+
|
|
7
|
+
declare const client: ApiClient
|
|
8
|
+
|
|
9
|
+
// ── roles: the backend drops `permissionIds` in silence ─────────────
|
|
10
|
+
|
|
11
|
+
const roles = new RolesApi(client)
|
|
12
|
+
roles.create({ displayName: 'Supervisor', permissions: ['p-1', { id: 'p-2' }] })
|
|
13
|
+
expectError(roles.create({ displayName: 'Supervisor', permissionIds: ['p-1'] }))
|
|
14
|
+
expectError(roles.updateById('r-1', { permissions: ['p-1'] }))
|
|
15
|
+
|
|
16
|
+
// ── timetable: the old workPlan shape answers 400 ───────────────────
|
|
17
|
+
|
|
18
|
+
const timetable = new TimetableApi(client)
|
|
19
|
+
timetable.create({
|
|
20
|
+
name: 'Business hours',
|
|
21
|
+
previousTimeToNotification: 15,
|
|
22
|
+
workPlan: [{ start: '08:00', end: '18:00', weekDays: ['mon'] }],
|
|
23
|
+
})
|
|
24
|
+
expectError(
|
|
25
|
+
timetable.create({
|
|
26
|
+
name: 'Business hours',
|
|
27
|
+
previousTimeToNotification: 15,
|
|
28
|
+
workPlan: [{ day: 1, start: '08:00', end: '18:00', active: true }],
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
expectError(timetable.updateById('t-1', { name: 'Business hours' }))
|
|
32
|
+
|
|
33
|
+
// ── personal access tokens: `expiresIn` is the only lifetime accepted ─
|
|
34
|
+
|
|
35
|
+
const tokens = new PersonalAccessTokensApi(client)
|
|
36
|
+
tokens.create({ name: 'CI', expiresIn: '90d' })
|
|
37
|
+
expectError(tokens.create({ name: 'CI', accessTokenExpiresAt: '2030-01-01T00:00:00Z' }))
|
|
38
|
+
expectError(tokens.create({ name: 'CI', expiresIn: '7d' }))
|
|
39
|
+
expectError(tokens.updateById('t-1', { scope: '*' }))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../core/ApiClient'
|
|
3
|
+
import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
|
|
4
|
+
import { RolesApi } from './roles/RolesApi'
|
|
5
|
+
import { TimetableApi } from './timetable/TimetableApi'
|
|
6
|
+
|
|
7
|
+
// Pins the body keys the backend reads: a key it does not know is dropped in silence, so a
|
|
8
|
+
// regression would only show up as missing data (e.g. a role created without permissions).
|
|
9
|
+
|
|
10
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function createMockClient(): ApiClient {
|
|
13
|
+
return {
|
|
14
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
15
|
+
request: vi.fn(),
|
|
16
|
+
get: vi.fn(),
|
|
17
|
+
post: vi.fn(),
|
|
18
|
+
put: vi.fn(),
|
|
19
|
+
patch: vi.fn(),
|
|
20
|
+
delete: vi.fn(),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe('payloads sent to the backend', () => {
|
|
27
|
+
let client: ReturnType<typeof createMockClient>
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
client = createMockClient()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('RolesApi', () => {
|
|
34
|
+
it('create sends the permissions under `permissions`', async () => {
|
|
35
|
+
await new RolesApi(client).create({
|
|
36
|
+
displayName: 'Supervisor',
|
|
37
|
+
permissions: ['p-1', { id: 'p-2' }],
|
|
38
|
+
})
|
|
39
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
40
|
+
'/roles',
|
|
41
|
+
{ displayName: 'Supervisor', permissions: ['p-1', { id: 'p-2' }] },
|
|
42
|
+
undefined,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('updateById sends displayName and the permissions under `permissions`', async () => {
|
|
47
|
+
await new RolesApi(client).updateById('r-1', {
|
|
48
|
+
displayName: 'Supervisor',
|
|
49
|
+
permissions: ['p-1'],
|
|
50
|
+
})
|
|
51
|
+
expect(client.put).toHaveBeenCalledWith(
|
|
52
|
+
'/roles/r-1',
|
|
53
|
+
{ displayName: 'Supervisor', permissions: ['p-1'] },
|
|
54
|
+
undefined,
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('TimetableApi', () => {
|
|
60
|
+
it('create sends the workPlan as { start, end, weekDays }', async () => {
|
|
61
|
+
const body = {
|
|
62
|
+
name: 'Business hours',
|
|
63
|
+
previousTimeToNotification: 15,
|
|
64
|
+
workPlan: [{ start: '08:00', end: '18:00', weekDays: ['mon' as const, 'fri' as const] }],
|
|
65
|
+
}
|
|
66
|
+
await new TimetableApi(client).create(body)
|
|
67
|
+
expect(client.post).toHaveBeenCalledWith('/timetable', body, undefined)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('PersonalAccessTokensApi', () => {
|
|
72
|
+
it('create sends the lifetime under `expiresIn`', async () => {
|
|
73
|
+
await new PersonalAccessTokensApi(client).create({ name: 'CI', expiresIn: '90d' })
|
|
74
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
75
|
+
'/me/tokens',
|
|
76
|
+
{ name: 'CI', expiresIn: '90d' },
|
|
77
|
+
undefined,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
})
|
package/src/apis/people/types.ts
CHANGED
|
@@ -17,10 +17,15 @@ export type PersonRelationships = 'organizations' | 'contacts'
|
|
|
17
17
|
|
|
18
18
|
export type CreatePersonPayload = {
|
|
19
19
|
name: string
|
|
20
|
-
document?: string
|
|
20
|
+
document?: string | null
|
|
21
|
+
/** Organizations of the account to link the person to (400 if any belongs to another account). */
|
|
22
|
+
organizationIds?: string[]
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export type UpdatePersonPayload = {
|
|
24
|
-
|
|
26
|
+
/** Required by the backend on every update (400 `required` otherwise). */
|
|
27
|
+
name: string
|
|
25
28
|
document?: string | null
|
|
29
|
+
/** Replaces the linked organizations: `[]` unlinks all, omitting it keeps them. */
|
|
30
|
+
organizationIds?: string[]
|
|
26
31
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { PermissionsApi } from './PermissionsApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type { Permission } 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 permission: Permission = { id: 'p-1', name: 'customFields.create', type: 'customFields' }
|
|
23
|
+
|
|
24
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe('PermissionsApi', () => {
|
|
27
|
+
let client: ReturnType<typeof createMockClient>
|
|
28
|
+
let api: PermissionsApi
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
client = createMockClient()
|
|
32
|
+
api = new PermissionsApi(client)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('exposes only the read routes served by the backend', () => {
|
|
36
|
+
expect(api.basePath).toBe('/permissions')
|
|
37
|
+
expect(api).not.toHaveProperty('create')
|
|
38
|
+
expect(api).not.toHaveProperty('updateById')
|
|
39
|
+
expect(api).not.toHaveProperty('deleteById')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('getMany', () => {
|
|
43
|
+
it('calls client.get with /permissions and no query string when there is no query', async () => {
|
|
44
|
+
vi.mocked(client.get).mockResolvedValue({ data: [permission], total: 1 })
|
|
45
|
+
await api.getMany()
|
|
46
|
+
expect(client.get).toHaveBeenCalledWith('/permissions', undefined)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('serializes the query in the client format', async () => {
|
|
50
|
+
vi.mocked(client.get).mockResolvedValue([permission])
|
|
51
|
+
const result = await api.getMany({ paginate: false })
|
|
52
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
53
|
+
`/permissions?query=${encodeURIComponent(JSON.stringify({ paginate: false }))}`,
|
|
54
|
+
undefined,
|
|
55
|
+
)
|
|
56
|
+
expect(result).toEqual([permission])
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('getOne', () => {
|
|
61
|
+
it('asks for a single unpaginated result and returns it', async () => {
|
|
62
|
+
vi.mocked(client.get).mockResolvedValue([permission])
|
|
63
|
+
const result = await api.getOne({ where: { name: 'customFields.create' } })
|
|
64
|
+
const query = { where: { name: 'customFields.create' }, limit: 1, paginate: false }
|
|
65
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
66
|
+
`/permissions?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
67
|
+
undefined,
|
|
68
|
+
)
|
|
69
|
+
expect(result).toEqual(permission)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('returns null when nothing matches', async () => {
|
|
73
|
+
vi.mocked(client.get).mockResolvedValue([])
|
|
74
|
+
expect(await api.getOne()).toBeNull()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('getById', () => {
|
|
79
|
+
it('calls client.get with /permissions/:id', async () => {
|
|
80
|
+
vi.mocked(client.get).mockResolvedValue(permission)
|
|
81
|
+
const result = await api.getById('p-1')
|
|
82
|
+
expect(client.get).toHaveBeenCalledWith('/permissions/p-1', undefined)
|
|
83
|
+
expect(result).toEqual(permission)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('forwards custom headers', async () => {
|
|
87
|
+
vi.mocked(client.get).mockResolvedValue(permission)
|
|
88
|
+
const headers = { 'X-Custom': 'h' }
|
|
89
|
+
await api.getById('p-1', undefined, headers)
|
|
90
|
+
expect(client.get).toHaveBeenCalledWith('/permissions/p-1', headers)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
-
import {
|
|
3
|
-
import type { Permission
|
|
2
|
+
import { BaseReadApi } from '../../core/BaseReadApi'
|
|
3
|
+
import type { Permission } from './types'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
> {
|
|
5
|
+
/**
|
|
6
|
+
* The permission catalog used by roles. The backend only serves `GET /permissions` and
|
|
7
|
+
* `GET /permissions/:id` (guarded by `roles.view`), so there is no create/update/delete.
|
|
8
|
+
*/
|
|
9
|
+
export class PermissionsApi extends BaseReadApi<Permission> {
|
|
10
10
|
constructor(client: ApiClient) {
|
|
11
11
|
super(client, '/permissions')
|
|
12
12
|
}
|
|
@@ -210,24 +210,12 @@ export type PermissionName =
|
|
|
210
210
|
| 'kanban.update'
|
|
211
211
|
| 'kanban.destroy'
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* A permission, as `GET /permissions` answers it (the transformer only picks these fields).
|
|
215
|
+
* Permissions are a fixed catalog: the API is read-only.
|
|
216
|
+
*/
|
|
213
217
|
export type Permission = {
|
|
214
218
|
id: string
|
|
215
219
|
name: PermissionName
|
|
216
220
|
type: PermissionType | null
|
|
217
|
-
description: string | null
|
|
218
|
-
createdAt: string
|
|
219
|
-
updatedAt: string
|
|
220
|
-
deletedAt: string | null
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export type CreatePermissionPayload = {
|
|
224
|
-
name: PermissionName
|
|
225
|
-
type?: PermissionType
|
|
226
|
-
description?: string
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export type UpdatePermissionPayload = {
|
|
230
|
-
name?: PermissionName
|
|
231
|
-
type?: PermissionType | null
|
|
232
|
-
description?: string | null
|
|
233
221
|
}
|
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
UpdatePersonalAccessTokenPayload,
|
|
7
7
|
} from './types'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Personal access tokens of the authenticated user (`/me/tokens`, all routes guarded by
|
|
11
|
+
* `settingsApi.update`). The listing is scoped to the user; `getById` of a missing token answers
|
|
12
|
+
* 404 (`{ message: 'Not found' }`) instead of `null`.
|
|
13
|
+
*/
|
|
9
14
|
export class PersonalAccessTokensApi extends BaseCrudApi<
|
|
10
15
|
PersonalAccessToken,
|
|
11
16
|
CreatePersonalAccessTokenPayload,
|
|
@@ -1,28 +1,49 @@
|
|
|
1
1
|
import type { User } from '../users/types'
|
|
2
2
|
|
|
3
|
+
export type PersonalAccessTokenStatus = 'active' | 'revoked' | 'expired'
|
|
4
|
+
|
|
5
|
+
/** Accepted lifetimes of a token (`No expiration` keeps `accessTokenExpiresAt` null). */
|
|
6
|
+
export type PersonalAccessTokenExpiresIn = '30d' | '90d' | '180d' | '1y' | 'No expiration'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A personal access token of the authenticated user.
|
|
10
|
+
*
|
|
11
|
+
* `accessToken` comes in full only in the `create` answer; every other route masks it
|
|
12
|
+
* (`abcde***vwxyz`). `status` and `user` (only `{ name }`) come on `getMany`/`getById`.
|
|
13
|
+
* `deleteById` revokes the token (soft delete): it keeps being listed with `status: 'revoked'`.
|
|
14
|
+
*/
|
|
3
15
|
export type PersonalAccessToken = {
|
|
4
16
|
id: string
|
|
5
17
|
name: string
|
|
18
|
+
/** `*` when created without a scope. */
|
|
6
19
|
scope: string | null
|
|
7
20
|
accessToken: string
|
|
8
21
|
accessTokenExpiresAt: string | null
|
|
9
22
|
userId: string
|
|
23
|
+
clientId: string | null
|
|
24
|
+
impersonate?: boolean | null
|
|
10
25
|
createdAt: string
|
|
11
26
|
updatedAt: string
|
|
12
27
|
deletedAt: string | null
|
|
28
|
+
/** Only on `getMany`/`getById` answers. */
|
|
29
|
+
status?: PersonalAccessTokenStatus
|
|
13
30
|
// relationships
|
|
14
|
-
|
|
31
|
+
/** Only on `getMany`/`getById` answers, with just the name. */
|
|
32
|
+
user?: Pick<User, 'name'>
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
export type PersonalAccessTokenRelationships = 'user'
|
|
18
36
|
|
|
19
37
|
export type CreatePersonalAccessTokenPayload = {
|
|
38
|
+
/** Up to 255 characters. */
|
|
20
39
|
name: string
|
|
40
|
+
/** Up to 255 characters (`*` by default). */
|
|
21
41
|
scope?: string
|
|
22
|
-
|
|
42
|
+
expiresIn?: PersonalAccessTokenExpiresIn
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
export type UpdatePersonalAccessTokenPayload = {
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
/** Required by the backend on every update (400 `required` otherwise). */
|
|
47
|
+
name: string
|
|
48
|
+
scope?: string
|
|
28
49
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import qs from 'qs'
|
|
3
|
+
import type { ApiClient } from '../core/ApiClient'
|
|
4
|
+
import { BaseApiClient } from '../core/BaseApiClient'
|
|
5
|
+
import type { QueryFormat } from '../core/queryString'
|
|
6
|
+
import { AuthHistoryApi } from './authHistory/AuthHistoryApi'
|
|
7
|
+
import { ContactsApi } from './contacts/ContactsApi'
|
|
8
|
+
import { DashboardApi } from './dashboard/DashboardApi'
|
|
9
|
+
import { NowApi } from './now/NowApi'
|
|
10
|
+
import { PermissionsApi } from './permissions/PermissionsApi'
|
|
11
|
+
import { ServiceAccessManagementApi } from './serviceAccessManagement/ServiceAccessManagementApi'
|
|
12
|
+
import { StickerUsersApi } from './stickerUsers/StickerUsersApi'
|
|
13
|
+
import { TermsApi } from './terms/TermsApi'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Every method that sends a list/get query must serialize it in the client's `queryFormat` and
|
|
17
|
+
* honor `withQueryFormat`. A method listed here that ignored the format would fail below.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
function createMockClient(queryFormat?: QueryFormat): ApiClient {
|
|
21
|
+
return {
|
|
22
|
+
...(queryFormat && { queryFormat }),
|
|
23
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
24
|
+
request: vi.fn(),
|
|
25
|
+
get: vi.fn().mockResolvedValue([]),
|
|
26
|
+
post: vi.fn(),
|
|
27
|
+
put: vi.fn(),
|
|
28
|
+
patch: vi.fn(),
|
|
29
|
+
delete: vi.fn(),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const listQuery = { where: { name: 'x' }, page: 2, perPage: 5 }
|
|
34
|
+
const getQuery = { attributes: ['id'] }
|
|
35
|
+
|
|
36
|
+
type Case = {
|
|
37
|
+
name: string
|
|
38
|
+
path: string
|
|
39
|
+
query: object
|
|
40
|
+
/** Calls the method on the API built from `client`, optionally through `withQueryFormat`. */
|
|
41
|
+
call: (client: ApiClient, override?: QueryFormat) => Promise<unknown>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cases: Case[] = [
|
|
45
|
+
{
|
|
46
|
+
name: 'ContactsApi.getMany (BaseCrudApi)',
|
|
47
|
+
path: '/contacts',
|
|
48
|
+
query: listQuery,
|
|
49
|
+
call: (client, f) => {
|
|
50
|
+
const api = new ContactsApi(client)
|
|
51
|
+
return (f ? api.withQueryFormat(f) : api).getMany(listQuery)
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'ContactsApi.getById (BaseCrudApi)',
|
|
56
|
+
path: '/contacts/c1',
|
|
57
|
+
query: getQuery,
|
|
58
|
+
call: (client, f) => {
|
|
59
|
+
const api = new ContactsApi(client)
|
|
60
|
+
return (f ? api.withQueryFormat(f) : api).getById('c1', { attributes: ['id'] })
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'ContactsApi.count',
|
|
65
|
+
path: '/contacts/count',
|
|
66
|
+
query: listQuery,
|
|
67
|
+
call: (client, f) => {
|
|
68
|
+
const api = new ContactsApi(client)
|
|
69
|
+
return (f ? api.withQueryFormat(f) : api).count(listQuery)
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'ContactsApi.forward',
|
|
74
|
+
path: '/contacts/forward',
|
|
75
|
+
query: listQuery,
|
|
76
|
+
call: (client, f) => {
|
|
77
|
+
const api = new ContactsApi(client)
|
|
78
|
+
return (f ? api.withQueryFormat(f) : api).forward(listQuery)
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'AuthHistoryApi.getMany',
|
|
83
|
+
path: '/auth-history',
|
|
84
|
+
query: { page: 2, perPage: 5 },
|
|
85
|
+
call: (client, f) => {
|
|
86
|
+
const api = new AuthHistoryApi(client)
|
|
87
|
+
return (f ? api.withQueryFormat(f) : api).getMany({ page: 2, perPage: 5 })
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'DashboardApi.getGeneral',
|
|
92
|
+
path: '/dashboard/general',
|
|
93
|
+
query: { status: 'closed', departmentId: ['d1'] },
|
|
94
|
+
call: (client, f) => {
|
|
95
|
+
const api = new DashboardApi(client)
|
|
96
|
+
return (f ? api.withQueryFormat(f) : api).getGeneral({
|
|
97
|
+
status: 'closed',
|
|
98
|
+
departmentId: ['d1'],
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'NowApi.getResume',
|
|
104
|
+
path: '/now/resume',
|
|
105
|
+
query: { departmentId: ['d1'], userId: 'u1' },
|
|
106
|
+
call: (client, f) => {
|
|
107
|
+
const api = new NowApi(client)
|
|
108
|
+
return (f ? api.withQueryFormat(f) : api).getResume({ departmentId: ['d1'], userId: 'u1' })
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'NowApi.getAttendanceResume',
|
|
113
|
+
path: '/now/attendance-resume',
|
|
114
|
+
query: { departmentId: ['d1'], userId: 'u1' },
|
|
115
|
+
call: (client, f) => {
|
|
116
|
+
const api = new NowApi(client)
|
|
117
|
+
return (f ? api.withQueryFormat(f) : api).getAttendanceResume({
|
|
118
|
+
departmentId: ['d1'],
|
|
119
|
+
userId: 'u1',
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'NowApi.getDepartmentsResume',
|
|
125
|
+
path: '/now/departments-resume',
|
|
126
|
+
query: { departmentId: ['d1'], userId: 'u1' },
|
|
127
|
+
call: (client, f) => {
|
|
128
|
+
const api = new NowApi(client)
|
|
129
|
+
return (f ? api.withQueryFormat(f) : api).getDepartmentsResume({
|
|
130
|
+
departmentId: ['d1'],
|
|
131
|
+
userId: 'u1',
|
|
132
|
+
})
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'PermissionsApi.getMany',
|
|
137
|
+
path: '/permissions',
|
|
138
|
+
query: listQuery,
|
|
139
|
+
call: (client, f) => {
|
|
140
|
+
const api = new PermissionsApi(client)
|
|
141
|
+
return (f ? api.withQueryFormat(f) : api).getMany(listQuery)
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'PermissionsApi.getById',
|
|
146
|
+
path: '/permissions/p1',
|
|
147
|
+
query: getQuery,
|
|
148
|
+
call: (client, f) => {
|
|
149
|
+
const api = new PermissionsApi(client)
|
|
150
|
+
return (f ? api.withQueryFormat(f) : api).getById('p1', { attributes: ['id'] })
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'AuthHistoryApi.getById',
|
|
155
|
+
path: '/auth-history/a1',
|
|
156
|
+
query: getQuery,
|
|
157
|
+
call: (client, f) => {
|
|
158
|
+
const api = new AuthHistoryApi(client)
|
|
159
|
+
return (f ? api.withQueryFormat(f) : api).getById('a1', { attributes: ['id'] })
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'ServiceAccessManagementApi.getMany',
|
|
164
|
+
path: '/service-access-management',
|
|
165
|
+
query: { page: 2, perPage: 5 },
|
|
166
|
+
call: (client, f) => {
|
|
167
|
+
const api = new ServiceAccessManagementApi(client)
|
|
168
|
+
return (f ? api.withQueryFormat(f) : api).getMany({ page: 2, perPage: 5 })
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'StickerUsersApi.getMany',
|
|
173
|
+
path: '/sticker-users',
|
|
174
|
+
query: { page: 2, perPage: 5 },
|
|
175
|
+
call: (client, f) => {
|
|
176
|
+
const api = new StickerUsersApi(client)
|
|
177
|
+
return (f ? api.withQueryFormat(f) : api).getMany({ page: 2, perPage: 5 })
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'TermsApi.getById',
|
|
182
|
+
path: '/terms/t1',
|
|
183
|
+
query: getQuery,
|
|
184
|
+
call: (client, f) => {
|
|
185
|
+
const api = new TermsApi(client)
|
|
186
|
+
return (f ? api.withQueryFormat(f) : api).getById('t1', { attributes: ['id'] })
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
const jsonUrl = (path: string, query: object) =>
|
|
192
|
+
`${path}?query=${encodeURIComponent(JSON.stringify(query))}`
|
|
193
|
+
const qsUrl = (path: string, query: object) => `${path}?${qs.stringify(query)}`
|
|
194
|
+
const calledUrl = (client: ApiClient) => vi.mocked(client.get).mock.calls.at(0)?.at(0)
|
|
195
|
+
|
|
196
|
+
describe.each(cases)('$name', ({ path, query, call }) => {
|
|
197
|
+
it("sends JSON with the default client format ('json')", async () => {
|
|
198
|
+
const client = createMockClient()
|
|
199
|
+
await call(client)
|
|
200
|
+
expect(calledUrl(client)).toBe(jsonUrl(path, query))
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it("sends bracket notation with a 'qs' client", async () => {
|
|
204
|
+
const client = createMockClient('qs')
|
|
205
|
+
await call(client)
|
|
206
|
+
expect(calledUrl(client)).toBe(qsUrl(path, query))
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it("withQueryFormat('qs') overrides a 'json' client", async () => {
|
|
210
|
+
const client = createMockClient()
|
|
211
|
+
await call(client, 'qs')
|
|
212
|
+
expect(calledUrl(client)).toBe(qsUrl(path, query))
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("withQueryFormat('json') overrides a 'qs' client", async () => {
|
|
216
|
+
const client = createMockClient('qs')
|
|
217
|
+
await call(client, 'json')
|
|
218
|
+
expect(calledUrl(client)).toBe(jsonUrl(path, query))
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('query format through BaseApiClient', () => {
|
|
223
|
+
afterEach(() => {
|
|
224
|
+
vi.restoreAllMocks()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const mockFetch = () =>
|
|
228
|
+
vi
|
|
229
|
+
.spyOn(globalThis, 'fetch')
|
|
230
|
+
.mockImplementation(
|
|
231
|
+
async () =>
|
|
232
|
+
new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } }),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
it('builds the full ?query=<JSON> URL by default, keeping null typed', async () => {
|
|
236
|
+
const fetchSpy = mockFetch()
|
|
237
|
+
const contacts = new ContactsApi(new BaseApiClient('https://api.example.com/'))
|
|
238
|
+
|
|
239
|
+
await contacts.getMany({ where: { name: null }, paginate: false })
|
|
240
|
+
|
|
241
|
+
const query = '{"where":{"name":null},"paginate":false}'
|
|
242
|
+
expect(fetchSpy.mock.calls[0]?.[0]).toBe(
|
|
243
|
+
`https://api.example.com/contacts?query=${encodeURIComponent(query)}`,
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("builds the bracket URL with queryFormat 'qs'", async () => {
|
|
248
|
+
const fetchSpy = mockFetch()
|
|
249
|
+
const client = new BaseApiClient('https://api.example.com', undefined, { queryFormat: 'qs' })
|
|
250
|
+
|
|
251
|
+
await new ContactsApi(client).getMany({ where: { name: 'x' }, paginate: false })
|
|
252
|
+
|
|
253
|
+
expect(decodeURIComponent(fetchSpy.mock.calls[0]?.[0] as string)).toBe(
|
|
254
|
+
'https://api.example.com/contacts?where[name]=x&paginate=false',
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it("setAccessToken on a copy's client only affects the copy", async () => {
|
|
259
|
+
const fetchSpy = mockFetch()
|
|
260
|
+
const client = new BaseApiClient('https://api.example.com', 'original')
|
|
261
|
+
const contacts = new ContactsApi(client)
|
|
262
|
+
const copy = contacts.withQueryFormat('qs')
|
|
263
|
+
|
|
264
|
+
copy.client.setAccessToken('copy-only')
|
|
265
|
+
await contacts.getMany({ paginate: false })
|
|
266
|
+
await copy.getMany({ paginate: false })
|
|
267
|
+
|
|
268
|
+
const authorization = (call: number) =>
|
|
269
|
+
(fetchSpy.mock.calls[call]?.[1]?.headers as Record<string, string>).Authorization
|
|
270
|
+
expect(authorization(0)).toBe('Bearer original')
|
|
271
|
+
expect(authorization(1)).toBe('Bearer copy-only')
|
|
272
|
+
})
|
|
273
|
+
})
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type { Role, CreateRolePayload, UpdateRolePayload } from './types'
|
|
3
|
+
import type { Role, CreateRolePayload, UpdateRolePayload, RolesListQuery } from './types'
|
|
4
4
|
|
|
5
|
-
export class RolesApi extends BaseCrudApi<
|
|
5
|
+
export class RolesApi extends BaseCrudApi<
|
|
6
|
+
Role,
|
|
7
|
+
CreateRolePayload,
|
|
8
|
+
UpdateRolePayload,
|
|
9
|
+
RolesListQuery
|
|
10
|
+
> {
|
|
6
11
|
constructor(client: ApiClient) {
|
|
7
12
|
super(client, '/roles')
|
|
8
13
|
}
|