@omniq/sdk 0.1.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 +360 -0
- package/dist/__tests__/client.test.d.ts +2 -0
- package/dist/__tests__/client.test.d.ts.map +1 -0
- package/dist/__tests__/client.test.js +784 -0
- package/dist/__tests__/client.test.js.map +1 -0
- package/dist/client.d.ts +53 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +174 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/ai.d.ts +33 -0
- package/dist/resources/ai.d.ts.map +1 -0
- package/dist/resources/ai.js +37 -0
- package/dist/resources/ai.js.map +1 -0
- package/dist/resources/auth.d.ts +16 -0
- package/dist/resources/auth.d.ts.map +1 -0
- package/dist/resources/auth.js +22 -0
- package/dist/resources/auth.js.map +1 -0
- package/dist/resources/calendar.d.ts +25 -0
- package/dist/resources/calendar.d.ts.map +1 -0
- package/dist/resources/calendar.js +38 -0
- package/dist/resources/calendar.js.map +1 -0
- package/dist/resources/files.d.ts +24 -0
- package/dist/resources/files.d.ts.map +1 -0
- package/dist/resources/files.js +48 -0
- package/dist/resources/files.js.map +1 -0
- package/dist/resources/gitlab.d.ts +11 -0
- package/dist/resources/gitlab.d.ts.map +1 -0
- package/dist/resources/gitlab.js +25 -0
- package/dist/resources/gitlab.js.map +1 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +12 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/issues.d.ts +17 -0
- package/dist/resources/issues.d.ts.map +1 -0
- package/dist/resources/issues.js +41 -0
- package/dist/resources/issues.js.map +1 -0
- package/dist/resources/notifications.d.ts +15 -0
- package/dist/resources/notifications.d.ts.map +1 -0
- package/dist/resources/notifications.js +18 -0
- package/dist/resources/notifications.js.map +1 -0
- package/dist/resources/projects.d.ts +15 -0
- package/dist/resources/projects.d.ts.map +1 -0
- package/dist/resources/projects.js +35 -0
- package/dist/resources/projects.js.map +1 -0
- package/dist/resources/sprints.d.ts +20 -0
- package/dist/resources/sprints.d.ts.map +1 -0
- package/dist/resources/sprints.js +40 -0
- package/dist/resources/sprints.js.map +1 -0
- package/dist/resources/tokens.d.ts +10 -0
- package/dist/resources/tokens.d.ts.map +1 -0
- package/dist/resources/tokens.js +16 -0
- package/dist/resources/tokens.js.map +1 -0
- package/dist/resources/wiki.d.ts +23 -0
- package/dist/resources/wiki.d.ts.map +1 -0
- package/dist/resources/wiki.js +40 -0
- package/dist/resources/wiki.js.map +1 -0
- package/dist/types.d.ts +478 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { OmniqClient, OmniqApiError } from '../client.js';
|
|
3
|
+
function jsonResponse(body, status = 200, statusText = 'OK') {
|
|
4
|
+
return {
|
|
5
|
+
ok: status >= 200 && status < 300,
|
|
6
|
+
status,
|
|
7
|
+
statusText,
|
|
8
|
+
json: () => Promise.resolve(body),
|
|
9
|
+
headers: new Headers(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function errorResponse(status, statusText, body = null) {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
status,
|
|
16
|
+
statusText,
|
|
17
|
+
json: () => Promise.resolve(body),
|
|
18
|
+
headers: new Headers(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
let fetchMock;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
fetchMock = vi.fn();
|
|
24
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
25
|
+
vi.useFakeTimers();
|
|
26
|
+
});
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// OmniqClient core
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
describe('OmniqClient', () => {
|
|
31
|
+
const BASE = 'https://api.omniq.pro';
|
|
32
|
+
function createClient(opts) {
|
|
33
|
+
return new OmniqClient({
|
|
34
|
+
baseUrl: BASE,
|
|
35
|
+
...opts,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
describe('successful requests', () => {
|
|
39
|
+
it('returns parsed JSON on 200', async () => {
|
|
40
|
+
const payload = { id: '1', name: 'Test' };
|
|
41
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(payload));
|
|
42
|
+
const client = createClient({ token: 'tok' });
|
|
43
|
+
const result = await client.request('GET', '/things');
|
|
44
|
+
expect(result).toEqual(payload);
|
|
45
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
46
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
47
|
+
expect(url).toBe(`${BASE}/things`);
|
|
48
|
+
expect(init.method).toBe('GET');
|
|
49
|
+
});
|
|
50
|
+
it('sends JSON body for POST', async () => {
|
|
51
|
+
const body = { title: 'new' };
|
|
52
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: '2', ...body }));
|
|
53
|
+
const client = createClient({ token: 'tok' });
|
|
54
|
+
await client.request('POST', '/things', { body });
|
|
55
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
56
|
+
expect(init.body).toBe(JSON.stringify(body));
|
|
57
|
+
expect(init.method).toBe('POST');
|
|
58
|
+
});
|
|
59
|
+
it('appends query parameters to url', async () => {
|
|
60
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
61
|
+
const client = createClient();
|
|
62
|
+
await client.request('GET', '/items', { query: { status: 'active', page: 1 } });
|
|
63
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
64
|
+
expect(url.searchParams.get('status')).toBe('active');
|
|
65
|
+
expect(url.searchParams.get('page')).toBe('1');
|
|
66
|
+
});
|
|
67
|
+
it('omits undefined query params', async () => {
|
|
68
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
69
|
+
const client = createClient();
|
|
70
|
+
await client.request('GET', '/items', { query: { a: 'yes', b: undefined } });
|
|
71
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
72
|
+
expect(url.searchParams.get('a')).toBe('yes');
|
|
73
|
+
expect(url.searchParams.has('b')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('204 response', () => {
|
|
77
|
+
it('returns undefined', async () => {
|
|
78
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204, 'No Content'));
|
|
79
|
+
const client = createClient();
|
|
80
|
+
const result = await client.request('DELETE', '/things/1');
|
|
81
|
+
expect(result).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('authorization header', () => {
|
|
85
|
+
it('sends Bearer token when token is set', async () => {
|
|
86
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
87
|
+
const client = createClient({ token: 'secret-token' });
|
|
88
|
+
await client.request('GET', '/me');
|
|
89
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
90
|
+
expect(headers['Authorization']).toBe('Bearer secret-token');
|
|
91
|
+
});
|
|
92
|
+
it('does not send Authorization when no token', async () => {
|
|
93
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
94
|
+
const client = createClient();
|
|
95
|
+
await client.request('GET', '/public');
|
|
96
|
+
const headers = fetchMock.mock.calls[0][1].headers;
|
|
97
|
+
expect(headers['Authorization']).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
it('reflects token set via setToken()', async () => {
|
|
100
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
101
|
+
const client = createClient();
|
|
102
|
+
client.setToken('dynamic-tok');
|
|
103
|
+
await client.request('GET', '/me');
|
|
104
|
+
expect(fetchMock.mock.calls[0][1].headers['Authorization']).toBe('Bearer dynamic-tok');
|
|
105
|
+
});
|
|
106
|
+
it('clears token via clearToken()', async () => {
|
|
107
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
108
|
+
const client = createClient({ token: 'will-be-cleared' });
|
|
109
|
+
client.clearToken();
|
|
110
|
+
await client.request('GET', '/me');
|
|
111
|
+
expect(fetchMock.mock.calls[0][1].headers['Authorization']).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('cookie header', () => {
|
|
115
|
+
it('sends Cookie header when cookieHeader is set', async () => {
|
|
116
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
117
|
+
const client = createClient({ cookieHeader: 'session=abc123' });
|
|
118
|
+
await client.request('GET', '/me');
|
|
119
|
+
expect(fetchMock.mock.calls[0][1].headers['Cookie']).toBe('session=abc123');
|
|
120
|
+
});
|
|
121
|
+
it('does not send Cookie header when not configured', async () => {
|
|
122
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
123
|
+
const client = createClient();
|
|
124
|
+
await client.request('GET', '/me');
|
|
125
|
+
expect(fetchMock.mock.calls[0][1].headers['Cookie']).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('4xx errors (no retry)', () => {
|
|
129
|
+
it('throws OmniqApiError on 403', async () => {
|
|
130
|
+
const body = { error: 'Forbidden' };
|
|
131
|
+
fetchMock.mockResolvedValueOnce(errorResponse(403, 'Forbidden', body));
|
|
132
|
+
const client = createClient({ token: 'tok' });
|
|
133
|
+
await expect(client.request('GET', '/admin')).rejects.toThrow(OmniqApiError);
|
|
134
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
135
|
+
});
|
|
136
|
+
it('contains correct status and body', async () => {
|
|
137
|
+
const body = { message: 'Not Found' };
|
|
138
|
+
fetchMock.mockResolvedValueOnce(errorResponse(404, 'Not Found', body));
|
|
139
|
+
const client = createClient();
|
|
140
|
+
try {
|
|
141
|
+
await client.request('GET', '/missing');
|
|
142
|
+
expect.unreachable('should have thrown');
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
expect(err).toBeInstanceOf(OmniqApiError);
|
|
146
|
+
const apiErr = err;
|
|
147
|
+
expect(apiErr.status).toBe(404);
|
|
148
|
+
expect(apiErr.statusText).toBe('Not Found');
|
|
149
|
+
expect(apiErr.body).toEqual(body);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
it('does not retry 4xx errors', async () => {
|
|
153
|
+
fetchMock.mockResolvedValueOnce(errorResponse(422, 'Unprocessable Entity'));
|
|
154
|
+
const client = createClient({ maxRetries: 3 });
|
|
155
|
+
await expect(client.request('POST', '/things')).rejects.toThrow(OmniqApiError);
|
|
156
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('5xx errors (retry)', () => {
|
|
160
|
+
it('retries and eventually succeeds', async () => {
|
|
161
|
+
const payload = { ok: true };
|
|
162
|
+
fetchMock
|
|
163
|
+
.mockResolvedValueOnce(errorResponse(500, 'Internal Server Error'))
|
|
164
|
+
.mockResolvedValueOnce(errorResponse(502, 'Bad Gateway'))
|
|
165
|
+
.mockResolvedValueOnce(jsonResponse(payload));
|
|
166
|
+
const client = createClient({ maxRetries: 3 });
|
|
167
|
+
const promise = client.request('GET', '/unstable');
|
|
168
|
+
await vi.runAllTimersAsync();
|
|
169
|
+
const result = await promise;
|
|
170
|
+
expect(result).toEqual(payload);
|
|
171
|
+
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
172
|
+
});
|
|
173
|
+
it('throws after exhausting retries', async () => {
|
|
174
|
+
fetchMock
|
|
175
|
+
.mockResolvedValueOnce(errorResponse(503, 'Service Unavailable'))
|
|
176
|
+
.mockResolvedValueOnce(errorResponse(503, 'Service Unavailable'))
|
|
177
|
+
.mockResolvedValueOnce(errorResponse(503, 'Service Unavailable'));
|
|
178
|
+
const client = createClient({ maxRetries: 2 });
|
|
179
|
+
const promise = client.request('GET', '/down');
|
|
180
|
+
const assertion = expect(promise).rejects.toThrow(OmniqApiError);
|
|
181
|
+
await vi.runAllTimersAsync();
|
|
182
|
+
await assertion;
|
|
183
|
+
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('trailing slash normalization', () => {
|
|
187
|
+
it('strips trailing slashes from baseUrl', async () => {
|
|
188
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({}));
|
|
189
|
+
const client = new OmniqClient({ baseUrl: 'https://api.omniq.pro///' });
|
|
190
|
+
await client.request('GET', '/test');
|
|
191
|
+
expect(fetchMock.mock.calls[0][0]).toBe('https://api.omniq.pro/test');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Pagination
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
describe('OmniqClient.paginate()', () => {
|
|
199
|
+
const BASE = 'https://api.omniq.pro';
|
|
200
|
+
it('yields all items across multiple pages', async () => {
|
|
201
|
+
const page1Items = Array.from({ length: 50 }, (_, i) => ({ id: String(i + 1) }));
|
|
202
|
+
const page2Items = [{ id: '51' }, { id: '52' }];
|
|
203
|
+
const page1 = { data: page1Items, total: 52 };
|
|
204
|
+
const page2 = { data: page2Items, total: 52 };
|
|
205
|
+
fetchMock
|
|
206
|
+
.mockResolvedValueOnce(jsonResponse(page1))
|
|
207
|
+
.mockResolvedValueOnce(jsonResponse(page2));
|
|
208
|
+
const client = new OmniqClient({ baseUrl: BASE });
|
|
209
|
+
const items = [];
|
|
210
|
+
for await (const item of client.paginate('/items')) {
|
|
211
|
+
items.push(item);
|
|
212
|
+
}
|
|
213
|
+
expect(items).toHaveLength(52);
|
|
214
|
+
expect(items[0]).toEqual({ id: '1' });
|
|
215
|
+
expect(items[51]).toEqual({ id: '52' });
|
|
216
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
217
|
+
const url2 = new URL(fetchMock.mock.calls[1][0]);
|
|
218
|
+
expect(url2.searchParams.get('page')).toBe('2');
|
|
219
|
+
});
|
|
220
|
+
it('sends page and limit as query params', async () => {
|
|
221
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: [{ id: '1' }], total: 1 }));
|
|
222
|
+
const client = new OmniqClient({ baseUrl: BASE });
|
|
223
|
+
const items = [];
|
|
224
|
+
for await (const item of client.paginate('/things', { status: 'done' })) {
|
|
225
|
+
items.push(item);
|
|
226
|
+
}
|
|
227
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
228
|
+
expect(url.searchParams.get('page')).toBe('1');
|
|
229
|
+
expect(url.searchParams.get('limit')).toBe('50');
|
|
230
|
+
expect(url.searchParams.get('status')).toBe('done');
|
|
231
|
+
});
|
|
232
|
+
it('stops when page * limit >= total', async () => {
|
|
233
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: Array.from({ length: 50 }, (_, i) => ({ id: String(i) })), total: 50 }));
|
|
234
|
+
const client = new OmniqClient({ baseUrl: BASE });
|
|
235
|
+
const items = [];
|
|
236
|
+
for await (const item of client.paginate('/big')) {
|
|
237
|
+
items.push(item);
|
|
238
|
+
}
|
|
239
|
+
expect(items).toHaveLength(50);
|
|
240
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Resource: Issues
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
describe('IssuesResource', () => {
|
|
247
|
+
const BASE = 'https://api.omniq.pro';
|
|
248
|
+
function setup() {
|
|
249
|
+
const client = new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
250
|
+
return client;
|
|
251
|
+
}
|
|
252
|
+
it('list() → GET /issues with query', async () => {
|
|
253
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: [], total: 0 }));
|
|
254
|
+
const client = setup();
|
|
255
|
+
await client.issues.list({ projectId: 'p1' });
|
|
256
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
257
|
+
expect(init.method).toBe('GET');
|
|
258
|
+
expect(new URL(url).pathname).toBe('/issues');
|
|
259
|
+
expect(new URL(url).searchParams.get('projectId')).toBe('p1');
|
|
260
|
+
});
|
|
261
|
+
it('get() → GET /issues/:id', async () => {
|
|
262
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'i1' }));
|
|
263
|
+
const client = setup();
|
|
264
|
+
await client.issues.get('i1');
|
|
265
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
266
|
+
expect(init.method).toBe('GET');
|
|
267
|
+
expect(new URL(url).pathname).toBe('/issues/i1');
|
|
268
|
+
});
|
|
269
|
+
it('create() → POST /issues with body', async () => {
|
|
270
|
+
const input = { projectId: 'p1', title: 'New issue' };
|
|
271
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'i2', ...input }));
|
|
272
|
+
const client = setup();
|
|
273
|
+
await client.issues.create(input);
|
|
274
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
275
|
+
expect(init.method).toBe('POST');
|
|
276
|
+
expect(new URL(url).pathname).toBe('/issues');
|
|
277
|
+
expect(JSON.parse(init.body)).toEqual(input);
|
|
278
|
+
});
|
|
279
|
+
it('update() → PATCH /issues/:id', async () => {
|
|
280
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'i1', title: 'Updated' }));
|
|
281
|
+
const client = setup();
|
|
282
|
+
await client.issues.update('i1', { title: 'Updated' });
|
|
283
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
284
|
+
expect(init.method).toBe('PATCH');
|
|
285
|
+
expect(new URL(url).pathname).toBe('/issues/i1');
|
|
286
|
+
expect(JSON.parse(init.body)).toEqual({ title: 'Updated' });
|
|
287
|
+
});
|
|
288
|
+
it('delete() → DELETE /issues/:id', async () => {
|
|
289
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204, 'No Content'));
|
|
290
|
+
const client = setup();
|
|
291
|
+
await client.issues.delete('i1');
|
|
292
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
293
|
+
expect(init.method).toBe('DELETE');
|
|
294
|
+
expect(new URL(url).pathname).toBe('/issues/i1');
|
|
295
|
+
});
|
|
296
|
+
it('updateStatus() → POST /issues/:id/status with body', async () => {
|
|
297
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'i1', status: 'done' }));
|
|
298
|
+
const client = setup();
|
|
299
|
+
await client.issues.updateStatus('i1', 'done');
|
|
300
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
301
|
+
expect(init.method).toBe('POST');
|
|
302
|
+
expect(new URL(url).pathname).toBe('/issues/i1/status');
|
|
303
|
+
expect(JSON.parse(init.body)).toEqual({ status: 'done' });
|
|
304
|
+
});
|
|
305
|
+
it('bulkUpdate() → POST /issues/bulk with body', async () => {
|
|
306
|
+
const input = { ids: ['i1', 'i2'], status: 'done' };
|
|
307
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
308
|
+
const client = setup();
|
|
309
|
+
await client.issues.bulkUpdate(input);
|
|
310
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
311
|
+
expect(init.method).toBe('POST');
|
|
312
|
+
expect(new URL(url).pathname).toBe('/issues/bulk');
|
|
313
|
+
expect(JSON.parse(init.body)).toEqual(input);
|
|
314
|
+
});
|
|
315
|
+
it('addComment() → POST /issues/:id/comments', async () => {
|
|
316
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'c1' }));
|
|
317
|
+
const client = setup();
|
|
318
|
+
await client.issues.addComment('i1', { content: 'hello' });
|
|
319
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
320
|
+
expect(init.method).toBe('POST');
|
|
321
|
+
expect(new URL(url).pathname).toBe('/issues/i1/comments');
|
|
322
|
+
expect(JSON.parse(init.body)).toEqual({ content: 'hello' });
|
|
323
|
+
});
|
|
324
|
+
it('addLink() → POST /issues/:id/links', async () => {
|
|
325
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'l1' }));
|
|
326
|
+
const client = setup();
|
|
327
|
+
await client.issues.addLink('i1', { targetId: 'i2', relation: 'blocks' });
|
|
328
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
329
|
+
expect(init.method).toBe('POST');
|
|
330
|
+
expect(new URL(url).pathname).toBe('/issues/i1/links');
|
|
331
|
+
expect(JSON.parse(init.body)).toEqual({ targetId: 'i2', relation: 'blocks' });
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Resource: Projects
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
describe('ProjectsResource', () => {
|
|
338
|
+
const BASE = 'https://api.omniq.pro';
|
|
339
|
+
function setup() {
|
|
340
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
341
|
+
}
|
|
342
|
+
it('list() → GET /projects, unwraps .data', async () => {
|
|
343
|
+
const projects = [{ id: 'p1', name: 'Alpha' }];
|
|
344
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: projects }));
|
|
345
|
+
const client = setup();
|
|
346
|
+
const result = await client.projects.list();
|
|
347
|
+
expect(result).toEqual(projects);
|
|
348
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
349
|
+
expect(init.method).toBe('GET');
|
|
350
|
+
expect(new URL(url).pathname).toBe('/projects');
|
|
351
|
+
});
|
|
352
|
+
it('get() → GET /projects/:id', async () => {
|
|
353
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'p1' }));
|
|
354
|
+
const client = setup();
|
|
355
|
+
await client.projects.get('p1');
|
|
356
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/projects/p1');
|
|
357
|
+
});
|
|
358
|
+
it('create() → POST /projects', async () => {
|
|
359
|
+
const input = { name: 'New', key: 'NEW' };
|
|
360
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'p2', ...input }));
|
|
361
|
+
const client = setup();
|
|
362
|
+
await client.projects.create(input);
|
|
363
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
364
|
+
expect(init.method).toBe('POST');
|
|
365
|
+
expect(new URL(url).pathname).toBe('/projects');
|
|
366
|
+
expect(JSON.parse(init.body)).toEqual(input);
|
|
367
|
+
});
|
|
368
|
+
it('update() → PATCH /projects/:id', async () => {
|
|
369
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'p1' }));
|
|
370
|
+
const client = setup();
|
|
371
|
+
await client.projects.update('p1', { name: 'Renamed' });
|
|
372
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
373
|
+
expect(init.method).toBe('PATCH');
|
|
374
|
+
expect(new URL(url).pathname).toBe('/projects/p1');
|
|
375
|
+
});
|
|
376
|
+
it('delete() → DELETE /projects/:id', async () => {
|
|
377
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
378
|
+
const client = setup();
|
|
379
|
+
await client.projects.delete('p1');
|
|
380
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('DELETE');
|
|
381
|
+
});
|
|
382
|
+
it('listMembers() → GET /projects/:id/members, unwraps .data', async () => {
|
|
383
|
+
const members = [{ userId: 'u1', role: 'developer' }];
|
|
384
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: members }));
|
|
385
|
+
const client = setup();
|
|
386
|
+
const result = await client.projects.listMembers('p1');
|
|
387
|
+
expect(result).toEqual(members);
|
|
388
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
389
|
+
expect(init.method).toBe('GET');
|
|
390
|
+
expect(new URL(url).pathname).toBe('/projects/p1/members');
|
|
391
|
+
});
|
|
392
|
+
it('addMember() → POST /projects/:id/members', async () => {
|
|
393
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
394
|
+
const client = setup();
|
|
395
|
+
await client.projects.addMember('p1', { userId: 'u1', role: 'developer' });
|
|
396
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
397
|
+
expect(init.method).toBe('POST');
|
|
398
|
+
expect(new URL(url).pathname).toBe('/projects/p1/members');
|
|
399
|
+
expect(JSON.parse(init.body)).toEqual({ userId: 'u1', role: 'developer' });
|
|
400
|
+
});
|
|
401
|
+
it('removeMember() → DELETE /projects/:id/members/:userId', async () => {
|
|
402
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
403
|
+
const client = setup();
|
|
404
|
+
await client.projects.removeMember('p1', 'u1');
|
|
405
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
406
|
+
expect(init.method).toBe('DELETE');
|
|
407
|
+
expect(new URL(url).pathname).toBe('/projects/p1/members/u1');
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
// Resource: Sprints
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
describe('SprintsResource', () => {
|
|
414
|
+
const BASE = 'https://api.omniq.pro';
|
|
415
|
+
function setup() {
|
|
416
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
417
|
+
}
|
|
418
|
+
it('list() → GET /sprints?projectId=x, unwraps .data', async () => {
|
|
419
|
+
const sprints = [{ id: 's1' }];
|
|
420
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: sprints }));
|
|
421
|
+
const client = setup();
|
|
422
|
+
const result = await client.sprints.list({ projectId: 'p1' });
|
|
423
|
+
expect(result).toEqual(sprints);
|
|
424
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
425
|
+
expect(url.pathname).toBe('/sprints');
|
|
426
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
427
|
+
});
|
|
428
|
+
it('get() → GET /sprints/:id', async () => {
|
|
429
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1' }));
|
|
430
|
+
const client = setup();
|
|
431
|
+
await client.sprints.get('s1');
|
|
432
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/sprints/s1');
|
|
433
|
+
});
|
|
434
|
+
it('create() → POST /sprints', async () => {
|
|
435
|
+
const input = { projectId: 'p1', name: 'Sprint 1' };
|
|
436
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1', ...input }));
|
|
437
|
+
const client = setup();
|
|
438
|
+
await client.sprints.create(input);
|
|
439
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
440
|
+
expect(init.method).toBe('POST');
|
|
441
|
+
expect(new URL(url).pathname).toBe('/sprints');
|
|
442
|
+
expect(JSON.parse(init.body)).toEqual(input);
|
|
443
|
+
});
|
|
444
|
+
it('update() → PATCH /sprints/:id', async () => {
|
|
445
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1' }));
|
|
446
|
+
const client = setup();
|
|
447
|
+
await client.sprints.update('s1', { name: 'Renamed' });
|
|
448
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
449
|
+
expect(init.method).toBe('PATCH');
|
|
450
|
+
expect(new URL(url).pathname).toBe('/sprints/s1');
|
|
451
|
+
});
|
|
452
|
+
it('start() → POST /sprints/:id/start', async () => {
|
|
453
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1', status: 'active' }));
|
|
454
|
+
const client = setup();
|
|
455
|
+
await client.sprints.start('s1');
|
|
456
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
457
|
+
expect(init.method).toBe('POST');
|
|
458
|
+
expect(new URL(url).pathname).toBe('/sprints/s1/start');
|
|
459
|
+
});
|
|
460
|
+
it('close() → POST /sprints/:id/close with transferToSprintId', async () => {
|
|
461
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1', status: 'completed' }));
|
|
462
|
+
const client = setup();
|
|
463
|
+
await client.sprints.close('s1', 's2');
|
|
464
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
465
|
+
expect(init.method).toBe('POST');
|
|
466
|
+
expect(new URL(url).pathname).toBe('/sprints/s1/close');
|
|
467
|
+
expect(JSON.parse(init.body)).toEqual({ transferToSprintId: 's2' });
|
|
468
|
+
});
|
|
469
|
+
it('close() → POST /sprints/:id/close without transfer (no body)', async () => {
|
|
470
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 's1', status: 'completed' }));
|
|
471
|
+
const client = setup();
|
|
472
|
+
await client.sprints.close('s1');
|
|
473
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
474
|
+
expect(init.body).toBeUndefined();
|
|
475
|
+
});
|
|
476
|
+
it('burndown() → GET /sprints/:id/burndown', async () => {
|
|
477
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
478
|
+
const client = setup();
|
|
479
|
+
await client.sprints.burndown('s1');
|
|
480
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/sprints/s1/burndown');
|
|
481
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('GET');
|
|
482
|
+
});
|
|
483
|
+
it('velocity() → GET /sprints/velocity?projectId=x', async () => {
|
|
484
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ sprints: [], averageVelocity: 0 }));
|
|
485
|
+
const client = setup();
|
|
486
|
+
await client.sprints.velocity({ projectId: 'p1' });
|
|
487
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
488
|
+
expect(url.pathname).toBe('/sprints/velocity');
|
|
489
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
// Resource: GitLab
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
describe('GitLabResource', () => {
|
|
496
|
+
const BASE = 'https://api.omniq.pro';
|
|
497
|
+
function setup() {
|
|
498
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
499
|
+
}
|
|
500
|
+
it('listActivity() → GET /git/events?projectId=x, unwraps .data', async () => {
|
|
501
|
+
const events = [{ id: 'e1', type: 'push' }];
|
|
502
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: events }));
|
|
503
|
+
const client = setup();
|
|
504
|
+
const result = await client.gitlab.listActivity('p1');
|
|
505
|
+
expect(result).toEqual(events);
|
|
506
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
507
|
+
expect(url.pathname).toBe('/git/events');
|
|
508
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
509
|
+
});
|
|
510
|
+
it('linkCommit() → POST /git/commits/link', async () => {
|
|
511
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
512
|
+
const client = setup();
|
|
513
|
+
await client.gitlab.linkCommit('i1', 'abc123', 'https://gl.example.com/commit/abc123');
|
|
514
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
515
|
+
expect(init.method).toBe('POST');
|
|
516
|
+
expect(new URL(url).pathname).toBe('/git/commits/link');
|
|
517
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
518
|
+
issueId: 'i1',
|
|
519
|
+
sha: 'abc123',
|
|
520
|
+
url: 'https://gl.example.com/commit/abc123',
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
// Resource: Auth
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
describe('AuthResource', () => {
|
|
528
|
+
const BASE = 'https://api.omniq.pro';
|
|
529
|
+
function setup() {
|
|
530
|
+
return new OmniqClient({ baseUrl: BASE });
|
|
531
|
+
}
|
|
532
|
+
it('login() → POST /auth/login', async () => {
|
|
533
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ user: { id: 'u1' } }));
|
|
534
|
+
const client = setup();
|
|
535
|
+
await client.auth.login({ email: 'a@b.com', password: 'pass' });
|
|
536
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
537
|
+
expect(init.method).toBe('POST');
|
|
538
|
+
expect(new URL(url).pathname).toBe('/auth/login');
|
|
539
|
+
expect(JSON.parse(init.body)).toEqual({ email: 'a@b.com', password: 'pass' });
|
|
540
|
+
});
|
|
541
|
+
it('refresh() → POST /auth/refresh', async () => {
|
|
542
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ success: true }));
|
|
543
|
+
const client = setup();
|
|
544
|
+
await client.auth.refresh();
|
|
545
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('POST');
|
|
546
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/auth/refresh');
|
|
547
|
+
});
|
|
548
|
+
it('logout() → POST /auth/logout', async () => {
|
|
549
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
550
|
+
const client = setup();
|
|
551
|
+
await client.auth.logout();
|
|
552
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/auth/logout');
|
|
553
|
+
});
|
|
554
|
+
it('me() → GET /auth/me', async () => {
|
|
555
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ user: { id: 'u1' } }));
|
|
556
|
+
const client = setup();
|
|
557
|
+
await client.auth.me();
|
|
558
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('GET');
|
|
559
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/auth/me');
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
// Resource: Wiki
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
describe('WikiResource', () => {
|
|
566
|
+
const BASE = 'https://api.omniq.pro';
|
|
567
|
+
function setup() {
|
|
568
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
569
|
+
}
|
|
570
|
+
it('listSpaces() → GET /wiki/spaces?projectId=x', async () => {
|
|
571
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
572
|
+
const client = setup();
|
|
573
|
+
await client.wiki.listSpaces({ projectId: 'p1' });
|
|
574
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
575
|
+
expect(url.pathname).toBe('/wiki/spaces');
|
|
576
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
577
|
+
});
|
|
578
|
+
it('createSpace() → POST /wiki/spaces', async () => {
|
|
579
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'ws1' }));
|
|
580
|
+
const client = setup();
|
|
581
|
+
await client.wiki.createSpace({ projectId: 'p1', name: 'Docs' });
|
|
582
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
583
|
+
expect(init.method).toBe('POST');
|
|
584
|
+
expect(new URL(url).pathname).toBe('/wiki/spaces');
|
|
585
|
+
});
|
|
586
|
+
it('createPage() → POST /wiki/pages', async () => {
|
|
587
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'wp1' }));
|
|
588
|
+
const client = setup();
|
|
589
|
+
await client.wiki.createPage({ spaceId: 'ws1', title: 'Getting Started', content: '# Hello' });
|
|
590
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
591
|
+
expect(init.method).toBe('POST');
|
|
592
|
+
expect(new URL(url).pathname).toBe('/wiki/pages');
|
|
593
|
+
});
|
|
594
|
+
it('updatePage() → PATCH /wiki/pages/:id', async () => {
|
|
595
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'wp1' }));
|
|
596
|
+
const client = setup();
|
|
597
|
+
await client.wiki.updatePage('wp1', { title: 'Updated' });
|
|
598
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
599
|
+
expect(init.method).toBe('PATCH');
|
|
600
|
+
expect(new URL(url).pathname).toBe('/wiki/pages/wp1');
|
|
601
|
+
});
|
|
602
|
+
it('search() → GET /wiki/search?projectId&q', async () => {
|
|
603
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
604
|
+
const client = setup();
|
|
605
|
+
await client.wiki.search({ projectId: 'p1', q: 'setup' });
|
|
606
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
607
|
+
expect(url.pathname).toBe('/wiki/search');
|
|
608
|
+
expect(url.searchParams.get('q')).toBe('setup');
|
|
609
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
610
|
+
});
|
|
611
|
+
it('templates() → GET /wiki/templates', async () => {
|
|
612
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
613
|
+
const client = setup();
|
|
614
|
+
await client.wiki.templates();
|
|
615
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/wiki/templates');
|
|
616
|
+
});
|
|
617
|
+
it('restorePage() → POST /wiki/pages/:id/restore/:versionId', async () => {
|
|
618
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'wp1' }));
|
|
619
|
+
const client = setup();
|
|
620
|
+
await client.wiki.restorePage('wp1', 'v3');
|
|
621
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
622
|
+
expect(init.method).toBe('POST');
|
|
623
|
+
expect(new URL(url).pathname).toBe('/wiki/pages/wp1/restore/v3');
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
// Resource: Calendar
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
describe('CalendarResource', () => {
|
|
630
|
+
const BASE = 'https://api.omniq.pro';
|
|
631
|
+
function setup() {
|
|
632
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
633
|
+
}
|
|
634
|
+
it('listEvents() → GET /calendar/events with date range', async () => {
|
|
635
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
636
|
+
const client = setup();
|
|
637
|
+
await client.calendar.listEvents({ start: '2026-01-01', end: '2026-01-31' });
|
|
638
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
639
|
+
expect(url.pathname).toBe('/calendar/events');
|
|
640
|
+
expect(url.searchParams.get('start')).toBe('2026-01-01');
|
|
641
|
+
expect(url.searchParams.get('end')).toBe('2026-01-31');
|
|
642
|
+
});
|
|
643
|
+
it('createEvent() → POST /calendar/events', async () => {
|
|
644
|
+
const input = { type: 'meeting', title: 'Standup', start: '2026-01-01T10:00:00Z' };
|
|
645
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'ev1', ...input }));
|
|
646
|
+
const client = setup();
|
|
647
|
+
await client.calendar.createEvent(input);
|
|
648
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
649
|
+
expect(init.method).toBe('POST');
|
|
650
|
+
expect(new URL(url).pathname).toBe('/calendar/events');
|
|
651
|
+
});
|
|
652
|
+
it('updateEvent() → PATCH /calendar/events/:id', async () => {
|
|
653
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 'ev1' }));
|
|
654
|
+
const client = setup();
|
|
655
|
+
await client.calendar.updateEvent('ev1', { title: 'Updated' });
|
|
656
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
657
|
+
expect(init.method).toBe('PATCH');
|
|
658
|
+
expect(new URL(url).pathname).toBe('/calendar/events/ev1');
|
|
659
|
+
});
|
|
660
|
+
it('deleteEvent() → DELETE /calendar/events/:id', async () => {
|
|
661
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
662
|
+
const client = setup();
|
|
663
|
+
await client.calendar.deleteEvent('ev1');
|
|
664
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('DELETE');
|
|
665
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/calendar/events/ev1');
|
|
666
|
+
});
|
|
667
|
+
it('exportIcs() → GET /calendar/export.ics with rawResponse', async () => {
|
|
668
|
+
const fakeResponse = { ok: true, status: 200, text: () => 'ics-data' };
|
|
669
|
+
fetchMock.mockResolvedValueOnce(fakeResponse);
|
|
670
|
+
const client = setup();
|
|
671
|
+
const result = await client.calendar.exportIcs({ start: '2026-01-01', end: '2026-12-31' });
|
|
672
|
+
expect(result).toBe(fakeResponse);
|
|
673
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/calendar/export.ics');
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
// ---------------------------------------------------------------------------
|
|
677
|
+
// Resource: AI
|
|
678
|
+
// ---------------------------------------------------------------------------
|
|
679
|
+
describe('AIResource', () => {
|
|
680
|
+
const BASE = 'https://api.omniq.pro';
|
|
681
|
+
function setup() {
|
|
682
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
683
|
+
}
|
|
684
|
+
it('chat() → POST /ai/chat', async () => {
|
|
685
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ conversationId: 'c1', content: 'hi', tokensUsed: 10 }));
|
|
686
|
+
const client = setup();
|
|
687
|
+
await client.ai.chat({ message: 'hello' });
|
|
688
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
689
|
+
expect(init.method).toBe('POST');
|
|
690
|
+
expect(new URL(url).pathname).toBe('/ai/chat');
|
|
691
|
+
expect(JSON.parse(init.body)).toEqual({ message: 'hello' });
|
|
692
|
+
});
|
|
693
|
+
it('generate() → POST /ai/generate', async () => {
|
|
694
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ content: '...', tokensUsed: 5 }));
|
|
695
|
+
const client = setup();
|
|
696
|
+
await client.ai.generate({ title: 'Feature X' });
|
|
697
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/ai/generate');
|
|
698
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('POST');
|
|
699
|
+
});
|
|
700
|
+
it('estimate() → POST /ai/estimate', async () => {
|
|
701
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ storyPoints: 5, confidence: 0.8, reasoning: 'ok' }));
|
|
702
|
+
const client = setup();
|
|
703
|
+
await client.ai.estimate({ title: 'Task', description: 'Desc' });
|
|
704
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/ai/estimate');
|
|
705
|
+
});
|
|
706
|
+
it('insights() → GET /ai/insights?projectId=x', async () => {
|
|
707
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ content: 'insight', tokensUsed: 3 }));
|
|
708
|
+
const client = setup();
|
|
709
|
+
await client.ai.insights({ projectId: 'p1' });
|
|
710
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
711
|
+
expect(url.pathname).toBe('/ai/insights');
|
|
712
|
+
expect(url.searchParams.get('projectId')).toBe('p1');
|
|
713
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('GET');
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
// Resource: Notifications
|
|
718
|
+
// ---------------------------------------------------------------------------
|
|
719
|
+
describe('NotificationsResource', () => {
|
|
720
|
+
const BASE = 'https://api.omniq.pro';
|
|
721
|
+
function setup() {
|
|
722
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
723
|
+
}
|
|
724
|
+
it('list() → GET /notifications', async () => {
|
|
725
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: [], unreadCount: 0 }));
|
|
726
|
+
const client = setup();
|
|
727
|
+
await client.notifications.list();
|
|
728
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/notifications');
|
|
729
|
+
});
|
|
730
|
+
it('list({ unreadOnly: true }) passes query param', async () => {
|
|
731
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ data: [], unreadCount: 0 }));
|
|
732
|
+
const client = setup();
|
|
733
|
+
await client.notifications.list({ unreadOnly: true });
|
|
734
|
+
const url = new URL(fetchMock.mock.calls[0][0]);
|
|
735
|
+
expect(url.searchParams.get('unreadOnly')).toBe('true');
|
|
736
|
+
});
|
|
737
|
+
it('markRead() → POST /notifications/:id/read', async () => {
|
|
738
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
739
|
+
const client = setup();
|
|
740
|
+
await client.notifications.markRead('n1');
|
|
741
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
742
|
+
expect(init.method).toBe('POST');
|
|
743
|
+
expect(new URL(url).pathname).toBe('/notifications/n1/read');
|
|
744
|
+
});
|
|
745
|
+
it('markAllRead() → POST /notifications/read-all', async () => {
|
|
746
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
747
|
+
const client = setup();
|
|
748
|
+
await client.notifications.markAllRead();
|
|
749
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/notifications/read-all');
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
// Resource: Tokens
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
describe('TokensResource', () => {
|
|
756
|
+
const BASE = 'https://api.omniq.pro';
|
|
757
|
+
function setup() {
|
|
758
|
+
return new OmniqClient({ baseUrl: BASE, token: 'tok' });
|
|
759
|
+
}
|
|
760
|
+
it('list() → GET /tokens', async () => {
|
|
761
|
+
fetchMock.mockResolvedValueOnce(jsonResponse([]));
|
|
762
|
+
const client = setup();
|
|
763
|
+
await client.tokens.list();
|
|
764
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/tokens');
|
|
765
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('GET');
|
|
766
|
+
});
|
|
767
|
+
it('create() → POST /tokens', async () => {
|
|
768
|
+
fetchMock.mockResolvedValueOnce(jsonResponse({ id: 't1', token: 'omniq_xxx' }));
|
|
769
|
+
const client = setup();
|
|
770
|
+
await client.tokens.create({ name: 'CI', scopes: ['read'] });
|
|
771
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
772
|
+
expect(init.method).toBe('POST');
|
|
773
|
+
expect(new URL(url).pathname).toBe('/tokens');
|
|
774
|
+
expect(JSON.parse(init.body)).toEqual({ name: 'CI', scopes: ['read'] });
|
|
775
|
+
});
|
|
776
|
+
it('delete() → DELETE /tokens/:id', async () => {
|
|
777
|
+
fetchMock.mockResolvedValueOnce(jsonResponse(null, 204));
|
|
778
|
+
const client = setup();
|
|
779
|
+
await client.tokens.delete('t1');
|
|
780
|
+
expect(fetchMock.mock.calls[0][1].method).toBe('DELETE');
|
|
781
|
+
expect(new URL(fetchMock.mock.calls[0][0]).pathname).toBe('/tokens/t1');
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
//# sourceMappingURL=client.test.js.map
|