Skip to content

Commit 01809bc

Browse files
test(sdk): reorganize tests into per-module folders
Co-authored-by: marioserrano09 <5221275+marioserrano09@users.noreply.github.com>
1 parent 7621850 commit 01809bc

File tree

6 files changed

+175
-164
lines changed

6 files changed

+175
-164
lines changed
Lines changed: 3 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
1-
import { describe, it, expect, vi, beforeEach } from 'vitest';
1+
import { describe, it, expect } from 'vitest';
22
import { DynamiaClient, DynamiaApiError } from '../src/index.js';
3-
// ── Raw response helper ───────────────────────────────────────────────────────
4-
function rawPageResponse<T>(items: T[], page = 1, pageSize = 50) {
5-
return {
6-
data: items,
7-
pageable: { firstResult: 0, page, pageSize, pagesNumber: 1, totalSize: items.length },
8-
response: 'OK',
9-
};
10-
}
11-
// ── Fetch mock helpers ────────────────────────────────────────────────────────
12-
function mockFetch(status: number, body: unknown, contentType = 'application/json') {
13-
return vi.fn().mockResolvedValue({
14-
ok: status >= 200 && status < 300,
15-
status,
16-
statusText: status === 200 ? 'OK' : 'Error',
17-
headers: { get: (key: string) => (key === 'content-type' ? contentType : null) },
18-
json: () => Promise.resolve(body),
19-
text: () => Promise.resolve(String(body)),
20-
blob: () => Promise.resolve(new Blob()),
21-
} as unknown as Response);
22-
}
23-
function makeClient(fetchMock: ReturnType<typeof vi.fn>) {
24-
return new DynamiaClient({ baseUrl: 'https://app.example.com', token: 'test-token', fetch: fetchMock });
25-
}
3+
import { mockFetch, makeClient, rawPageResponse } from './helpers.js';
4+
265
// ── DynamiaClient construction ────────────────────────────────────────────────
276
describe('DynamiaClient', () => {
287
it('creates an instance with all sub-APIs', () => {
@@ -43,126 +22,6 @@ describe('DynamiaClient', () => {
4322
expect(client.crudService('com.example.Book')).toBeDefined();
4423
});
4524
});
46-
// ── MetadataApi ───────────────────────────────────────────────────────────────
47-
describe('MetadataApi', () => {
48-
let fetchMock: ReturnType<typeof vi.fn>;
49-
let client: DynamiaClient;
50-
beforeEach(() => {
51-
fetchMock = mockFetch(200, { name: 'Demo App', version: '1.0.0' });
52-
client = makeClient(fetchMock);
53-
});
54-
it('getApp() calls GET /api/app/metadata', async () => {
55-
const result = await client.metadata.getApp();
56-
const [url] = fetchMock.mock.calls[0] as [string];
57-
expect(url).toContain('/api/app/metadata');
58-
expect(result.name).toBe('Demo App');
59-
});
60-
it('getNavigation() calls GET /api/app/metadata/navigation', async () => {
61-
fetchMock.mockResolvedValue({
62-
ok: true, status: 200,
63-
headers: { get: () => 'application/json' },
64-
json: () => Promise.resolve({ modules: [] }),
65-
} as unknown as Response);
66-
const result = await client.metadata.getNavigation();
67-
const [url] = fetchMock.mock.calls[0] as [string];
68-
expect(url).toContain('/api/app/metadata/navigation');
69-
expect(result.modules).toEqual([]);
70-
});
71-
it('getEntity(className) encodes the class name in the URL', async () => {
72-
await client.metadata.getEntity('com.example.Book');
73-
const [url] = fetchMock.mock.calls[0] as [string];
74-
expect(url).toContain('/api/app/metadata/entities/com.example.Book');
75-
});
76-
});
77-
// ── CrudResourceApi — normalisation ──────────────────────────────────────────
78-
describe('CrudResourceApi', () => {
79-
it('findAll() calls GET /api/{path}', async () => {
80-
const fetchMock = mockFetch(200, rawPageResponse([]));
81-
const client = makeClient(fetchMock);
82-
await client.crud('store/books').findAll();
83-
const [url] = fetchMock.mock.calls[0] as [string];
84-
expect(url).toContain('/api/store/books');
85-
});
86-
it('findAll() attaches query params', async () => {
87-
const fetchMock = mockFetch(200, rawPageResponse([]));
88-
const client = makeClient(fetchMock);
89-
await client.crud('books').findAll({ page: 2, size: 10 });
90-
const [url] = fetchMock.mock.calls[0] as [string];
91-
expect(url).toContain('page=2');
92-
expect(url).toContain('size=10');
93-
});
94-
it('findAll() normalises raw pageable envelope into CrudListResult', async () => {
95-
const books = [
96-
{ id: 13, title: 'Clean Code', stockStatus: 'OUT_STOCK', price: 25.00 },
97-
{ id: 14, title: 'Design Patterns', stockStatus: 'IN_STOCK', price: 0.00 },
98-
];
99-
const fetchMock = mockFetch(200, rawPageResponse(books, 1, 50));
100-
const client = makeClient(fetchMock);
101-
const result = await client.crud('library/books').findAll();
102-
expect(result.content).toHaveLength(2);
103-
expect(result.content[0]).toMatchObject({ id: 13, title: 'Clean Code' });
104-
expect(result.total).toBe(2);
105-
expect(result.page).toBe(1);
106-
expect(result.pageSize).toBe(50);
107-
expect(result.totalPages).toBe(1);
108-
});
109-
it('findAll() handles null pageable (non-paginated flat response)', async () => {
110-
// Java: @JsonInclude(NON_NULL) — pageable is absent when not paginated
111-
const rawResp = {
112-
data: [{ id: 1, title: 'Book A' }, { id: 2, title: 'Book B' }],
113-
pageable: null,
114-
response: 'OK',
115-
};
116-
const fetchMock = mockFetch(200, rawResp);
117-
const client = makeClient(fetchMock);
118-
119-
const result = await client.crud('books').findAll();
120-
expect(result.content).toHaveLength(2);
121-
expect(result.total).toBe(2); // falls back to data.length
122-
expect(result.page).toBe(1);
123-
expect(result.totalPages).toBe(1);
124-
});
125-
126-
it('findAll() handles multi-page results correctly', async () => {
127-
const rawResp = {
128-
data: [{ id: 1, title: 'Book A' }],
129-
pageable: { firstResult: 0, page: 2, pageSize: 10, pagesNumber: 5, totalSize: 48 },
130-
response: 'OK',
131-
};
132-
const fetchMock = mockFetch(200, rawResp);
133-
const client = makeClient(fetchMock);
134-
const result = await client.crud('books').findAll({ page: 2, size: 10 });
135-
expect(result.page).toBe(2);
136-
expect(result.pageSize).toBe(10);
137-
expect(result.totalPages).toBe(5);
138-
expect(result.total).toBe(48);
139-
});
140-
it('findById() calls GET /api/{path}/{id}', async () => {
141-
const fetchMock = mockFetch(200, { id: 42, title: 'Clean Code' });
142-
const client = makeClient(fetchMock);
143-
const book = await client.crud<{ id: number; title: string }>('books').findById(42);
144-
const [url] = fetchMock.mock.calls[0] as [string];
145-
expect(url).toContain('/api/books/42');
146-
expect(book.title).toBe('Clean Code');
147-
});
148-
it('create() calls POST /api/{path}', async () => {
149-
const fetchMock = mockFetch(200, { id: 1, title: 'New Book' });
150-
const client = makeClient(fetchMock);
151-
await client.crud('books').create({ title: 'New Book' });
152-
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
153-
expect(url).toContain('/api/books');
154-
expect(init.method).toBe('POST');
155-
});
156-
it('delete() calls DELETE /api/{path}/{id}', async () => {
157-
const fetchMock = mockFetch(204, null);
158-
fetchMock.mockResolvedValue({ ok: true, status: 204, headers: { get: () => '' } } as unknown as Response);
159-
const client = makeClient(fetchMock);
160-
await client.crud('books').delete(5);
161-
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
162-
expect(url).toContain('/api/books/5');
163-
expect(init.method).toBe('DELETE');
164-
});
165-
});
16625
// ── DynamiaApiError ───────────────────────────────────────────────────────────
16726
describe('DynamiaApiError', () => {
16827
it('is thrown on non-2xx responses', async () => {
@@ -199,23 +58,3 @@ describe('Authentication', () => {
19958
expect((init.headers as Record<string, string>)['Authorization']).toBe('Basic ' + btoa('admin:pass'));
20059
});
20160
});
202-
// ── FilesApi ──────────────────────────────────────────────────────────────────
203-
describe('FilesApi', () => {
204-
it('getUrl() returns a URL with uuid query param', () => {
205-
const client = makeClient(mockFetch(200, {}));
206-
const url = client.files.getUrl('photo.png', 'uuid-123');
207-
expect(url).toContain('/storage/photo.png');
208-
expect(url).toContain('uuid=uuid-123');
209-
});
210-
});
211-
// ── ActionsApi ────────────────────────────────────────────────────────────────
212-
describe('ActionsApi', () => {
213-
it('executeGlobal() calls POST /api/app/metadata/actions/{action}', async () => {
214-
const fetchMock = mockFetch(200, { message: 'ok', status: 'SUCCESS', code: 200 });
215-
const client = makeClient(fetchMock);
216-
await client.actions.executeGlobal('sendEmail', { params: { to: 'a@b.com' } });
217-
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
218-
expect(url).toContain('/api/app/metadata/actions/sendEmail');
219-
expect(init.method).toBe('POST');
220-
});
221-
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { mockFetch, makeClient, rawPageResponse } from '../helpers.js';
3+
4+
describe('CrudResourceApi', () => {
5+
it('findAll() calls GET /api/{path}', async () => {
6+
const fetchMock = mockFetch(200, rawPageResponse([]));
7+
const client = makeClient(fetchMock);
8+
await client.crud('store/books').findAll();
9+
const [url] = fetchMock.mock.calls[0] as [string];
10+
expect(url).toContain('/api/store/books');
11+
});
12+
it('findAll() attaches query params', async () => {
13+
const fetchMock = mockFetch(200, rawPageResponse([]));
14+
const client = makeClient(fetchMock);
15+
await client.crud('books').findAll({ page: 2, size: 10 });
16+
const [url] = fetchMock.mock.calls[0] as [string];
17+
expect(url).toContain('page=2');
18+
expect(url).toContain('size=10');
19+
});
20+
it('findAll() normalises raw pageable envelope into CrudListResult', async () => {
21+
const books = [
22+
{ id: 13, title: 'Clean Code', stockStatus: 'OUT_STOCK', price: 25.00 },
23+
{ id: 14, title: 'Design Patterns', stockStatus: 'IN_STOCK', price: 0.00 },
24+
];
25+
const fetchMock = mockFetch(200, rawPageResponse(books, 1, 50));
26+
const client = makeClient(fetchMock);
27+
const result = await client.crud('library/books').findAll();
28+
expect(result.content).toHaveLength(2);
29+
expect(result.content[0]).toMatchObject({ id: 13, title: 'Clean Code' });
30+
expect(result.total).toBe(2);
31+
expect(result.page).toBe(1);
32+
expect(result.pageSize).toBe(50);
33+
expect(result.totalPages).toBe(1);
34+
});
35+
it('findAll() handles null pageable (non-paginated flat response)', async () => {
36+
const rawResp = {
37+
data: [{ id: 1, title: 'Book A' }, { id: 2, title: 'Book B' }],
38+
pageable: null,
39+
response: 'OK',
40+
};
41+
const fetchMock = mockFetch(200, rawResp);
42+
const client = makeClient(fetchMock);
43+
const result = await client.crud('books').findAll();
44+
expect(result.content).toHaveLength(2);
45+
expect(result.total).toBe(2);
46+
expect(result.page).toBe(1);
47+
expect(result.totalPages).toBe(1);
48+
});
49+
it('findAll() handles multi-page results correctly', async () => {
50+
const rawResp = {
51+
data: [{ id: 1, title: 'Book A' }],
52+
pageable: { firstResult: 0, page: 2, pageSize: 10, pagesNumber: 5, totalSize: 48 },
53+
response: 'OK',
54+
};
55+
const fetchMock = mockFetch(200, rawResp);
56+
const client = makeClient(fetchMock);
57+
const result = await client.crud('books').findAll({ page: 2, size: 10 });
58+
expect(result.page).toBe(2);
59+
expect(result.pageSize).toBe(10);
60+
expect(result.totalPages).toBe(5);
61+
expect(result.total).toBe(48);
62+
});
63+
it('findById() calls GET /api/{path}/{id}', async () => {
64+
const fetchMock = mockFetch(200, { id: 42, title: 'Clean Code' });
65+
const client = makeClient(fetchMock);
66+
const book = await client.crud<{ id: number; title: string }>('books').findById(42);
67+
const [url] = fetchMock.mock.calls[0] as [string];
68+
expect(url).toContain('/api/books/42');
69+
expect(book.title).toBe('Clean Code');
70+
});
71+
it('create() calls POST /api/{path}', async () => {
72+
const fetchMock = mockFetch(200, { id: 1, title: 'New Book' });
73+
const client = makeClient(fetchMock);
74+
await client.crud('books').create({ title: 'New Book' });
75+
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
76+
expect(url).toContain('/api/books');
77+
expect(init.method).toBe('POST');
78+
});
79+
it('delete() calls DELETE /api/{path}/{id}', async () => {
80+
const fetchMock = mockFetch(204, null);
81+
fetchMock.mockResolvedValue({ ok: true, status: 204, headers: { get: () => '' } } as unknown as Response);
82+
const client = makeClient(fetchMock);
83+
await client.crud('books').delete(5);
84+
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
85+
expect(url).toContain('/api/books/5');
86+
expect(init.method).toBe('DELETE');
87+
});
88+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { mockFetch, makeClient } from '../helpers.js';
3+
4+
describe('FilesApi', () => {
5+
it('getUrl() returns a URL with uuid query param', () => {
6+
const client = makeClient(mockFetch(200, {}));
7+
const url = client.files.getUrl('photo.png', 'uuid-123');
8+
expect(url).toContain('/storage/photo.png');
9+
expect(url).toContain('uuid=uuid-123');
10+
});
11+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { vi } from 'vitest';
2+
import { DynamiaClient } from '../src/index.js';
3+
4+
export function rawPageResponse<T>(items: T[], page = 1, pageSize = 50) {
5+
return {
6+
data: items,
7+
pageable: { firstResult: 0, page, pageSize, pagesNumber: 1, totalSize: items.length },
8+
response: 'OK',
9+
};
10+
}
11+
12+
export function mockFetch(status: number, body: unknown, contentType = 'application/json') {
13+
return vi.fn().mockResolvedValue({
14+
ok: status >= 200 && status < 300,
15+
status,
16+
statusText: status === 200 ? 'OK' : 'Error',
17+
headers: { get: (key: string) => (key === 'content-type' ? contentType : null) },
18+
json: () => Promise.resolve(body),
19+
text: () => Promise.resolve(String(body)),
20+
blob: () => Promise.resolve(new Blob()),
21+
} as unknown as Response);
22+
}
23+
24+
export function makeClient(fetchMock: ReturnType<typeof vi.fn>) {
25+
return new DynamiaClient({ baseUrl: 'https://app.example.com', token: 'test-token', fetch: fetchMock });
26+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { mockFetch, makeClient } from '../helpers.js';
3+
4+
describe('ActionsApi', () => {
5+
it('executeGlobal() calls POST /api/app/metadata/actions/{action}', async () => {
6+
const fetchMock = mockFetch(200, { message: 'ok', status: 'SUCCESS', code: 200 });
7+
const client = makeClient(fetchMock);
8+
await client.actions.executeGlobal('sendEmail', { params: { to: 'a@b.com' } });
9+
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
10+
expect(url).toContain('/api/app/metadata/actions/sendEmail');
11+
expect(init.method).toBe('POST');
12+
});
13+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
import { DynamiaClient } from '../../src/index.js';
3+
import { mockFetch, makeClient } from '../helpers.js';
4+
5+
describe('MetadataApi', () => {
6+
let fetchMock: ReturnType<typeof vi.fn>;
7+
let client: DynamiaClient;
8+
beforeEach(() => {
9+
fetchMock = mockFetch(200, { name: 'Demo App', version: '1.0.0' });
10+
client = makeClient(fetchMock);
11+
});
12+
it('getApp() calls GET /api/app/metadata', async () => {
13+
const result = await client.metadata.getApp();
14+
const [url] = fetchMock.mock.calls[0] as [string];
15+
expect(url).toContain('/api/app/metadata');
16+
expect(result.name).toBe('Demo App');
17+
});
18+
it('getNavigation() calls GET /api/app/metadata/navigation', async () => {
19+
fetchMock.mockResolvedValue({
20+
ok: true, status: 200,
21+
headers: { get: () => 'application/json' },
22+
json: () => Promise.resolve({ modules: [] }),
23+
} as unknown as Response);
24+
const result = await client.metadata.getNavigation();
25+
const [url] = fetchMock.mock.calls[0] as [string];
26+
expect(url).toContain('/api/app/metadata/navigation');
27+
expect(result.modules).toEqual([]);
28+
});
29+
it('getEntity(className) encodes the class name in the URL', async () => {
30+
await client.metadata.getEntity('com.example.Book');
31+
const [url] = fetchMock.mock.calls[0] as [string];
32+
expect(url).toContain('/api/app/metadata/entities/com.example.Book');
33+
});
34+
});

0 commit comments

Comments
 (0)