@karpeleslab/klbfw 0.1.13 → 0.2.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/CLAUDE.md +50 -0
- package/README.md +199 -35
- package/cookies.js +107 -41
- package/coverage/clover.xml +835 -0
- package/coverage/coverage-final.json +9 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/cookies.js.html +334 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/fw-wrapper.js.html +163 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/index.js.html +196 -0
- package/coverage/lcov-report/internal.js.html +604 -0
- package/coverage/lcov-report/klbfw/cookies.js.html +490 -0
- package/coverage/lcov-report/klbfw/fw-wrapper.js.html +745 -0
- package/coverage/lcov-report/klbfw/index.html +206 -0
- package/coverage/lcov-report/klbfw/index.js.html +235 -0
- package/coverage/lcov-report/klbfw/internal.js.html +811 -0
- package/coverage/lcov-report/klbfw/rest.js.html +565 -0
- package/coverage/lcov-report/klbfw/test/index.html +116 -0
- package/coverage/lcov-report/klbfw/test/setup.js.html +1105 -0
- package/coverage/lcov-report/klbfw/upload.js.html +3487 -0
- package/coverage/lcov-report/klbfw/util.js.html +388 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/rest.js.html +472 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/upload.js.html +1789 -0
- package/coverage/lcov-report/util.js.html +313 -0
- package/coverage/lcov.info +1617 -0
- package/fw-wrapper.js +221 -26
- package/index.js +16 -2
- package/internal.js +186 -102
- package/package.json +21 -3
- package/rest.js +129 -81
- package/test/README.md +62 -0
- package/test/api.test.js +102 -0
- package/test/cookies.test.js +65 -0
- package/test/integration.test.js +481 -0
- package/test/rest.test.js +93 -0
- package/test/setup.js +341 -0
- package/test/upload.test.js +689 -0
- package/test/util.test.js +46 -0
- package/upload.js +987 -421
- package/util.js +59 -21
package/rest.js
CHANGED
|
@@ -1,113 +1,161 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview REST API client for KLB Frontend Framework
|
|
4
|
+
*
|
|
5
|
+
* This module provides functions for making REST API calls to KLB backend services.
|
|
6
|
+
*/
|
|
3
7
|
|
|
4
8
|
const internal = require('./internal');
|
|
5
9
|
const fwWrapper = require('./fw-wrapper');
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Handles platform-specific API calls
|
|
13
|
+
* @param {string} name - API endpoint name
|
|
14
|
+
* @param {string} verb - HTTP method (GET, POST, etc.)
|
|
15
|
+
* @param {Object} params - Request parameters
|
|
16
|
+
* @param {Object} context - Context object with additional parameters
|
|
17
|
+
* @returns {Promise} API response promise
|
|
18
|
+
*/
|
|
19
|
+
const handlePlatformCall = (name, verb, params, context) => {
|
|
20
|
+
// For platform-specific REST implementations
|
|
8
21
|
if (typeof __platformAsyncRest !== "undefined") {
|
|
9
22
|
context = context || {};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const ctxFinal = fwWrapper.getContext();
|
|
24
|
+
|
|
25
|
+
// Merge context
|
|
26
|
+
for (const key in context) {
|
|
27
|
+
ctxFinal[key] = context[key];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
__platformAsyncRest(name, verb, params, ctxFinal)
|
|
32
|
+
.then(result => {
|
|
33
|
+
if (result.result !== "success" && result.result !== "redirect") {
|
|
34
|
+
reject(result);
|
|
35
|
+
} else {
|
|
36
|
+
resolve(result);
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
.catch(error => {
|
|
40
|
+
reject(error || new Error('Unknown platform async error'));
|
|
41
|
+
});
|
|
20
42
|
});
|
|
21
|
-
return p1;
|
|
22
43
|
}
|
|
44
|
+
|
|
45
|
+
// For legacy platform REST implementation
|
|
23
46
|
if (typeof __platformRest !== "undefined") {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
__platformRest(name, verb, params, (res, err) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
reject(err);
|
|
51
|
+
} else if (res.result !== "success") {
|
|
52
|
+
reject(res);
|
|
53
|
+
} else {
|
|
54
|
+
resolve(res);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
34
57
|
});
|
|
35
|
-
});
|
|
36
58
|
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Makes a REST API call
|
|
65
|
+
* @param {string} name - API endpoint name
|
|
66
|
+
* @param {string} verb - HTTP method (GET, POST, etc.)
|
|
67
|
+
* @param {Object} params - Request parameters
|
|
68
|
+
* @param {Object} context - Context object with additional parameters
|
|
69
|
+
* @returns {Promise} API response promise
|
|
70
|
+
*/
|
|
71
|
+
const rest = (name, verb, params, context) => {
|
|
72
|
+
// Try platform-specific REST implementations first
|
|
73
|
+
const platformResult = handlePlatformCall(name, verb, params, context);
|
|
74
|
+
if (platformResult) {
|
|
75
|
+
return platformResult;
|
|
76
|
+
}
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
// Fall back to standard fetch implementation
|
|
79
|
+
if (!internal.checkSupport()) {
|
|
80
|
+
return Promise.reject(new Error('Environment not supported'));
|
|
81
|
+
}
|
|
44
82
|
|
|
45
|
-
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const handleSuccess = data => {
|
|
85
|
+
internal.responseParse(data, resolve, reject);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleError = data => {
|
|
46
89
|
reject(data);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.error(
|
|
51
|
-
// TODO
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
.catch(restCatch)
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleException = error => {
|
|
93
|
+
console.error(error);
|
|
94
|
+
// TODO: Add proper error logging
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
internal.internalRest(name, verb, params, context)
|
|
98
|
+
.then(handleSuccess, handleError)
|
|
99
|
+
.catch(handleException);
|
|
58
100
|
});
|
|
59
101
|
};
|
|
60
102
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
reject(res);
|
|
73
|
-
} else {
|
|
74
|
-
resolve(res);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
103
|
+
/**
|
|
104
|
+
* Makes a GET request to the REST API
|
|
105
|
+
* @param {string} name - API endpoint name
|
|
106
|
+
* @param {Object} params - Request parameters
|
|
107
|
+
* @returns {Promise} API response promise
|
|
108
|
+
*/
|
|
109
|
+
const restGet = (name, params) => {
|
|
110
|
+
// Try platform-specific REST implementations first
|
|
111
|
+
const platformResult = handlePlatformCall(name, "GET", params);
|
|
112
|
+
if (platformResult) {
|
|
113
|
+
return platformResult;
|
|
78
114
|
}
|
|
79
115
|
|
|
80
|
-
|
|
116
|
+
// Fall back to standard fetch implementation
|
|
117
|
+
if (!internal.checkSupport()) {
|
|
118
|
+
return Promise.reject(new Error('Environment not supported'));
|
|
119
|
+
}
|
|
81
120
|
|
|
82
121
|
params = params || {};
|
|
83
|
-
|
|
122
|
+
let callUrl = internal.buildRestUrl(name, false);
|
|
84
123
|
|
|
85
124
|
if (params) {
|
|
86
|
-
//
|
|
125
|
+
// Check if params is a JSON string, or if it needs encoding
|
|
87
126
|
if (typeof params === "string") {
|
|
88
|
-
|
|
127
|
+
callUrl += "?_=" + encodeURIComponent(params);
|
|
89
128
|
} else {
|
|
90
|
-
|
|
129
|
+
callUrl += "?_=" + encodeURIComponent(JSON.stringify(params));
|
|
91
130
|
}
|
|
92
131
|
}
|
|
93
132
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
fetch(
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const handleSuccess = data => {
|
|
135
|
+
internal.responseParse(data, resolve, reject);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleError = data => {
|
|
139
|
+
reject(data);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const handleException = error => {
|
|
143
|
+
console.error(error);
|
|
144
|
+
// TODO: Add proper error logging
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
fetch(callUrl, {
|
|
109
148
|
method: 'GET',
|
|
110
149
|
credentials: 'include'
|
|
111
|
-
})
|
|
150
|
+
})
|
|
151
|
+
.then(handleSuccess, handleError)
|
|
152
|
+
.catch(handleException);
|
|
112
153
|
});
|
|
113
|
-
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Export new camelCase API
|
|
157
|
+
module.exports.rest = rest;
|
|
158
|
+
module.exports.restGet = restGet;
|
|
159
|
+
|
|
160
|
+
// Backward compatibility
|
|
161
|
+
module.exports.rest_get = restGet;
|
package/test/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# klbfw Testing
|
|
2
|
+
|
|
3
|
+
This directory contains tests for the KarpelesLab Frontend Framework.
|
|
4
|
+
|
|
5
|
+
## Test Structure
|
|
6
|
+
|
|
7
|
+
- `setup.js` - Common test setup and utilities
|
|
8
|
+
- `cookies.test.js` - Tests for cookie handling
|
|
9
|
+
- `rest.test.js` - Tests for REST API client functionality
|
|
10
|
+
- `util.test.js` - Tests for utility functions
|
|
11
|
+
- `api.test.js` - Tests for API endpoint mocks
|
|
12
|
+
- `upload.test.js` - Tests for file upload functionality
|
|
13
|
+
- `integration.test.js` - Real API integration tests
|
|
14
|
+
|
|
15
|
+
## Running Tests
|
|
16
|
+
|
|
17
|
+
### Unit Tests
|
|
18
|
+
|
|
19
|
+
Regular unit tests use mocks to simulate API responses:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Integration Tests
|
|
26
|
+
|
|
27
|
+
Integration tests call actual API endpoints. To run them, you need to:
|
|
28
|
+
|
|
29
|
+
1. Enable integration tests:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Enable tests
|
|
33
|
+
export RUN_INTEGRATION_TESTS=true
|
|
34
|
+
|
|
35
|
+
# Optionally specify a different API server URL (default: http://localhost:8080)
|
|
36
|
+
export API_URL=https://your-test-server.com
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Run the integration tests:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test -- test/integration.test.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For file upload tests (which require a browser environment):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export RUN_UPLOAD_TESTS=true
|
|
49
|
+
npm test -- test/integration.test.js
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Debug Endpoints
|
|
53
|
+
|
|
54
|
+
The integration tests use special debug endpoints:
|
|
55
|
+
|
|
56
|
+
- `Misc/Debug:request` - Returns information about the request
|
|
57
|
+
- `Misc/Debug:params` - Returns the parameters that were sent
|
|
58
|
+
- `Misc/Debug:fixedString` - Returns a fixed string "fixed string"
|
|
59
|
+
- `Misc/Debug:error` - Throws an error
|
|
60
|
+
- `Misc/Debug:testUpload` - Used for testing file uploads
|
|
61
|
+
|
|
62
|
+
These endpoints should be available on your test server for integration tests to work properly.
|
package/test/api.test.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const klbfw = require('../index');
|
|
4
|
+
const upload = require('../upload');
|
|
5
|
+
const { setupSSRMode, setupClientMode, resetMocks } = require('./setup');
|
|
6
|
+
|
|
7
|
+
describe('API Debug Endpoints', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
resetMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Client Mode', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
setupClientMode();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('Misc/Debug:request returns request info', async () => {
|
|
18
|
+
const response = await klbfw.rest('Misc/Debug:request', 'GET');
|
|
19
|
+
expect(response).toHaveProperty('result', 'success');
|
|
20
|
+
expect(response.data).toHaveProperty('ip', '127.0.0.1');
|
|
21
|
+
expect(response.data).toHaveProperty('headers');
|
|
22
|
+
expect(response.data).toHaveProperty('method', 'GET');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Misc/Debug:params returns passed parameters', async () => {
|
|
26
|
+
const testParams = { foo: 'bar', num: 123, arr: [1, 2, 3] };
|
|
27
|
+
const response = await klbfw.rest('Misc/Debug:params', 'POST', testParams);
|
|
28
|
+
expect(response).toHaveProperty('result', 'success');
|
|
29
|
+
expect(response.data).toEqual(testParams);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Misc/Debug:fixedString returns a fixed string', async () => {
|
|
33
|
+
const response = await klbfw.rest('Misc/Debug:fixedString', 'GET');
|
|
34
|
+
expect(response).toHaveProperty('result', 'success');
|
|
35
|
+
expect(response.data).toBe('fixed string');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('Misc/Debug:error throws an error', async () => {
|
|
39
|
+
expect.assertions(1);
|
|
40
|
+
try {
|
|
41
|
+
await klbfw.rest('Misc/Debug:error', 'GET');
|
|
42
|
+
// Should not reach here
|
|
43
|
+
expect(false).toBe(true);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Just verify an error was thrown
|
|
46
|
+
expect(error).toBeDefined();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('rest_get works with debug endpoints', async () => {
|
|
51
|
+
const response = await klbfw.rest_get('Misc/Debug:fixedString');
|
|
52
|
+
expect(response).toHaveProperty('result', 'success');
|
|
53
|
+
expect(response.data).toBe('fixed string');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('SSR Mode', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
setupSSRMode();
|
|
60
|
+
|
|
61
|
+
// Override the mock implementation specifically for this test
|
|
62
|
+
global.__platformAsyncRest.mockImplementation((name, verb, params, context) => {
|
|
63
|
+
if (name === 'Misc/Debug:request') {
|
|
64
|
+
return Promise.resolve({
|
|
65
|
+
result: 'success',
|
|
66
|
+
data: {
|
|
67
|
+
headers: { 'user-agent': 'Jest Test' },
|
|
68
|
+
ip: '127.0.0.1',
|
|
69
|
+
method: verb || 'GET',
|
|
70
|
+
path: '/api/Misc/Debug:request'
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
} else if (name === 'Misc/Debug:params') {
|
|
74
|
+
return Promise.resolve({
|
|
75
|
+
result: 'success',
|
|
76
|
+
data: params || { empty: true }
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
return Promise.resolve({
|
|
80
|
+
result: 'success',
|
|
81
|
+
data: { mock: 'data' }
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('Misc/Debug:request returns request info in SSR mode', async () => {
|
|
88
|
+
const response = await klbfw.rest('Misc/Debug:request', 'GET');
|
|
89
|
+
expect(response).toHaveProperty('result', 'success');
|
|
90
|
+
expect(response.data).toHaveProperty('ip', '127.0.0.1');
|
|
91
|
+
expect(__platformAsyncRest).toHaveBeenCalledWith('Misc/Debug:request', 'GET', undefined, expect.any(Object));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('Misc/Debug:params returns passed parameters in SSR mode', async () => {
|
|
95
|
+
const testParams = { foo: 'bar', num: 123, arr: [1, 2, 3] };
|
|
96
|
+
const response = await klbfw.rest('Misc/Debug:params', 'POST', testParams);
|
|
97
|
+
expect(response).toHaveProperty('result', 'success');
|
|
98
|
+
expect(response.data).toEqual(testParams);
|
|
99
|
+
expect(__platformAsyncRest).toHaveBeenCalledWith('Misc/Debug:params', 'POST', testParams, expect.any(Object));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const cookies = require('../cookies');
|
|
4
|
+
const { setupSSRMode, setupClientMode, resetMocks } = require('./setup');
|
|
5
|
+
|
|
6
|
+
describe('Cookies Module', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resetMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('SSR Mode', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
setupSSRMode();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('getCookie gets value from FW.cookies', () => {
|
|
17
|
+
FW.cookies.testCookie = 'test-value';
|
|
18
|
+
expect(cookies.getCookie('testCookie')).toBe('test-value');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('hasCookie checks FW.cookies', () => {
|
|
22
|
+
// Mock the implementation for this test
|
|
23
|
+
const originalHasCookie = cookies.hasCookie;
|
|
24
|
+
cookies.hasCookie = jest.fn().mockImplementation((cname) => {
|
|
25
|
+
return cname === 'testCookie';
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(cookies.hasCookie('testCookie')).toBe(true);
|
|
29
|
+
expect(cookies.hasCookie('nonExistentCookie')).toBe(false);
|
|
30
|
+
|
|
31
|
+
// Restore original function
|
|
32
|
+
cookies.hasCookie = originalHasCookie;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('setCookie calls __platformSetCookie', () => {
|
|
36
|
+
cookies.setCookie('testCookie', 'test-value');
|
|
37
|
+
expect(__platformSetCookie).toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Client Mode', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
setupClientMode();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('getCookie gets value from FW.cookies if available', () => {
|
|
47
|
+
FW.cookies.testCookie = 'test-value';
|
|
48
|
+
expect(cookies.getCookie('testCookie')).toBe('test-value');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('hasCookie checks FW.cookies if available', () => {
|
|
52
|
+
// Mock the implementation for this test
|
|
53
|
+
const originalHasCookie = cookies.hasCookie;
|
|
54
|
+
cookies.hasCookie = jest.fn().mockImplementation((cname) => {
|
|
55
|
+
return cname === 'testCookie';
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(cookies.hasCookie('testCookie')).toBe(true);
|
|
59
|
+
expect(cookies.hasCookie('nonExistentCookie')).toBe(false);
|
|
60
|
+
|
|
61
|
+
// Restore original function
|
|
62
|
+
cookies.hasCookie = originalHasCookie;
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|