1- import { describe , it , expect , vi , beforeEach } from 'vitest' ;
1+ import { describe , it , expect } from 'vitest' ;
22import { 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 ────────────────────────────────────────────────
276describe ( '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 ───────────────────────────────────────────────────────────
16726describe ( '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- } ) ;
0 commit comments