metaowl 0.4.1 → 0.6.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +267 -2
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +144 -0
  10. package/build/runtime/modules/app-mounter.js +73 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/constants.js +38 -0
  15. package/build/runtime/modules/error-boundary.js +116 -0
  16. package/build/runtime/modules/fetch.js +31 -0
  17. package/build/runtime/modules/file-router.js +207 -0
  18. package/build/runtime/modules/fonts.js +172 -0
  19. package/build/runtime/modules/forms.js +193 -0
  20. package/build/runtime/modules/i18n.js +180 -0
  21. package/build/runtime/modules/image.js +175 -0
  22. package/build/runtime/modules/layouts.js +214 -0
  23. package/build/runtime/modules/link.js +141 -0
  24. package/build/runtime/modules/meta.js +117 -0
  25. package/build/runtime/modules/odoo-rpc.js +265 -0
  26. package/build/runtime/modules/pwa.js +272 -0
  27. package/build/runtime/modules/router.js +384 -0
  28. package/build/runtime/modules/seo.js +186 -0
  29. package/build/runtime/modules/store.js +198 -0
  30. package/build/runtime/modules/templates-manager.js +52 -0
  31. package/build/runtime/modules/test-utils.js +238 -0
  32. package/build/runtime/vite/plugin.js +197 -0
  33. package/eslint.js +29 -0
  34. package/package.json +45 -27
  35. package/CONTRIBUTING.md +0 -49
  36. package/bin/metaowl-build.js +0 -12
  37. package/bin/metaowl-dev.js +0 -12
  38. package/bin/metaowl-generate.js +0 -339
  39. package/bin/metaowl-lint.js +0 -71
  40. package/bin/utils.js +0 -82
  41. package/eslint.config.js +0 -3
  42. package/index.js +0 -328
  43. package/modules/app-mounter.js +0 -104
  44. package/modules/auto-import.js +0 -225
  45. package/modules/cache.js +0 -59
  46. package/modules/composables.js +0 -600
  47. package/modules/error-boundary.js +0 -228
  48. package/modules/fetch.js +0 -51
  49. package/modules/file-router.js +0 -478
  50. package/modules/forms.js +0 -353
  51. package/modules/i18n.js +0 -333
  52. package/modules/layouts.js +0 -431
  53. package/modules/link.js +0 -255
  54. package/modules/meta.js +0 -119
  55. package/modules/odoo-rpc.js +0 -511
  56. package/modules/pwa.js +0 -515
  57. package/modules/router.js +0 -769
  58. package/modules/seo.js +0 -501
  59. package/modules/store.js +0 -409
  60. package/modules/templates-manager.js +0 -89
  61. package/modules/test-utils.js +0 -532
  62. package/test/auto-import.test.js +0 -110
  63. package/test/cache.test.js +0 -55
  64. package/test/composables.test.js +0 -103
  65. package/test/dynamic-routes.test.js +0 -469
  66. package/test/error-boundary.test.js +0 -126
  67. package/test/fetch.test.js +0 -100
  68. package/test/file-router.test.js +0 -55
  69. package/test/forms.test.js +0 -203
  70. package/test/i18n.test.js +0 -188
  71. package/test/layouts.test.js +0 -395
  72. package/test/link.test.js +0 -189
  73. package/test/meta.test.js +0 -146
  74. package/test/odoo-rpc.test.js +0 -547
  75. package/test/pwa.test.js +0 -154
  76. package/test/router-guards.test.js +0 -229
  77. package/test/router.test.js +0 -77
  78. package/test/seo.test.js +0 -353
  79. package/test/store.test.js +0 -476
  80. package/test/templates-manager.test.js +0 -83
  81. package/test/test-utils.test.js +0 -314
  82. package/vite/plugin.js +0 -290
  83. package/vitest.config.js +0 -8
