cj-tauri 0.8.0 → 1.1.1
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/README.md +53 -7
- package/cjpm.toml +52 -25
- package/cli/cjpm.toml +1 -1
- package/cli/src/doctor.cj +304 -0
- package/cli/src/main.cj +33 -7
- package/cli/src/project.cj +59 -1
- package/cli/src/resolve.cj +348 -304
- package/cli/src/scaffold.cj +139 -22
- package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/README.md +79 -0
- package/cli/templates/ohos-pc/build-hap.sh +190 -0
- package/cli/templates/ohos-pc/build-profile.json5 +46 -0
- package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
- package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
- package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
- package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
- package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
- package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/oh-package.json5 +10 -0
- package/native/build_linux.sh +30 -2
- package/native/build_win.bat +62 -61
- package/native/core/bridge_core.c +1626 -0
- package/native/{bridge_core.h → core/bridge_core.h} +246 -6
- package/native/platforms/harmonyos/README.md +106 -0
- package/native/platforms/linux/bridge_linux.c +2485 -0
- package/native/platforms/windows/bridge_win.c +3248 -0
- package/native/tests/test_bridge_core.c +528 -1
- package/package.json +12 -3
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/api_system.cj +10 -2
- package/src/app.cj +248 -85
- package/src/arkweb_proxy.cj +56 -0
- package/src/arkweb_script.cj +154 -0
- package/src/capability.cj +140 -0
- package/src/clipboard_bridge.cj +55 -0
- package/src/dragdrop.cj +26 -0
- package/src/embedded_assets.cj +98 -0
- package/src/hexutil.cj +53 -0
- package/src/host.cj +197 -26
- package/src/host_arkweb.cj +740 -0
- package/src/host_factory.cj +48 -0
- package/src/host_webkit.cj +202 -26
- package/src/host_webview2.cj +176 -3
- package/src/hotkey.cj +66 -0
- package/src/ipc_hub.cj +49 -0
- package/src/ipc_message.cj +2 -1
- package/src/jsutil.cj +33 -0
- package/src/log_bridge.cj +107 -0
- package/src/logging.cj +127 -0
- package/src/menu.cj +197 -196
- package/src/plugin.cj +40 -0
- package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
- package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
- package/src/plugins/fs/plugin_fs.cj +194 -0
- package/src/plugins/http/plugin_http.cj +459 -0
- package/src/plugins/log/plugin_log.cj +105 -0
- package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
- package/src/plugins/mqtt/mqtt_codec.cj +661 -0
- package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
- package/src/plugins/notification/plugin_notification.cj +146 -0
- package/src/plugins/plugins.cj +5 -0
- package/src/plugins/redis/plugin_redis.cj +2409 -0
- package/src/plugins/redis/redis_codec.cj +556 -0
- package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
- package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
- package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
- package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
- package/src/plugins/store/plugin_store.cj +287 -0
- package/src/plugins/ws/plugin_ws.cj +470 -0
- package/src/process_hidden.cj +174 -0
- package/src/scope.cj +391 -0
- package/src/shell_event.cj +69 -68
- package/src/tests/capability_test.cj +53 -0
- package/src/tests/fake_host.cj +85 -20
- package/src/tests/ipc_hub_test.cj +13 -0
- package/src/tests/jsutil_test.cj +62 -0
- package/src/tests/logging_test.cj +62 -0
- package/src/tests/mqtt_codec_test.cj +405 -0
- package/src/tests/plugin_clipboard_test.cj +222 -0
- package/src/tests/plugin_dialog_test.cj +98 -4
- package/src/tests/plugin_http_test.cj +124 -0
- package/src/tests/plugin_menu_test.cj +1 -0
- package/src/tests/plugin_notification_test.cj +205 -0
- package/src/tests/plugin_serial_test.cj +269 -267
- package/src/tests/plugin_shell_test.cj +189 -188
- package/src/tests/plugin_sqlite_test.cj +230 -0
- package/src/tests/plugin_store_test.cj +260 -0
- package/src/tests/plugin_test.cj +542 -524
- package/src/tests/plugin_ws_test.cj +40 -0
- package/src/tests/process_hidden_test.cj +129 -0
- package/src/tests/redis_codec_test.cj +396 -0
- package/src/tests/scope_test.cj +350 -0
- package/src/tests/shell_event_test.cj +0 -1
- package/src/tests/system_host_test.cj +81 -79
- package/src/tests/urlutil_test.cj +53 -0
- package/src/tests/version_test.cj +75 -65
- package/src/tray.cj +90 -0
- package/src/urlutil.cj +159 -0
- package/src/version.cj +23 -2
- package/src/window.cj +51 -42
- package/native/bridge_core.c +0 -797
- package/native/bridge_linux.c +0 -1268
- package/native/bridge_win.c +0 -1633
- package/src/plugin_fs.cj +0 -118
- /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
|
@@ -1,267 +1,269 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import std.
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let
|
|
45
|
-
@Expect(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@Expect(js.contains("
|
|
61
|
-
|
|
62
|
-
@Expect(js.contains("
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let
|
|
70
|
-
@Expect(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@Expect(serialBaudSupported(
|
|
87
|
-
|
|
88
|
-
@Expect(serialBaudSupported(
|
|
89
|
-
|
|
90
|
-
@Expect(serialBaudSupported(
|
|
91
|
-
|
|
92
|
-
@Expect(
|
|
93
|
-
|
|
94
|
-
@Expect(text.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@Expect(serialParityCode("
|
|
102
|
-
@Expect(serialParityCode("
|
|
103
|
-
@Expect(serialParityCode("
|
|
104
|
-
@Expect(serialParityCode("
|
|
105
|
-
|
|
106
|
-
@Expect(serialParityCode("
|
|
107
|
-
|
|
108
|
-
@Expect(serialParityCode("
|
|
109
|
-
@Expect(serialParityCode(""),
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
117
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
118
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
119
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
120
|
-
@Expect(serialPathLooksAllowed("
|
|
121
|
-
@Expect(serialPathLooksAllowed("
|
|
122
|
-
|
|
123
|
-
@Expect(serialPathLooksAllowed("
|
|
124
|
-
|
|
125
|
-
@Expect(serialPathLooksAllowed("/
|
|
126
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
127
|
-
@Expect(serialPathLooksAllowed("/dev/
|
|
128
|
-
@Expect(serialPathLooksAllowed("
|
|
129
|
-
@Expect(serialPathLooksAllowed("
|
|
130
|
-
@Expect(serialPathLooksAllowed(""), false)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@Expect(
|
|
143
|
-
//
|
|
144
|
-
@Expect(
|
|
145
|
-
|
|
146
|
-
@Expect(
|
|
147
|
-
|
|
148
|
-
@Expect(
|
|
149
|
-
|
|
150
|
-
@Expect(serialBytesToText(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
@Expect(serialErrOf("open",
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
args
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
args
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
args
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
args
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
args
|
|
195
|
-
|
|
196
|
-
args
|
|
197
|
-
args.put("
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
args
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
let
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
@Expect(
|
|
263
|
-
//
|
|
264
|
-
@Expect(ports[i]
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import cjTauri.plugins.serial.*
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import std.sort.*
|
|
7
|
+
import stdx.encoding.json.*
|
|
8
|
+
import std.unittest.*
|
|
9
|
+
import std.unittest.testmacro.*
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* serial 插件单元测试:纯函数(档位 / 校验位文案 / 路径白名单 / hex / 文本)+ 命令面参数校验
|
|
13
|
+
* + 命名权限集的闸门。
|
|
14
|
+
*
|
|
15
|
+
* 为什么这里不测真的读写:设备 I/O 在 C 桥里(native/bridge_linux.c 的 termios 层),
|
|
16
|
+
* 需要真实设备节点或伪终端 —— 归实机验证(examples/plugin-serial + socat 造的虚拟串口对)。
|
|
17
|
+
* 这里只锁「不碰设备就能判定的部分」,且这几条正是页面最容易踩错的入口。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** 造带路径的 open 参数 */
|
|
21
|
+
func serialOpenArgs(path: String): JsonObject {
|
|
22
|
+
var o = JsonObject()
|
|
23
|
+
o.put("path", JsonString(path))
|
|
24
|
+
return o
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 取一条命令处理器 */
|
|
28
|
+
func serialCmd(short: String): CommandHandler {
|
|
29
|
+
return SerialPlugin().commands().get(short).getOrThrow()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 直接调命令,把 CommandException 的原文取出来(没抛就返回空串)*/
|
|
33
|
+
func serialErrOf(short: String, args: JsonObject): String {
|
|
34
|
+
try {
|
|
35
|
+
serialCmd(short).handle("serial:${short}", args, IpcContext("main", IpcHub()))
|
|
36
|
+
return ""
|
|
37
|
+
} catch (e: CommandException) {
|
|
38
|
+
return e.message
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
func testSerialPluginSurface(): Unit {
|
|
44
|
+
let p = SerialPlugin()
|
|
45
|
+
@Expect(p.name(), "serial")
|
|
46
|
+
let names = pluginCommandNames(p)
|
|
47
|
+
@Expect(names.size, 5)
|
|
48
|
+
var found = 0
|
|
49
|
+
for (n in names) {
|
|
50
|
+
if (n == "serial:list" || n == "serial:open" || n == "serial:read" || n == "serial:write" || n == "serial:close") {
|
|
51
|
+
found++
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
@Expect(found, 5)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
func testSerialPluginShimTargetsItsOwnNamespace(): Unit {
|
|
59
|
+
let js = SerialPlugin().jsShim()
|
|
60
|
+
@Expect(js.contains("t.serial"), true)
|
|
61
|
+
@Expect(js.contains("'serial:open'"), true)
|
|
62
|
+
@Expect(js.contains("'serial:write'"), true)
|
|
63
|
+
// shim 只挂自己的命名空间:不该动 invoke / listen / emit
|
|
64
|
+
@Expect(js.contains("invoke('serial:"), true)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test
|
|
68
|
+
func testSerialPermissionSetsGateWrite(): Unit {
|
|
69
|
+
let sets = pluginPermissionSets(SerialPlugin())
|
|
70
|
+
@Expect(sets.size, 2)
|
|
71
|
+
let ro = sets.get("serial:readonly").getOrThrow()
|
|
72
|
+
@Expect(ro.size, 4)
|
|
73
|
+
var roHasWrite = false
|
|
74
|
+
for (m in ro) {
|
|
75
|
+
if (m == "serial:write") {
|
|
76
|
+
roHasWrite = true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 只想读设备的应用不该顺带拿到写能力
|
|
80
|
+
@Expect(roHasWrite, false)
|
|
81
|
+
@Expect(sets.get("serial:default").getOrThrow().size, 5)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
func testSerialBaudTableMatchesBridge(): Unit {
|
|
86
|
+
@Expect(serialBaudSupported(9600), true)
|
|
87
|
+
@Expect(serialBaudSupported(115200), true)
|
|
88
|
+
@Expect(serialBaudSupported(1000000), true)
|
|
89
|
+
// 档位之外一律不收(C 侧 termios 也没有对应宏,收了只会变成运行期报错)
|
|
90
|
+
@Expect(serialBaudSupported(12345), false)
|
|
91
|
+
@Expect(serialBaudSupported(0), false)
|
|
92
|
+
@Expect(serialBaudSupported(-1), false)
|
|
93
|
+
let text = serialBaudListText()
|
|
94
|
+
@Expect(text.contains("115200"), true)
|
|
95
|
+
@Expect(text.startsWith("300/"), true)
|
|
96
|
+
@Expect(text.endsWith("1000000"), true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
func testSerialParityTextMapping(): Unit {
|
|
101
|
+
@Expect(serialParityCode("none"), 0)
|
|
102
|
+
@Expect(serialParityCode("n"), 0)
|
|
103
|
+
@Expect(serialParityCode("odd"), 1)
|
|
104
|
+
@Expect(serialParityCode("o"), 1)
|
|
105
|
+
@Expect(serialParityCode("even"), 2)
|
|
106
|
+
@Expect(serialParityCode("e"), 2)
|
|
107
|
+
// 大小写不敏感:前端从配置里读到的常是大写
|
|
108
|
+
@Expect(serialParityCode("None"), 0)
|
|
109
|
+
@Expect(serialParityCode("EVEN"), 2)
|
|
110
|
+
@Expect(serialParityCode("mark"), -1)
|
|
111
|
+
@Expect(serialParityCode(""), -1)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@Test
|
|
115
|
+
func testSerialPathWhitelistMirror(): Unit {
|
|
116
|
+
@Expect(serialPathLooksAllowed("/dev/ttyUSB0"), true)
|
|
117
|
+
@Expect(serialPathLooksAllowed("/dev/ttyACM0"), true)
|
|
118
|
+
@Expect(serialPathLooksAllowed("/dev/ttyS3"), true)
|
|
119
|
+
@Expect(serialPathLooksAllowed("/dev/pts/7"), true)
|
|
120
|
+
@Expect(serialPathLooksAllowed("/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0"), true)
|
|
121
|
+
@Expect(serialPathLooksAllowed("/dev/cu.usbserial-1420"), true)
|
|
122
|
+
@Expect(serialPathLooksAllowed("COM3"), true)
|
|
123
|
+
@Expect(serialPathLooksAllowed("\\\\.\\COM12"), true)
|
|
124
|
+
|
|
125
|
+
@Expect(serialPathLooksAllowed("/etc/shadow"), false)
|
|
126
|
+
@Expect(serialPathLooksAllowed("/dev/tty"), false)
|
|
127
|
+
@Expect(serialPathLooksAllowed("/dev/ttyS"), false)
|
|
128
|
+
@Expect(serialPathLooksAllowed("/dev/ttyUSB0/../../etc/shadow"), false)
|
|
129
|
+
@Expect(serialPathLooksAllowed("/dev/serial/by-id/../../etc/passwd"), false)
|
|
130
|
+
@Expect(serialPathLooksAllowed("COMfoo"), false)
|
|
131
|
+
@Expect(serialPathLooksAllowed("COM1234"), false)
|
|
132
|
+
@Expect(serialPathLooksAllowed(""), false)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Test
|
|
136
|
+
func testSerialHexAndTextConversions(): Unit {
|
|
137
|
+
let bytes = [UInt8(0), UInt8(15), UInt8(165), UInt8(255)]
|
|
138
|
+
@Expect(bytesToHex(bytes), "000fa5ff")
|
|
139
|
+
|
|
140
|
+
let back = hexToBytes("000fa5ff").getOrThrow()
|
|
141
|
+
@Expect(back.size, 4)
|
|
142
|
+
@Expect(bytesToHex(back), "000fa5ff")
|
|
143
|
+
// 大写十六进制也认
|
|
144
|
+
@Expect(bytesToHex(hexToBytes("A5").getOrThrow()), "a5")
|
|
145
|
+
// 奇数长度 / 非十六进制字符一律拒绝,不猜
|
|
146
|
+
@Expect(hexToBytes("0ff").isNone(), true)
|
|
147
|
+
@Expect(hexToBytes("zz").isNone(), true)
|
|
148
|
+
@Expect(hexToBytes("").getOrThrow().size, 0)
|
|
149
|
+
|
|
150
|
+
@Expect(serialBytesToText("hi".toArray()).getOrThrow(), "hi")
|
|
151
|
+
// 二进制帧(不是合法 UTF-8)不该硬塞成一个坏字符串
|
|
152
|
+
@Expect(serialBytesToText([UInt8(255), UInt8(254)]).isNone(), true)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
func testSerialOpenRejectsBadArgumentsBeforeTouchingDevice(): Unit {
|
|
157
|
+
// 白名单在仓颉侧先给一句人话;真正的边界在 C 桥,但错误文案要能定位
|
|
158
|
+
@Expect(serialErrOf("open", serialOpenArgs("/etc/shadow")).contains("不在允许的设备名单内"), true)
|
|
159
|
+
// 缺参数
|
|
160
|
+
@Expect(serialErrOf("open", JsonObject()).contains("missing argument \"path\""), true)
|
|
161
|
+
|
|
162
|
+
var args = serialOpenArgs("/dev/ttyUSB0")
|
|
163
|
+
args.put("baud", JsonInt(12345))
|
|
164
|
+
@Expect(serialErrOf("open", args).contains("不支持的波特率"), true)
|
|
165
|
+
|
|
166
|
+
args = serialOpenArgs("/dev/ttyUSB0")
|
|
167
|
+
args.put("dataBits", JsonInt(9))
|
|
168
|
+
@Expect(serialErrOf("open", args).contains("dataBits"), true)
|
|
169
|
+
|
|
170
|
+
args = serialOpenArgs("/dev/ttyUSB0")
|
|
171
|
+
args.put("parity", JsonString("mark"))
|
|
172
|
+
@Expect(serialErrOf("open", args).contains("parity"), true)
|
|
173
|
+
|
|
174
|
+
args = serialOpenArgs("/dev/ttyUSB0")
|
|
175
|
+
args.put("stopBits", JsonInt(3))
|
|
176
|
+
@Expect(serialErrOf("open", args).contains("stopBits"), true)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Test
|
|
180
|
+
func testSerialHandleAndPayloadValidation(): Unit {
|
|
181
|
+
// 句柄缺失 / 不是 open 给的
|
|
182
|
+
@Expect(serialErrOf("read", JsonObject()).contains("missing argument \"handle\""), true)
|
|
183
|
+
var args = JsonObject()
|
|
184
|
+
args.put("handle", JsonInt(0))
|
|
185
|
+
@Expect(serialErrOf("read", args).contains("must be a handle returned by serial:open"), true)
|
|
186
|
+
@Expect(serialErrOf("close", args).contains("must be a handle returned by serial:open"), true)
|
|
187
|
+
|
|
188
|
+
args = JsonObject()
|
|
189
|
+
args.put("handle", JsonInt(1))
|
|
190
|
+
@Expect(serialErrOf("write", args).contains("missing argument \"data\""), true)
|
|
191
|
+
|
|
192
|
+
args.put("data", JsonString("hi"))
|
|
193
|
+
args.put("encoding", JsonString("base64"))
|
|
194
|
+
@Expect(serialErrOf("write", args).contains("encoding 只认"), true)
|
|
195
|
+
|
|
196
|
+
args = JsonObject()
|
|
197
|
+
args.put("handle", JsonInt(1))
|
|
198
|
+
args.put("data", JsonString("abc"))
|
|
199
|
+
args.put("encoding", JsonString("hex"))
|
|
200
|
+
@Expect(serialErrOf("write", args).contains("不是合法十六进制串"), true)
|
|
201
|
+
|
|
202
|
+
// 超长写入在仓颉侧就挡住(单次上限与 read 的 maxBytes 同量级)
|
|
203
|
+
var big = ""
|
|
204
|
+
var i = 0
|
|
205
|
+
while (i < 4097) {
|
|
206
|
+
big = big + "a"
|
|
207
|
+
i++
|
|
208
|
+
}
|
|
209
|
+
args = JsonObject()
|
|
210
|
+
args.put("handle", JsonInt(1))
|
|
211
|
+
args.put("data", JsonString(big))
|
|
212
|
+
@Expect(serialErrOf("write", args).contains("单次最多 4096 字节"), true)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@Test
|
|
216
|
+
func testSerialRejectsUnknownCommand(): Unit {
|
|
217
|
+
var threw = false
|
|
218
|
+
try {
|
|
219
|
+
serialCmd("open").handle("serial:nope", JsonObject(), IpcContext("main", IpcHub()))
|
|
220
|
+
} catch (e: CommandException) {
|
|
221
|
+
threw = true
|
|
222
|
+
@Expect(e.message.contains("unknown serial command"), true)
|
|
223
|
+
}
|
|
224
|
+
@Expect(threw, true)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@Test
|
|
228
|
+
func testSerialPermissionSetGatesCommands(): Unit {
|
|
229
|
+
let out = ArrayList<String>()
|
|
230
|
+
let reg = CapabilityRegistry()
|
|
231
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"s\",\"permissions\":[\"serial:readonly\"]}"))
|
|
232
|
+
let hub = hubWithRegistry(reg, out)
|
|
233
|
+
registerPluginCommands(hub, SerialPlugin())
|
|
234
|
+
registerPluginPermissionSets(reg, SerialPlugin())
|
|
235
|
+
|
|
236
|
+
// 集内命令放行:没有参数 → 报的是业务错误(缺 handle),不是「未授权」
|
|
237
|
+
hub.handleRawMessage("{\"type\":\"invoke\",\"id\":41,\"cmd\":\"serial:read\"}")
|
|
238
|
+
waitSink(out, 1)
|
|
239
|
+
@Expect(out.size, 1)
|
|
240
|
+
@Expect(strField(out[0], "error").contains("command not allowed"), false)
|
|
241
|
+
|
|
242
|
+
// 写命令不在 readonly 集内:仍然拒
|
|
243
|
+
hub.handleRawMessage("{\"type\":\"invoke\",\"id\":42,\"cmd\":\"serial:write\"}")
|
|
244
|
+
@Expect(out.size, 2)
|
|
245
|
+
@Expect(strField(out[1], "error"), "command not allowed: serial:write")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@Test
|
|
249
|
+
func testSerialPortListIsSortedAndWhitelisted(): Unit {
|
|
250
|
+
let ports = serialPortList()
|
|
251
|
+
// 再排一次应逐元素相等:证明输出有序且无重复(页面下拉按这个顺序渲染)
|
|
252
|
+
let sorted = ArrayList<String>()
|
|
253
|
+
var i = 0
|
|
254
|
+
while (i < ports.size) {
|
|
255
|
+
sorted.add(ports[i])
|
|
256
|
+
i++
|
|
257
|
+
}
|
|
258
|
+
sort(sorted)
|
|
259
|
+
@Expect(sorted.size, ports.size)
|
|
260
|
+
i = 0
|
|
261
|
+
while (i < ports.size) {
|
|
262
|
+
@Expect(sorted[i], ports[i])
|
|
263
|
+
// 枚举出的端口必须过得了 C 桥白名单——列表与白名单是同一套设备面
|
|
264
|
+
@Expect(serialPathLooksAllowed(ports[i]), true)
|
|
265
|
+
// 伪终端(探针用的 pty)不算「本机串口」,不进下拉(白名单仍放行它)
|
|
266
|
+
@Expect(ports[i].contains("/pts/"), false)
|
|
267
|
+
i++
|
|
268
|
+
}
|
|
269
|
+
}
|