hap-cli 0.5.0__py3-none-any.whl

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.
Files changed (58) hide show
  1. hap_cli/README.md +194 -0
  2. hap_cli/README_CN.md +601 -0
  3. hap_cli/__init__.py +3 -0
  4. hap_cli/commands/__init__.py +1 -0
  5. hap_cli/commands/ai_cmd.py +224 -0
  6. hap_cli/commands/app_cmd.py +308 -0
  7. hap_cli/commands/calendar_cmd.py +138 -0
  8. hap_cli/commands/chat_cmd.py +101 -0
  9. hap_cli/commands/config_cmd.py +169 -0
  10. hap_cli/commands/contact_cmd.py +125 -0
  11. hap_cli/commands/department_cmd.py +168 -0
  12. hap_cli/commands/group_cmd.py +128 -0
  13. hap_cli/commands/instance_cmd.py +310 -0
  14. hap_cli/commands/node_cmd.py +538 -0
  15. hap_cli/commands/optionset_cmd.py +99 -0
  16. hap_cli/commands/page_cmd.py +102 -0
  17. hap_cli/commands/plugin_cmd.py +133 -0
  18. hap_cli/commands/post_cmd.py +155 -0
  19. hap_cli/commands/record_cmd.py +228 -0
  20. hap_cli/commands/role_cmd.py +221 -0
  21. hap_cli/commands/workflow_cmd.py +284 -0
  22. hap_cli/commands/worksheet_cmd.py +342 -0
  23. hap_cli/context.py +43 -0
  24. hap_cli/core/__init__.py +1 -0
  25. hap_cli/core/ai.py +133 -0
  26. hap_cli/core/app.py +307 -0
  27. hap_cli/core/auth.py +219 -0
  28. hap_cli/core/calendar_mod.py +114 -0
  29. hap_cli/core/chat.py +73 -0
  30. hap_cli/core/contact.py +85 -0
  31. hap_cli/core/department.py +131 -0
  32. hap_cli/core/flow_node.py +1001 -0
  33. hap_cli/core/group.py +99 -0
  34. hap_cli/core/instance.py +572 -0
  35. hap_cli/core/optionset.py +112 -0
  36. hap_cli/core/page.py +138 -0
  37. hap_cli/core/plugin.py +87 -0
  38. hap_cli/core/post.py +118 -0
  39. hap_cli/core/record.py +268 -0
  40. hap_cli/core/role.py +227 -0
  41. hap_cli/core/session.py +348 -0
  42. hap_cli/core/workflow.py +556 -0
  43. hap_cli/core/worksheet.py +403 -0
  44. hap_cli/hap_cli.py +105 -0
  45. hap_cli/skills/SKILL.md +383 -0
  46. hap_cli/skills/__init__.py +0 -0
  47. hap_cli/tests/__init__.py +1 -0
  48. hap_cli/tests/test_core.py +1824 -0
  49. hap_cli/tests/test_full_e2e.py +136 -0
  50. hap_cli/tests/test_integration.py +805 -0
  51. hap_cli/utils/__init__.py +1 -0
  52. hap_cli/utils/formatting.py +111 -0
  53. hap_cli/utils/options.py +10 -0
  54. hap_cli-0.5.0.dist-info/METADATA +223 -0
  55. hap_cli-0.5.0.dist-info/RECORD +58 -0
  56. hap_cli-0.5.0.dist-info/WHEEL +5 -0
  57. hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
  58. hap_cli-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1824 @@
