@webitel/api-services 26.8.75 → 26.8.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/api-services",
3
- "version": "26.8.75",
3
+ "version": "26.8.77",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,144 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const wireItem = {
4
+ id: '1',
5
+ name: 'lunch',
6
+ description: 'desc',
7
+ causes: [
8
+ {
9
+ cause: {
10
+ id: '7',
11
+ name: 'Coffee',
12
+ },
13
+ duration: '15',
14
+ },
15
+ ],
16
+ domain_id: '1',
17
+ };
18
+
19
+ const service = {
20
+ pauseTemplateServiceSearchPauseTemplate: vi.fn(() =>
21
+ Promise.resolve({
22
+ data: {
23
+ items: [
24
+ wireItem,
25
+ ],
26
+ next: true,
27
+ },
28
+ }),
29
+ ),
30
+ pauseTemplateServiceReadPauseTemplate: vi.fn(() =>
31
+ Promise.resolve({
32
+ data: {
33
+ item: wireItem,
34
+ },
35
+ }),
36
+ ),
37
+ pauseTemplateServiceCreatePauseTemplate: vi.fn(() =>
38
+ Promise.resolve({
39
+ data: {
40
+ item: wireItem,
41
+ },
42
+ }),
43
+ ),
44
+ pauseTemplateServiceUpdatePauseTemplate: vi.fn(() =>
45
+ Promise.resolve({
46
+ data: {
47
+ item: wireItem,
48
+ },
49
+ }),
50
+ ),
51
+ };
52
+
53
+ vi.mock('../../../../gen-wire', async (importOriginal) => ({
54
+ ...(await importOriginal<Record<string, unknown>>()),
55
+ getPauseTemplateService: () => service,
56
+ }));
57
+
58
+ const { PauseTemplatesAPI } = await import('../pauseTemplates');
59
+
60
+ const formItem = {
61
+ id: '1',
62
+ name: 'lunch',
63
+ description: 'desc',
64
+ causes: [
65
+ {
66
+ cause: {
67
+ id: '7',
68
+ name: 'Coffee',
69
+ },
70
+ duration: 15,
71
+ },
72
+ {
73
+ duration: 30,
74
+ },
75
+ ],
76
+ domainId: '1',
77
+ _dirty: true,
78
+ };
79
+
80
+ describe('PauseTemplatesAPI', () => {
81
+ beforeEach(() => {
82
+ vi.clearAllMocks();
83
+ });
84
+
85
+ it('getList sends the search as `q`', async () => {
86
+ const { items, next } = await PauseTemplatesAPI.getList({
87
+ search: 'lun',
88
+ page: 2,
89
+ size: 10,
90
+ });
91
+
92
+ expect(
93
+ service.pauseTemplateServiceSearchPauseTemplate,
94
+ ).toHaveBeenCalledWith(
95
+ expect.objectContaining({
96
+ q: 'lun*',
97
+ page: 2,
98
+ size: 10,
99
+ }),
100
+ );
101
+ expect(items[0].domainId).toBe('1');
102
+ expect(next).toBe(true);
103
+ });
104
+
105
+ it('get unwraps the `item` envelope and keeps causes nested', async () => {
106
+ const item = await PauseTemplatesAPI.get({
107
+ itemId: '1',
108
+ });
109
+
110
+ expect(item.name).toBe('lunch');
111
+ expect(item.causes).toEqual(wireItem.causes);
112
+ });
113
+
114
+ it('add wraps the item and sends only the form fields', async () => {
115
+ await PauseTemplatesAPI.add({
116
+ itemInstance: formItem,
117
+ });
118
+
119
+ const [body] =
120
+ service.pauseTemplateServiceCreatePauseTemplate.mock.calls[0];
121
+ expect(body).toEqual({
122
+ item: {
123
+ name: 'lunch',
124
+ description: 'desc',
125
+ causes: formItem.causes,
126
+ },
127
+ });
128
+ });
129
+
130
+ it('update sends the id separately and drops it from the item', async () => {
131
+ await PauseTemplatesAPI.update({
132
+ itemId: '1',
133
+ itemInstance: formItem,
134
+ });
135
+
136
+ const [id, body] =
137
+ service.pauseTemplateServiceUpdatePauseTemplate.mock.calls[0];
138
+ expect(id).toBe('1');
139
+ expect(body.item).not.toHaveProperty('id');
140
+ expect(body.item).not.toHaveProperty('domain_id');
141
+ expect(body.item).not.toHaveProperty('_dirty');
142
+ expect(body.item.causes).toEqual(formItem.causes);
143
+ });
144
+ });
@@ -1,13 +1,18 @@
1
- import deepCopy from 'deep-copy';
2
- import { getPauseTemplateService } from '../../../gen-wire';
1
+ import { getShallowFieldsToSendFromZodSchema } from '@webitel/api-services/gen/utils';
2
+ import { pauseTemplateSchema } from '@webitel/api-services/validations';
3
+ import {
4
+ getPauseTemplateService,
5
+ PauseTemplateServiceSearchPauseTemplateQueryParams,
6
+ } from '../../../gen-wire';
3
7
  import { getDefaultGetListResponse, getDefaultGetParams } from '../../defaults';
