sizebay-core-sdk 1.0.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.
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/publish.yml +48 -0
- package/.github/workflows/tests-on-pr.yml +37 -0
- package/.prettierrc +7 -0
- package/.releaserc.json +32 -0
- package/CHANGELOG.md +13 -0
- package/README.dev.md +184 -0
- package/README.md +115 -0
- package/dist/sizebay-core-sdk.es.js +124 -0
- package/dist/sizebay-core-sdk.umd.js +1 -0
- package/dist/types/src/config/endpoints.d.ts +2 -0
- package/dist/types/src/config/index.d.ts +10 -0
- package/dist/types/src/core/client.d.ts +2 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/modules/ai-image-service.d.ts +8 -0
- package/dist/types/src/modules/index.d.ts +3 -0
- package/dist/types/src/modules/tracker.d.ts +7 -0
- package/dist/types/src/types/ai-image-service.types.d.ts +48 -0
- package/dist/types/src/types/client.types.d.ts +13 -0
- package/dist/types/src/types/event.types.d.ts +13 -0
- package/dist/types/src/types/index.d.ts +4 -0
- package/dist/types/src/types/sdk.types.d.ts +15 -0
- package/dist/types/test/config/config.test.d.ts +1 -0
- package/dist/types/test/core/client.test.d.ts +1 -0
- package/dist/types/test/example.d.ts +1 -0
- package/dist/types/test/index.test.d.ts +1 -0
- package/dist/types/test/jest.setup.d.ts +0 -0
- package/dist/types/test/modules/ai-image-service.test.d.ts +1 -0
- package/dist/types/test/modules/tracker.test.d.ts +1 -0
- package/jest.config.cjs +18 -0
- package/package.json +44 -0
- package/src/config/endpoints.ts +14 -0
- package/src/config/index.ts +42 -0
- package/src/core/client.ts +26 -0
- package/src/index.ts +2 -0
- package/src/modules/ai-image-service.ts +68 -0
- package/src/modules/index.ts +7 -0
- package/src/modules/tracker.ts +37 -0
- package/src/types/ai-image-service.types.ts +52 -0
- package/src/types/client.types.ts +30 -0
- package/src/types/event.types.ts +28 -0
- package/src/types/index.ts +4 -0
- package/src/types/sdk.types.ts +17 -0
- package/test/config/config.test.ts +59 -0
- package/test/core/client.test.ts +32 -0
- package/test/example.ts +53 -0
- package/test/index.test.ts +12 -0
- package/test/jest.setup.ts +8 -0
- package/test/modules/ai-image-service.test.ts +234 -0
- package/test/modules/tracker.test.ts +86 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createClient } from '@src/core/client';
|
|
2
|
+
import * as Types from '@src/types';
|
|
3
|
+
|
|
4
|
+
describe('Index Module', () => {
|
|
5
|
+
it('should export Client', () => {
|
|
6
|
+
expect(createClient).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should export types', () => {
|
|
10
|
+
expect(Types).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { AIImageService } from '@src/modules/ai-image-service';
|
|
2
|
+
import {
|
|
3
|
+
GetSimilarProductsParams,
|
|
4
|
+
GetSimilarProductsResponse,
|
|
5
|
+
GetRecommendedProductSizeParams,
|
|
6
|
+
GetRecommendedProductSizeResponse,
|
|
7
|
+
} from '@src/types/ai-image-service.types';
|
|
8
|
+
|
|
9
|
+
describe('AIImageService', () => {
|
|
10
|
+
// Use a valid base URL for constructing request URLs.
|
|
11
|
+
const fakeEndpoint = 'https://example.com/ai-image-service';
|
|
12
|
+
let fakeConfig: { getEndpoint: (serviceName: string) => string };
|
|
13
|
+
let aiImageService: AIImageService;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Reinitialize the fake configuration for each test.
|
|
17
|
+
fakeConfig = {
|
|
18
|
+
getEndpoint: (serviceName: string) => fakeEndpoint,
|
|
19
|
+
};
|
|
20
|
+
// Cast fakeConfig as any to bypass missing properties required by Config.
|
|
21
|
+
aiImageService = new AIImageService(fakeConfig as any);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
// Reset global.fetch if it was set.
|
|
26
|
+
if (global.fetch && (global.fetch as jest.Mock).mockReset) {
|
|
27
|
+
(global.fetch as jest.Mock).mockReset();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('getSimilarProducts', () => {
|
|
32
|
+
it('should send the GET request with correct query parameters and return the JSON response', async () => {
|
|
33
|
+
const fakeResponseData: GetSimilarProductsResponse = {
|
|
34
|
+
data: [
|
|
35
|
+
{
|
|
36
|
+
id: '1',
|
|
37
|
+
title: 'Product 1',
|
|
38
|
+
productType: 'type',
|
|
39
|
+
link: 'https://example.com/product/1',
|
|
40
|
+
imageLink: 'https://example.com/product/1.jpg',
|
|
41
|
+
gender: 'unisex',
|
|
42
|
+
availability: 'in stock',
|
|
43
|
+
productHash: 'abc',
|
|
44
|
+
price: '100',
|
|
45
|
+
salePrice: '80',
|
|
46
|
+
itemGroupId: 'group1',
|
|
47
|
+
brand: 'BrandA',
|
|
48
|
+
color: 'red',
|
|
49
|
+
gtin: '123456',
|
|
50
|
+
additionalImageLinks: ['https://example.com/product/1_2.jpg'],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
page: 1,
|
|
54
|
+
perPage: 10,
|
|
55
|
+
total: 1,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const fakeFetchResponse = {
|
|
59
|
+
ok: true,
|
|
60
|
+
json: jest.fn().mockResolvedValue(fakeResponseData),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
64
|
+
|
|
65
|
+
const params: GetSimilarProductsParams = {
|
|
66
|
+
tenantId: 1,
|
|
67
|
+
permalink: 'https://example.com/product/1',
|
|
68
|
+
collectionName: 'collectionA',
|
|
69
|
+
sessionId: 123,
|
|
70
|
+
page: 1,
|
|
71
|
+
perPage: 10,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = await aiImageService.getSimilarProducts(params);
|
|
75
|
+
|
|
76
|
+
// Build expected URL with query parameters.
|
|
77
|
+
const expectedUrl = new URL(`${fakeEndpoint}/recommendations/similar`);
|
|
78
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
79
|
+
if (value !== undefined) {
|
|
80
|
+
expectedUrl.searchParams.append(key, String(value));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(global.fetch).toHaveBeenCalledWith(expectedUrl.toString(), {
|
|
85
|
+
method: 'GET',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
});
|
|
88
|
+
expect(result).toEqual(fakeResponseData);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should throw an error if the response is not ok', async () => {
|
|
92
|
+
const errorText = 'Bad Request';
|
|
93
|
+
const fakeFetchResponse = {
|
|
94
|
+
ok: false,
|
|
95
|
+
status: 400,
|
|
96
|
+
text: jest.fn().mockResolvedValue(errorText),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
100
|
+
|
|
101
|
+
const params: GetSimilarProductsParams = {
|
|
102
|
+
tenantId: 1,
|
|
103
|
+
permalink: 'https://example.com/product/1',
|
|
104
|
+
collectionName: 'collectionA',
|
|
105
|
+
sessionId: 123,
|
|
106
|
+
page: 1,
|
|
107
|
+
perPage: 10,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
await expect(aiImageService.getSimilarProducts(params)).rejects.toThrow(
|
|
111
|
+
`Request error: 400 - ${errorText}`,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should throw an error if fetch fails', async () => {
|
|
116
|
+
const errorMessage = 'Network error';
|
|
117
|
+
global.fetch = jest.fn().mockRejectedValue(new Error(errorMessage));
|
|
118
|
+
|
|
119
|
+
const params: GetSimilarProductsParams = {
|
|
120
|
+
tenantId: 1,
|
|
121
|
+
permalink: 'https://example.com/product/1',
|
|
122
|
+
collectionName: 'collectionA',
|
|
123
|
+
sessionId: 123,
|
|
124
|
+
page: 1,
|
|
125
|
+
perPage: 10,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await expect(aiImageService.getSimilarProducts(params)).rejects.toThrow(
|
|
129
|
+
`Error fetching similar products: ${errorMessage}`,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('getRecommendedProductSize', () => {
|
|
135
|
+
it('should send the POST request with the correct payload and return the JSON response', async () => {
|
|
136
|
+
const fakeResponseData: GetRecommendedProductSizeResponse = {
|
|
137
|
+
recommendedSize: 'M',
|
|
138
|
+
someOtherKey: 123,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const fakeFetchResponse = {
|
|
142
|
+
ok: true,
|
|
143
|
+
json: jest.fn().mockResolvedValue(fakeResponseData),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
147
|
+
|
|
148
|
+
const payload: GetRecommendedProductSizeParams = {
|
|
149
|
+
products: [
|
|
150
|
+
{
|
|
151
|
+
permalink: 'https://example.com/product/1',
|
|
152
|
+
analysisResponse: null,
|
|
153
|
+
productInfo: null,
|
|
154
|
+
recommendedSize: 'M',
|
|
155
|
+
recommendedComposedMeasure: null,
|
|
156
|
+
composedMeasureOrder: null,
|
|
157
|
+
productGender: 'unisex',
|
|
158
|
+
profileName: 'profile1',
|
|
159
|
+
sizeSystem: 'US',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const result = await aiImageService.getRecommendedProductSize(payload);
|
|
165
|
+
|
|
166
|
+
const expectedUrl = `${fakeEndpoint}/recommendations/size-by-products`;
|
|
167
|
+
|
|
168
|
+
expect(global.fetch).toHaveBeenCalledWith(expectedUrl, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: JSON.stringify(payload),
|
|
172
|
+
});
|
|
173
|
+
expect(result).toEqual(fakeResponseData);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should throw an error if the response is not ok', async () => {
|
|
177
|
+
const errorText = 'Server Error';
|
|
178
|
+
const fakeFetchResponse = {
|
|
179
|
+
ok: false,
|
|
180
|
+
status: 500,
|
|
181
|
+
text: jest.fn().mockResolvedValue(errorText),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
185
|
+
|
|
186
|
+
const payload: GetRecommendedProductSizeParams = {
|
|
187
|
+
products: [
|
|
188
|
+
{
|
|
189
|
+
permalink: 'https://example.com/product/1',
|
|
190
|
+
analysisResponse: null,
|
|
191
|
+
productInfo: null,
|
|
192
|
+
recommendedSize: 'M',
|
|
193
|
+
recommendedComposedMeasure: null,
|
|
194
|
+
composedMeasureOrder: null,
|
|
195
|
+
productGender: 'unisex',
|
|
196
|
+
profileName: 'profile1',
|
|
197
|
+
sizeSystem: 'US',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
await expect(
|
|
203
|
+
aiImageService.getRecommendedProductSize(payload),
|
|
204
|
+
).rejects.toThrow(`Request error: 500 - ${errorText}`);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should throw an error if fetch fails', async () => {
|
|
208
|
+
const errorMessage = 'Network error';
|
|
209
|
+
global.fetch = jest.fn().mockRejectedValue(new Error(errorMessage));
|
|
210
|
+
|
|
211
|
+
const payload: GetRecommendedProductSizeParams = {
|
|
212
|
+
products: [
|
|
213
|
+
{
|
|
214
|
+
permalink: 'https://example.com/product/1',
|
|
215
|
+
analysisResponse: null,
|
|
216
|
+
productInfo: null,
|
|
217
|
+
recommendedSize: 'M',
|
|
218
|
+
recommendedComposedMeasure: null,
|
|
219
|
+
composedMeasureOrder: null,
|
|
220
|
+
productGender: 'unisex',
|
|
221
|
+
profileName: 'profile1',
|
|
222
|
+
sizeSystem: 'US',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await expect(
|
|
228
|
+
aiImageService.getRecommendedProductSize(payload),
|
|
229
|
+
).rejects.toThrow(
|
|
230
|
+
`Error fetching recommended product size: ${errorMessage}`,
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Tracker } from '@src/modules/tracker';
|
|
2
|
+
import { TrackData } from '@src/types';
|
|
3
|
+
|
|
4
|
+
describe('Tracker', () => {
|
|
5
|
+
const fakeEndpoint = 'https://example.com/tracker';
|
|
6
|
+
const fakeConfig = {
|
|
7
|
+
getEndpoint: jest.fn().mockReturnValue(fakeEndpoint),
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let tracker: Tracker;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tracker = new Tracker(fakeConfig as any);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.resetAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should send the event and return the JSON response', async () => {
|
|
21
|
+
const fakeResponseData = { success: true };
|
|
22
|
+
|
|
23
|
+
const fakeFetchResponse = {
|
|
24
|
+
ok: true,
|
|
25
|
+
json: jest.fn().mockResolvedValue(fakeResponseData),
|
|
26
|
+
};
|
|
27
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
28
|
+
|
|
29
|
+
const eventName = 'testEvent';
|
|
30
|
+
const payload: TrackData = {
|
|
31
|
+
sid: 'abc123',
|
|
32
|
+
tenantId: 1,
|
|
33
|
+
sessionId: 1,
|
|
34
|
+
properties: { some: 'data' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const result = await tracker.track(eventName, payload);
|
|
38
|
+
|
|
39
|
+
expect(global.fetch).toHaveBeenCalledWith(fakeEndpoint, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ eventName, ...payload }),
|
|
43
|
+
});
|
|
44
|
+
expect(result).toEqual(fakeResponseData);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should throw an error if the response is not ok', async () => {
|
|
48
|
+
const errorText = 'Internal Server Error';
|
|
49
|
+
|
|
50
|
+
const fakeFetchResponse = {
|
|
51
|
+
ok: false,
|
|
52
|
+
status: 500,
|
|
53
|
+
text: jest.fn().mockResolvedValue(errorText),
|
|
54
|
+
};
|
|
55
|
+
global.fetch = jest.fn().mockResolvedValue(fakeFetchResponse as any);
|
|
56
|
+
|
|
57
|
+
const eventName = 'testEvent';
|
|
58
|
+
const payload: TrackData = {
|
|
59
|
+
sid: 'abc123',
|
|
60
|
+
tenantId: 1,
|
|
61
|
+
sessionId: 1,
|
|
62
|
+
properties: { some: 'data' },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await expect(tracker.track(eventName, payload)).rejects.toThrow(
|
|
66
|
+
`Request error: 500 - ${errorText}`,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should throw an error if fetch fails', async () => {
|
|
71
|
+
const errorMessage = 'Network error';
|
|
72
|
+
global.fetch = jest.fn().mockRejectedValue(new Error(errorMessage));
|
|
73
|
+
|
|
74
|
+
const eventName = 'testEvent';
|
|
75
|
+
const payload: TrackData = {
|
|
76
|
+
sid: 'abc123',
|
|
77
|
+
tenantId: 1,
|
|
78
|
+
sessionId: 1,
|
|
79
|
+
properties: { some: 'data' },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await expect(tracker.track(eventName, payload)).rejects.toThrow(
|
|
83
|
+
errorMessage,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "node",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"removeComments": true,
|
|
7
|
+
"emitDecoratorMetadata": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"target": "ES2021",
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"baseUrl": "./",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strictNullChecks": false,
|
|
17
|
+
"noImplicitAny": false,
|
|
18
|
+
"strictBindCallApply": false,
|
|
19
|
+
"forceConsistentCasingInFileNames": false,
|
|
20
|
+
"noFallthroughCasesInSwitch": false,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"paths": {
|
|
23
|
+
"@src/*": ["src/*"],
|
|
24
|
+
"@test/*": ["test/*"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*", "templates/**/*", "test/**/*" ]
|
|
28
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import dts from 'vite-plugin-dts'
|
|
3
|
+
import path from "path";
|
|
4
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
dts({
|
|
9
|
+
outDir: "dist/types",
|
|
10
|
+
}),
|
|
11
|
+
tsconfigPaths()
|
|
12
|
+
],
|
|
13
|
+
build: {
|
|
14
|
+
lib: {
|
|
15
|
+
entry: path.resolve(__dirname, "src/index.ts"),
|
|
16
|
+
name: "sizebay-core-sdk",
|
|
17
|
+
fileName: (format) => `sizebay-core-sdk.${format}.js`,
|
|
18
|
+
formats: ["es", "umd"],
|
|
19
|
+
},
|
|
20
|
+
rollupOptions: {
|
|
21
|
+
external: [],
|
|
22
|
+
output: {
|
|
23
|
+
globals: {},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|