metaowl 0.4.1 → 0.5.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 (80) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +12 -0
  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 +141 -0
  10. package/build/runtime/modules/app-mounter.js +65 -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/error-boundary.js +116 -0
  15. package/build/runtime/modules/fetch.js +31 -0
  16. package/build/runtime/modules/file-router.js +205 -0
  17. package/build/runtime/modules/forms.js +193 -0
  18. package/build/runtime/modules/i18n.js +167 -0
  19. package/build/runtime/modules/layouts.js +163 -0
  20. package/build/runtime/modules/link.js +141 -0
  21. package/build/runtime/modules/meta.js +117 -0
  22. package/build/runtime/modules/odoo-rpc.js +264 -0
  23. package/build/runtime/modules/pwa.js +262 -0
  24. package/build/runtime/modules/router.js +389 -0
  25. package/build/runtime/modules/seo.js +186 -0
  26. package/build/runtime/modules/store.js +196 -0
  27. package/build/runtime/modules/templates-manager.js +52 -0
  28. package/build/runtime/modules/test-utils.js +238 -0
  29. package/build/runtime/vite/plugin.js +183 -0
  30. package/eslint.js +29 -0
  31. package/package.json +28 -10
  32. package/CONTRIBUTING.md +0 -49
  33. package/bin/metaowl-build.js +0 -12
  34. package/bin/metaowl-dev.js +0 -12
  35. package/bin/metaowl-generate.js +0 -339
  36. package/bin/metaowl-lint.js +0 -71
  37. package/bin/utils.js +0 -82
  38. package/eslint.config.js +0 -3
  39. package/index.js +0 -328
  40. package/modules/app-mounter.js +0 -104
  41. package/modules/auto-import.js +0 -225
  42. package/modules/cache.js +0 -59
  43. package/modules/composables.js +0 -600
  44. package/modules/error-boundary.js +0 -228
  45. package/modules/fetch.js +0 -51
  46. package/modules/file-router.js +0 -478
  47. package/modules/forms.js +0 -353
  48. package/modules/i18n.js +0 -333
  49. package/modules/layouts.js +0 -431
  50. package/modules/link.js +0 -255
  51. package/modules/meta.js +0 -119
  52. package/modules/odoo-rpc.js +0 -511
  53. package/modules/pwa.js +0 -515
  54. package/modules/router.js +0 -769
  55. package/modules/seo.js +0 -501
  56. package/modules/store.js +0 -409
  57. package/modules/templates-manager.js +0 -89
  58. package/modules/test-utils.js +0 -532
  59. package/test/auto-import.test.js +0 -110
  60. package/test/cache.test.js +0 -55
  61. package/test/composables.test.js +0 -103
  62. package/test/dynamic-routes.test.js +0 -469
  63. package/test/error-boundary.test.js +0 -126
  64. package/test/fetch.test.js +0 -100
  65. package/test/file-router.test.js +0 -55
  66. package/test/forms.test.js +0 -203
  67. package/test/i18n.test.js +0 -188
  68. package/test/layouts.test.js +0 -395
  69. package/test/link.test.js +0 -189
  70. package/test/meta.test.js +0 -146
  71. package/test/odoo-rpc.test.js +0 -547
  72. package/test/pwa.test.js +0 -154
  73. package/test/router-guards.test.js +0 -229
  74. package/test/router.test.js +0 -77
  75. package/test/seo.test.js +0 -353
  76. package/test/store.test.js +0 -476
  77. package/test/templates-manager.test.js +0 -83
  78. package/test/test-utils.test.js +0 -314
  79. package/vite/plugin.js +0 -290
  80. 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
- })