package/test/meta.test.js DELETED
@@ -1,146 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import { JSDOM } from 'jsdom'
3
- import * as Meta from '../modules/meta.js'
4
-
5
- let dom
6
-
7
- beforeEach(() => {
8
- dom = new JSDOM('<!doctype html><html><head></head><body></body></html>')
9
- globalThis.document = dom.window.document
10
- })
11
-
12
- describe('Meta.title', () => {
13
- it('sets document.title', () => {
14
- Meta.title('My Page')
15
- expect(document.title).toBe('My Page')
16
- })
17
-
18
- it('updates title on second call', () => {
19
- Meta.title('First')
20
- Meta.title('Second')
21
- expect(document.title).toBe('Second')
22
- })
23
-
24
- it('does nothing when called with empty string', () => {
25
- document.title = 'Original'
26
- Meta.title('')
27
- expect(document.title).toBe('Original')
28
- })
29
- })
30
-
31
- describe('Meta name-based tags (description, keywords, author)', () => {
32
- it('creates a meta[name="description"] tag', () => {
33
- Meta.description('A test page')
34
- const el = document.querySelector('meta[name="description"]')
35
- expect(el).not.toBeNull()
36
- expect(el.content).toBe('A test page')
37
- })
38
-
39
- it('updates description content on second call', () => {
40
- Meta.description('First')
41
- Meta.description('Second')
42
- const els = document.querySelectorAll('meta[name="description"]')
43
- expect(els).toHaveLength(1)
44
- expect(els[0].content).toBe('Second')
45
- })
46
-
47
- it('creates meta[name="keywords"]', () => {
48
- Meta.keywords('owl, odoo')
49
- expect(document.querySelector('meta[name="keywords"]').content).toBe('owl, odoo')
50
- })
51
-
52
- it('creates meta[name="author"]', () => {
53
- Meta.author('Dennis')
54
- expect(document.querySelector('meta[name="author"]').content).toBe('Dennis')
55
- })
56
-
57
- it('does nothing for falsy value', () => {
58
- Meta.description(null)
59
- expect(document.querySelector('meta[name="description"]')).toBeNull()
60
- })
61
- })
62
-
63
- describe('Meta.canonical', () => {
64
- it('creates a link[rel="canonical"]', () => {
65
- Meta.canonical('https://example.com/page')
66
- const el = document.querySelector('link[rel="canonical"]')
67
- expect(el).not.toBeNull()
68
- expect(el.href).toBe('https://example.com/page')
69
- })
70
-
71
- it('updates href on second call without duplicating', () => {
72
- Meta.canonical('https://example.com/a')
73
- Meta.canonical('https://example.com/b')
74
- const els = document.querySelectorAll('link[rel="canonical"]')
75
- expect(els).toHaveLength(1)
76
- expect(els[0].href).toBe('https://example.com/b')
77
- })
78
- })
79
-
80
- describe('Meta Open Graph tags', () => {
81
- it('creates og:title', () => {
82
- Meta.ogTitle('OG Title')
83
- expect(document.querySelector('meta[property="og:title"]').content).toBe('OG Title')
84
- })
85
-
86
- it('updates og:title on second call without duplicating', () => {
87
- Meta.ogTitle('First')
88
- Meta.ogTitle('Second')
89
- expect(document.querySelectorAll('meta[property="og:title"]')).toHaveLength(1)
90
- expect(document.querySelector('meta[property="og:title"]').content).toBe('Second')
91
- })
92
-
93
- it('creates og:description', () => {
94
- Meta.ogDescription('Desc')
95
- expect(document.querySelector('meta[property="og:description"]').content).toBe('Desc')
96
- })
97
-
98
- it('creates og:image', () => {
99
- Meta.ogImage('https://example.com/img.png')
100
- expect(document.querySelector('meta[property="og:image"]').content).toBe('https://example.com/img.png')
101
- })
102
-
103
- it('creates og:url', () => {
104
- Meta.ogUrl('https://example.com')
105
- expect(document.querySelector('meta[property="og:url"]').content).toBe('https://example.com')
106
- })
107
-
108
- it('creates og:type', () => {
109
- Meta.ogType('website')
110
- expect(document.querySelector('meta[property="og:type"]').content).toBe('website')
111
- })
112
-
113
- it('creates og:site_name', () => {
114
- Meta.ogSiteName('My Site')
115
- expect(document.querySelector('meta[property="og:site_name"]').content).toBe('My Site')
116
- })
117
- })
118
-
119
- describe('Meta Twitter Card tags', () => {
120
- it('creates twitter:card', () => {
121
- Meta.twitterCard('summary_large_image')
122
- expect(document.querySelector('meta[name="twitter:card"]').content).toBe('summary_large_image')
123
- })
124
-
125
- it('creates twitter:title', () => {
126
- Meta.twitterTitle('TW Title')
127
- expect(document.querySelector('meta[name="twitter:title"]').content).toBe('TW Title')
128
- })
129
-
130
- it('creates twitter:description', () => {
131
- Meta.twitterDescription('TW Desc')
132
- expect(document.querySelector('meta[name="twitter:description"]').content).toBe('TW Desc')
133
- })
134
-
135
- it('creates twitter:image', () => {
136
- Meta.twitterImage('https://example.com/tw.png')
137
- expect(document.querySelector('meta[name="twitter:image"]').content).toBe('https://example.com/tw.png')
138
- })
139
-
140
- it('updates twitter:card on second call without duplicating', () => {
141
- Meta.twitterCard('summary')
142
- Meta.twitterCard('summary_large_image')
143
- expect(document.querySelectorAll('meta[name="twitter:card"]')).toHaveLength(1)
144
- expect(document.querySelector('meta[name="twitter:card"]').content).toBe('summary_large_image')
145
- })
146
- })
@@ -1,547 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import {
3
- OdooService,
4
- configure,
5
- isConfigured,
6
- isAuthenticated,
7
- getSession,
8
- getConfig,
9
- onAuthChange,
10
- authenticate,
11
- logout,
12
- searchRead,
13
- call,
14
- read,
15
- create,
16
- write,
17
- unlink,
18
- searchCount,
19
- listDatabases,
20
- versionInfo
21
- } from '../modules/odoo-rpc.js'
22
-
23
- describe('Odoo RPC Service', () => {
24
- beforeEach(() => {
25
- // Reset state
26
- logout()
27
- localStorage.clear()
28
- vi.restoreAllMocks()
29
- })
30
-
31
- describe('Configuration', () => {
32
- it('should configure with valid config', () => {
33
- configure({
34
- baseUrl: 'https://test.odoo.com',
35
- database: 'test_db',
36
- username: 'admin',
37
- password: 'admin'
38
- })
39
-
40
- expect(isConfigured()).toBe(true)
41
- expect(getConfig()).toMatchObject({
42
- baseUrl: 'https://test.odoo.com',
43
- database: 'test_db',
44
- username: 'admin',
45
- password: 'admin',
46
- persistSession: true
47
- })
48
- })
49
-
50
- it('should not be configured without baseUrl', () => {
51
- configure({
52
- database: 'test_db'
53
- })
54
- expect(isConfigured()).toBe(false)
55
- })
56
-
57
- it('should not be configured without database', () => {
58
- configure({
59
- baseUrl: 'https://test.odoo.com'
60
- })
61
- expect(isConfigured()).toBe(false)
62
- })
63
-
64
- it('should allow apiKey instead of password', () => {
65
- configure({
66
- baseUrl: 'https://test.odoo.com',
67
- database: 'test_db',
68
- username: 'admin',
69
- apiKey: 'test-api-key-123'
70
- })
71
-
72
- expect(getConfig().apiKey).toBe('test-api-key-123')
73
- expect(getConfig().password).toBeUndefined()
74
- })
75
-
76
- it('should support disabling session persistence', () => {
77
- configure({
78
- baseUrl: 'https://test.odoo.com',
79
- database: 'test_db',
80
- persistSession: false
81
- })
82
-
83
- expect(getConfig().persistSession).toBe(false)
84
- })
85
- })
86
-
87
- describe('Authentication', () => {
88
- beforeEach(() => {
89
- configure({
90
- baseUrl: 'https://test.odoo.com',
91
- database: 'test_db',
92
- username: 'admin',
93
- password: 'admin'
94
- })
95
- })
96
-
97
- it('should authenticate successfully', async () => {
98
- global.fetch = vi.fn().mockResolvedValueOnce({
99
- ok: true,
100
- headers: new Headers(),
101
- json: async () => ({
102
- jsonrpc: '2.0',
103
- id: 123,
104
- result: 1 // User ID
105
- })
106
- }).mockResolvedValueOnce({ // For user info search_read
107
- ok: true,
108
- headers: new Headers(),
109
- json: async () => ({
110
- jsonrpc: '2.0',
111
- id: 123,
112
- result: [{
113
- id: 1,
114
- name: 'Administrator',
115
- partner_id: [3, 'Administrator'],
116
- lang: 'en_US',
117
- tz: 'Europe/Brussels'
118
- }]
119
- })
120
- })
121
-
122
- const session = await authenticate()
123
-
124
- expect(session.uid).toBe(1)
125
- expect(session.username).toBe('admin')
126
- expect(session.name).toBe('Administrator')
127
- expect(isAuthenticated()).toBe(true)
128
- })
129
-
130
- it('should throw error without credentials', async () => {
131
- configure({
132
- baseUrl: 'https://test.odoo.com',
133
- database: 'test_db'
134
- })
135
-
136
- await expect(authenticate()).rejects.toThrow('requires username and password')
137
- })
138
-
139
- it('should allow custom credentials in authenticate()', async () => {
140
- global.fetch = vi.fn().mockResolvedValueOnce({
141
- ok: true,
142
- headers: new Headers(),
143
- json: async () => ({
144
- jsonrpc: '2.0',
145
- id: 123,
146
- result: 2
147
- })
148
- }).mockResolvedValueOnce({
149
- ok: true,
150
- headers: new Headers(),
151
- json: async () => ({
152
- jsonrpc: '2.0',
153
- id: 123,
154
- result: []
155
- })
156
- })
157
-
158
- const session = await authenticate('custom_user', 'custom_pass')
159
- expect(session.username).toBe('custom_user')
160
- })
161
-
162
- it('should handle authentication failure', async () => {
163
- global.fetch = vi.fn().mockResolvedValueOnce({
164
- ok: true,
165
- headers: new Headers(),
166
- json: async () => ({
167
- jsonrpc: '2.0',
168
- id: 123,
169
- result: false // Authentication failed
170
- })
171
- })
172
-
173
- await expect(authenticate()).rejects.toThrow('invalid credentials')
174
- })
175
-
176
- it('should logout and clear session', async () => {
177
- global.fetch = vi.fn().mockResolvedValueOnce({
178
- ok: true,
179
- headers: new Headers(),
180
- json: async () => ({
181
- jsonrpc: '2.0',
182
- id: 123,
183
- result: 1
184
- })
185
- }).mockResolvedValueOnce({
186
- ok: true,
187
- headers: new Headers(),
188
- json: async () => ({
189
- jsonrpc: '2.0',
190
- id: 123,
191
- result: []
192
- })
193
- })
194
-
195
- await authenticate()
196
- expect(isAuthenticated()).toBe(true)
197
-
198
- logout()
199
- expect(isAuthenticated()).toBe(false)
200
- expect(getSession()).toBeNull()
201
- })
202
-
203
- it('should notify auth listeners on change', async () => {
204
- global.fetch = vi.fn().mockResolvedValueOnce({
205
- ok: true,
206
- headers: new Headers(),
207
- json: async () => ({
208
- jsonrpc: '2.0',
209
- id: 123,
210
- result: 1
211
- })
212
- }).mockResolvedValueOnce({
213
- ok: true,
214
- headers: new Headers(),
215
- json: async () => ({
216
- jsonrpc: '2.0',
217
- id: 123,
218
- result: []
219
- })
220
- })
221
-
222
- const listener = vi.fn()
223
- const unsubscribe = onAuthChange(listener)
224
-
225
- await authenticate()
226
- expect(listener).toHaveBeenCalledTimes(1)
227
- expect(listener).toHaveBeenCalledWith(expect.objectContaining({ uid: 1 }))
228
-
229
- unsubscribe()
230
- })
231
- })
232
-
233
- describe('RPC Operations', () => {
234
- beforeEach(() => {
235
- configure({
236
- baseUrl: 'https://test.odoo.com',
237
- database: 'test_db',
238
- username: 'admin',
239
- password: 'admin'
240
- })
241
-
242
- // Mock successful authentication
243
- global.fetch = vi.fn().mockResolvedValue({
244
- ok: true,
245
- headers: new Headers(),
246
- json: async () => ({
247
- jsonrpc: '2.0',
248
- id: 123,
249
- result: 1
250
- })
251
- })
252
- })
253
-
254
- it('should throw if not authenticated', async () => {
255
- await expect(searchRead('res.partner')).rejects.toThrow('Not authenticated')
256
- })
257
-
258
- it('should perform search_read', async () => {
259
- const mockResult = [
260
- { id: 1, name: 'Partner 1' },
261
- { id: 2, name: 'Partner 2' }
262
- ]
263
-
264
- global.fetch = vi.fn().mockResolvedValueOnce({
265
- ok: true,
266
- headers: new Headers(),
267
- json: async () => ({
268
- jsonrpc: '2.0',
269
- id: 123,
270
- result: 1
271
- })
272
- }).mockResolvedValueOnce({
273
- ok: true,
274
- headers: new Headers(),
275
- json: async () => ({
276
- jsonrpc: '2.0',
277
- id: 123,
278
- result: []
279
- })
280
- }).mockResolvedValueOnce({
281
- ok: true,
282
- headers: new Headers(),
283
- json: async () => ({
284
- jsonrpc: '2.0',
285
- id: 123,
286
- result: mockResult
287
- })
288
- })
289
-
290
- await authenticate()
291
- const partners = await searchRead('res.partner', {
292
- domain: [['is_company', '=', true]],
293
- fields: ['name', 'email'],
294
- limit: 10
295
- })
296
-
297
- expect(partners).toEqual(mockResult)
298
- })
299
-
300
- it('should call model methods', async () => {
301
- global.fetch = vi.fn()
302
- .mockResolvedValueOnce({
303
- ok: true,
304
- headers: new Headers(),
305
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
306
- })
307
- .mockResolvedValueOnce({
308
- ok: true,
309
- headers: new Headers(),
310
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
311
- })
312
- .mockResolvedValueOnce({
313
- ok: true,
314
- headers: new Headers(),
315
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 42 })
316
- })
317
-
318
- await authenticate()
319
- const result = await call('res.partner', 'custom_method', [['arg1']])
320
-
321
- expect(result).toBe(42)
322
- })
323
-
324
- it('should read specific records', async () => {
325
- global.fetch = vi.fn()
326
- .mockResolvedValueOnce({
327
- ok: true,
328
- headers: new Headers(),
329
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
330
- })
331
- .mockResolvedValueOnce({
332
- ok: true,
333
- headers: new Headers(),
334
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
335
- })
336
- .mockResolvedValueOnce({
337
- ok: true,
338
- headers: new Headers(),
339
- json: async () => ({
340
- jsonrpc: '2.0',
341
- id: 123,
342
- result: [{ id: 1, name: 'Partner 1' }]
343
- })
344
- })
345
-
346
- await authenticate()
347
- const records = await read('res.partner', [1], ['name', 'email'])
348
-
349
- expect(records).toEqual([{ id: 1, name: 'Partner 1' }])
350
- })
351
-
352
- it('should create records', async () => {
353
- global.fetch = vi.fn()
354
- .mockResolvedValueOnce({
355
- ok: true,
356
- headers: new Headers(),
357
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
358
- })
359
- .mockResolvedValueOnce({
360
- ok: true,
361
- headers: new Headers(),
362
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
363
- })
364
- .mockResolvedValueOnce({
365
- ok: true,
366
- headers: new Headers(),
367
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 123 })
368
- })
369
-
370
- await authenticate()
371
- const newId = await create('res.partner', { name: 'New Partner' })
372
-
373
- expect(newId).toBe(123)
374
- })
375
-
376
- it('should update records', async () => {
377
- global.fetch = vi.fn()
378
- .mockResolvedValueOnce({
379
- ok: true,
380
- headers: new Headers(),
381
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
382
- })
383
- .mockResolvedValueOnce({
384
- ok: true,
385
- headers: new Headers(),
386
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
387
- })
388
- .mockResolvedValueOnce({
389
- ok: true,
390
- headers: new Headers(),
391
- json: async () => ({ jsonrpc: '2.0', id: 123, result: true })
392
- })
393
-
394
- await authenticate()
395
- const result = await write('res.partner', [1], { name: 'Updated Partner' })
396
-
397
- expect(result).toBe(true)
398
- })
399
-
400
- it('should delete records', async () => {
401
- global.fetch = vi.fn()
402
- .mockResolvedValueOnce({
403
- ok: true,
404
- headers: new Headers(),
405
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
406
- })
407
- .mockResolvedValueOnce({
408
- ok: true,
409
- headers: new Headers(),
410
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
411
- })
412
- .mockResolvedValueOnce({
413
- ok: true,
414
- headers: new Headers(),
415
- json: async () => ({ jsonrpc: '2.0', id: 123, result: true })
416
- })
417
-
418
- await authenticate()
419
- const result = await unlink('res.partner', [1])
420
-
421
- expect(result).toBe(true)
422
- })
423
-
424
- it('should get search count', async () => {
425
- global.fetch = vi.fn()
426
- .mockResolvedValueOnce({
427
- ok: true,
428
- headers: new Headers(),
429
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 1 })
430
- })
431
- .mockResolvedValueOnce({
432
- ok: true,
433
- headers: new Headers(),
434
- json: async () => ({ jsonrpc: '2.0', id: 123, result: [] })
435
- })
436
- .mockResolvedValueOnce({
437
- ok: true,
438
- headers: new Headers(),
439
- json: async () => ({ jsonrpc: '2.0', id: 123, result: 42 })
440
- })
441
-
442
- await authenticate()
443
- const count = await searchCount('res.partner', [['is_company', '=', true]])
444
-
445
- expect(count).toBe(42)
446
- })
447
- })
448
-
449
- describe('Database Operations', () => {
450
- beforeEach(() => {
451
- configure({
452
- baseUrl: 'https://test.odoo.com',
453
- database: 'test_db'
454
- })
455
- })
456
-
457
- it('should list databases', async () => {
458
- global.fetch = vi.fn().mockResolvedValueOnce({
459
- ok: true,
460
- headers: new Headers(),
461
- json: async () => ({
462
- jsonrpc: '2.0',
463
- id: 123,
464
- result: ['db1', 'db2', 'db3']
465
- })
466
- })
467
-
468
- const dbs = await listDatabases()
469
- expect(dbs).toEqual(['db1', 'db2', 'db3'])
470
- })
471
-
472
- it('should get version info', async () => {
473
- global.fetch = vi.fn().mockResolvedValueOnce({
474
- ok: true,
475
- headers: new Headers(),
476
- json: async () => ({
477
- result: {
478
- server_version: '16.0',
479
- server_version_info: [16, 0, 0, 'final', 0],
480
- protocol_version: 1
481
- }
482
- })
483
- })
484
-
485
- const version = await versionInfo()
486
- expect(version.server_version).toBe('16.0')
487
- })
488
- })
489
-
490
- describe('Error Handling', () => {
491
- beforeEach(() => {
492
- configure({
493
- baseUrl: 'https://test.odoo.com',
494
- database: 'test_db',
495
- username: 'admin',
496
- password: 'admin'
497
- })
498
- })
499
-
500
- it('should handle RPC errors', async () => {
501
- global.fetch = vi.fn().mockResolvedValueOnce({
502
- ok: true,
503
- headers: new Headers(),
504
- json: async () => ({
505
- jsonrpc: '2.0',
506
- id: 123,
507
- error: {
508
- message: 'Access Denied',
509
- data: { message: 'You do not have permission' }
510
- }
511
- })
512
- })
513
-
514
- await expect(authenticate()).rejects.toThrow('Access Denied')
515
- })
516
-
517
- it('should handle HTTP errors', async () => {
518
- global.fetch = vi.fn().mockResolvedValueOnce({
519
- ok: false,
520
- status: 500,
521
- statusText: 'Internal Server Error',
522
- headers: new Headers(),
523
- json: async () => ({})
524
- })
525
-
526
- await expect(authenticate()).rejects.toThrow('HTTP 500')
527
- })
528
-
529
- it('should throw if not configured', async () => {
530
- // Reset configuration by configuring with empty values
531
- configure({ baseUrl: '', database: '' })
532
-
533
- await expect(listDatabases()).rejects.toThrow('not configured')
534
- })
535
- })
536
-
537
- describe('OdooService Namespace', () => {
538
- it('should expose all methods via namespace', () => {
539
- expect(OdooService.configure).toBe(configure)
540
- expect(OdooService.authenticate).toBe(authenticate)
541
- expect(OdooService.logout).toBe(logout)
542
- expect(OdooService.searchRead).toBe(searchRead)
543
- expect(OdooService.call).toBe(call)
544
- expect(OdooService.isAuthenticated).toBe(isAuthenticated)
545
- })
546
- })
547
- })