simple-dynamsoft-mcp 2.0.3 ā 2.0.4
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.
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from dynamsoft_barcode_reader_bundle import *
|
|
2
|
+
import cv2
|
|
3
|
+
import numpy as np
|
|
4
|
+
import queue
|
|
5
|
+
from utils import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FrameFetcher(ImageSourceAdapter):
|
|
9
|
+
def has_next_image_to_fetch(self) -> bool:
|
|
10
|
+
return True
|
|
11
|
+
|
|
12
|
+
def add_frame(self, imageData):
|
|
13
|
+
self.add_image_to_buffer(imageData)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MyCapturedResultReceiver(CapturedResultReceiver):
|
|
17
|
+
def __init__(self, result_queue):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.result_queue = result_queue
|
|
20
|
+
|
|
21
|
+
def on_captured_result_received(self, captured_result):
|
|
22
|
+
self.result_queue.put(captured_result)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == '__main__':
|
|
26
|
+
errorCode, errorMsg = LicenseManager.init_license(
|
|
27
|
+
"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
|
|
28
|
+
if errorCode != EnumErrorCode.EC_OK and errorCode != EnumErrorCode.EC_LICENSE_CACHE_USED:
|
|
29
|
+
print("License initialization failed: ErrorCode:",
|
|
30
|
+
errorCode, ", ErrorString:", errorMsg)
|
|
31
|
+
else:
|
|
32
|
+
vc = cv2.VideoCapture(0)
|
|
33
|
+
if not vc.isOpened():
|
|
34
|
+
print("Error: Camera is not opened!")
|
|
35
|
+
exit(1)
|
|
36
|
+
|
|
37
|
+
cvr = CaptureVisionRouter()
|
|
38
|
+
fetcher = FrameFetcher()
|
|
39
|
+
cvr.set_input(fetcher)
|
|
40
|
+
|
|
41
|
+
# Create a thread-safe queue to store captured items
|
|
42
|
+
result_queue = queue.Queue()
|
|
43
|
+
|
|
44
|
+
receiver = MyCapturedResultReceiver(result_queue)
|
|
45
|
+
cvr.add_result_receiver(receiver)
|
|
46
|
+
|
|
47
|
+
errorCode, errorMsg = cvr.start_capturing(
|
|
48
|
+
EnumPresetTemplate.PT_READ_BARCODES.value)
|
|
49
|
+
|
|
50
|
+
if errorCode != EnumErrorCode.EC_OK:
|
|
51
|
+
print("error:", errorMsg)
|
|
52
|
+
|
|
53
|
+
while True:
|
|
54
|
+
ret, frame = vc.read()
|
|
55
|
+
if not ret:
|
|
56
|
+
print("Error: Cannot read frame!")
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
fetcher.add_frame(convertMat2ImageData(frame))
|
|
60
|
+
|
|
61
|
+
if not result_queue.empty():
|
|
62
|
+
captured_result = result_queue.get_nowait()
|
|
63
|
+
|
|
64
|
+
items = captured_result.get_items()
|
|
65
|
+
for item in items:
|
|
66
|
+
|
|
67
|
+
if item.get_type() == EnumCapturedResultItemType.CRIT_BARCODE:
|
|
68
|
+
text = item.get_text()
|
|
69
|
+
location = item.get_location()
|
|
70
|
+
x1 = location.points[0].x
|
|
71
|
+
y1 = location.points[0].y
|
|
72
|
+
x2 = location.points[1].x
|
|
73
|
+
y2 = location.points[1].y
|
|
74
|
+
x3 = location.points[2].x
|
|
75
|
+
y3 = location.points[2].y
|
|
76
|
+
x4 = location.points[3].x
|
|
77
|
+
y4 = location.points[3].y
|
|
78
|
+
pts = np.array([(x1, y1), (x2, y2), (x3, y3), (x4, y4)], np.int32).reshape((-1, 1, 2))
|
|
79
|
+
cv2.drawContours(
|
|
80
|
+
frame, [pts], 0, (0, 255, 0), 2)
|
|
81
|
+
|
|
82
|
+
cv2.putText(frame, text, (x1, y1),
|
|
83
|
+
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
|
84
|
+
|
|
85
|
+
del location
|
|
86
|
+
|
|
87
|
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
88
|
+
break
|
|
89
|
+
elif cv2.waitKey(1) & 0xFF == ord('c'):
|
|
90
|
+
cv2.imshow('Captured Image', frame)
|
|
91
|
+
|
|
92
|
+
cv2.imshow('frame', frame)
|
|
93
|
+
|
|
94
|
+
cvr.stop_capturing()
|
|
95
|
+
vc.release()
|
|
96
|
+
cv2.destroyAllWindows()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dynamsoft_barcode_reader_bundle import *
|
|
3
|
+
import os
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
from utils import *
|
|
7
|
+
|
|
8
|
+
if __name__ == '__main__':
|
|
9
|
+
|
|
10
|
+
print("**********************************************************")
|
|
11
|
+
print("Welcome to Dynamsoft Capture Vision - Barcode Sample")
|
|
12
|
+
print("**********************************************************")
|
|
13
|
+
|
|
14
|
+
error_code, error_message = LicenseManager.init_license(
|
|
15
|
+
"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
|
|
16
|
+
if error_code != EnumErrorCode.EC_OK and error_code != EnumErrorCode.EC_LICENSE_CACHE_USED:
|
|
17
|
+
print("License initialization failed: ErrorCode:",
|
|
18
|
+
error_code, ", ErrorString:", error_message)
|
|
19
|
+
else:
|
|
20
|
+
cvr_instance = CaptureVisionRouter()
|
|
21
|
+
while (True):
|
|
22
|
+
image_path = input(
|
|
23
|
+
">> Input your image full path:\n"
|
|
24
|
+
">> 'Enter' for sample image or 'Q'/'q' to quit\n"
|
|
25
|
+
).strip('\'"')
|
|
26
|
+
|
|
27
|
+
if image_path.lower() == "q":
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
|
|
30
|
+
if image_path == "":
|
|
31
|
+
image_path = "../../../images/multi.png"
|
|
32
|
+
|
|
33
|
+
if not os.path.exists(image_path):
|
|
34
|
+
print("The image path does not exist.")
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
cv_image = cv2.imread(image_path)
|
|
38
|
+
result = cvr_instance.capture(
|
|
39
|
+
cv_image, EnumPresetTemplate.PT_READ_BARCODES.value)
|
|
40
|
+
if result.get_error_code() != EnumErrorCode.EC_OK:
|
|
41
|
+
print("Error:", result.get_error_code(),
|
|
42
|
+
result.get_error_string())
|
|
43
|
+
else:
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
items = result.get_items()
|
|
47
|
+
print('Found {} barcodes.'.format(len(items)))
|
|
48
|
+
for item in items:
|
|
49
|
+
format_type = item.get_format_string()
|
|
50
|
+
text = item.get_text()
|
|
51
|
+
print("Barcode Format:", format_type)
|
|
52
|
+
print("Barcode Text:", text)
|
|
53
|
+
|
|
54
|
+
location = item.get_location()
|
|
55
|
+
x1 = location.points[0].x
|
|
56
|
+
y1 = location.points[0].y
|
|
57
|
+
x2 = location.points[1].x
|
|
58
|
+
y2 = location.points[1].y
|
|
59
|
+
x3 = location.points[2].x
|
|
60
|
+
y3 = location.points[2].y
|
|
61
|
+
x4 = location.points[3].x
|
|
62
|
+
y4 = location.points[3].y
|
|
63
|
+
print("Location Points:")
|
|
64
|
+
print("({}, {})".format(x1, y1))
|
|
65
|
+
print("({}, {})".format(x2, y2))
|
|
66
|
+
print("({}, {})".format(x3, y3))
|
|
67
|
+
print("({}, {})".format(x4, y4))
|
|
68
|
+
print("-------------------------------------------------")
|
|
69
|
+
|
|
70
|
+
pts = np.array([(x1, y1), (x2, y2), (x3, y3), (x4, y4)], np.int32).reshape((-1, 1, 2))
|
|
71
|
+
cv2.drawContours(
|
|
72
|
+
cv_image, [pts], 0, (0, 255, 0), 2)
|
|
73
|
+
|
|
74
|
+
cv2.putText(cv_image, text, (x1, y1 - 10),
|
|
75
|
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
|
|
76
|
+
|
|
77
|
+
cv2.imshow(
|
|
78
|
+
"Original Image with Detected Barcodes", cv_image)
|
|
79
|
+
cv2.waitKey(0)
|
|
80
|
+
cv2.destroyAllWindows()
|
|
81
|
+
|
|
82
|
+
input("Press Enter to quit...")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from dynamsoft_barcode_reader_bundle import *
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def convertImageData2Mat(normalized_image):
|
|
6
|
+
ba = bytearray(normalized_image.get_bytes())
|
|
7
|
+
width = normalized_image.get_width()
|
|
8
|
+
height = normalized_image.get_height()
|
|
9
|
+
|
|
10
|
+
channels = 3
|
|
11
|
+
if normalized_image.get_image_pixel_format() == EnumImagePixelFormat.IPF_BINARY:
|
|
12
|
+
channels = 1
|
|
13
|
+
all = []
|
|
14
|
+
skip = normalized_image.stride * 8 - width
|
|
15
|
+
|
|
16
|
+
index = 0
|
|
17
|
+
n = 1
|
|
18
|
+
for byte in ba:
|
|
19
|
+
|
|
20
|
+
byteCount = 7
|
|
21
|
+
while byteCount >= 0:
|
|
22
|
+
b = (byte & (1 << byteCount)) >> byteCount
|
|
23
|
+
|
|
24
|
+
if index < normalized_image.stride * 8 * n - skip:
|
|
25
|
+
if b == 1:
|
|
26
|
+
all.append(255)
|
|
27
|
+
else:
|
|
28
|
+
all.append(0)
|
|
29
|
+
|
|
30
|
+
byteCount -= 1
|
|
31
|
+
index += 1
|
|
32
|
+
|
|
33
|
+
if index == normalized_image.stride * 8 * n:
|
|
34
|
+
n += 1
|
|
35
|
+
|
|
36
|
+
mat = np.array(all, dtype=np.uint8).reshape(height, width, channels)
|
|
37
|
+
return mat
|
|
38
|
+
|
|
39
|
+
elif normalized_image.get_image_pixel_format() == EnumImagePixelFormat.IPF_GRAYSCALED:
|
|
40
|
+
channels = 1
|
|
41
|
+
|
|
42
|
+
mat = np.array(ba, dtype=np.uint8).reshape(height, width, channels)
|
|
43
|
+
|
|
44
|
+
return mat
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def convertMat2ImageData(mat):
|
|
48
|
+
if len(mat.shape) == 3:
|
|
49
|
+
height, width, channels = mat.shape
|
|
50
|
+
pixel_format = EnumImagePixelFormat.IPF_RGB_888
|
|
51
|
+
else:
|
|
52
|
+
height, width = mat.shape
|
|
53
|
+
channels = 1
|
|
54
|
+
pixel_format = EnumImagePixelFormat.IPF_GRAYSCALED
|
|
55
|
+
|
|
56
|
+
stride = width * channels
|
|
57
|
+
imagedata = ImageData(mat.tobytes(), width, height, stride, pixel_format)
|
|
58
|
+
return imagedata
|
|
59
|
+
|
|
60
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-dynamsoft-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "MCP server for Dynamsoft SDKs - Barcode Reader (Mobile/Python/Web) and Dynamic Web TWAIN. Provides documentation, code snippets, and API guidance.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/yushulx/simple-dynamsoft-mcp.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"engines": {
|
|
7
11
|
"node": ">=18"
|
|
@@ -13,10 +17,12 @@
|
|
|
13
17
|
"src",
|
|
14
18
|
"data",
|
|
15
19
|
"code-snippet",
|
|
20
|
+
"test",
|
|
16
21
|
"README.md"
|
|
17
22
|
],
|
|
18
23
|
"scripts": {
|
|
19
|
-
"start": "node src/index.js"
|
|
24
|
+
"start": "node src/index.js",
|
|
25
|
+
"test": "node test/server.test.js"
|
|
20
26
|
},
|
|
21
27
|
"keywords": [
|
|
22
28
|
"mcp",
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Automated tests for Dynamsoft MCP Server
|
|
5
|
+
* Run with: node test/server.test.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const serverPath = join(__dirname, '..', 'src', 'index.js');
|
|
15
|
+
|
|
16
|
+
// Test counters
|
|
17
|
+
let passed = 0;
|
|
18
|
+
let failed = 0;
|
|
19
|
+
const results = [];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Send a JSON-RPC request to the server and get the response
|
|
23
|
+
*/
|
|
24
|
+
async function sendRequest(request) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const proc = spawn('node', [serverPath], {
|
|
27
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let stdout = '';
|
|
31
|
+
let stderr = '';
|
|
32
|
+
|
|
33
|
+
proc.stdout.on('data', (data) => {
|
|
34
|
+
stdout += data.toString();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
proc.stderr.on('data', (data) => {
|
|
38
|
+
stderr += data.toString();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
proc.on('close', (code) => {
|
|
42
|
+
try {
|
|
43
|
+
// Parse only the JSON-RPC response (last complete JSON object)
|
|
44
|
+
const lines = stdout.trim().split('\n');
|
|
45
|
+
const jsonLine = lines.find(line => {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(line);
|
|
48
|
+
return parsed.jsonrpc === '2.0';
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (jsonLine) {
|
|
55
|
+
resolve(JSON.parse(jsonLine));
|
|
56
|
+
} else {
|
|
57
|
+
reject(new Error(`No valid JSON-RPC response. stdout: ${stdout}, stderr: ${stderr}`));
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
reject(new Error(`Failed to parse response: ${e.message}. stdout: ${stdout}`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
proc.on('error', reject);
|
|
65
|
+
|
|
66
|
+
// Send the request and close stdin
|
|
67
|
+
proc.stdin.write(JSON.stringify(request) + '\n');
|
|
68
|
+
proc.stdin.end();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Run a test case
|
|
74
|
+
*/
|
|
75
|
+
async function test(name, fn) {
|
|
76
|
+
try {
|
|
77
|
+
await fn();
|
|
78
|
+
passed++;
|
|
79
|
+
results.push({ name, status: 'ā
PASSED' });
|
|
80
|
+
console.log(`ā
${name}`);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
failed++;
|
|
83
|
+
results.push({ name, status: 'ā FAILED', error: error.message });
|
|
84
|
+
console.log(`ā ${name}`);
|
|
85
|
+
console.log(` Error: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Assert helper
|
|
91
|
+
*/
|
|
92
|
+
function assert(condition, message) {
|
|
93
|
+
if (!condition) {
|
|
94
|
+
throw new Error(message || 'Assertion failed');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================
|
|
99
|
+
// Test Cases
|
|
100
|
+
// ============================================
|
|
101
|
+
|
|
102
|
+
console.log('\nš§Ŗ Dynamsoft MCP Server Test Suite\n');
|
|
103
|
+
console.log('='.repeat(50));
|
|
104
|
+
|
|
105
|
+
// Test 1: Server initialization
|
|
106
|
+
await test('Server responds to initialize request', async () => {
|
|
107
|
+
const response = await sendRequest({
|
|
108
|
+
jsonrpc: '2.0',
|
|
109
|
+
id: 1,
|
|
110
|
+
method: 'initialize',
|
|
111
|
+
params: {
|
|
112
|
+
protocolVersion: '2024-11-05',
|
|
113
|
+
capabilities: {},
|
|
114
|
+
clientInfo: { name: 'test-client', version: '1.0.0' }
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
assert(response.result, 'Should have result');
|
|
119
|
+
assert(response.result.serverInfo, 'Should have serverInfo');
|
|
120
|
+
assert(response.result.serverInfo.name === 'simple-dynamsoft-mcp', 'Server name should match');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Test 2: List tools
|
|
124
|
+
await test('tools/list returns all 13 tools', async () => {
|
|
125
|
+
const response = await sendRequest({
|
|
126
|
+
jsonrpc: '2.0',
|
|
127
|
+
id: 1,
|
|
128
|
+
method: 'tools/list'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
assert(response.result, 'Should have result');
|
|
132
|
+
assert(response.result.tools, 'Should have tools array');
|
|
133
|
+
assert(response.result.tools.length === 13, `Expected 13 tools, got ${response.result.tools.length}`);
|
|
134
|
+
|
|
135
|
+
const toolNames = response.result.tools.map(t => t.name);
|
|
136
|
+
const expectedTools = [
|
|
137
|
+
'list_sdks', 'get_sdk_info', 'list_samples', 'list_python_samples',
|
|
138
|
+
'list_dwt_categories', 'get_code_snippet', 'get_python_sample',
|
|
139
|
+
'get_dwt_sample', 'get_quick_start', 'get_gradle_config',
|
|
140
|
+
'get_license_info', 'get_api_usage', 'search_samples'
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const expected of expectedTools) {
|
|
144
|
+
assert(toolNames.includes(expected), `Missing tool: ${expected}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Test 3: list_sdks tool
|
|
149
|
+
await test('list_sdks returns SDK information', async () => {
|
|
150
|
+
const response = await sendRequest({
|
|
151
|
+
jsonrpc: '2.0',
|
|
152
|
+
id: 1,
|
|
153
|
+
method: 'tools/call',
|
|
154
|
+
params: {
|
|
155
|
+
name: 'list_sdks',
|
|
156
|
+
arguments: {}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
assert(response.result, 'Should have result');
|
|
161
|
+
assert(response.result.content, 'Should have content');
|
|
162
|
+
assert(response.result.content.length > 0, 'Should have content items');
|
|
163
|
+
|
|
164
|
+
const text = response.result.content[0].text;
|
|
165
|
+
assert(text.includes('dbr-mobile'), 'Should include dbr-mobile');
|
|
166
|
+
assert(text.includes('dbr-python'), 'Should include dbr-python');
|
|
167
|
+
assert(text.includes('dbr-web'), 'Should include dbr-web');
|
|
168
|
+
assert(text.includes('dwt'), 'Should include dwt');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Test 4: get_sdk_info tool
|
|
172
|
+
await test('get_sdk_info returns detailed SDK info', async () => {
|
|
173
|
+
const response = await sendRequest({
|
|
174
|
+
jsonrpc: '2.0',
|
|
175
|
+
id: 1,
|
|
176
|
+
method: 'tools/call',
|
|
177
|
+
params: {
|
|
178
|
+
name: 'get_sdk_info',
|
|
179
|
+
arguments: { sdk_id: 'dbr-mobile' }
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
assert(response.result, 'Should have result');
|
|
184
|
+
assert(response.result.content, 'Should have content');
|
|
185
|
+
|
|
186
|
+
const text = response.result.content[0].text;
|
|
187
|
+
assert(text.includes('Android') || text.includes('android'), 'Should include Android');
|
|
188
|
+
assert(text.includes('11.2.5000'), 'Should include version');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Test 5: get_license_info tool (requires platform parameter)
|
|
192
|
+
await test('get_license_info returns trial license', async () => {
|
|
193
|
+
const response = await sendRequest({
|
|
194
|
+
jsonrpc: '2.0',
|
|
195
|
+
id: 1,
|
|
196
|
+
method: 'tools/call',
|
|
197
|
+
params: {
|
|
198
|
+
name: 'get_license_info',
|
|
199
|
+
arguments: { platform: 'android' }
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
assert(response.result, 'Should have result');
|
|
204
|
+
assert(!response.result.isError, 'Should not be an error');
|
|
205
|
+
|
|
206
|
+
const text = response.result.content[0].text;
|
|
207
|
+
assert(text.includes('DLS2') || text.includes('License'), 'Should include license info');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Test 6: list_samples tool
|
|
211
|
+
await test('list_samples returns mobile samples', async () => {
|
|
212
|
+
const response = await sendRequest({
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id: 1,
|
|
215
|
+
method: 'tools/call',
|
|
216
|
+
params: {
|
|
217
|
+
name: 'list_samples',
|
|
218
|
+
arguments: { platform: 'android' }
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
assert(response.result, 'Should have result');
|
|
223
|
+
|
|
224
|
+
const text = response.result.content[0].text;
|
|
225
|
+
assert(text.includes('android'), 'Should include android');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Test 7: list_python_samples tool
|
|
229
|
+
await test('list_python_samples returns Python samples', async () => {
|
|
230
|
+
const response = await sendRequest({
|
|
231
|
+
jsonrpc: '2.0',
|
|
232
|
+
id: 1,
|
|
233
|
+
method: 'tools/call',
|
|
234
|
+
params: {
|
|
235
|
+
name: 'list_python_samples',
|
|
236
|
+
arguments: {}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
assert(response.result, 'Should have result');
|
|
241
|
+
// Should return samples or indicate no local samples
|
|
242
|
+
assert(response.result.content, 'Should have content');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Test 8: list_dwt_categories tool
|
|
246
|
+
await test('list_dwt_categories returns DWT categories', async () => {
|
|
247
|
+
const response = await sendRequest({
|
|
248
|
+
jsonrpc: '2.0',
|
|
249
|
+
id: 1,
|
|
250
|
+
method: 'tools/call',
|
|
251
|
+
params: {
|
|
252
|
+
name: 'list_dwt_categories',
|
|
253
|
+
arguments: {}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
assert(response.result, 'Should have result');
|
|
258
|
+
// Should return categories or indicate they exist
|
|
259
|
+
assert(response.result.content, 'Should have content');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Test 9: get_quick_start tool
|
|
263
|
+
await test('get_quick_start returns quick start guide', async () => {
|
|
264
|
+
const response = await sendRequest({
|
|
265
|
+
jsonrpc: '2.0',
|
|
266
|
+
id: 1,
|
|
267
|
+
method: 'tools/call',
|
|
268
|
+
params: {
|
|
269
|
+
name: 'get_quick_start',
|
|
270
|
+
arguments: { sdk_id: 'dbr-mobile', platform: 'android' }
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
assert(response.result, 'Should have result');
|
|
275
|
+
|
|
276
|
+
const text = response.result.content[0].text;
|
|
277
|
+
assert(text.includes('Quick Start') || text.includes('Android'), 'Should include quick start info');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Test 10: get_gradle_config tool
|
|
281
|
+
await test('get_gradle_config returns Gradle configuration', async () => {
|
|
282
|
+
const response = await sendRequest({
|
|
283
|
+
jsonrpc: '2.0',
|
|
284
|
+
id: 1,
|
|
285
|
+
method: 'tools/call',
|
|
286
|
+
params: {
|
|
287
|
+
name: 'get_gradle_config',
|
|
288
|
+
arguments: {}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
assert(response.result, 'Should have result');
|
|
293
|
+
|
|
294
|
+
const text = response.result.content[0].text;
|
|
295
|
+
assert(text.includes('gradle') || text.includes('Gradle') || text.includes('implementation'),
|
|
296
|
+
'Should include Gradle config');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Test 11: get_api_usage tool
|
|
300
|
+
await test('get_api_usage returns API usage info', async () => {
|
|
301
|
+
const response = await sendRequest({
|
|
302
|
+
jsonrpc: '2.0',
|
|
303
|
+
id: 1,
|
|
304
|
+
method: 'tools/call',
|
|
305
|
+
params: {
|
|
306
|
+
name: 'get_api_usage',
|
|
307
|
+
arguments: { sdk_id: 'dbr-mobile', api_name: 'decode' }
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
assert(response.result, 'Should have result');
|
|
312
|
+
assert(response.result.content, 'Should have content');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Test 12: search_samples tool
|
|
316
|
+
await test('search_samples finds samples by keyword', async () => {
|
|
317
|
+
const response = await sendRequest({
|
|
318
|
+
jsonrpc: '2.0',
|
|
319
|
+
id: 1,
|
|
320
|
+
method: 'tools/call',
|
|
321
|
+
params: {
|
|
322
|
+
name: 'search_samples',
|
|
323
|
+
arguments: { query: 'barcode' }
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
assert(response.result, 'Should have result');
|
|
328
|
+
assert(response.result.content, 'Should have content');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Test 13: resources/list returns resources
|
|
332
|
+
await test('resources/list returns registered resources', async () => {
|
|
333
|
+
const response = await sendRequest({
|
|
334
|
+
jsonrpc: '2.0',
|
|
335
|
+
id: 1,
|
|
336
|
+
method: 'resources/list'
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
assert(response.result, 'Should have result');
|
|
340
|
+
assert(response.result.resources, 'Should have resources array');
|
|
341
|
+
assert(response.result.resources.length > 0, 'Should have at least one resource');
|
|
342
|
+
|
|
343
|
+
// Check for expected resource types
|
|
344
|
+
const uris = response.result.resources.map(r => r.uri);
|
|
345
|
+
assert(uris.some(u => u.includes('sdk-info')), 'Should have sdk-info resources');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Test 14: Invalid tool call returns error
|
|
349
|
+
await test('Invalid tool call returns proper error', async () => {
|
|
350
|
+
const response = await sendRequest({
|
|
351
|
+
jsonrpc: '2.0',
|
|
352
|
+
id: 1,
|
|
353
|
+
method: 'tools/call',
|
|
354
|
+
params: {
|
|
355
|
+
name: 'nonexistent_tool',
|
|
356
|
+
arguments: {}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
assert(response.error || (response.result && response.result.isError),
|
|
361
|
+
'Should return error for invalid tool');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Test 15: Tool with invalid arguments returns error
|
|
365
|
+
await test('Tool with missing required arguments returns error', async () => {
|
|
366
|
+
const response = await sendRequest({
|
|
367
|
+
jsonrpc: '2.0',
|
|
368
|
+
id: 1,
|
|
369
|
+
method: 'tools/call',
|
|
370
|
+
params: {
|
|
371
|
+
name: 'get_license_info',
|
|
372
|
+
arguments: {} // Missing required platform
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
assert(response.result && response.result.isError,
|
|
377
|
+
'Should return error for missing required argument');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// ============================================
|
|
381
|
+
// Test Summary
|
|
382
|
+
// ============================================
|
|
383
|
+
|
|
384
|
+
console.log('\n' + '='.repeat(50));
|
|
385
|
+
console.log('\nš Test Summary\n');
|
|
386
|
+
console.log(` Total: ${passed + failed}`);
|
|
387
|
+
console.log(` Passed: ${passed} ā
`);
|
|
388
|
+
console.log(` Failed: ${failed} ā`);
|
|
389
|
+
console.log(` Rate: ${((passed / (passed + failed)) * 100).toFixed(1)}%`);
|
|
390
|
+
|
|
391
|
+
if (failed > 0) {
|
|
392
|
+
console.log('\nā Failed Tests:');
|
|
393
|
+
results.filter(r => r.status.includes('FAILED')).forEach(r => {
|
|
394
|
+
console.log(` - ${r.name}: ${r.error}`);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log('\n' + '='.repeat(50));
|
|
399
|
+
|
|
400
|
+
// Exit with appropriate code
|
|
401
|
+
process.exit(failed > 0 ? 1 : 0);
|