hap-cli 0.6.6__tar.gz → 0.6.7__tar.gz
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.
- {hap_cli-0.6.6 → hap_cli-0.6.7}/PKG-INFO +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/__init__.py +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/contact_cmd.py +35 -21
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/department_cmd.py +26 -23
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/contact.py +40 -22
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_core.py +35 -23
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_approval.py +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_social.py +14 -7
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.7}/setup.py +1 -1
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/README.md +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/context.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/app.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/department.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/group.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/page.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/post.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/record.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/role.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/session.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/skills/SKILL.md +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/skills/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/SOURCES.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.6.6 → hap_cli-0.6.7}/setup.cfg +0 -0
|
@@ -51,14 +51,33 @@ def _simplify_user(u: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
51
51
|
v = u.get(src)
|
|
52
52
|
if v:
|
|
53
53
|
out[dst] = v
|
|
54
|
+
# Newer endpoints (User.GetContactUserList,
|
|
55
|
+
# User.GetProjectResignedUserList) nest the department under
|
|
56
|
+
# `departmentInfo`. Flatten it here so the rendered shape stays flat.
|
|
57
|
+
dept = u.get("departmentInfo") or {}
|
|
58
|
+
if isinstance(dept, dict):
|
|
59
|
+
if dept.get("departmentName") and not out.get("department"):
|
|
60
|
+
out["department"] = dept["departmentName"]
|
|
61
|
+
if dept.get("departmentId"):
|
|
62
|
+
out["department_id"] = dept["departmentId"]
|
|
54
63
|
if u.get("accountStatus") and u["accountStatus"] != 0:
|
|
55
64
|
out["status"] = u["accountStatus"]
|
|
56
65
|
return out
|
|
57
66
|
|
|
58
67
|
|
|
59
68
|
def _simplify_search_result(raw: dict[str, Any]) -> dict[str, Any]:
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
"""Unified envelope for the two contact-search endpoints.
|
|
70
|
+
|
|
71
|
+
Both `User.GetContactUserList` (after extracting the `users`
|
|
72
|
+
sub-dict in the core layer) and `User.GetProjectResignedUserList`
|
|
73
|
+
hand us a ``{"list": [...], "allCount": N}`` shape — project it to
|
|
74
|
+
``{"total": N, "users": [...]}`` so a single renderer handles both.
|
|
75
|
+
"""
|
|
76
|
+
users = [_simplify_user(u) for u in (raw.get("list") or [])]
|
|
77
|
+
return {
|
|
78
|
+
"total": raw.get("allCount", 0),
|
|
79
|
+
"users": [u for u in users if u],
|
|
80
|
+
}
|
|
62
81
|
|
|
63
82
|
|
|
64
83
|
def _simplify_friends_result(raw: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -151,40 +170,35 @@ def _render_friend_requests(summary: dict[str, Any]) -> None:
|
|
|
151
170
|
@contact.command("search")
|
|
152
171
|
@click.argument("keywords")
|
|
153
172
|
@org_id_option()
|
|
154
|
-
@click.option(
|
|
155
|
-
"--range", "range_", type=int, default=None,
|
|
156
|
-
help="Server-defined range bucket (numeric)",
|
|
157
|
-
)
|
|
158
173
|
@click.option("--page-size", "-n", default=20, help="Items per page")
|
|
159
174
|
@click.option("--page", "-p", default=1, help="Page number")
|
|
160
175
|
@click.option(
|
|
161
|
-
"--
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"--take-count/--no-take-count", "take_total_count", default=None,
|
|
166
|
-
help="Include total count in response",
|
|
176
|
+
"--resigned/--no-resigned",
|
|
177
|
+
"resigned",
|
|
178
|
+
default=False,
|
|
179
|
+
help="Search resigned members instead of active ones",
|
|
167
180
|
)
|
|
168
181
|
@pass_context
|
|
169
|
-
def contact_search(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
def contact_search(ctx, keywords, org_id, page_size, page, resigned):
|
|
183
|
+
"""Search members of an organization by keyword.
|
|
184
|
+
|
|
185
|
+
Defaults to active members. Pass --resigned to search former
|
|
186
|
+
members. Uses the current organization when --org-id is omitted.
|
|
187
|
+
"""
|
|
173
188
|
try:
|
|
174
189
|
session = ctx.get_session()
|
|
175
190
|
pid = require_api_project_id(session, org_id)
|
|
176
|
-
|
|
191
|
+
fn = contact_mod.search_resigned_users if resigned else contact_mod.search_contacts
|
|
192
|
+
raw = fn(
|
|
177
193
|
session,
|
|
178
194
|
keywords,
|
|
179
195
|
project_id=pid,
|
|
180
|
-
range_=range_,
|
|
181
196
|
page_index=page,
|
|
182
197
|
page_size=page_size,
|
|
183
|
-
is_filter_other=is_filter_other,
|
|
184
|
-
take_total_count=take_total_count,
|
|
185
198
|
)
|
|
186
199
|
summary = _simplify_search_result(raw)
|
|
187
|
-
|
|
200
|
+
header = "Resigned matches" if resigned else "Matches"
|
|
201
|
+
ctx.output(summary, lambda d: _render_users(d, header))
|
|
188
202
|
except Exception as e:
|
|
189
203
|
ctx.handle_error(e)
|
|
190
204
|
|
|
@@ -34,8 +34,8 @@ def _simplify_department(d: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
34
34
|
"id": d.get("departmentId") or d.get("id"),
|
|
35
35
|
"name": d.get("departmentName") or d.get("name"),
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
# userCount from these list/tree endpoints is unreliable (always 0) —
|
|
38
|
+
# use `department members` for a real count. Intentionally omitted.
|
|
39
39
|
if d.get("haveSubDepartment") is not None:
|
|
40
40
|
out["has_children"] = bool(d.get("haveSubDepartment"))
|
|
41
41
|
if d.get("disabled"):
|
|
@@ -75,13 +75,23 @@ def _simplify_department_members(raw: dict[str, Any]) -> dict[str, Any]:
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _simplify_dept_search_node(node: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
79
|
+
# SearchDeptAndUsers returns departments as a recursive tree under
|
|
80
|
+
# the `subs` field (distinct from GetProjectSubDepartmentTree which
|
|
81
|
+
# uses `subDepartments`).
|
|
82
|
+
base = _simplify_department(node)
|
|
83
|
+
if base is None or not isinstance(node, dict):
|
|
84
|
+
return base
|
|
85
|
+
subs = node.get("subs") or []
|
|
86
|
+
children = [c for c in (_simplify_dept_search_node(s) for s in subs) if c]
|
|
87
|
+
if children:
|
|
88
|
+
base["children"] = children
|
|
89
|
+
return base
|
|
90
|
+
|
|
91
|
+
|
|
78
92
|
def _simplify_dept_search(raw: dict[str, Any]) -> dict[str, Any]:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
"users": [u for u in users if u],
|
|
83
|
-
"departments": [d for d in depts if d],
|
|
84
|
-
}
|
|
93
|
+
depts = [_simplify_dept_search_node(d) for d in (raw.get("departments") or [])]
|
|
94
|
+
return {"departments": [d for d in depts if d]}
|
|
85
95
|
|
|
86
96
|
|
|
87
97
|
# ── Human-readable renderers ───────────────────────────────────────────────
|
|
@@ -94,8 +104,6 @@ def _render_departments(depts: list[dict[str, Any]], header: str = "Departments"
|
|
|
94
104
|
click.echo(f"{header} ({len(depts)}):")
|
|
95
105
|
for d in depts:
|
|
96
106
|
extras = []
|
|
97
|
-
if d.get("user_count") is not None:
|
|
98
|
-
extras.append(f"{d['user_count']} members")
|
|
99
107
|
if d.get("has_children"):
|
|
100
108
|
extras.append("has sub-departments")
|
|
101
109
|
if d.get("disabled"):
|
|
@@ -111,8 +119,7 @@ def _render_tree(nodes: list[dict[str, Any]], depth: int = 0) -> None:
|
|
|
111
119
|
return
|
|
112
120
|
for n in nodes:
|
|
113
121
|
indent = " " * depth
|
|
114
|
-
|
|
115
|
-
click.echo(f"{indent}• {n.get('name') or '?'}{count}")
|
|
122
|
+
click.echo(f"{indent}• {n.get('name') or '?'}")
|
|
116
123
|
click.echo(f"{indent} id: {n.get('id')}")
|
|
117
124
|
if n.get("children"):
|
|
118
125
|
_render_tree(n["children"], depth + 1)
|
|
@@ -123,8 +130,6 @@ def _render_department_info(info: dict[str, Any]) -> None:
|
|
|
123
130
|
click.echo(f"ID: {info.get('id')}")
|
|
124
131
|
if info.get("parent_name") or info.get("parent_id"):
|
|
125
132
|
click.echo(f"Parent: {info.get('parent_name') or ''} ({info.get('parent_id') or ''})")
|
|
126
|
-
if info.get("user_count") is not None:
|
|
127
|
-
click.echo(f"Members: {info['user_count']}")
|
|
128
133
|
if info.get("disabled"):
|
|
129
134
|
click.echo("Status: disabled")
|
|
130
135
|
chargers = info.get("charge_users") or []
|
|
@@ -136,14 +141,11 @@ def _render_department_info(info: dict[str, Any]) -> None:
|
|
|
136
141
|
|
|
137
142
|
def _render_dept_search(summary: dict[str, Any]) -> None:
|
|
138
143
|
depts = summary.get("departments") or []
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
click.echo("No matches.")
|
|
144
|
+
if not depts:
|
|
145
|
+
click.echo("No matching departments.")
|
|
142
146
|
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if users:
|
|
146
|
-
_render_users({"users": users}, "Matching users")
|
|
147
|
+
click.echo(f"Matching departments ({len(depts)}):")
|
|
148
|
+
_render_tree(depts)
|
|
147
149
|
|
|
148
150
|
|
|
149
151
|
# ── Commands ───────────────────────────────────────────────────────────────
|
|
@@ -249,11 +251,12 @@ def department_members(
|
|
|
249
251
|
"--include-disabled/--no-include-disabled",
|
|
250
252
|
"include_disabled",
|
|
251
253
|
default=None,
|
|
252
|
-
help="Include disabled departments
|
|
254
|
+
help="Include disabled departments",
|
|
253
255
|
)
|
|
254
256
|
@pass_context
|
|
255
257
|
def department_search(ctx, keywords, org_id, include_disabled):
|
|
256
|
-
"""Search departments
|
|
258
|
+
"""Search departments by keyword. Results render as a tree showing
|
|
259
|
+
each match with its sub-departments."""
|
|
257
260
|
try:
|
|
258
261
|
session = ctx.get_session()
|
|
259
262
|
pid = require_api_project_id(session, org_id)
|
|
@@ -26,37 +26,55 @@ from hap_cli.core.session import Session
|
|
|
26
26
|
def search_contacts(
|
|
27
27
|
session: Session,
|
|
28
28
|
keywords: str,
|
|
29
|
-
project_id: str
|
|
30
|
-
range_: Optional[int] = None,
|
|
29
|
+
project_id: str,
|
|
31
30
|
page_index: int = 1,
|
|
32
31
|
page_size: int = 20,
|
|
33
|
-
is_filter_other: Optional[bool] = None,
|
|
34
|
-
take_total_count: Optional[bool] = None,
|
|
35
32
|
) -> dict[str, Any]:
|
|
36
|
-
"""Search
|
|
37
|
-
|
|
38
|
-
Wraps `
|
|
39
|
-
(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
:
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
"""Search active members of an organization by keyword.
|
|
34
|
+
|
|
35
|
+
Wraps `User.GetContactUserList`. The server response carries both
|
|
36
|
+
``oftenUsers`` (frequently-contacted shortcut) and ``users`` (full
|
|
37
|
+
matches); only the latter is useful for a general search, so this
|
|
38
|
+
function returns ``raw["users"]`` with shape
|
|
39
|
+
``{"list": [...], "allCount": N}`` — the same envelope produced by
|
|
40
|
+
:func:`search_resigned_users` so both can share simplifiers.
|
|
41
|
+
|
|
42
|
+
``dataRange`` is fixed at 2 (whole organization) because the
|
|
43
|
+
alternate buckets are not exposed via the CLI.
|
|
45
44
|
"""
|
|
46
45
|
data: dict[str, Any] = {
|
|
47
46
|
"keywords": keywords,
|
|
47
|
+
"projectId": project_id,
|
|
48
|
+
"dataRange": 2,
|
|
48
49
|
"pageIndex": page_index,
|
|
49
50
|
"pageSize": page_size,
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
raw = session.api_call("User", "GetContactUserList", data) or {}
|
|
53
|
+
return raw.get("users") or {"list": [], "allCount": 0}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def search_resigned_users(
|
|
57
|
+
session: Session,
|
|
58
|
+
keywords: str,
|
|
59
|
+
project_id: str,
|
|
60
|
+
page_index: int = 1,
|
|
61
|
+
page_size: int = 20,
|
|
62
|
+
) -> dict[str, Any]:
|
|
63
|
+
"""Search resigned members of an organization by keyword.
|
|
64
|
+
|
|
65
|
+
Wraps `User.GetProjectResignedUserList`. Returns the raw
|
|
66
|
+
``{"list": [...], "allCount": N}`` envelope — matches the shape
|
|
67
|
+
produced by :func:`search_contacts` on purpose so callers can feed
|
|
68
|
+
both through one simplifier.
|
|
69
|
+
"""
|
|
70
|
+
data: dict[str, Any] = {
|
|
71
|
+
"projectId": project_id,
|
|
72
|
+
"pageIndex": page_index,
|
|
73
|
+
"pageSize": page_size,
|
|
74
|
+
}
|
|
75
|
+
if keywords:
|
|
76
|
+
data["keywords"] = keywords
|
|
77
|
+
return session.api_call("User", "GetProjectResignedUserList", data)
|
|
60
78
|
|
|
61
79
|
|
|
62
80
|
def get_friends(
|
|
@@ -1621,44 +1621,56 @@ class TestPage:
|
|
|
1621
1621
|
class TestContact:
|
|
1622
1622
|
@patch("hap_cli.core.session.requests.post")
|
|
1623
1623
|
def test_search_contacts_defaults(self, mock_post, mock_session):
|
|
1624
|
-
"""`
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
mock_post.return_value = _mock_response([{"accountId": "u1", "fullname": "Alice"}])
|
|
1624
|
+
"""`User.GetContactUserList` — defaults + `users` sub-field extraction."""
|
|
1625
|
+
mock_post.return_value = _mock_response({
|
|
1626
|
+
"oftenUsers": {"list": [{"accountId": "skip"}]},
|
|
1627
|
+
"users": {"list": [{"accountId": "u1", "fullname": "Alice"}], "allCount": 1},
|
|
1628
|
+
})
|
|
1630
1629
|
from hap_cli.core import contact as contact_mod
|
|
1631
1630
|
result = contact_mod.search_contacts(mock_session, "Alice", project_id="proj1")
|
|
1632
|
-
|
|
1631
|
+
# Only the `users` envelope is returned; `oftenUsers` is dropped.
|
|
1632
|
+
assert result == {"list": [{"accountId": "u1", "fullname": "Alice"}], "allCount": 1}
|
|
1633
1633
|
args = mock_post.call_args
|
|
1634
|
-
assert "
|
|
1634
|
+
assert "User/GetContactUserList" in args[0][0]
|
|
1635
1635
|
body = args[1]["json"]
|
|
1636
1636
|
assert body["keywords"] == "Alice"
|
|
1637
1637
|
assert body["projectId"] == "proj1"
|
|
1638
|
-
|
|
1638
|
+
assert body["dataRange"] == 2
|
|
1639
1639
|
assert body["pageIndex"] == 1
|
|
1640
1640
|
assert body["pageSize"] == 20
|
|
1641
1641
|
|
|
1642
1642
|
@patch("hap_cli.core.session.requests.post")
|
|
1643
|
-
def
|
|
1644
|
-
|
|
1643
|
+
def test_search_contacts_empty_envelope(self, mock_post, mock_session):
|
|
1644
|
+
"""Missing `users` sub-field degrades gracefully to an empty envelope."""
|
|
1645
|
+
mock_post.return_value = _mock_response({})
|
|
1645
1646
|
from hap_cli.core import contact as contact_mod
|
|
1646
|
-
contact_mod.search_contacts(
|
|
1647
|
-
mock_session,
|
|
1648
|
-
keywords="Zhang",
|
|
1649
|
-
project_id="proj1",
|
|
1650
|
-
range_=10,
|
|
1651
|
-
page_index=2,
|
|
1652
|
-
page_size=50,
|
|
1653
|
-
is_filter_other=True,
|
|
1654
|
-
take_total_count=True,
|
|
1647
|
+
result = contact_mod.search_contacts(
|
|
1648
|
+
mock_session, keywords="Zhang", project_id="proj1", page_index=2, page_size=50,
|
|
1655
1649
|
)
|
|
1650
|
+
assert result == {"list": [], "allCount": 0}
|
|
1656
1651
|
body = mock_post.call_args[1]["json"]
|
|
1657
|
-
assert body["range"] == 10
|
|
1658
1652
|
assert body["pageIndex"] == 2
|
|
1659
1653
|
assert body["pageSize"] == 50
|
|
1660
|
-
|
|
1661
|
-
|
|
1654
|
+
|
|
1655
|
+
@patch("hap_cli.core.session.requests.post")
|
|
1656
|
+
def test_search_resigned_users(self, mock_post, mock_session):
|
|
1657
|
+
"""`User.GetProjectResignedUserList` — shape matches active search."""
|
|
1658
|
+
mock_post.return_value = _mock_response({
|
|
1659
|
+
"list": [{"accountId": "r1", "fullname": "Gone"}],
|
|
1660
|
+
"allCount": 1,
|
|
1661
|
+
})
|
|
1662
|
+
from hap_cli.core import contact as contact_mod
|
|
1663
|
+
result = contact_mod.search_resigned_users(
|
|
1664
|
+
mock_session, keywords="Gone", project_id="proj1", page_index=2, page_size=50,
|
|
1665
|
+
)
|
|
1666
|
+
assert result == {"list": [{"accountId": "r1", "fullname": "Gone"}], "allCount": 1}
|
|
1667
|
+
args = mock_post.call_args
|
|
1668
|
+
assert "User/GetProjectResignedUserList" in args[0][0]
|
|
1669
|
+
body = args[1]["json"]
|
|
1670
|
+
assert body["keywords"] == "Gone"
|
|
1671
|
+
assert body["projectId"] == "proj1"
|
|
1672
|
+
assert body["pageIndex"] == 2
|
|
1673
|
+
assert body["pageSize"] == 50
|
|
1662
1674
|
|
|
1663
1675
|
@patch("hap_cli.core.session.requests.post")
|
|
1664
1676
|
def test_get_friends_full_fields(self, mock_post, mock_session):
|
|
@@ -60,7 +60,7 @@ def _dummy_account_id(integration_session, project_id):
|
|
|
60
60
|
res = contact.search_contacts(
|
|
61
61
|
integration_session, project_id=project_id, keywords="a", page_size=1
|
|
62
62
|
)
|
|
63
|
-
users = (res or {}).get("
|
|
63
|
+
users = (res or {}).get("list") or []
|
|
64
64
|
if not users:
|
|
65
65
|
pytest.skip("project has no users matching 'a' — cannot run delegation tests")
|
|
66
66
|
return users[0].get("accountId")
|
|
@@ -39,19 +39,26 @@ class TestChat:
|
|
|
39
39
|
class TestContact:
|
|
40
40
|
|
|
41
41
|
def test_01_search(self, integration_session, project_id):
|
|
42
|
-
"""
|
|
43
|
-
named ``SearchAddressbookAndDepartment`` and also emits an
|
|
44
|
-
always-empty ``departmentResult`` alongside it — the CLI only
|
|
45
|
-
surfaces the user matches, but both fields remain in the raw
|
|
46
|
-
envelope, so we assert on ``userResult`` only."""
|
|
42
|
+
"""`User.GetContactUserList` → unified ``{list, allCount}`` envelope."""
|
|
47
43
|
result = contact.search_contacts(
|
|
48
44
|
integration_session,
|
|
49
45
|
project_id=project_id,
|
|
50
46
|
keywords="a",
|
|
51
47
|
page_size=5,
|
|
52
48
|
)
|
|
53
|
-
assert_dict_like(result, ["
|
|
54
|
-
assert isinstance(result["
|
|
49
|
+
assert_dict_like(result, ["list"])
|
|
50
|
+
assert isinstance(result["list"], list)
|
|
51
|
+
|
|
52
|
+
def test_01b_search_resigned(self, integration_session, project_id):
|
|
53
|
+
"""`User.GetProjectResignedUserList` — same envelope as active search."""
|
|
54
|
+
result = contact.search_resigned_users(
|
|
55
|
+
integration_session,
|
|
56
|
+
project_id=project_id,
|
|
57
|
+
keywords="",
|
|
58
|
+
page_size=5,
|
|
59
|
+
)
|
|
60
|
+
assert_dict_like(result, ["list"])
|
|
61
|
+
assert isinstance(result["list"], list)
|
|
55
62
|
|
|
56
63
|
def test_02_resolve(self, integration_session):
|
|
57
64
|
"""AddressBook.GetUserAddressbookByKeywords returns a paged dict."""
|
|
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
|
|
|
4
4
|
|
|
5
5
|
setup(
|
|
6
6
|
name="hap-cli",
|
|
7
|
-
version="0.6.
|
|
7
|
+
version="0.6.7",
|
|
8
8
|
description="CLI harness for MingDAO HAP - Enterprise no-code platform",
|
|
9
9
|
long_description=open("hap_cli/README.md").read(),
|
|
10
10
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|