4
8
  import {
5
9
  applyTransform,
6
10
  camelToSnake,
7
11
  merge,
8
12
  notify,
9
- sanitize,
13
+ sanitizeToWire,
10
14
  snakeToCamel,
15
+ starToSearch,
11
16
  } from '../../transformers';
12
17
  import type {
13
18
  AddItemParams,
@@ -17,71 +22,36 @@ import type {
17
22
  UpdateItemParams,
18
23
  } from '../_shared/types';
19
24
 
20
- const fieldsToSend = [
21
- 'name',
22
- 'description',
23
- 'causes',
24
- 'domainId',
25
- 'createdAt',
26
- 'createdBy',
27
- 'updatedAt',
28
- 'updatedBy',
29
- ];
25
+ const fieldsToSend = getShallowFieldsToSendFromZodSchema(pauseTemplateSchema);
30
26
 
31
27
  /**
32
- * WFM services wrap both request and response payloads in an `item` envelope,
33
- * and nest each pause cause under a `cause` key. The form binds a flat
34
- * `{ id, name, duration }`.
28
+ * [Claude] WFM services wrap both request and response payloads in an `item` envelope.
35
29
  */
36
- const itemResponseHandler = (response: ApiParams) => {
37
- const item = {
38
- ...response.item,
39
- };
40
-
41
- item.causes = item.causes?.map((cause: ApiParams) => ({
42
- id: cause.cause?.id,
43
- name: cause.cause?.name,
44
- duration: cause?.duration,
45
- }));
46
-
47
- return item;
48
- };
49
-
50
- const preRequestHandler = (item: ApiParams) => {
51
- const copy = deepCopy(item);
52
- copy.causes = copy.causes.map((cause: ApiParams) => {
53
- if (!cause.name && !cause.id) return cause;
54
- return {
55
- cause: {
56
- id: cause?.id,
57
- name: cause?.name,
58
- },
59
- duration: cause.duration,
60
- };
61
- });
62
- return copy;
63
- };
30
+ const itemResponseHandler = (response: ApiParams) => ({
31
+ ...response.item,
32
+ });
64
33
 
65
34
  const getPauseTemplatesList = async (params: ApiParams) => {
66
- const {
67
- search: q,
68
- page,
69
- size,
70
- sort,
71
- fields,
72
- } = applyTransform(params, [
35
+ const requestParams = applyTransform(params, [
73
36
  merge(getDefaultGetParams()),
37
+ starToSearch('search'),
38
+ (params: ApiParams) => ({
39
+ ...params,
40
+ q: params.q ?? params.search,
41
+ }),
42
+ sanitizeToWire(
43
+ getShallowFieldsToSendFromZodSchema(
44
+ PauseTemplateServiceSearchPauseTemplateQueryParams,
45
+ ),
46
+ ),
47
+ camelToSnake(),
74
48
  ]);
75
49
 
76
50
  try {
77
51
  const response =
78
- await getPauseTemplateService().pauseTemplateServiceSearchPauseTemplate({
79
- q,
80
- page,
81
- size,
82
- sort,
83
- fields,
84
- });
52
+ await getPauseTemplateService().pauseTemplateServiceSearchPauseTemplate(
53
+ requestParams,
54
+ );
85
55
  const { items, next } = applyTransform(response.data, [
86
56
  snakeToCamel(),
87
57
  merge(getDefaultGetListResponse()),
@@ -116,16 +86,13 @@ const getPauseTemplate = async ({ itemId: id }: GetItemParams) => {
116
86
 
117
87
  const addPauseTemplate = async ({ itemInstance }: AddItemParams) => {
118
88
  const item = applyTransform(itemInstance, [
119
- sanitize(fieldsToSend),
89
+ sanitizeToWire(fieldsToSend),
120
90
  camelToSnake(),
121
- preRequestHandler,
122
91
  ]);
123
92
  try {
124
93
  const response =
125
94
  await getPauseTemplateService().pauseTemplateServiceCreatePauseTemplate({
126
- item: {
127
- ...item,
128
- },
95
+ item,
129
96
  });
130
97
  return applyTransform(response.data, [
131
98
  snakeToCamel(),
@@ -143,18 +110,15 @@ const updatePauseTemplate = async ({
143
110
  itemId: id,
144
111
  }: UpdateItemParams) => {
145
112
  const item = applyTransform(itemInstance, [
146
- sanitize(fieldsToSend),
113
+ sanitizeToWire(fieldsToSend),
147
114
  camelToSnake(),
148
- preRequestHandler,
149
115
  ]);
150
116
  try {
151
117
  const response =
152
118
  await getPauseTemplateService().pauseTemplateServiceUpdatePauseTemplate(
153
119
  String(id),
154
120
  {
155
- item: {
156
- ...item,
157
- },
121
+ item,
158
122
  },
159
123
  );
160
124
  return applyTransform(response.data, [
@@ -182,9 +146,7 @@ const deletePauseTemplate = async ({ id }: DeleteItemParams) => {
182
146
  }
183
147
  };
184
148
 
185
- const getPauseTemplatesLookup = (
186
- params: Parameters<typeof getPauseTemplatesList>[0],
187
- ) =>
149
+ const getPauseTemplatesLookup = (params: ApiParams) =>
188
150
  getPauseTemplatesList({
189
151
  ...params,
190
152
  fields: params.fields || [
@@ -0,0 +1,145 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const wireItem = {
4
+ id: '1',
5
+ name: 'Day',
6
+ description: 'desc',
7
+ times: [
8
+ {
9
+ start: 540,
10
+ end: 1200,
11
+ },
12
+ ],
13
+ domain_id: '1',
14
+ created_at: '1',
15
+ };
16
+
17
+ const service = {
18
+ shiftTemplateServiceSearchShiftTemplate: vi.fn(() =>
19
+ Promise.resolve({
20
+ data: {
21
+ items: [
22
+ wireItem,
23
+ ],
24
+ next: true,
25
+ },
26
+ }),
27
+ ),
28
+ shiftTemplateServiceReadShiftTemplate: vi.fn(() =>
29
+ Promise.resolve({
30
+ data: {
31
+ item: wireItem,
32
+ },
33
+ }),
34
+ ),
35
+ shiftTemplateServiceCreateShiftTemplate: vi.fn(() =>
36
+ Promise.resolve({
37
+ data: {
38
+ item: wireItem,
39
+ },
40
+ }),
41
+ ),
42
+ shiftTemplateServiceUpdateShiftTemplate: vi.fn(() =>
43
+ Promise.resolve({
44
+ data: {
45
+ item: wireItem,
46
+ },
47
+ }),
48
+ ),
49
+ };
50
+
51
+ vi.mock('../../../../gen-wire', async (importOriginal) => ({
52
+ ...(await importOriginal<Record<string, unknown>>()),
53
+ getShiftTemplateService: () => service,
54
+ }));
55
+
56
+ const { ShiftTemplatesAPI } = await import('../shiftTemplates');
57
+
58
+ const formItem = {
59
+ id: '1',
60
+ name: 'Day',
61
+ description: 'desc',
62
+ times: [
63
+ {
64
+ start: 540,
65
+ end: 1200,
66
+ },
67
+ ],
68
+ domainId: '1',
69
+ createdAt: '1',
70
+ _dirty: true,
71
+ };
72
+
73
+ describe('ShiftTemplatesAPI', () => {
74
+ beforeEach(() => {
75
+ vi.clearAllMocks();
76
+ });
77
+
78
+ it('getList sends the search as `q`', async () => {
79
+ const { items, next } = await ShiftTemplatesAPI.getList({
80
+ search: 'day',
81
+ page: 2,
82
+ size: 10,
83
+ });
84
+
85
+ expect(
86
+ service.shiftTemplateServiceSearchShiftTemplate,
87
+ ).toHaveBeenCalledWith(
88
+ expect.objectContaining({
89
+ q: 'day*',
90
+ page: 2,
91
+ size: 10,
92
+ }),
93
+ );
94
+ expect(items[0].name).toBe('Day');
95
+ expect(next).toBe(true);
96
+ });
97
+
98
+ it('get and update unwrap the `item` envelope', async () => {
99
+ const item = await ShiftTemplatesAPI.get({
100
+ itemId: '1',
101
+ });
102
+ expect(item.times).toEqual(wireItem.times);
103
+
104
+ const updated = await ShiftTemplatesAPI.update({
105
+ itemId: '1',
106
+ itemInstance: formItem,
107
+ });
108
+ expect(updated.name).toBe('Day');
109
+ });
110
+
111
+ it('add wraps the item and sends only the form fields', async () => {
112
+ await ShiftTemplatesAPI.add({
113
+ itemInstance: formItem,
114
+ });
115
+
116
+ const [body] =
117
+ service.shiftTemplateServiceCreateShiftTemplate.mock.calls[0];
118
+ expect(body).toEqual({
119
+ item: {
120
+ name: 'Day',
121
+ description: 'desc',
122
+ times: [
123
+ {
124
+ start: 540,
125
+ end: 1200,
126
+ },
127
+ ],
128
+ },
129
+ });
130
+ });
131
+
132
+ it('update sends the id separately and drops it from the item', async () => {
133
+ await ShiftTemplatesAPI.update({
134
+ itemId: '1',
135
+ itemInstance: formItem,
136
+ });
137
+
138
+ const [id, body] =
139
+ service.shiftTemplateServiceUpdateShiftTemplate.mock.calls[0];
140
+ expect(id).toBe('1');
141
+ expect(body.item).not.toHaveProperty('id');
142
+ expect(body.item).not.toHaveProperty('domain_id');
143
+ expect(body.item).not.toHaveProperty('_dirty');
144
+ });
145
+ });
@@ -1,12 +1,18 @@
1
- import { getShiftTemplateService } from '../../../gen-wire';
1
+ import { getShallowFieldsToSendFromZodSchema } from '@webitel/api-services/gen/utils';
2
+ import { shiftTemplateSchema } from '@webitel/api-services/validations';
3
+ import {
4
+ getShiftTemplateService,
5
+ ShiftTemplateServiceSearchShiftTemplateQueryParams,
6
+ } from '../../../gen-wire';
2
7
  import { getDefaultGetListResponse, getDefaultGetParams } from '../../defaults';
3
8
  import {
4
9
  applyTransform,
5
10
  camelToSnake,
6
11
  merge,
7
12
  notify,
8
- sanitize,
13
+ sanitizeToWire,
9
14
  snakeToCamel,
15
+ starToSearch,
10
16
  } from '../../transformers';
11
17
  import type {
12
18
  AddItemParams,
@@ -16,48 +22,36 @@ import type {
16
22
  UpdateItemParams,
17
23
  } from '../_shared/types';
18
24
 
19
- const fieldsToSend = [
20
- 'name',
21
- 'description',
22
- 'times',
23
- ];
25
+ const fieldsToSend = getShallowFieldsToSendFromZodSchema(shiftTemplateSchema);
24
26
 
25
27
  /**
26
- * WFM services wrap both request and response payloads in an `item` envelope.
28
+ * [Claude] WFM services wrap both request and response payloads in an `item` envelope.
27
29
  */
28
- const itemResponseHandler = (response: ApiParams) => {
29
- const copy = {
30
- ...response.item,
31
- };
32
-
33
- copy.times = copy.times?.map((time: ApiParams) => ({
34
- ...time,
35
- duration: time.end - time.start,
36
- }));
37
-
38
- return copy;
39
- };
30
+ const itemResponseHandler = (response: ApiParams) => ({
31
+ ...response.item,
32
+ });
40
33
 
41
34
  const getShiftTemplatesList = async (params: ApiParams) => {
42
- const {
43
- search: q,
44
- page,
45
- size,
46
- sort,
47
- fields,
48
- } = applyTransform(params, [
35
+ const requestParams = applyTransform(params, [
49
36
  merge(getDefaultGetParams()),
37
+ starToSearch('search'),
38
+ (params: ApiParams) => ({
39
+ ...params,
40
+ q: params.q ?? params.search,
41
+ }),
42
+ sanitizeToWire(
43
+ getShallowFieldsToSendFromZodSchema(
44
+ ShiftTemplateServiceSearchShiftTemplateQueryParams,
45
+ ),
46
+ ),
47
+ camelToSnake(),
50
48
  ]);
51
49
 
52
50
  try {
53
51
  const response =
54
- await getShiftTemplateService().shiftTemplateServiceSearchShiftTemplate({
55
- q,
56
- page,
57
- size,
58
- sort,
59
- fields,
60
- });
52
+ await getShiftTemplateService().shiftTemplateServiceSearchShiftTemplate(
53
+ requestParams,
54
+ );
61
55
  const { items, next } = applyTransform(response.data, [
62
56
  snakeToCamel(),
63
57
  merge(getDefaultGetListResponse()),
@@ -92,15 +86,13 @@ const getShiftTemplate = async ({ itemId: id }: GetItemParams) => {
92
86
 
93
87
  const addShiftTemplate = async ({ itemInstance }: AddItemParams) => {
94
88
  const item = applyTransform(itemInstance, [
95
- sanitize(fieldsToSend),
89
+ sanitizeToWire(fieldsToSend),
96
90
  camelToSnake(),
97
91
  ]);
98
92
  try {
99
93
  const response =
100
94
  await getShiftTemplateService().shiftTemplateServiceCreateShiftTemplate({
101
- item: {
102
- ...item,
103
- },
95
+ item,
104
96
  });
105
97
  return applyTransform(response.data, [
106
98
  snakeToCamel(),
@@ -118,7 +110,7 @@ const updateShiftTemplate = async ({
118
110
  itemId: id,
119
111
  }: UpdateItemParams) => {
120
112
  const item = applyTransform(itemInstance, [
121
- sanitize(fieldsToSend),
113
+ sanitizeToWire(fieldsToSend),
122
114
  camelToSnake(),
123
115
  ]);
124
116
  try {
@@ -126,13 +118,12 @@ const updateShiftTemplate = async ({
126
118
  await getShiftTemplateService().shiftTemplateServiceUpdateShiftTemplate(
127
119
  String(id),
128
120
  {
129
- item: {
130
- ...item,
131
- },
121
+ item,
132
122
  },
133
123
  );
134
124
  return applyTransform(response.data, [
135
125
  snakeToCamel(),
126
+ itemResponseHandler,
136
127
  ]);
137
128
  } catch (err) {
138
129
  throw applyTransform(err, [
@@ -155,9 +146,7 @@ const deleteShiftTemplate = async ({ id }: DeleteItemParams) => {
155
146
  }
156
147
  };
157
148
 
158
- const getShiftTemplatesLookup = (
159
- params: Parameters<typeof getShiftTemplatesList>[0],
160
- ) =>
149
+ const getShiftTemplatesLookup = (params: ApiParams) =>
161
150
  getShiftTemplatesList({
162
151
  ...params,
163
152
  fields: params.fields || [
@@ -34,6 +34,7 @@ export * from './device/device.validations';
34
34
  export * from './license/licenseImport.validations';
35
35
  export * from './OAuth/OAuth.validations';
36
36
  export * from './onlineSkill/onlineSkill.validations';
37
+ export * from './pauseTemplate/pauseTemplate.validations';
37
38
  export * from './queue/queue.rules';
38
39
  export * from './queue/queue.validations';
39
40
  export * from './queue/queueAmd.validations';
@@ -49,6 +50,7 @@ export * from './resource/resource.validations';
49
50
  export * from './resourceDisplay/resourceDisplay.validations';
50
51
  export * from './resourceGroup/resourceGroup.validations';
51
52
  export * from './resourceInGroup/resourceInGroup.validations';
53
+ export * from './shiftTemplate/shiftTemplate.validations';
52
54
  export * from './sla/sla.validations';
53
55
  export * from './slaCondition/slaCondition.validations';
54
56
  export * from './team/team.validations';
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { pauseTemplateSchema } from '../pauseTemplate.validations';
4
+
5
+ describe('pauseTemplateSchema', () => {
6
+ it('fills a new template with one 30 min row and no cause', () => {
7
+ expect(pauseTemplateSchema.parse({}).causes).toEqual([
8
+ {
9
+ duration: 30,
10
+ },
11
+ ]);
12
+ });
13
+
14
+ it('accepts a row without a pause cause', () => {
15
+ const result = pauseTemplateSchema.safeParse({
16
+ name: 'lunch',
17
+ causes: [
18
+ {
19
+ duration: 15,
20
+ },
21
+ ],
22
+ });
23
+ expect(result.success).toBe(true);
24
+ });
25
+
26
+ it('rejects an empty name', () => {
27
+ expect(
28
+ pauseTemplateSchema.safeParse({
29
+ name: '',
30
+ }).success,
31
+ ).toBe(false);
32
+ });
33
+
34
+ it('rejects a name longer than 250 symbols', () => {
35
+ expect(
36
+ pauseTemplateSchema.safeParse({
37
+ name: 'a'.repeat(251),
38
+ }).success,
39
+ ).toBe(false);
40
+ });
41
+
42
+ it('rejects a template without rows', () => {
43
+ expect(
44
+ pauseTemplateSchema.safeParse({
45
+ name: 'lunch',
46
+ causes: [],
47
+ }).success,
48
+ ).toBe(false);
49
+ });
50
+
51
+ it('accepts a duration the API returns as a string', () => {
52
+ expect(
53
+ pauseTemplateSchema.safeParse({
54
+ name: 'lunch',
55
+ causes: [
56
+ {
57
+ cause: null,
58
+ duration: '15',
59
+ },
60
+ ],
61
+ }).success,
62
+ ).toBe(true);
63
+ });
64
+
65
+ it.each([
66
+ 0,
67
+ '0',
68
+ null,
69
+ ])('rejects a row duration of %j', (duration) => {
70
+ expect(
71
+ pauseTemplateSchema.safeParse({
72
+ name: 'lunch',
73
+ causes: [
74
+ {
75
+ cause: {
76
+ id: '1',
77
+ name: 'Coffee',
78
+ },
79
+ duration,
80
+ },
81
+ ],
82
+ }).success,
83
+ ).toBe(false);
84
+ });
85
+ });