1
+ """Unit tests for hap-cli harness core modules."""
2
+
3
+ import json
4
+ import os
5
+ import tempfile
6
+ from pathlib import Path
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+ from click.testing import CliRunner
11
+
12
+ from hap_cli.core.session import (
13
+ Session,
14
+ SessionError,
15
+ APIError,
16
+ load_config,
17
+ save_config,
18
+ CONFIG_DIR,
19
+ CONFIG_FILE,
20
+ )
21
+ from hap_cli.core import app as app_mod
22
+ from hap_cli.core import worksheet as ws_mod
23
+ from hap_cli.core import record as rec_mod
24
+ from hap_cli.core import workflow as wf_mod
25
+ from hap_cli.core import flow_node as node_mod
26
+ from hap_cli.core import instance as inst_mod
27
+ from hap_cli.core import role as role_mod
28
+ from hap_cli.utils.formatting import (
29
+ output_json,
30
+ output_table,
31
+ output_kv,
32
+ format_record_row,
33
+ )
34
+ from hap_cli.hap_cli import cli
35
+
36
+
37
+ # ── Fixtures ─────────────────────────────────────────────────────────────
38
+
39
+
40
+ @pytest.fixture
41
+ def tmp_config(tmp_path, monkeypatch):
42
+ """Use a temporary config directory."""
43
+ config_dir = tmp_path / ".hap-cli"
44
+ config_file = config_dir / "config.json"
45
+ monkeypatch.setattr("hap_cli.core.session.CONFIG_DIR", config_dir)
46
+ monkeypatch.setattr("hap_cli.core.session.CONFIG_FILE", config_file)
47
+ return config_dir, config_file
48
+
49
+
50
+ @pytest.fixture
51
+ def mock_session():
52
+ """Create a configured mock session."""
53
+ session = Session(server_url="https://test.mingdao.com", auth_token="test_token_123")
54
+ return session
55
+
56
+
57
+ def _mock_response(data, state=1):
58
+ """Create a mock requests.Response."""
59
+ resp = MagicMock()
60
+ resp.status_code = 200
61
+ resp.json.return_value = {"data": data, "state": state}
62
+ resp.raise_for_status = MagicMock()
63
+ return resp
64
+
65
+
66
+ # ── Session Tests ────────────────────────────────────────────────────────
67
+
68
+
69
+ class TestSession:
70
+ def test_load_empty_config(self, tmp_config):
71
+ session = Session.load()
72
+ assert session.server_url == ""
73
+ assert session.auth_token == ""
74
+ assert not session.is_configured()
75
+
76
+ def test_save_and_load_config(self, tmp_config):
77
+ config_dir, config_file = tmp_config
78
+ session = Session(server_url="https://test.com", auth_token="tok123")
79
+ session.default_app_id = "app1"
80
+ session.default_project_id = "proj1"
81
+ session.save()
82
+
83
+ assert config_file.exists()
84
+ loaded = Session.load()
85
+ assert loaded.server_url == "https://test.com"
86
+ assert loaded.auth_token == "tok123"
87
+ assert loaded.default_app_id == "app1"
88
+ assert loaded.default_project_id == "proj1"
89
+
90
+ def test_is_configured(self):
91
+ assert not Session().is_configured()
92
+ assert not Session(server_url="https://x.com").is_configured()
93
+ assert not Session(auth_token="tok").is_configured()
94
+ assert Session(server_url="https://x.com", auth_token="tok").is_configured()
95
+
96
+ @patch("hap_cli.core.session.requests.post")
97
+ def test_api_call_success(self, mock_post, mock_session):
98
+ mock_post.return_value = _mock_response({"id": "123", "name": "Test"})
99
+ result = mock_session.api_call("Worksheet", "GetWorksheetInfo", {"worksheetId": "ws1"})
100
+ assert result == {"id": "123", "name": "Test"}
101
+
102
+ call_args = mock_post.call_args
103
+ assert "api/Worksheet/GetWorksheetInfo" in call_args[0][0]
104
+ assert call_args[1]["json"] == {"worksheetId": "ws1"}
105
+ assert "md_pss_id test_token_123" in call_args[1]["headers"]["Authorization"]
106
+
107
+ @patch("hap_cli.core.session.requests.post")
108
+ def test_api_call_error_state(self, mock_post, mock_session):
109
+ resp = MagicMock()
110
+ resp.status_code = 200
111
+ resp.json.return_value = {"state": 0, "exception": "Not found"}
112
+ resp.raise_for_status = MagicMock()
113
+ mock_post.return_value = resp
114
+
115
+ with pytest.raises(APIError, match="Not found"):
116
+ mock_session.api_call("Worksheet", "GetWorksheetInfo", {})
117
+
118
+ def test_api_call_not_configured(self):
119
+ session = Session()
120
+ with pytest.raises(SessionError, match="not configured"):
121
+ session.api_call("Worksheet", "GetWorksheetInfo", {})
122
+
123
+ @patch("hap_cli.core.session.requests.post")
124
+ def test_api_call_strips_trailing_slash(self, mock_post, mock_session):
125
+ mock_session.server_url = "https://test.com/"
126
+ mock_post.return_value = _mock_response({})
127
+ mock_session.api_call("Test", "Action", {})
128
+ url = mock_post.call_args[0][0]
129
+ assert "//api" not in url
130
+
131
+
132
+ # ── App Tests ────────────────────────────────────────────────────────────
133
+
134
+
135
+ class TestApp:
136
+ @patch("hap_cli.core.session.requests.post")
137
+ def test_get_app_info(self, mock_post, mock_session):
138
+ mock_post.return_value = _mock_response({"appId": "a1", "name": "TestApp"})
139
+ result = app_mod.get_app_info(mock_session, "a1")
140
+ assert result["appId"] == "a1"
141
+
142
+ @patch("hap_cli.core.session.requests.post")
143
+ def test_get_app_worksheets_list(self, mock_post, mock_session):
144
+ mock_post.return_value = _mock_response([
145
+ {"worksheetId": "ws1", "name": "Sheet1"},
146
+ {"worksheetId": "ws2", "name": "Sheet2"},
147
+ ])
148
+ result = app_mod.get_app_worksheets(mock_session, "a1")
149
+ assert len(result) == 2
150
+ assert result[0]["worksheetId"] == "ws1"
151
+
152
+ @patch("hap_cli.core.session.requests.post")
153
+ def test_get_apps_for_project_list(self, mock_post, mock_session):
154
+ mock_post.return_value = _mock_response([
155
+ {"appId": "a1", "name": "App1"},
156
+ ])
157
+ result = app_mod.get_apps_for_project(mock_session, "proj1")
158
+ assert len(result) == 1
159
+
160
+
161
+ # ── Worksheet Tests ──────────────────────────────────────────────────────
162
+
163
+
164
+ class TestWorksheet:
165
+ @patch("hap_cli.core.session.requests.post")
166
+ def test_get_worksheet_info(self, mock_post, mock_session):
167
+ mock_post.return_value = _mock_response({"worksheetId": "ws1", "name": "Test"})
168
+ result = ws_mod.get_worksheet_info(mock_session, "ws1")
169
+ assert result["name"] == "Test"
170
+
171
+ @patch("hap_cli.core.session.requests.post")
172
+ def test_get_worksheet_controls(self, mock_post, mock_session):
173
+ mock_post.return_value = _mock_response([
174
+ {"controlId": "c1", "controlName": "Name", "type": 2},
175
+ ])
176
+ result = ws_mod.get_worksheet_controls(mock_session, "ws1")
177
+ assert len(result) == 1
178
+ assert result[0]["controlName"] == "Name"
179
+
180
+ @patch("hap_cli.core.session.requests.post")
181
+ def test_get_worksheet_views(self, mock_post, mock_session):
182
+ mock_post.return_value = _mock_response([
183
+ {"viewId": "v1", "name": "Default"},
184
+ ])
185
+ result = ws_mod.get_worksheet_views(mock_session, "ws1")
186
+ assert len(result) == 1
187
+
188
+
189
+ # ── Record Tests ─────────────────────────────────────────────────────────
190
+
191
+
192
+ class TestRecord:
193
+ @patch("hap_cli.core.session.requests.post")
194
+ def test_get_records(self, mock_post, mock_session):
195
+ mock_post.return_value = _mock_response({
196
+ "data": [{"rowid": "r1"}, {"rowid": "r2"}],
197
+ "count": 2,
198
+ })
199
+ result = rec_mod.get_records(mock_session, "ws1", page_size=10)
200
+ assert result["count"] == 2
201
+ assert len(result["data"]) == 2
202
+
203
+ @patch("hap_cli.core.session.requests.post")
204
+ def test_get_record(self, mock_post, mock_session):
205
+ mock_post.return_value = _mock_response({"rowid": "r1", "c1": "val1"})
206
+ result = rec_mod.get_record(mock_session, "ws1", "r1")
207
+ assert result["rowid"] == "r1"
208
+
209
+ @patch("hap_cli.core.session.requests.post")
210
+ def test_create_record(self, mock_post, mock_session):
211
+ mock_post.return_value = _mock_response({"rowid": "r_new"})
212
+ result = rec_mod.create_record(
213
+ mock_session, "ws1",
214
+ [{"controlId": "c1", "value": "hello"}],
215
+ )
216
+ assert result["rowid"] == "r_new"
217
+ body = mock_post.call_args[1]["json"]
218
+ assert body["receiveControls"] == [{"controlId": "c1", "value": "hello"}]
219
+
220
+ @patch("hap_cli.core.session.requests.post")
221
+ def test_update_record(self, mock_post, mock_session):
222
+ mock_post.return_value = _mock_response(True)
223
+ result = rec_mod.update_record(
224
+ mock_session, "ws1", "r1",
225
+ [{"controlId": "c1", "value": "updated"}],
226
+ )
227
+ body = mock_post.call_args[1]["json"]
228
+ assert body["rowId"] == "r1"
229
+ assert body["newOldControl"] == [{"controlId": "c1", "value": "updated"}]
230
+
231
+ @patch("hap_cli.core.session.requests.post")
232
+ def test_delete_records(self, mock_post, mock_session):
233
+ mock_post.return_value = _mock_response({"isSuccess": True})
234
+ result = rec_mod.delete_records(mock_session, "ws1", ["r1", "r2"])
235
+ body = mock_post.call_args[1]["json"]
236
+ assert body["rowIds"] == ["r1", "r2"]
237
+
238
+
239
+ # ── Workflow Tests ───────────────────────────────────────────────────────
240
+
241
+
242
+ class TestWorkflow:
243
+ @patch("hap_cli.core.session.requests.post")
244
+ def test_get_process_list(self, mock_post, mock_session):
245
+ mock_post.return_value = _mock_response({
246
+ "list": [{"id": "wf1", "name": "Flow1"}],
247
+ "total": 1,
248
+ })
249
+ result = wf_mod.get_process_list(mock_session, "a1")
250
+ assert result["count"] == 1
251
+
252
+ @patch("hap_cli.core.session.requests.post")
253
+ def test_create_process(self, mock_post, mock_session):
254
+ mock_post.return_value = _mock_response({"processId": "wf_new"})
255
+ result = wf_mod.create_process(mock_session, "comp1", "My Flow", relation_id="app1")
256
+ body = mock_post.call_args[1]["json"]
257
+ assert body["companyId"] == "comp1"
258
+ assert body["name"] == "My Flow"
259
+ assert body["relationId"] == "app1"
260
+ assert result["processId"] == "wf_new"
261
+
262
+ @patch("hap_cli.core.session.requests.post")
263
+ def test_delete_process(self, mock_post, mock_session):
264
+ mock_post.return_value = _mock_response(True)
265
+ wf_mod.delete_process(mock_session, "wf1")
266
+ body = mock_post.call_args[1]["json"]
267
+ assert body["processId"] == "wf1"
268
+
269
+ @patch("hap_cli.core.session.requests.post")
270
+ def test_update_process(self, mock_post, mock_session):
271
+ mock_post.return_value = _mock_response(True)
272
+ wf_mod.update_process(mock_session, "wf1", name="Renamed")
273
+ body = mock_post.call_args[1]["json"]
274
+ assert body["processId"] == "wf1"
275
+ assert body["name"] == "Renamed"
276
+
277
+ @patch("hap_cli.core.session.requests.post")
278
+ def test_copy_process(self, mock_post, mock_session):
279
+ mock_post.return_value = _mock_response({"processId": "wf_copy"})
280
+ result = wf_mod.copy_process(mock_session, "wf1", "Copy of Flow")
281
+ body = mock_post.call_args[1]["json"]
282
+ assert body["processId"] == "wf1"
283
+ assert body["name"] == "Copy of Flow"
284
+
285
+ @patch("hap_cli.core.session.requests.post")
286
+ def test_publish(self, mock_post, mock_session):
287
+ mock_post.return_value = _mock_response(True)
288
+ wf_mod.publish(mock_session, "wf1", is_publish=True)
289
+ body = mock_post.call_args[1]["json"]
290
+ assert body["processId"] == "wf1"
291
+ assert body["isPublish"] is True
292
+
293
+ @patch("hap_cli.core.session.requests.post")
294
+ def test_publish_disable(self, mock_post, mock_session):
295
+ mock_post.return_value = _mock_response(True)
296
+ wf_mod.publish(mock_session, "wf1", is_publish=False)
297
+ body = mock_post.call_args[1]["json"]
298
+ assert body["isPublish"] is False
299
+
300
+ @patch("hap_cli.core.session.requests.post")
301
+ def test_rollback_version(self, mock_post, mock_session):
302
+ mock_post.return_value = _mock_response(True)
303
+ wf_mod.rollback_version(mock_session, "wf1")
304
+ body = mock_post.call_args[1]["json"]
305
+ assert body["processId"] == "wf1"
306
+
307
+ @patch("hap_cli.core.session.requests.post")
308
+ def test_get_process_config(self, mock_post, mock_session):
309
+ mock_post.return_value = _mock_response({"executeType": 1, "allowRevoke": True})
310
+ result = wf_mod.get_process_config(mock_session, "wf1")
311
+ assert result["allowRevoke"] is True
312
+
313
+ @patch("hap_cli.core.session.requests.post")
314
+ def test_save_process_config(self, mock_post, mock_session):
315
+ mock_post.return_value = _mock_response(True)
316
+ wf_mod.save_process_config(mock_session, "wf1", {"allowRevoke": False, "executeType": 2})
317
+ body = mock_post.call_args[1]["json"]
318
+ assert body["processId"] == "wf1"
319
+ assert body["allowRevoke"] is False
320
+
321
+ @patch("hap_cli.core.session.requests.post")
322
+ def test_start_process_by_id(self, mock_post, mock_session):
323
+ mock_post.return_value = _mock_response({"instanceId": "inst1"})
324
+ result = wf_mod.start_process_by_id(mock_session, "wf1", source_id="r1")
325
+ body = mock_post.call_args[1]["json"]
326
+ assert body["processId"] == "wf1"
327
+ assert body["sourceId"] == "r1"
328
+
329
+ @patch("hap_cli.core.session.requests.post")
330
+ def test_get_process_by_id(self, mock_post, mock_session):
331
+ mock_post.return_value = _mock_response({"id": "wf1", "name": "Flow"})
332
+ result = wf_mod.get_process_by_id(mock_session, "wf1")
333
+ assert result["name"] == "Flow"
334
+
335
+ @patch("hap_cli.core.session.requests.post")
336
+ def test_get_version_history(self, mock_post, mock_session):
337
+ mock_post.return_value = _mock_response([{"version": 1}, {"version": 2}])
338
+ result = wf_mod.get_version_history(mock_session, "wf1")
339
+ assert isinstance(result, list)
340
+
341
+ @patch("hap_cli.core.session.requests.post")
342
+ def test_move_process(self, mock_post, mock_session):
343
+ mock_post.return_value = _mock_response(True)
344
+ wf_mod.move_process(mock_session, "wf1", "app2")
345
+ body = mock_post.call_args[1]["json"]
346
+ assert body["relationId"] == "app2"
347
+
348
+ @patch("hap_cli.core.session.requests.post")
349
+ def test_get_group_list(self, mock_post, mock_session):
350
+ mock_post.return_value = _mock_response([{"id": "g1", "name": "Group1"}])
351
+ result = wf_mod.get_group_list(mock_session, "app1")
352
+ assert len(result) == 1
353
+
354
+ @patch("hap_cli.core.session.requests.post")
355
+ def test_create_group(self, mock_post, mock_session):
356
+ mock_post.return_value = _mock_response({"id": "g_new"})
357
+ result = wf_mod.create_group(mock_session, "app1", "New Group")
358
+ body = mock_post.call_args[1]["json"]
359
+ assert body["name"] == "New Group"
360
+
361
+
362
+ # ── FlowNode Tests ──────────────────────────────────────────────────────
363
+
364
+
365
+ class TestFlowNode:
366
+ @patch("hap_cli.core.session.requests.post")
367
+ def test_add_node(self, mock_post, mock_session):
368
+ mock_post.return_value = _mock_response({"nodeId": "n_new"})
369
+ result = node_mod.add_node(mock_session, "wf1", 4, name="Approval")
370
+ body = mock_post.call_args[1]["json"]
371
+ assert body["processId"] == "wf1"
372
+ assert body["flowNodeType"] == 4
373
+ assert body["name"] == "Approval"
374
+
375
+ @patch("hap_cli.core.session.requests.post")
376
+ def test_delete_node(self, mock_post, mock_session):
377
+ mock_post.return_value = _mock_response(True)
378
+ node_mod.delete_node(mock_session, "wf1", "n1")
379
+ body = mock_post.call_args[1]["json"]
380
+ assert body["nodeId"] == "n1"
381
+
382
+ @patch("hap_cli.core.session.requests.post")
383
+ def test_get_nodes(self, mock_post, mock_session):
384
+ mock_post.return_value = _mock_response({"startId": "n0", "nodes": []})
385
+ result = node_mod.get_nodes(mock_session, "wf1")
386
+ assert "startId" in result
387
+
388
+ @patch("hap_cli.core.session.requests.post")
389
+ def test_get_node_detail(self, mock_post, mock_session):
390
+ mock_post.return_value = _mock_response({"nodeId": "n1", "flowNodeType": 4})
391
+ result = node_mod.get_node_detail(mock_session, "wf1", "n1")
392
+ assert result["flowNodeType"] == 4
393
+
394
+ @patch("hap_cli.core.session.requests.post")
395
+ def test_update_node_name(self, mock_post, mock_session):
396
+ mock_post.return_value = _mock_response(True)
397
+ node_mod.update_node_name(mock_session, "wf1", "n1", "New Name")
398
+ body = mock_post.call_args[1]["json"]
399
+ assert body["name"] == "New Name"
400
+
401
+ @patch("hap_cli.core.session.requests.post")
402
+ def test_update_node_desc(self, mock_post, mock_session):
403
+ mock_post.return_value = _mock_response(True)
404
+ node_mod.update_node_desc(mock_session, "wf1", "n1", desc="A desc", alias="Alias1")
405
+ body = mock_post.call_args[1]["json"]
406
+ assert body["desc"] == "A desc"
407
+ assert body["alias"] == "Alias1"
408
+
409
+ @patch("hap_cli.core.session.requests.post")
410
+ def test_save_node(self, mock_post, mock_session):
411
+ mock_post.return_value = _mock_response(True)
412
+ config = {"accounts": [{"accountId": "u1"}], "condition": "auto"}
413
+ node_mod.save_node(mock_session, "wf1", "n1", 4, config, name="Approval")
414
+ body = mock_post.call_args[1]["json"]
415
+ assert body["processId"] == "wf1"
416
+ assert body["flowNodeType"] == 4
417
+ assert body["accounts"] == [{"accountId": "u1"}]
418
+ assert body["name"] == "Approval"
419
+
420
+ @patch("hap_cli.core.session.requests.post")
421
+ def test_test_code(self, mock_post, mock_session):
422
+ mock_post.return_value = _mock_response({"output": "Hello"})
423
+ result = node_mod.test_code(
424
+ mock_session, "wf1", "n1", "return 'Hello'",
425
+ input_data=[{"name": "x", "value": "1", "type": "number"}],
426
+ )
427
+ body = mock_post.call_args[1]["json"]
428
+ assert body["code"] == "return 'Hello'"
429
+ assert body["inputDatas"][0]["name"] == "x"
430
+
431
+ @patch("hap_cli.core.session.requests.post")
432
+ def test_test_webhook(self, mock_post, mock_session):
433
+ mock_post.return_value = _mock_response({"status": 200, "body": "{}"})
434
+ result = node_mod.test_webhook(
435
+ mock_session, "wf1", "n1", "https://example.com/api",
436
+ method="GET", headers=[{"name": "X-Key", "value": "abc"}],
437
+ )
438
+ body = mock_post.call_args[1]["json"]
439
+ assert body["url"] == "https://example.com/api"
440
+ assert body["method"] == "GET"
441
+
442
+ @patch("hap_cli.core.session.requests.post")
443
+ def test_test_aigc(self, mock_post, mock_session):
444
+ mock_post.return_value = _mock_response({"text": "AI response"})
445
+ result = node_mod.test_aigc(
446
+ mock_session, "wf1", "n1", "Summarize this",
447
+ model="gpt-4", system_message="You are helpful", temperature=0.5,
448
+ )
449
+ body = mock_post.call_args[1]["json"]
450
+ assert body["prompt"] == "Summarize this"
451
+ assert body["model"] == "gpt-4"
452
+ assert body["temperature"] == 0.5
453
+
454
+ @patch("hap_cli.core.session.requests.post")
455
+ def test_json_to_controls(self, mock_post, mock_session):
456
+ mock_post.return_value = _mock_response([{"controlId": "auto1"}])
457
+ result = node_mod.json_to_controls(mock_session, '{"name": "test"}')
458
+ body = mock_post.call_args[1]["json"]
459
+ assert body["json"] == '{"name": "test"}'
460
+
461
+ @patch("hap_cli.core.session.requests.post")
462
+ def test_get_code_template_list(self, mock_post, mock_session):
463
+ mock_post.return_value = _mock_response([{"id": "t1", "name": "Template1"}])
464
+ result = node_mod.get_code_template_list(mock_session, keyword="test")
465
+ body = mock_post.call_args[1]["json"]
466
+ assert body["keyword"] == "test"
467
+
468
+ @patch("hap_cli.core.session.requests.post")
469
+ def test_create_code_template(self, mock_post, mock_session):
470
+ mock_post.return_value = _mock_response({"id": "t_new"})
471
+ result = node_mod.create_code_template(mock_session, "My Tmpl", "console.log('hi')")
472
+ body = mock_post.call_args[1]["json"]
473
+ assert body["name"] == "My Tmpl"
474
+ assert body["code"] == "console.log('hi')"
475
+
476
+ @patch("hap_cli.core.session.requests.post")
477
+ def test_get_sub_process_list(self, mock_post, mock_session):
478
+ mock_post.return_value = _mock_response([{"id": "sub1"}])
479
+ result = node_mod.get_sub_process_list(mock_session, "wf1")
480
+ body = mock_post.call_args[1]["json"]
481
+ assert body["processId"] == "wf1"
482
+
483
+ @patch("hap_cli.core.session.requests.post")
484
+ def test_add_node_with_action_id(self, mock_post, mock_session):
485
+ mock_post.return_value = _mock_response({"nodeId": "n_act"})
486
+ node_mod.add_node(mock_session, "wf1", 6, name="Add Record", action_id="1")
487
+ body = mock_post.call_args[1]["json"]
488
+ assert body["flowNodeType"] == 6
489
+ assert body["actionId"] == "1"
490
+
491
+ @patch("hap_cli.core.session.requests.post")
492
+ def test_save_action_node_add_record(self, mock_post, mock_session):
493
+ mock_post.return_value = _mock_response(True)
494
+ fields = [{"fieldId": "c001", "type": 2, "fieldValue": "hello"}]
495
+ node_mod.save_action_node(
496
+ mock_session, "wf1", "n1", "1", "ws_abc",
497
+ fields=fields, name="Add Record",
498
+ )
499
+ body = mock_post.call_args[1]["json"]
500
+ assert body["flowNodeType"] == 6
501
+ assert body["actionId"] == "1"
502
+ assert body["appId"] == "ws_abc"
503
+ assert body["appType"] == 1
504
+ assert body["fields"][0]["fieldId"] == "c001"
505
+ assert body["name"] == "Add Record"
506
+
507
+ @patch("hap_cli.core.session.requests.post")
508
+ def test_save_action_node_edit_record(self, mock_post, mock_session):
509
+ mock_post.return_value = _mock_response(True)
510
+ node_mod.save_action_node(
511
+ mock_session, "wf1", "n1", "2", "ws_abc",
512
+ select_node_id="src_node",
513
+ fields=[{"fieldId": "c001", "type": 2, "fieldValue": "updated"}],
514
+ )
515
+ body = mock_post.call_args[1]["json"]
516
+ assert body["actionId"] == "2"
517
+ assert body["selectNodeId"] == "src_node"
518
+
519
+ @patch("hap_cli.core.session.requests.post")
520
+ def test_save_action_node_delete_record(self, mock_post, mock_session):
521
+ mock_post.return_value = _mock_response(True)
522
+ node_mod.save_action_node(
523
+ mock_session, "wf1", "n1", "3", "ws_abc",
524
+ select_node_id="src_node",
525
+ )
526
+ body = mock_post.call_args[1]["json"]
527
+ assert body["actionId"] == "3"
528
+
529
+ @patch("hap_cli.core.session.requests.post")
530
+ def test_save_search_node_worksheet_find(self, mock_post, mock_session):
531
+ mock_post.return_value = _mock_response(True)
532
+ cond = [{"fieldId": "c001", "conditionId": "1", "value": ["test"]}]
533
+ node_mod.save_search_node(
534
+ mock_session, "wf1", "n1", "406",
535
+ app_id="ws_abc", operate_condition=cond,
536
+ )
537
+ body = mock_post.call_args[1]["json"]
538
+ assert body["flowNodeType"] == 7
539
+ assert body["actionId"] == "406"
540
+ assert body["appId"] == "ws_abc"
541
+ assert body["operateCondition"][0]["fieldId"] == "c001"
542
+
543
+ @patch("hap_cli.core.session.requests.post")
544
+ def test_save_search_node_find_and_update(self, mock_post, mock_session):
545
+ mock_post.return_value = _mock_response(True)
546
+ node_mod.save_search_node(
547
+ mock_session, "wf1", "n1", "421",
548
+ app_id="ws_abc",
549
+ fields=[{"fieldId": "c002", "type": 2, "fieldValue": "new"}],
550
+ )
551
+ body = mock_post.call_args[1]["json"]
552
+ assert body["actionId"] == "421"
553
+ assert body["fields"][0]["fieldValue"] == "new"
554
+
555
+ @patch("hap_cli.core.session.requests.post")
556
+ def test_save_search_node_execute_type(self, mock_post, mock_session):
557
+ mock_post.return_value = _mock_response(True)
558
+ node_mod.save_search_node(
559
+ mock_session, "wf1", "n1", "406",
560
+ app_id="ws_abc", execute_type=2, random=True,
561
+ )
562
+ body = mock_post.call_args[1]["json"]
563
+ assert body["executeType"] == 2
564
+ assert body["random"] is True
565
+
566
+ @patch("hap_cli.core.session.requests.post")
567
+ def test_save_get_more_record_from_worksheet(self, mock_post, mock_session):
568
+ mock_post.return_value = _mock_response(True)
569
+ node_mod.save_get_more_record_node(
570
+ mock_session, "wf1", "n1", "400",
571
+ app_id="ws_abc",
572
+ sorts=[{"fieldId": "c001", "isAsc": True}],
573
+ )
574
+ body = mock_post.call_args[1]["json"]
575
+ assert body["flowNodeType"] == 13
576
+ assert body["actionId"] == "400"
577
+ assert body["appId"] == "ws_abc"
578
+ assert body["sorts"][0]["isAsc"] is True
579
+
580
+ @patch("hap_cli.core.session.requests.post")
581
+ def test_save_get_more_record_batch_update(self, mock_post, mock_session):
582
+ mock_post.return_value = _mock_response(True)
583
+ node_mod.save_get_more_record_node(
584
+ mock_session, "wf1", "n1", "412",
585
+ app_id="ws_abc",
586
+ fields=[{"fieldId": "c001", "type": 2, "fieldValue": "batch"}],
587
+ )
588
+ body = mock_post.call_args[1]["json"]
589
+ assert body["actionId"] == "412"
590
+ assert body["fields"][0]["fieldValue"] == "batch"
591
+
592
+ @patch("hap_cli.core.session.requests.post")
593
+ def test_save_get_more_record_batch_delete(self, mock_post, mock_session):
594
+ mock_post.return_value = _mock_response(True)
595
+ node_mod.save_get_more_record_node(
596
+ mock_session, "wf1", "n1", "413",
597
+ app_id="ws_abc",
598
+ )
599
+ body = mock_post.call_args[1]["json"]
600
+ assert body["actionId"] == "413"
601
+
602
+ @patch("hap_cli.core.session.requests.post")
603
+ def test_save_get_more_record_with_limit(self, mock_post, mock_session):
604
+ mock_post.return_value = _mock_response(True)
605
+ node_mod.save_get_more_record_node(
606
+ mock_session, "wf1", "n1", "400",
607
+ app_id="ws_abc",
608
+ number_field_value={"fieldValue": 100},
609
+ )
610
+ body = mock_post.call_args[1]["json"]
611
+ assert body["numberFieldValue"]["fieldValue"] == 100
612
+
613
+ @patch("hap_cli.core.session.requests.post")
614
+ def test_get_app_template_controls(self, mock_post, mock_session):
615
+ mock_post.return_value = _mock_response([{"controlId": "c001", "controlName": "Name"}])
616
+ result = node_mod.get_app_template_controls(mock_session, "wf1", "n1", "ws_abc")
617
+ body = mock_post.call_args[1]["json"]
618
+ assert body["appId"] == "ws_abc"
619
+ assert body["appType"] == 1
620
+ assert result[0]["controlId"] == "c001"
621
+
622
+ @patch("hap_cli.core.session.requests.post")
623
+ def test_node_type_constants(self, mock_post, mock_session):
624
+ assert node_mod.NODE_TYPE["ACTION"] == 6
625
+ assert node_mod.NODE_TYPE["SEARCH"] == 7
626
+ assert node_mod.NODE_TYPE["GET_MORE_RECORD"] == 13
627
+ assert node_mod.NODE_TYPE["AIGC"] == 31
628
+ assert node_mod.NODE_TYPE["LOOP"] == 29
629
+
630
+ def test_action_id_constants(self, mock_session):
631
+ assert node_mod.ACTION_ID["ADD_RECORD"] == "1"
632
+ assert node_mod.ACTION_ID["EDIT_RECORD"] == "2"
633
+ assert node_mod.ACTION_ID["DELETE_RECORD"] == "3"
634
+ assert node_mod.ACTION_ID["WORKSHEET_FIND"] == "406"
635
+ assert node_mod.ACTION_ID["BATCH_UPDATE"] == "412"
636
+ assert node_mod.ACTION_ID["BATCH_DELETE"] == "413"
637
+ assert node_mod.ACTION_ID["FROM_WORKSHEET"] == "400"
638
+
639
+
640
+ # ── Instance Tests ──────────────────────────────────────────────────────
641
+
642
+
643
+ class TestInstance:
644
+ @patch("hap_cli.core.session.requests.post")
645
+ def test_get_todo_count(self, mock_post, mock_session):
646
+ mock_post.return_value = _mock_response({"waitingApproval": 3, "waitingFill": 1})
647
+ result = inst_mod.get_todo_count(mock_session)
648
+ assert result["waitingApproval"] == 3
649
+
650
+ @patch("hap_cli.core.session.requests.post")
651
+ def test_get_todo_list(self, mock_post, mock_session):
652
+ mock_post.return_value = _mock_response({
653
+ "list": [{"id": "inst1", "title": "Task1"}],
654
+ "total": 1,
655
+ })
656
+ result = inst_mod.get_todo_list(mock_session, type_=4)
657
+ assert result["count"] == 1
658
+
659
+ @patch("hap_cli.core.session.requests.post")
660
+ def test_get_instance_detail(self, mock_post, mock_session):
661
+ mock_post.return_value = _mock_response({"id": "inst1", "status": 1})
662
+ result = inst_mod.get_instance_detail(mock_session, "inst1")
663
+ assert result["status"] == 1
664
+
665
+ @patch("hap_cli.core.session.requests.post")
666
+ def test_approve(self, mock_post, mock_session):
667
+ mock_post.return_value = _mock_response(True)
668
+ inst_mod.approve(mock_session, "inst1", work_id="w1", opinion="LGTM")
669
+ body = mock_post.call_args[1]["json"]
670
+ assert body["id"] == "inst1"
671
+ assert body["workId"] == "w1"
672
+ assert body["opinion"] == "LGTM"
673
+
674
+ @patch("hap_cli.core.session.requests.post")
675
+ def test_reject(self, mock_post, mock_session):
676
+ mock_post.return_value = _mock_response(True)
677
+ inst_mod.reject(mock_session, "inst1", opinion="Not ready", back_node_id="n0")
678
+ body = mock_post.call_args[1]["json"]
679
+ assert body["opinion"] == "Not ready"
680
+ assert body["backNodeId"] == "n0"
681
+
682
+ @patch("hap_cli.core.session.requests.post")
683
+ def test_forward(self, mock_post, mock_session):
684
+ mock_post.return_value = _mock_response(True)
685
+ inst_mod.forward(mock_session, "inst1", "user2", opinion="Please review")
686
+ body = mock_post.call_args[1]["json"]
687
+ assert body["forwardAccountId"] == "user2"
688
+
689
+ @patch("hap_cli.core.session.requests.post")
690
+ def test_sign_task(self, mock_post, mock_session):
691
+ mock_post.return_value = _mock_response(True)
692
+ inst_mod.sign_task(mock_session, "inst1", "user3", before=False)
693
+ body = mock_post.call_args[1]["json"]
694
+ assert body["forwardAccountId"] == "user3"
695
+ assert body["before"] is False
696
+
697
+ @patch("hap_cli.core.session.requests.post")
698
+ def test_submit(self, mock_post, mock_session):
699
+ mock_post.return_value = _mock_response(True)
700
+ inst_mod.submit(mock_session, "inst1", work_id="w1")
701
+ body = mock_post.call_args[1]["json"]
702
+ assert body["id"] == "inst1"
703
+
704
+ @patch("hap_cli.core.session.requests.post")
705
+ def test_revoke(self, mock_post, mock_session):
706
+ mock_post.return_value = _mock_response(True)
707
+ inst_mod.revoke(mock_session, "inst1")
708
+ body = mock_post.call_args[1]["json"]
709
+ assert body["id"] == "inst1"
710
+
711
+ @patch("hap_cli.core.session.requests.post")
712
+ def test_urge(self, mock_post, mock_session):
713
+ mock_post.return_value = _mock_response(True)
714
+ inst_mod.urge(mock_session, "inst1", work_id="w1")
715
+ body = mock_post.call_args[1]["json"]
716
+ assert body["operationType"] == 18
717
+
718
+ @patch("hap_cli.core.session.requests.post")
719
+ def test_end_instance(self, mock_post, mock_session):
720
+ mock_post.return_value = _mock_response(True)
721
+ inst_mod.end_instance(mock_session, "inst1")
722
+ body = mock_post.call_args[1]["json"]
723
+ assert body["instanceId"] == "inst1"
724
+
725
+ @patch("hap_cli.core.session.requests.post")
726
+ def test_reset_instance(self, mock_post, mock_session):
727
+ mock_post.return_value = _mock_response(True)
728
+ inst_mod.reset_instance(mock_session, "inst1")
729
+ body = mock_post.call_args[1]["json"]
730
+ assert body["instanceId"] == "inst1"
731
+
732
+ @patch("hap_cli.core.session.requests.post")
733
+ def test_get_history_list(self, mock_post, mock_session):
734
+ mock_post.return_value = _mock_response({
735
+ "list": [{"instanceId": "inst1", "status": 2}],
736
+ "total": 1,
737
+ })
738
+ result = inst_mod.get_history_list(mock_session, process_id="wf1", status=2)
739
+ assert result["count"] == 1
740
+
741
+ @patch("hap_cli.core.session.requests.post")
742
+ def test_get_history_detail(self, mock_post, mock_session):
743
+ mock_post.return_value = _mock_response({"instanceId": "inst1", "nodes": []})
744
+ result = inst_mod.get_history_detail(mock_session, "inst1")
745
+ assert result["instanceId"] == "inst1"
746
+
747
+ @patch("hap_cli.core.session.requests.post")
748
+ def test_get_operation_history(self, mock_post, mock_session):
749
+ mock_post.return_value = _mock_response([{"action": "pass", "time": "2024-01-01"}])
750
+ result = inst_mod.get_operation_history(mock_session, "inst1")
751
+ assert len(result) == 1
752
+
753
+ @patch("hap_cli.core.session.requests.post")
754
+ def test_batch_operation(self, mock_post, mock_session):
755
+ mock_post.return_value = _mock_response({"success": 5})
756
+ result = inst_mod.batch_operation(
757
+ mock_session, 4, process_id="wf1", selects=["s1", "s2"],
758
+ )
759
+ body = mock_post.call_args[1]["json"]
760
+ assert body["batchOperationType"] == 4
761
+ assert body["selects"] == ["s1", "s2"]
762
+
763
+
764
+ # ── Role Tests ───────────────────────────────────────────────────────────
765
+
766
+
767
+ class TestRole:
768
+ @patch("hap_cli.core.session.requests.post")
769
+ def test_get_roles(self, mock_post, mock_session):
770
+ mock_post.return_value = _mock_response([
771
+ {"roleId": "role1", "name": "Admin"},
772
+ ])
773
+ result = role_mod.get_roles(mock_session, "a1")
774
+ assert len(result) == 1
775
+
776
+ @patch("hap_cli.core.session.requests.post")
777
+ def test_create_role(self, mock_post, mock_session):
778
+ mock_post.return_value = _mock_response({"roleId": "role_new"})
779
+ result = role_mod.create_role(mock_session, "a1", "Viewer", "Read-only", 10)
780
+ body = mock_post.call_args[1]["json"]
781
+ assert body["name"] == "Viewer"
782
+ assert body["permissionWay"] == 10
783
+
784
+ @patch("hap_cli.core.session.requests.post")
785
+ def test_delete_role(self, mock_post, mock_session):
786
+ mock_post.return_value = _mock_response(True)
787
+ role_mod.delete_role(mock_session, "a1", "role1", "role2")
788
+ body = mock_post.call_args[1]["json"]
789
+ assert body["roleId"] == "role1"
790
+ assert body["resultRoleId"] == "role2"
791
+
792
+ @patch("hap_cli.core.session.requests.post")
793
+ def test_add_role_members(self, mock_post, mock_session):
794
+ mock_post.return_value = _mock_response(True)
795
+ role_mod.add_role_members(mock_session, "a1", "role1", user_ids=["u1", "u2"])
796
+ body = mock_post.call_args[1]["json"]
797
+ assert body["userIds"] == ["u1", "u2"]
798
+
799
+ @patch("hap_cli.core.session.requests.post")
800
+ def test_remove_role_members(self, mock_post, mock_session):
801
+ mock_post.return_value = _mock_response(True)
802
+ role_mod.remove_role_members(mock_session, "a1", "role1", department_ids=["d1"])
803
+ body = mock_post.call_args[1]["json"]
804
+ assert body["departmentIds"] == ["d1"]
805
+
806
+
807
+ # ── Formatting Tests ─────────────────────────────────────────────────────
808
+
809
+
810
+ class TestFormatting:
811
+ def test_output_json(self, capsys):
812
+ output_json({"key": "value", "num": 42})
813
+ captured = capsys.readouterr()
814
+ data = json.loads(captured.out)
815
+ assert data["key"] == "value"
816
+ assert data["num"] == 42
817
+
818
+ def test_output_table(self, capsys):
819
+ rows = [
820
+ {"id": "1", "name": "Alice"},
821
+ {"id": "2", "name": "Bob"},
822
+ ]
823
+ output_table(rows, ["id", "name"], ["ID", "Name"])
824
+ captured = capsys.readouterr()
825
+ assert "Alice" in captured.out
826
+ assert "Bob" in captured.out
827
+ assert "ID" in captured.out
828
+
829
+ def test_output_table_empty(self, capsys):
830
+ output_table([], ["id"], ["ID"])
831
+ captured = capsys.readouterr()
832
+ assert "no data" in captured.out
833
+
834
+ def test_format_record_row_basic(self):
835
+ record = {"rowid": "r1", "c1": "hello", "c2": "world"}
836
+ result = format_record_row(record)
837
+ assert result["rowId"] == "r1"
838
+ assert result["c1"] == "hello"
839
+
840
+ def test_format_record_row_with_controls(self):
841
+ record = {"rowid": "r1", "c1": "hello"}
842
+ controls = [{"controlId": "c1", "controlName": "Name"}]
843
+ result = format_record_row(record, controls)
844
+ assert "Name" in result
845
+ assert result["Name"] == "hello"
846
+
847
+ def test_format_record_row_json_array(self):
848
+ record = {"rowid": "r1", "c1": json.dumps([{"name": "A"}, {"name": "B"}])}
849
+ result = format_record_row(record)
850
+ assert result["c1"] == "A, B"
851
+
852
+
853
+ # ── Auth Tests ───────────────────────────────────────────────────────────
854
+
855
+
856
+ class TestAuth:
857
+ def test_resolve_server_mingdao(self):
858
+ from hap_cli.core.auth import resolve_server
859
+ api, webui = resolve_server("mingdao")
860
+ assert api == "https://www.mingdao.com/api"
861
+ assert webui == "https://www.mingdao.com"
862
+
863
+ def test_resolve_server_nocoly(self):
864
+ from hap_cli.core.auth import resolve_server
865
+ api, webui = resolve_server("nocoly")
866
+ assert api == "https://www.nocoly.com/wwwapi"
867
+ assert webui == "https://www.nocoly.com"
868
+
869
+ def test_resolve_server_self_hosted(self):
870
+ from hap_cli.core.auth import resolve_server
871
+ api, webui = resolve_server("https://hap.example.com")
872
+ assert api == "https://hap.example.com/wwwapi"
873
+ assert webui == "https://hap.example.com"
874
+
875
+ def test_resolve_server_trailing_slash(self):
876
+ from hap_cli.core.auth import resolve_server
877
+ api, webui = resolve_server("https://hap.example.com/")
878
+ assert api == "https://hap.example.com/wwwapi"
879
+ assert webui == "https://hap.example.com"
880
+
881
+ def test_build_auth_url(self):
882
+ from hap_cli.core.auth import build_auth_url
883
+ import base64
884
+ url = build_auth_url("https://www.mingdao.com", 5100)
885
+ assert url.startswith("https://www.mingdao.com/cliauth?p=")
886
+ # Decode and verify callback info
887
+ encoded = url.split("?p=")[1]
888
+ decoded = json.loads(base64.b64decode(encoded).decode())
889
+ assert decoded == {"url": "http://localhost:5100"}
890
+
891
+ def test_get_available_port(self):
892
+ from hap_cli.core.auth import get_available_port
893
+ port = get_available_port()
894
+ assert 5100 <= port < 5200
895
+
896
+ @patch("hap_cli.core.auth.requests.post")
897
+ def test_get_user_info_success(self, mock_post):
898
+ from hap_cli.core.auth import get_user_info
899
+ resp = MagicMock()
900
+ resp.json.return_value = {
901
+ "state": 1,
902
+ "data": {
903
+ "md.global": {
904
+ "Account": {
905
+ "accountId": "uid1",
906
+ "fullname": "Test User",
907
+ "email": "test@example.com",
908
+ "avatar": "avatar.png",
909
+ "lang": "zh-Hans",
910
+ }
911
+ }
912
+ },
913
+ }
914
+ resp.raise_for_status = MagicMock()
915
+ mock_post.return_value = resp
916
+
917
+ info = get_user_info("https://www.mingdao.com/api", "tok123")
918
+ assert info["id"] == "uid1"
919
+ assert info["name"] == "Test User"
920
+ assert info["email"] == "test@example.com"
921
+ assert info["lang"] == "zh-Hans"
922
+
923
+ # Verify correct API call
924
+ call_args = mock_post.call_args
925
+ assert "/Global/GetGlobalMeta" in call_args[0][0]
926
+ assert call_args[1]["headers"]["Authorization"] == "md_pss_id tok123"
927
+
928
+ @patch("hap_cli.core.auth.requests.post")
929
+ def test_get_user_info_invalid_token(self, mock_post):
930
+ from hap_cli.core.auth import get_user_info
931
+ resp = MagicMock()
932
+ resp.json.return_value = {"state": 0, "exception": "Invalid token"}
933
+ resp.raise_for_status = MagicMock()
934
+ mock_post.return_value = resp
935
+
936
+ with pytest.raises(ValueError, match="Invalid token"):
937
+ get_user_info("https://www.mingdao.com/api", "bad_token")
938
+
939
+ def test_config_login_help(self):
940
+ runner = CliRunner()
941
+ result = runner.invoke(cli, ["config", "login", "--help"])
942
+ assert result.exit_code == 0
943
+ assert "Login via browser" in result.output
944
+ assert "mingdao" in result.output
945
+
946
+ def test_config_logout(self, tmp_config):
947
+ runner = CliRunner()
948
+ runner.invoke(cli, [
949
+ "config", "set", "--server", "https://t.com", "--token", "tok",
950
+ ])
951
+ result = runner.invoke(cli, ["config", "logout"])
952
+ assert result.exit_code == 0
953
+ assert "Logged out" in result.output
954
+
955
+ # Verify token is cleared
956
+ result = runner.invoke(cli, ["--json", "config", "show"])
957
+ data = json.loads(result.output)
958
+ assert data["auth_token"] == ""
959
+ assert data["configured"] is False
960
+
961
+ @patch("hap_cli.core.auth.get_user_info")
962
+ def test_config_whoami(self, mock_user_info, tmp_config):
963
+ runner = CliRunner()
964
+ runner.invoke(cli, [
965
+ "config", "set", "--server", "https://t.com", "--token", "tok",
966
+ ])
967
+ mock_user_info.return_value = {
968
+ "id": "uid1", "name": "Test", "email": "t@e.com",
969
+ "avatar": "", "lang": "en",
970
+ }
971
+ result = runner.invoke(cli, ["--json", "config", "whoami"])
972
+ assert result.exit_code == 0
973
+ data = json.loads(result.output)
974
+ assert data["name"] == "Test"
975
+ assert data["email"] == "t@e.com"
976
+
977
+ def test_config_whoami_not_logged_in(self, tmp_config):
978
+ runner = CliRunner()
979
+ result = runner.invoke(cli, ["config", "whoami"])
980
+ assert result.exit_code != 0
981
+
982
+
983
+ # ── CLI Tests ────────────────────────────────────────────────────────────
984
+
985
+
986
+ class TestCLI:
987
+ def test_help(self):
988
+ runner = CliRunner()
989
+ result = runner.invoke(cli, ["--help"])
990
+ assert result.exit_code == 0
991
+ assert "MingDAO HAP" in result.output
992
+ assert "app" in result.output
993
+ assert "record" in result.output
994
+ assert "workflow" in result.output
995
+
996
+ def test_config_set_and_show(self, tmp_config):
997
+ runner = CliRunner()
998
+ result = runner.invoke(cli, [
999
+ "config", "set",
1000
+ "--server", "https://test.com",
1001
+ "--token", "tok123",
1002
+ ])
1003
+ assert result.exit_code == 0
1004
+
1005
+ result = runner.invoke(cli, ["config", "show"])
1006
+ assert result.exit_code == 0
1007
+ assert "test.com" in result.output
1008
+
1009
+ def test_config_show_json(self, tmp_config):
1010
+ runner = CliRunner()
1011
+ runner.invoke(cli, [
1012
+ "config", "set",
1013
+ "--server", "https://test.com",
1014
+ "--token", "tok123",
1015
+ ])
1016
+ result = runner.invoke(cli, ["--json", "config", "show"])
1017
+ assert result.exit_code == 0
1018
+ data = json.loads(result.output)
1019
+ assert data["server_url"] == "https://test.com"
1020
+ assert data["configured"] is True
1021
+
1022
+ @patch("hap_cli.core.session.requests.post")
1023
+ def test_record_list(self, mock_post, tmp_config):
1024
+ # First configure
1025
+ runner = CliRunner()
1026
+ runner.invoke(cli, [
1027
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1028
+ ])
1029
+ mock_post.return_value = _mock_response({
1030
+ "data": [{"rowid": "r1", "c1": "val"}],
1031
+ "count": 1,
1032
+ })
1033
+ result = runner.invoke(cli, ["--json", "record", "list", "ws1"])
1034
+ assert result.exit_code == 0
1035
+ data = json.loads(result.output)
1036
+ assert data["count"] == 1
1037
+
1038
+ @patch("hap_cli.core.session.requests.post")
1039
+ def test_worksheet_fields_json(self, mock_post, tmp_config):
1040
+ runner = CliRunner()
1041
+ runner.invoke(cli, [
1042
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1043
+ ])
1044
+ mock_post.return_value = _mock_response([
1045
+ {"controlId": "c1", "controlName": "Title", "type": 2},
1046
+ ])
1047
+ result = runner.invoke(cli, ["--json", "worksheet", "fields", "ws1"])
1048
+ assert result.exit_code == 0
1049
+ data = json.loads(result.output)
1050
+ assert len(data) == 1
1051
+ assert data[0]["controlName"] == "Title"
1052
+
1053
+ def test_missing_app_id_error(self, tmp_config):
1054
+ runner = CliRunner()
1055
+ runner.invoke(cli, [
1056
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1057
+ ])
1058
+ result = runner.invoke(cli, ["app", "worksheets"])
1059
+ assert result.exit_code != 0
1060
+
1061
+ def test_help_includes_new_groups(self):
1062
+ runner = CliRunner()
1063
+ result = runner.invoke(cli, ["--help"])
1064
+ assert "node" in result.output
1065
+ assert "instance" in result.output
1066
+ assert "workflow" in result.output
1067
+
1068
+ def test_workflow_help(self):
1069
+ runner = CliRunner()
1070
+ result = runner.invoke(cli, ["workflow", "--help"])
1071
+ assert result.exit_code == 0
1072
+ assert "create" in result.output
1073
+ assert "publish" in result.output
1074
+ assert "rollback" in result.output
1075
+ assert "trigger" in result.output
1076
+ assert "config-get" in result.output
1077
+
1078
+ def test_node_help(self):
1079
+ runner = CliRunner()
1080
+ result = runner.invoke(cli, ["node", "--help"])
1081
+ assert result.exit_code == 0
1082
+ assert "add" in result.output
1083
+ assert "save" in result.output
1084
+ assert "test-code" in result.output
1085
+ assert "test-webhook" in result.output
1086
+ assert "test-ai" in result.output
1087
+ assert "save-action" in result.output
1088
+ assert "save-search" in result.output
1089
+ assert "save-get-more" in result.output
1090
+ assert "controls" in result.output
1091
+ assert "types" in result.output
1092
+
1093
+ def test_node_types_command(self):
1094
+ runner = CliRunner()
1095
+ result = runner.invoke(cli, ["node", "types"])
1096
+ assert result.exit_code == 0
1097
+ assert "ACTION" in result.output
1098
+ assert "SEARCH" in result.output
1099
+ assert "GET_MORE_RECORD" in result.output
1100
+
1101
+ @patch("hap_cli.core.session.requests.post")
1102
+ def test_node_save_action_cli(self, mock_post, tmp_config):
1103
+ runner = CliRunner()
1104
+ runner.invoke(cli, [
1105
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1106
+ ])
1107
+ mock_post.return_value = _mock_response(True)
1108
+ result = runner.invoke(cli, [
1109
+ "node", "save-action", "wf1", "n1",
1110
+ "-a", "1", "--app-id", "ws_abc",
1111
+ "-f", '[{"fieldId":"c001","type":2,"fieldValue":"hello"}]',
1112
+ ])
1113
+ assert result.exit_code == 0
1114
+ assert "saved" in result.output.lower()
1115
+
1116
+ @patch("hap_cli.core.session.requests.post")
1117
+ def test_node_save_search_cli(self, mock_post, tmp_config):
1118
+ runner = CliRunner()
1119
+ runner.invoke(cli, [
1120
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1121
+ ])
1122
+ mock_post.return_value = _mock_response(True)
1123
+ result = runner.invoke(cli, [
1124
+ "node", "save-search", "wf1", "n1",
1125
+ "-a", "406", "--app-id", "ws_abc",
1126
+ ])
1127
+ assert result.exit_code == 0
1128
+
1129
+ @patch("hap_cli.core.session.requests.post")
1130
+ def test_node_save_get_more_cli(self, mock_post, tmp_config):
1131
+ runner = CliRunner()
1132
+ runner.invoke(cli, [
1133
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1134
+ ])
1135
+ mock_post.return_value = _mock_response(True)
1136
+ result = runner.invoke(cli, [
1137
+ "node", "save-get-more", "wf1", "n1",
1138
+ "-a", "400", "--app-id", "ws_abc",
1139
+ ])
1140
+ assert result.exit_code == 0
1141
+
1142
+ def test_instance_help(self):
1143
+ runner = CliRunner()
1144
+ result = runner.invoke(cli, ["instance", "--help"])
1145
+ assert result.exit_code == 0
1146
+ assert "approve" in result.output
1147
+ assert "reject" in result.output
1148
+ assert "forward" in result.output
1149
+ assert "todo" in result.output
1150
+ assert "batch" in result.output
1151
+
1152
+ @patch("hap_cli.core.session.requests.post")
1153
+ def test_workflow_create_json(self, mock_post, tmp_config):
1154
+ runner = CliRunner()
1155
+ runner.invoke(cli, [
1156
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1157
+ ])
1158
+ mock_post.return_value = _mock_response({"processId": "wf_new"})
1159
+ result = runner.invoke(cli, [
1160
+ "--json", "workflow", "create",
1161
+ "--company-id", "comp1", "--name", "Test Flow", "--app-id", "app1",
1162
+ ])
1163
+ assert result.exit_code == 0
1164
+ data = json.loads(result.output)
1165
+ assert data["processId"] == "wf_new"
1166
+
1167
+ @patch("hap_cli.core.session.requests.post")
1168
+ def test_node_list_json(self, mock_post, tmp_config):
1169
+ runner = CliRunner()
1170
+ runner.invoke(cli, [
1171
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1172
+ ])
1173
+ mock_post.return_value = _mock_response({"startId": "n0", "nodes": [{"id": "n1"}]})
1174
+ result = runner.invoke(cli, ["--json", "node", "list", "wf1"])
1175
+ assert result.exit_code == 0
1176
+ data = json.loads(result.output)
1177
+ assert "startId" in data
1178
+
1179
+ @patch("hap_cli.core.session.requests.post")
1180
+ def test_instance_todo_json(self, mock_post, tmp_config):
1181
+ runner = CliRunner()
1182
+ runner.invoke(cli, [
1183
+ "config", "set", "--server", "https://t.com", "--token", "tok",
1184
+ ])
1185
+ mock_post.return_value = _mock_response({
1186
+ "list": [{"id": "i1", "title": "Task"}], "total": 1,
1187
+ })
1188
+ result = runner.invoke(cli, ["--json", "instance", "todo"])
1189
+ assert result.exit_code == 0
1190
+ data = json.loads(result.output)
1191
+ assert data["count"] == 1
1192
+
1193
+
1194
+ # ── Optionset Tests ───────────────────────────────────────────────────────
1195
+
1196
+
1197
+ class TestOptionset:
1198
+ @patch("hap_cli.core.session.requests.post")
1199
+ def test_get_collections(self, mock_post, mock_session):
1200
+ mock_post.return_value = _mock_response([{"id": "os1", "name": "Status"}])
1201
+ from hap_cli.core import optionset as os_mod
1202
+ result = os_mod.get_collections(mock_session, "app1")
1203
+ assert result == [{"id": "os1", "name": "Status"}]
1204
+ args = mock_post.call_args
1205
+ assert "Worksheet/GetCollectionsByAppId" in args[0][0]
1206
+ assert args[1]["json"]["appId"] == "app1"
1207
+
1208
+ @patch("hap_cli.core.session.requests.post")
1209
+ def test_get_collection(self, mock_post, mock_session):
1210
+ mock_post.return_value = _mock_response({"id": "os1", "options": []})
1211
+ from hap_cli.core import optionset as os_mod
1212
+ result = os_mod.get_collection(mock_session, "os1")
1213
+ assert result["id"] == "os1"
1214
+ args = mock_post.call_args
1215
+ assert "Worksheet/GetCollectionByCollectId" in args[0][0]
1216
+ assert args[1]["json"]["collectId"] == "os1"
1217
+
1218
+ @patch("hap_cli.core.session.requests.post")
1219
+ def test_save_collection(self, mock_post, mock_session):
1220
+ mock_post.return_value = _mock_response({"id": "os1"})
1221
+ from hap_cli.core import optionset as os_mod
1222
+ result = os_mod.save_collection(mock_session, "app1", "Priority", [])
1223
+ assert result["id"] == "os1"
1224
+ args = mock_post.call_args
1225
+ assert "Worksheet/SaveOptionsCollection" in args[0][0]
1226
+
1227
+ @patch("hap_cli.core.session.requests.post")
1228
+ def test_delete_collection(self, mock_post, mock_session):
1229
+ mock_post.return_value = _mock_response(True)
1230
+ from hap_cli.core import optionset as os_mod
1231
+ result = os_mod.delete_collection(mock_session, "os1")
1232
+ assert result is True
1233
+ args = mock_post.call_args
1234
+ assert "Worksheet/DeleteOptionsCollection" in args[0][0]
1235
+ assert args[1]["json"]["collectId"] == "os1"
1236
+
1237
+ @patch("hap_cli.core.session.requests.post")
1238
+ def test_move_collection(self, mock_post, mock_session):
1239
+ mock_post.return_value = _mock_response(True)
1240
+ from hap_cli.core import optionset as os_mod
1241
+ result = os_mod.move_collection(mock_session, "os1", "app2")
1242
+ assert result is True
1243
+ args = mock_post.call_args
1244
+ assert "Worksheet/UpdateOptionsCollectionAppId" in args[0][0]
1245
+ assert args[1]["json"]["collectId"] == "os1"
1246
+ assert args[1]["json"]["appId"] == "app2"
1247
+
1248
+
1249
+ # ── Page Tests ────────────────────────────────────────────────────────────
1250
+
1251
+
1252
+ class TestPage:
1253
+ def test_component_types(self):
1254
+ from hap_cli.core.page import PAGE_COMPONENT_TYPES
1255
+ assert "analysis" in PAGE_COMPONENT_TYPES
1256
+ assert "button" in PAGE_COMPONENT_TYPES
1257
+ assert len(PAGE_COMPONENT_TYPES) >= 6
1258
+
1259
+ @patch("hap_cli.core.session.requests.post")
1260
+ def test_copy_page(self, mock_post, mock_session):
1261
+ mock_post.return_value = _mock_response({"pageId": "pg2"})
1262
+ from hap_cli.core import page as page_mod
1263
+ result = page_mod.copy_page(mock_session, "app1", "pg1", "Copy of Page")
1264
+ assert result["pageId"] == "pg2"
1265
+ args = mock_post.call_args
1266
+ assert "AppManagement/CopyCustomPage" in args[0][0]
1267
+ assert args[1]["json"]["pageId"] == "pg1"
1268
+
1269
+ @patch("hap_cli.core.session.requests.post")
1270
+ def test_add_authorize(self, mock_post, mock_session):
1271
+ mock_post.return_value = _mock_response({"id": "auth1"})
1272
+ from hap_cli.core import page as page_mod
1273
+ result = page_mod.add_authorize(mock_session, "app1", "pg1", "role1")
1274
+ assert result["id"] == "auth1"
1275
+ args = mock_post.call_args
1276
+ assert "AppManagement/AddAuthorize" in args[0][0]
1277
+
1278
+ @patch("hap_cli.core.session.requests.post")
1279
+ def test_get_authorizes(self, mock_post, mock_session):
1280
+ mock_post.return_value = _mock_response([{"id": "auth1"}])
1281
+ from hap_cli.core import page as page_mod
1282
+ result = page_mod.get_authorizes(mock_session, "app1", "pg1")
1283
+ assert result == [{"id": "auth1"}]
1284
+ args = mock_post.call_args
1285
+ assert "AppManagement/GetAuthorizes" in args[0][0]
1286
+
1287
+
1288
+ # ── Contact Tests ─────────────────────────────────────────────────────────
1289
+
1290
+
1291
+ class TestContact:
1292
+ @patch("hap_cli.core.session.requests.post")
1293
+ def test_search_contacts(self, mock_post, mock_session):
1294
+ mock_post.return_value = _mock_response([{"accountId": "u1", "fullname": "Alice"}])
1295
+ from hap_cli.core import contact as contact_mod
1296
+ result = contact_mod.search_contacts(mock_session, "Alice", project_id="proj1")
1297
+ assert result == [{"accountId": "u1", "fullname": "Alice"}]
1298
+ args = mock_post.call_args
1299
+ assert "AddressBook/SearchAddressbookAndDepartment" in args[0][0]
1300
+ assert args[1]["json"]["keywords"] == "Alice"
1301
+
1302
+ @patch("hap_cli.core.session.requests.post")
1303
+ def test_get_account_detail(self, mock_post, mock_session):
1304
+ mock_post.return_value = _mock_response({"accountId": "u1", "fullname": "Alice"})
1305
+ from hap_cli.core import contact as contact_mod
1306
+ result = contact_mod.get_account_detail(mock_session, "u1")
1307
+ assert result["accountId"] == "u1"
1308
+ args = mock_post.call_args
1309
+ assert "AddressBook/GetAccountDetail" in args[0][0]
1310
+
1311
+ @patch("hap_cli.core.session.requests.post")
1312
+ def test_get_friends(self, mock_post, mock_session):
1313
+ mock_post.return_value = _mock_response([{"accountId": "u2"}])
1314
+ from hap_cli.core import contact as contact_mod
1315
+ result = contact_mod.get_friends(mock_session)
1316
+ assert result == [{"accountId": "u2"}]
1317
+ args = mock_post.call_args
1318
+ assert "AddressBook/GetAllAddressbook" in args[0][0]
1319
+
1320
+ @patch("hap_cli.core.session.requests.post")
1321
+ def test_add_friend(self, mock_post, mock_session):
1322
+ mock_post.return_value = _mock_response(True)
1323
+ from hap_cli.core import contact as contact_mod
1324
+ result = contact_mod.add_friend(mock_session, "u2")
1325
+ assert result is True
1326
+ args = mock_post.call_args
1327
+ assert "AddressBook/AddFriend" in args[0][0]
1328
+ assert args[1]["json"]["accountId"] == "u2"
1329
+
1330
+ @patch("hap_cli.core.session.requests.post")
1331
+ def test_accept_friend(self, mock_post, mock_session):
1332
+ mock_post.return_value = _mock_response(True)
1333
+ from hap_cli.core import contact as contact_mod
1334
+ result = contact_mod.accept_friend(mock_session, "u2")
1335
+ assert result is True
1336
+ args = mock_post.call_args
1337
+ assert "AddressBook/EditAgreeFriend" in args[0][0]
1338
+
1339
+ @patch("hap_cli.core.session.requests.post")
1340
+ def test_reject_friend(self, mock_post, mock_session):
1341
+ mock_post.return_value = _mock_response(True)
1342
+ from hap_cli.core import contact as contact_mod
1343
+ result = contact_mod.reject_friend(mock_session, "u2")
1344
+ assert result is True
1345
+ args = mock_post.call_args
1346
+ assert "AddressBook/EditRefuseFriend" in args[0][0]
1347
+
1348
+
1349
+ # ── Department Tests ──────────────────────────────────────────────────────
1350
+
1351
+
1352
+ class TestDepartment:
1353
+ @patch("hap_cli.core.session.requests.post")
1354
+ def test_list_departments(self, mock_post, mock_session):
1355
+ mock_post.return_value = _mock_response({"list": [{"departmentId": "d1"}], "total": 1})
1356
+ from hap_cli.core import department as dept_mod
1357
+ result = dept_mod.list_departments(mock_session, "proj1", "d0")
1358
+ assert result["total"] == 1
1359
+ args = mock_post.call_args
1360
+ assert "Department/PagedSubDepartments" in args[0][0]
1361
+ assert args[1]["json"]["projectId"] == "proj1"
1362
+
1363
+ @patch("hap_cli.core.session.requests.post")
1364
+ def test_get_department_info(self, mock_post, mock_session):
1365
+ mock_post.return_value = _mock_response({"departmentId": "d1", "departmentName": "HR"})
1366
+ from hap_cli.core import department as dept_mod
1367
+ result = dept_mod.get_department_info(mock_session, "d1")
1368
+ assert result["departmentId"] == "d1"
1369
+ args = mock_post.call_args
1370
+ assert "Department/GetDepartmentInfo" in args[0][0]
1371
+
1372
+ @patch("hap_cli.core.session.requests.post")
1373
+ def test_create_department(self, mock_post, mock_session):
1374
+ mock_post.return_value = _mock_response({"departmentId": "d2"})
1375
+ from hap_cli.core import department as dept_mod
1376
+ result = dept_mod.create_department(mock_session, "proj1", "Engineering", parent_id="d0")
1377
+ assert result["departmentId"] == "d2"
1378
+ args = mock_post.call_args
1379
+ assert "Department/AddDepartment" in args[0][0]
1380
+ assert args[1]["json"]["departmentName"] == "Engineering"
1381
+
1382
+ @patch("hap_cli.core.session.requests.post")
1383
+ def test_delete_departments(self, mock_post, mock_session):
1384
+ mock_post.return_value = _mock_response(True)
1385
+ from hap_cli.core import department as dept_mod
1386
+ result = dept_mod.delete_departments(mock_session, "proj1", ["d1", "d2"])
1387
+ assert result is True
1388
+ args = mock_post.call_args
1389
+ assert "Department/DeleteDepartments" in args[0][0]
1390
+ assert "d1" in args[1]["json"]["departmentIds"]
1391
+
1392
+ @patch("hap_cli.core.session.requests.post")
1393
+ def test_get_department_users(self, mock_post, mock_session):
1394
+ mock_post.return_value = _mock_response([{"accountId": "u1"}])
1395
+ from hap_cli.core import department as dept_mod
1396
+ result = dept_mod.get_department_users(mock_session, "proj1", "d1")
1397
+ assert result == [{"accountId": "u1"}]
1398
+ args = mock_post.call_args
1399
+ assert "Department/GetProjectDepartmentUsers" in args[0][0]
1400
+
1401
+
1402
+ # ── Post Tests ────────────────────────────────────────────────────────────
1403
+
1404
+
1405
+ class TestPost:
1406
+ @patch("hap_cli.core.session.requests.post")
1407
+ def test_get_post_list(self, mock_post, mock_session):
1408
+ mock_post.return_value = _mock_response({"list": [{"postId": "p1"}], "total": 1})
1409
+ from hap_cli.core import post as post_mod
1410
+ result = post_mod.get_post_list(mock_session, "proj1")
1411
+ assert result["total"] == 1
1412
+ args = mock_post.call_args
1413
+ assert "Post/GetPostList" in args[0][0]
1414
+ assert args[1]["json"]["projectId"] == "proj1"
1415
+
1416
+ @patch("hap_cli.core.session.requests.post")
1417
+ def test_get_post_detail(self, mock_post, mock_session):
1418
+ mock_post.return_value = _mock_response({"postId": "p1", "message": "Hello"})
1419
+ from hap_cli.core import post as post_mod
1420
+ result = post_mod.get_post_detail(mock_session, "p1")
1421
+ assert result["postId"] == "p1"
1422
+ args = mock_post.call_args
1423
+ assert "Post/GetPostDetail" in args[0][0]
1424
+
1425
+ @patch("hap_cli.core.session.requests.post")
1426
+ def test_add_post(self, mock_post, mock_session):
1427
+ mock_post.return_value = _mock_response({"postId": "p2"})
1428
+ from hap_cli.core import post as post_mod
1429
+ result = post_mod.add_post(mock_session, "proj1", 1, "Hello World")
1430
+ assert result["postId"] == "p2"
1431
+ args = mock_post.call_args
1432
+ assert "Post/AddPost" in args[0][0]
1433
+ assert args[1]["json"]["postMsg"] == "Hello World"
1434
+
1435
+ @patch("hap_cli.core.session.requests.post")
1436
+ def test_add_comment(self, mock_post, mock_session):
1437
+ mock_post.return_value = _mock_response({"commentId": "c1"})
1438
+ from hap_cli.core import post as post_mod
1439
+ result = post_mod.add_comment(mock_session, "p1", "Great post!")
1440
+ assert result["commentId"] == "c1"
1441
+ args = mock_post.call_args
1442
+ assert "Post/AddPostComment" in args[0][0]
1443
+ assert args[1]["json"]["message"] == "Great post!"
1444
+
1445
+ @patch("hap_cli.core.session.requests.post")
1446
+ def test_like_post(self, mock_post, mock_session):
1447
+ mock_post.return_value = _mock_response(True)
1448
+ from hap_cli.core import post as post_mod
1449
+ result = post_mod.like_post(mock_session, "p1")
1450
+ assert result is True
1451
+ args = mock_post.call_args
1452
+ assert "Post/Like" in args[0][0]
1453
+
1454
+
1455
+ # ── Calendar Tests ────────────────────────────────────────────────────────
1456
+
1457
+
1458
+ class TestCalendar:
1459
+ @patch("hap_cli.core.session.requests.post")
1460
+ def test_get_calendars(self, mock_post, mock_session):
1461
+ mock_post.return_value = _mock_response([{"calendarId": "cal1"}])
1462
+ from hap_cli.core import calendar_mod
1463
+ result = calendar_mod.get_calendars(mock_session, "2026-01-01", "2026-12-31")
1464
+ assert result == [{"calendarId": "cal1"}]
1465
+ args = mock_post.call_args
1466
+ assert "Calendar/GetCalendars" in args[0][0]
1467
+
1468
+ @patch("hap_cli.core.session.requests.post")
1469
+ def test_insert_calendar(self, mock_post, mock_session):
1470
+ mock_post.return_value = _mock_response({"calendarId": "cal2"})
1471
+ from hap_cli.core import calendar_mod
1472
+ result = calendar_mod.insert_calendar(mock_session, "Team Meetings", "2026-01-01", "2026-01-02")
1473
+ assert result["calendarId"] == "cal2"
1474
+ args = mock_post.call_args
1475
+ assert "Calendar/InsertCalendar" in args[0][0]
1476
+ assert args[1]["json"]["name"] == "Team Meetings"
1477
+
1478
+ @patch("hap_cli.core.session.requests.post")
1479
+ def test_delete_calendar(self, mock_post, mock_session):
1480
+ mock_post.return_value = _mock_response(True)
1481
+ from hap_cli.core import calendar_mod
1482
+ result = calendar_mod.delete_calendar(mock_session, "cal1")
1483
+ assert result is True
1484
+ args = mock_post.call_args
1485
+ assert "Calendar/DeleteCalendar" in args[0][0]
1486
+ assert args[1]["json"]["calendarId"] == "cal1"
1487
+
1488
+ @patch("hap_cli.core.session.requests.post")
1489
+ def test_get_categories(self, mock_post, mock_session):
1490
+ mock_post.return_value = _mock_response([{"categoryId": "cat1"}])
1491
+ from hap_cli.core import calendar_mod
1492
+ result = calendar_mod.get_categories(mock_session)
1493
+ assert result == [{"categoryId": "cat1"}]
1494
+ args = mock_post.call_args
1495
+ assert "Calendar/GetUserAllCalCategories" in args[0][0]
1496
+
1497
+ @patch("hap_cli.core.session.requests.post")
1498
+ def test_add_members(self, mock_post, mock_session):
1499
+ mock_post.return_value = _mock_response(True)
1500
+ from hap_cli.core import calendar_mod
1501
+ result = calendar_mod.add_members(mock_session, "cal1", ["u1", "u2"])
1502
+ assert result is True
1503
+ args = mock_post.call_args
1504
+ assert "Calendar/AddMembers" in args[0][0]
1505
+ assert "u1" in args[1]["json"]["memberIds"]
1506
+
1507
+
1508
+ # ── Chat Tests ────────────────────────────────────────────────────────────
1509
+
1510
+
1511
+ class TestChat:
1512
+ @patch("hap_cli.core.session.requests.post")
1513
+ def test_get_chat_list(self, mock_post, mock_session):
1514
+ mock_post.return_value = _mock_response([{"chatId": "ch1"}])
1515
+ from hap_cli.core import chat as chat_mod
1516
+ result = chat_mod.get_chat_list(mock_session)
1517
+ assert result == [{"chatId": "ch1"}]
1518
+ args = mock_post.call_args
1519
+ assert "Chat/GetChatList" in args[0][0]
1520
+
1521
+ @patch("hap_cli.core.session.requests.post")
1522
+ def test_send_message(self, mock_post, mock_session):
1523
+ mock_post.return_value = _mock_response({"msgId": "m1"})
1524
+ from hap_cli.core import chat as chat_mod
1525
+ result = chat_mod.send_message(mock_session, ["u1", "u2"], "Hello!")
1526
+ assert result["msgId"] == "m1"
1527
+ args = mock_post.call_args
1528
+ assert "Message/SendMessageToAccountIds" in args[0][0]
1529
+ assert args[1]["json"]["message"] == "Hello!"
1530
+ assert "u1" in args[1]["json"]["accountIds"]
1531
+
1532
+ @patch("hap_cli.core.session.requests.post")
1533
+ def test_get_group_info(self, mock_post, mock_session):
1534
+ mock_post.return_value = _mock_response({"groupId": "g1", "name": "Dev Team"})
1535
+ from hap_cli.core import chat as chat_mod
1536
+ result = chat_mod.get_group_info(mock_session, "g1")
1537
+ assert result["groupId"] == "g1"
1538
+ args = mock_post.call_args
1539
+ assert "Chat/GetGroupInfo" in args[0][0]
1540
+ assert args[1]["json"]["groupId"] == "g1"
1541
+
1542
+ @patch("hap_cli.core.session.requests.post")
1543
+ def test_send_card(self, mock_post, mock_session):
1544
+ mock_post.return_value = _mock_response({"msgId": "m2"})
1545
+ from hap_cli.core import chat as chat_mod
1546
+ result = chat_mod.send_card(mock_session, "g1", {"title": "Alert"})
1547
+ assert result["msgId"] == "m2"
1548
+ args = mock_post.call_args
1549
+ assert "Chat/SendCardToChat" in args[0][0]
1550
+
1551
+
1552
+ # ── Group Tests ───────────────────────────────────────────────────────────
1553
+
1554
+
1555
+ class TestGroup:
1556
+ @patch("hap_cli.core.session.requests.post")
1557
+ def test_get_groups(self, mock_post, mock_session):
1558
+ mock_post.return_value = _mock_response({"list": [{"groupId": "gr1"}], "total": 1})
1559
+ from hap_cli.core import group as group_mod
1560
+ result = group_mod.get_groups(mock_session, "proj1")
1561
+ assert result["total"] == 1
1562
+ args = mock_post.call_args
1563
+ assert "Group/GetGroups" in args[0][0]
1564
+ assert args[1]["json"]["projectId"] == "proj1"
1565
+
1566
+ @patch("hap_cli.core.session.requests.post")
1567
+ def test_get_group_info(self, mock_post, mock_session):
1568
+ mock_post.return_value = _mock_response({"groupId": "gr1", "name": "Frontend"})
1569
+ from hap_cli.core import group as group_mod
1570
+ result = group_mod.get_group_info(mock_session, "gr1")
1571
+ assert result["groupId"] == "gr1"
1572
+ args = mock_post.call_args
1573
+ assert "Group/GetGroupInfo" in args[0][0]
1574
+
1575
+ @patch("hap_cli.core.session.requests.post")
1576
+ def test_add_group(self, mock_post, mock_session):
1577
+ mock_post.return_value = _mock_response({"groupId": "gr2"})
1578
+ from hap_cli.core import group as group_mod
1579
+ result = group_mod.add_group(mock_session, "proj1", "Backend", about="Backend team")
1580
+ assert result["groupId"] == "gr2"
1581
+ args = mock_post.call_args
1582
+ assert "Group/AddGroup" in args[0][0]
1583
+ assert args[1]["json"]["groupName"] == "Backend"
1584
+
1585
+ @patch("hap_cli.core.session.requests.post")
1586
+ def test_remove_group(self, mock_post, mock_session):
1587
+ mock_post.return_value = _mock_response(True)
1588
+ from hap_cli.core import group as group_mod
1589
+ result = group_mod.remove_group(mock_session, "gr1")
1590
+ assert result is True
1591
+ args = mock_post.call_args
1592
+ assert "Group/RemoveGroup" in args[0][0]
1593
+ assert args[1]["json"]["groupId"] == "gr1"
1594
+
1595
+ @patch("hap_cli.core.session.requests.post")
1596
+ def test_remove_user(self, mock_post, mock_session):
1597
+ mock_post.return_value = _mock_response(True)
1598
+ from hap_cli.core import group as group_mod
1599
+ result = group_mod.remove_user(mock_session, "gr1", "u1")
1600
+ assert result is True
1601
+ args = mock_post.call_args
1602
+ assert "Group/RemoveUser" in args[0][0]
1603
+ assert args[1]["json"]["accountId"] == "u1"
1604
+
1605
+
1606
+ # ── AI Tests ──────────────────────────────────────────────────────────────
1607
+
1608
+
1609
+ class TestAI:
1610
+ @patch("hap_cli.core.session.requests.post")
1611
+ def test_get_assistants(self, mock_post, mock_session):
1612
+ mock_post.return_value = _mock_response({"list": [{"assistantId": "a1"}], "total": 1})
1613
+ from hap_cli.core import ai as ai_mod
1614
+ result = ai_mod.get_assistants(mock_session, "proj1")
1615
+ assert result["total"] == 1
1616
+ args = mock_post.call_args
1617
+ assert "Assistant/GetList" in args[0][0]
1618
+ assert args[1]["json"]["projectId"] == "proj1"
1619
+
1620
+ @patch("hap_cli.core.session.requests.post")
1621
+ def test_get_assistant(self, mock_post, mock_session):
1622
+ mock_post.return_value = _mock_response({"assistantId": "a1", "name": "Bot"})
1623
+ from hap_cli.core import ai as ai_mod
1624
+ result = ai_mod.get_assistant(mock_session, "a1")
1625
+ assert result["assistantId"] == "a1"
1626
+ args = mock_post.call_args
1627
+ assert "Assistant/Get" in args[0][0]
1628
+ assert args[1]["json"]["assistantId"] == "a1"
1629
+
1630
+ @patch("hap_cli.core.session.requests.post")
1631
+ def test_upsert_assistant(self, mock_post, mock_session):
1632
+ mock_post.return_value = _mock_response({"assistantId": "a2"})
1633
+ from hap_cli.core import ai as ai_mod
1634
+ config = {"name": "NewBot", "projectId": "proj1"}
1635
+ result = ai_mod.upsert_assistant(mock_session, config)
1636
+ assert result["assistantId"] == "a2"
1637
+ args = mock_post.call_args
1638
+ assert "Assistant/Upsert" in args[0][0]
1639
+
1640
+ @patch("hap_cli.core.session.requests.post")
1641
+ def test_delete_assistant(self, mock_post, mock_session):
1642
+ mock_post.return_value = _mock_response(True)
1643
+ from hap_cli.core import ai as ai_mod
1644
+ result = ai_mod.delete_assistant(mock_session, "a1")
1645
+ assert result is True
1646
+ args = mock_post.call_args
1647
+ assert "Assistant/Delete" in args[0][0]
1648
+ assert args[1]["json"]["assistantId"] == "a1"
1649
+
1650
+ @patch("hap_cli.core.session.requests.post")
1651
+ def test_set_assistant_status(self, mock_post, mock_session):
1652
+ mock_post.return_value = _mock_response(True)
1653
+ from hap_cli.core import ai as ai_mod
1654
+ result = ai_mod.set_assistant_status(mock_session, "a1", 1)
1655
+ assert result is True
1656
+ args = mock_post.call_args
1657
+ assert "Assistant/SetStatus" in args[0][0]
1658
+ assert args[1]["json"]["status"] == 1
1659
+
1660
+ @patch("hap_cli.core.session.requests.post")
1661
+ def test_get_knowledge_bases(self, mock_post, mock_session):
1662
+ mock_post.return_value = _mock_response([{"knowledgeBaseId": "kb1"}])
1663
+ from hap_cli.core import ai as ai_mod
1664
+ result = ai_mod.get_knowledge_bases(mock_session, "proj1")
1665
+ assert result == [{"knowledgeBaseId": "kb1"}]
1666
+ args = mock_post.call_args
1667
+ assert "Assistant/GetListKnowledgeBase" in args[0][0]
1668
+
1669
+ @patch("hap_cli.core.session.requests.post")
1670
+ def test_upsert_knowledge_base(self, mock_post, mock_session):
1671
+ mock_post.return_value = _mock_response({"knowledgeBaseId": "kb2"})
1672
+ from hap_cli.core import ai as ai_mod
1673
+ config = {"name": "Docs KB", "projectId": "proj1"}
1674
+ result = ai_mod.upsert_knowledge_base(mock_session, config)
1675
+ assert result["knowledgeBaseId"] == "kb2"
1676
+ args = mock_post.call_args
1677
+ assert "Assistant/UpsertKnowledgeBase" in args[0][0]
1678
+
1679
+ @patch("hap_cli.core.session.requests.post")
1680
+ def test_get_chatbot_config(self, mock_post, mock_session):
1681
+ mock_post.return_value = _mock_response({"processId": "wf1", "enabled": True})
1682
+ from hap_cli.core import ai as ai_mod
1683
+ result = ai_mod.get_chatbot_config(mock_session, "wf1")
1684
+ assert result["processId"] == "wf1"
1685
+ args = mock_post.call_args
1686
+ assert "workflow/getChatbotConfig" in args[0][0]
1687
+
1688
+ @patch("hap_cli.core.session.requests.post")
1689
+ def test_get_ai_service_status(self, mock_post, mock_session):
1690
+ mock_post.return_value = _mock_response({"enabled": True})
1691
+ from hap_cli.core import ai as ai_mod
1692
+ result = ai_mod.get_ai_service_status(mock_session, "proj1")
1693
+ assert result["enabled"] is True
1694
+ args = mock_post.call_args
1695
+ assert "AIService/GetAIServiceStatus" in args[0][0]
1696
+
1697
+ @patch("hap_cli.core.session.requests.post")
1698
+ def test_get_models(self, mock_post, mock_session):
1699
+ mock_post.return_value = _mock_response([{"modelId": "gpt4"}])
1700
+ from hap_cli.core import ai as ai_mod
1701
+ result = ai_mod.get_models(mock_session, "proj1")
1702
+ assert result == [{"modelId": "gpt4"}]
1703
+ args = mock_post.call_args
1704
+ assert "AIService/GetDeveloperWithModes" in args[0][0]
1705
+
1706
+
1707
+ # ── Plugin Tests ──────────────────────────────────────────────────────────
1708
+
1709
+
1710
+ class TestPlugin:
1711
+ @patch("hap_cli.core.session.requests.post")
1712
+ def test_get_plugins(self, mock_post, mock_session):
1713
+ mock_post.return_value = _mock_response({"list": [{"id": "pl1"}], "total": 1})
1714
+ from hap_cli.core import plugin as plugin_mod
1715
+ result = plugin_mod.get_plugins(mock_session, "proj1")
1716
+ assert result["total"] == 1
1717
+ args = mock_post.call_args
1718
+ assert "Plugin/getList" in args[0][0]
1719
+ assert args[1]["json"]["projectId"] == "proj1"
1720
+
1721
+ @patch("hap_cli.core.session.requests.post")
1722
+ def test_get_plugin_detail(self, mock_post, mock_session):
1723
+ mock_post.return_value = _mock_response({"id": "pl1", "name": "MyPlugin"})
1724
+ from hap_cli.core import plugin as plugin_mod
1725
+ result = plugin_mod.get_plugin_detail(mock_session, "pl1")
1726
+ assert result["id"] == "pl1"
1727
+ args = mock_post.call_args
1728
+ assert "Plugin/getDetail" in args[0][0]
1729
+ assert args[1]["json"]["id"] == "pl1"
1730
+
1731
+ @patch("hap_cli.core.session.requests.post")
1732
+ def test_create_plugin(self, mock_post, mock_session):
1733
+ mock_post.return_value = _mock_response({"id": "pl2"})
1734
+ from hap_cli.core import plugin as plugin_mod
1735
+ result = plugin_mod.create_plugin(mock_session, "proj1", "NewPlugin", description="Desc")
1736
+ assert result["id"] == "pl2"
1737
+ args = mock_post.call_args
1738
+ assert "Plugin/create" in args[0][0]
1739
+ assert args[1]["json"]["name"] == "NewPlugin"
1740
+ assert args[1]["json"]["description"] == "Desc"
1741
+
1742
+ @patch("hap_cli.core.session.requests.post")
1743
+ def test_edit_plugin(self, mock_post, mock_session):
1744
+ mock_post.return_value = _mock_response(True)
1745
+ from hap_cli.core import plugin as plugin_mod
1746
+ result = plugin_mod.edit_plugin(mock_session, "pl1", name="Updated", description="NewDesc")
1747
+ assert result is True
1748
+ args = mock_post.call_args
1749
+ assert "Plugin/edit" in args[0][0]
1750
+ assert args[1]["json"]["name"] == "Updated"
1751
+
1752
+ @patch("hap_cli.core.session.requests.post")
1753
+ def test_remove_plugin(self, mock_post, mock_session):
1754
+ mock_post.return_value = _mock_response(True)
1755
+ from hap_cli.core import plugin as plugin_mod
1756
+ result = plugin_mod.remove_plugin(mock_session, "pl1")
1757
+ assert result is True
1758
+ args = mock_post.call_args
1759
+ assert "Plugin/remove" in args[0][0]
1760
+ assert args[1]["json"]["id"] == "pl1"
1761
+
1762
+ @patch("hap_cli.core.session.requests.post")
1763
+ def test_release_plugin(self, mock_post, mock_session):
1764
+ mock_post.return_value = _mock_response({"versionId": "v2"})
1765
+ from hap_cli.core import plugin as plugin_mod
1766
+ result = plugin_mod.release_plugin(mock_session, "pl1")
1767
+ assert result["versionId"] == "v2"
1768
+ args = mock_post.call_args
1769
+ assert "Plugin/release" in args[0][0]
1770
+ assert args[1]["json"]["id"] == "pl1"
1771
+
1772
+ @patch("hap_cli.core.session.requests.post")
1773
+ def test_rollback_plugin(self, mock_post, mock_session):
1774
+ mock_post.return_value = _mock_response(True)
1775
+ from hap_cli.core import plugin as plugin_mod
1776
+ result = plugin_mod.rollback_plugin(mock_session, "pl1", "v1")
1777
+ assert result is True
1778
+ args = mock_post.call_args
1779
+ assert "Plugin/rollback" in args[0][0]
1780
+ assert args[1]["json"]["versionId"] == "v1"
1781
+
1782
+ @patch("hap_cli.core.session.requests.post")
1783
+ def test_get_release_history(self, mock_post, mock_session):
1784
+ mock_post.return_value = _mock_response([{"versionId": "v1"}, {"versionId": "v2"}])
1785
+ from hap_cli.core import plugin as plugin_mod
1786
+ result = plugin_mod.get_release_history(mock_session, "pl1")
1787
+ assert len(result) == 2
1788
+ args = mock_post.call_args
1789
+ assert "Plugin/getReleaseHistory" in args[0][0]
1790
+
1791
+
1792
+ class TestConfigOrgs:
1793
+ @patch("hap_cli.core.session.requests.post")
1794
+ def test_orgs_list(self, mock_post, tmp_config):
1795
+ runner = CliRunner()
1796
+ runner.invoke(cli, ["config", "set", "--server", "https://t.com", "--token", "tok"])
1797
+ mock_post.return_value = _mock_response([
1798
+ {"projectId": "p1", "companyName": "Acme Corp", "roleName": "Admin"},
1799
+ {"projectId": "p2", "companyName": "Beta Inc", "roleName": "Member"},
1800
+ ])
1801
+ result = runner.invoke(cli, ["config", "orgs"])
1802
+ assert result.exit_code == 0
1803
+ assert "p1" in result.output
1804
+ assert "Acme Corp" in result.output
1805
+
1806
+ @patch("hap_cli.core.session.requests.post")
1807
+ def test_orgs_json(self, mock_post, tmp_config):
1808
+ runner = CliRunner()
1809
+ runner.invoke(cli, ["config", "set", "--server", "https://t.com", "--token", "tok"])
1810
+ mock_post.return_value = _mock_response([
1811
+ {"projectId": "p1", "companyName": "Acme Corp"},
1812
+ ])
1813
+ result = runner.invoke(cli, ["--json", "config", "orgs"])
1814
+ assert result.exit_code == 0
1815
+ import json as _json
1816
+ data = _json.loads(result.output)
1817
+ assert data[0]["projectId"] == "p1"
1818
+
1819
+ @patch("hap_cli.core.session.requests.post")
1820
+ def test_orgs_api_call(self, mock_post, mock_session):
1821
+ mock_post.return_value = _mock_response([{"projectId": "p1"}])
1822
+ mock_session.api_call("Account", "GetProjectList", {})
1823
+ args = mock_post.call_args
1824
+ assert "Account/GetProjectList" in args[0][0]