Skip to content

Commit e7fc98d

Browse files
committed
feat: implement API service methods and corresponding tests for ModuleArticleService, ModuleDataService, ModuleGalleryService, and BaseRestService
1 parent 4f687e7 commit e7fc98d

12 files changed

+235
-37
lines changed
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import { ApiService } from './api-services';
22

33
describe('ApiService', () => {
4-
const api = new ApiService({ apiBaseUrl: 'https://mixcore.net' });
4+
const config = {
5+
apiBaseUrl: 'https://mixcore.net',
6+
apiKey: process.env.MIXCORE_API_KEY || '',
7+
};
8+
const service = new ApiService(config);
59

610
it('should instantiate with config', () => {
7-
expect(api).toBeInstanceOf(ApiService);
11+
expect(service).toBeInstanceOf(ApiService);
812
});
913

1014
it('should GET page content (public endpoint)', async () => {
11-
const data = await api.get('/api/v2/rest/mixcore/page-content');
15+
const data = await service.get('/api/v2/rest/mixcore/page-content');
1216
expect(data).toBeDefined();
1317
// Optionally check for paging structure or items
1418
});
1519

1620
it('should fail login with invalid credentials (POST)', async () => {
1721
try {
18-
await api.post('/api/v2/rest/auth/user/login', {
22+
await service.post('/api/v2/rest/auth/user/login', {
1923
username: 'invalid',
2024
password: 'invalid',
2125
});
@@ -25,4 +29,11 @@ describe('ApiService', () => {
2529
expect(err).toBeDefined();
2630
}
2731
});
32+
33+
it('should GET a valid endpoint', async () => {
34+
const data = await service.get('/api/v2/rest/mixcore/module-data/get-module-data', { moduleId: '1' });
35+
expect(data).toBeDefined();
36+
});
37+
38+
// Note: POST and DELETE require valid credentials and real data
2839
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ModuleArticleService } from './module-article-services';
2+
3+
const config = {
4+
apiBaseUrl: 'https://mixcore.net',
5+
apiKey: process.env.MIXCORE_API_KEY || '',
6+
};
7+
8+
describe('ModuleArticleService', () => {
9+
const service = new ModuleArticleService(config);
10+
11+
it('fetchArticles should return an array', async () => {
12+
const articles = await service.fetchArticles('1');
13+
expect(Array.isArray(articles)).toBe(true);
14+
});
15+
16+
// Note: createArticle and deleteArticle require valid credentials and real data
17+
});

packages/apis/src/module-article-services.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,16 @@ export class ModuleArticleService {
2626
* @param params - Optional query params
2727
*/
2828
async fetchArticles(moduleId: string, params?: Record<string, any>): Promise<any[]> {
29-
// TODO: Implement API call
30-
return [];
29+
const url = new URL('/api/v2/rest/mixcore/module-article/get-module-article', this.config.apiBaseUrl);
30+
url.searchParams.append('moduleId', String(moduleId));
31+
if (params) {
32+
Object.entries(params).forEach(([k, v]) => url.searchParams.append(k, String(v)));
33+
}
34+
const res = await fetch(url.toString(), {
35+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
36+
});
37+
if (!res.ok) throw new Error(`GET ${url}: ${res.status} ${res.statusText}`);
38+
return res.json();
3139
}
3240

3341
/**
@@ -36,8 +44,17 @@ export class ModuleArticleService {
3644
* @param data - Article data
3745
*/
3846
async createArticle(moduleId: string, data: any): Promise<any> {
39-
// TODO: Implement create logic
40-
return {};
47+
const url = new URL(`/api/v2/rest/mixcore/module-article/${moduleId}`, this.config.apiBaseUrl);
48+
const res = await fetch(url.toString(), {
49+
method: 'POST',
50+
headers: {
51+
'Content-Type': 'application/json',
52+
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
53+
},
54+
body: JSON.stringify(data),
55+
});
56+
if (!res.ok) throw new Error(`POST ${url}: ${res.status} ${res.statusText}`);
57+
return res.json();
4158
}
4259

4360
/**
@@ -46,7 +63,12 @@ export class ModuleArticleService {
4663
* @param articleId - The article identifier
4764
*/
4865
async deleteArticle(moduleId: string, articleId: string): Promise<boolean> {
49-
// TODO: Implement delete logic
66+
const url = new URL(`/api/v2/rest/mixcore/module-article/${moduleId}/${articleId}`, this.config.apiBaseUrl);
67+
const res = await fetch(url.toString(), {
68+
method: 'DELETE',
69+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
70+
});
71+
if (!res.ok) throw new Error(`DELETE ${url}: ${res.status} ${res.statusText}`);
5072
return true;
5173
}
5274
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ModuleDataService } from './module-data-services';
2+
3+
const config = {
4+
apiBaseUrl: 'https://mixcore.net',
5+
apiKey: process.env.MIXCORE_API_KEY || '',
6+
};
7+
8+
describe('ModuleDataService', () => {
9+
const service = new ModuleDataService(config);
10+
11+
it('fetchDataItems should return an array', async () => {
12+
const items = await service.fetchDataItems('1');
13+
expect(Array.isArray(items)).toBe(true);
14+
});
15+
16+
// Note: createDataItem and deleteDataItem require valid credentials and real data
17+
});

packages/apis/src/module-data-services.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/**
32
* ModuleDataService
43
* Framework-agnostic, TypeScript-native data API client for Mixcore
@@ -26,8 +25,16 @@ export class ModuleDataService {
2625
* @param params - Optional query params
2726
*/
2827
async fetchDataItems(moduleId: string, params?: Record<string, any>): Promise<any[]> {
29-
// TODO: Implement API call
30-
return [];
28+
const url = new URL('/api/v2/rest/mixcore/module-data/get-module-data', this.config.apiBaseUrl);
29+
url.searchParams.append('moduleId', String(moduleId));
30+
if (params) {
31+
Object.entries(params).forEach(([k, v]) => url.searchParams.append(k, String(v)));
32+
}
33+
const res = await fetch(url.toString(), {
34+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
35+
});
36+
if (!res.ok) throw new Error(`GET ${url}: ${res.status} ${res.statusText}`);
37+
return res.json();
3138
}
3239

3340
/**
@@ -36,8 +43,17 @@ export class ModuleDataService {
3643
* @param data - Data to create
3744
*/
3845
async createDataItem(moduleId: string, data: any): Promise<any> {
39-
// TODO: Implement create logic
40-
return {};
46+
const url = new URL(`/api/v2/rest/mixcore/module-data/${moduleId}`, this.config.apiBaseUrl);
47+
const res = await fetch(url.toString(), {
48+
method: 'POST',
49+
headers: {
50+
'Content-Type': 'application/json',
51+
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
52+
},
53+
body: JSON.stringify(data),
54+
});
55+
if (!res.ok) throw new Error(`POST ${url}: ${res.status} ${res.statusText}`);
56+
return res.json();
4157
}
4258

4359
/**
@@ -46,7 +62,12 @@ export class ModuleDataService {
4662
* @param itemId - The data item identifier
4763
*/
4864
async deleteDataItem(moduleId: string, itemId: string): Promise<boolean> {
49-
// TODO: Implement delete logic
65+
const url = new URL(`/api/v2/rest/mixcore/module-data/${moduleId}/${itemId}`, this.config.apiBaseUrl);
66+
const res = await fetch(url.toString(), {
67+
method: 'DELETE',
68+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
69+
});
70+
if (!res.ok) throw new Error(`DELETE ${url}: ${res.status} ${res.statusText}`);
5071
return true;
5172
}
5273
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ModuleGalleryService } from './module-gallery-services';
2+
3+
const config = {
4+
apiBaseUrl: 'https://mixcore.net',
5+
apiKey: process.env.MIXCORE_API_KEY || '',
6+
};
7+
8+
describe('ModuleGalleryService', () => {
9+
const service = new ModuleGalleryService(config);
10+
11+
it('fetchGalleryItems should return an array', async () => {
12+
const items = await service.fetchGalleryItems('1');
13+
expect(Array.isArray(items)).toBe(true);
14+
});
15+
16+
// Note: uploadGalleryItem and deleteGalleryItem require valid credentials and real data
17+
});

packages/apis/src/module-gallery-services.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@ export class ModuleGalleryService {
2626
* @param params - Optional query params
2727
*/
2828
async fetchGalleryItems(moduleId: string, params?: Record<string, any>): Promise<any[]> {
29-
// TODO: Implement API call using fetch/axios, etc.
30-
// Example: `${this.config.apiBaseUrl}/modules/${moduleId}/gallery`
31-
return [];
29+
const url = new URL('/api/v2/rest/mixcore/module-gallery/get-module-gallery', this.config.apiBaseUrl);
30+
url.searchParams.append('moduleId', String(moduleId));
31+
if (params) {
32+
Object.entries(params).forEach(([k, v]) => url.searchParams.append(k, String(v)));
33+
}
34+
const res = await fetch(url.toString(), {
35+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
36+
});
37+
if (!res.ok) throw new Error(`GET ${url}: ${res.status} ${res.statusText}`);
38+
return res.json();
3239
}
3340

3441
/**
@@ -37,9 +44,22 @@ export class ModuleGalleryService {
3744
* @param file - File or data to upload
3845
* @param meta - Optional metadata
3946
*/
40-
async uploadGalleryItem(moduleId: string, file: any, meta?: Record<string, any>): Promise<any> {
41-
// TODO: Implement upload logic
42-
return {};
47+
async uploadGalleryItem(moduleId: string, file: File | Blob, meta?: Record<string, any>): Promise<any> {
48+
const url = new URL(`/api/v2/rest/mixcore/module-gallery/${moduleId}/upload`, this.config.apiBaseUrl);
49+
const formData = new FormData();
50+
formData.append('file', file);
51+
if (meta) {
52+
Object.entries(meta).forEach(([k, v]) => formData.append(k, String(v)));
53+
}
54+
const res = await fetch(url.toString(), {
55+
method: 'POST',
56+
headers: {
57+
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
58+
},
59+
body: formData,
60+
});
61+
if (!res.ok) throw new Error(`POST ${url}: ${res.status} ${res.statusText}`);
62+
return res.json();
4363
}
4464

4565
/**
@@ -48,7 +68,12 @@ export class ModuleGalleryService {
4868
* @param itemId - The gallery item identifier
4969
*/
5070
async deleteGalleryItem(moduleId: string, itemId: string): Promise<boolean> {
51-
// TODO: Implement delete logic
71+
const url = new URL(`/api/v2/rest/mixcore/module-gallery/${moduleId}/${itemId}`, this.config.apiBaseUrl);
72+
const res = await fetch(url.toString(), {
73+
method: 'DELETE',
74+
headers: this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : undefined,
75+
});
76+
if (!res.ok) throw new Error(`DELETE ${url}: ${res.status} ${res.statusText}`);
5277
return true;
5378
}
5479
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { BaseRestService } from './base-rest-service';
2+
import { BaseServiceConfig } from './base-service';
3+
4+
describe('BaseRestService', () => {
5+
class TestRestService extends BaseRestService {
6+
async get<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
7+
return { endpoint, params } as T;
8+
}
9+
async post<T = any>(endpoint: string, data: any): Promise<T> {
10+
return { endpoint, data } as T;
11+
}
12+
async delete<T = any>(endpoint: string): Promise<T> {
13+
return { endpoint } as T;
14+
}
15+
handleError(error: any): void {}
16+
}
17+
18+
it('should construct and call methods', async () => {
19+
const config: BaseServiceConfig = { apiBaseUrl: 'https://mixcore.net' };
20+
const service = new TestRestService(config);
21+
expect(service).toBeInstanceOf(TestRestService);
22+
expect(await service.get('/test')).toHaveProperty('endpoint', '/test');
23+
expect(await service.post('/test', { foo: 'bar' })).toHaveProperty('endpoint', '/test');
24+
expect(await service.delete('/test')).toHaveProperty('endpoint', '/test');
25+
});
26+
});

packages/base/src/base-service.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { BaseService, BaseServiceConfig } from './base-service';
22

33
describe('BaseService', () => {
44
class TestService extends BaseService {
5-
handleError(error: any) {}
5+
handleError(error: any): void {
6+
// Custom error handling for test
7+
}
68
}
7-
it('should instantiate with config', () => {
8-
const svc = new TestService({ apiBaseUrl: 'https://api.example.com' });
9-
expect(svc).toBeInstanceOf(TestService);
9+
10+
it('should construct with config', () => {
11+
const config: BaseServiceConfig = { apiBaseUrl: 'https://mixcore.net' };
12+
const service = new TestService(config);
13+
expect(service).toBeInstanceOf(TestService);
1014
});
1115
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { CommonService } from './common-services';
2+
3+
describe('CommonService', () => {
4+
const config = {
5+
apiBaseUrl: 'https://mixcore.net',
6+
getApiResult: jest.fn(async (req) => ({ ok: true, ...req })),
7+
getRestApiResult: jest.fn(async (req) => ({ ok: true, ...req })),
8+
getAnonymousApiResult: jest.fn(async (req) => ({ ok: true, ...req })),
9+
onAlert: jest.fn(),
10+
};
11+
const service = new CommonService(config);
12+
13+
it('loadJArrayData should call getAnonymousApiResult', async () => {
14+
await service.loadJArrayData('test');
15+
expect(config.getAnonymousApiResult).toHaveBeenCalled();
16+
});
17+
18+
it('showAlertMsg should call onAlert', async () => {
19+
await service.showAlertMsg('Title', 'Message');
20+
expect(config.onAlert).toHaveBeenCalledWith('Title', 'Message');
21+
});
22+
});

0 commit comments

Comments
 (0)