sophhub 0.4.21 → 0.4.22

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 (68) hide show
  1. package/package.json +1 -1
  2. package/skills/insurance-customer-policy/skill.json +12 -0
  3. package/skills/insurance-customer-policy/src/SKILL.md +121 -0
  4. package/skills/insurance-customer-policy/src/pyproject.toml +9 -0
  5. package/skills/insurance-customer-policy/src/scripts/cli.py +16 -0
  6. package/skills/insurance-customer-policy/src/scripts/cloud_insurance_full_test.py +785 -0
  7. package/skills/insurance-customer-policy/src/scripts/dashboard_all.py +205 -0
  8. package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/__init__.py +0 -0
  9. package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/__main__.py +4 -0
  10. package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/cli.py +816 -0
  11. package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/db.py +181 -0
  12. package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/insurance_paths.py +184 -0
  13. package/skills/insurance-customer-policy/src/scripts/run_e2e_smoke.py +164 -0
  14. package/skills/insurance-customer-policy/src/scripts/test_cloud_zero_config.py +217 -0
  15. package/skills/insurance-customer-policy/src/scripts/test_dashboard_all.py +113 -0
  16. package/skills/insurance-customer-policy/src/src/insurance_customer_cli/__init__.py +0 -0
  17. package/skills/insurance-customer-policy/src/src/insurance_customer_cli/__main__.py +4 -0
  18. package/skills/insurance-customer-policy/src/src/insurance_customer_cli/cli.py +816 -0
  19. package/skills/insurance-customer-policy/src/src/insurance_customer_cli/db.py +181 -0
  20. package/skills/insurance-customer-policy/src/src/insurance_customer_cli/insurance_paths.py +184 -0
  21. package/skills/insurance-product-analysis/skill.json +12 -0
  22. package/skills/insurance-product-analysis/src/SKILL.md +99 -0
  23. package/skills/insurance-product-analysis/src/pyproject.toml +9 -0
  24. package/skills/insurance-product-analysis/src/scripts/cli.py +16 -0
  25. package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/__init__.py +0 -0
  26. package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/__main__.py +4 -0
  27. package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/cli.py +545 -0
  28. package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/db.py +180 -0
  29. package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/orchestrator.py +163 -0
  30. package/skills/insurance-product-analysis/src/scripts/run_e2e_smoke.py +202 -0
  31. package/skills/insurance-product-analysis/src/src/insurance_product_cli/__init__.py +0 -0
  32. package/skills/insurance-product-analysis/src/src/insurance_product_cli/__main__.py +4 -0
  33. package/skills/insurance-product-analysis/src/src/insurance_product_cli/cli.py +545 -0
  34. package/skills/insurance-product-analysis/src/src/insurance_product_cli/db.py +180 -0
  35. package/skills/insurance-product-analysis/src/src/insurance_product_cli/orchestrator.py +163 -0
  36. package/skills/insurance-sales-pipeline/skill.json +12 -0
  37. package/skills/insurance-sales-pipeline/src/SKILL.md +102 -0
  38. package/skills/insurance-sales-pipeline/src/pyproject.toml +9 -0
  39. package/skills/insurance-sales-pipeline/src/scripts/cli.py +16 -0
  40. package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/__init__.py +0 -0
  41. package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/__main__.py +4 -0
  42. package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/cli.py +496 -0
  43. package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/db.py +180 -0
  44. package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/orchestrator.py +36 -0
  45. package/skills/insurance-sales-pipeline/src/scripts/run_e2e_smoke.py +208 -0
  46. package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/__init__.py +0 -0
  47. package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/__main__.py +4 -0
  48. package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/cli.py +496 -0
  49. package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/db.py +180 -0
  50. package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/orchestrator.py +36 -0
  51. package/skills/insurance-schedule-renewal/skill.json +12 -0
  52. package/skills/insurance-schedule-renewal/src/SKILL.md +94 -0
  53. package/skills/insurance-schedule-renewal/src/pyproject.toml +9 -0
  54. package/skills/insurance-schedule-renewal/src/scripts/cli.py +16 -0
  55. package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/__init__.py +0 -0
  56. package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/__main__.py +4 -0
  57. package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/cli.py +429 -0
  58. package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/db.py +180 -0
  59. package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/orchestrator.py +94 -0
  60. package/skills/insurance-schedule-renewal/src/scripts/run_e2e_smoke.py +218 -0
  61. package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/__init__.py +0 -0
  62. package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/__main__.py +4 -0
  63. package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/cli.py +429 -0
  64. package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/db.py +180 -0
  65. package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/orchestrator.py +94 -0
  66. package/skills/insurance-shared-data/skill.json +20 -0
  67. package/skills/insurance-shared-data/src/SKILL.md +33 -0
  68. package/skills/insurance-shared-data/src/scripts/cloud_insurance_super_test.py +246 -0
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env python3
2
+ """Cloud-style zero-configuration smoke test.
3
+
4
+ Simulates the cloud agent invoking the 4 insurance skills with **only**
5
+ the wrapper script paths documented in each ``SKILL.md`` — no PYTHONPATH,
6
+ no skill-specific env vars, an unrelated CWD, and a clean ``os.environ``
7
+ that explicitly drops ``PYTHONPATH`` if present.
8
+
9
+ Each skill is exercised with one representative read + one write to
10
+ prove the SQLite path defaults work without any setup, then
11
+ ``dashboard_all.py`` is run to verify cross-skill aggregation works
12
+ through the wrappers.
13
+
14
+ Usage::
15
+
16
+ python3 skills/insurance-customer-policy/src/scripts/test_cloud_zero_config.py
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import subprocess
23
+ import sys
24
+ import tempfile
25
+ import time
26
+ from pathlib import Path
27
+
28
+ HERE = Path(__file__).resolve().parent # .../insurance-customer-policy/src/scripts
29
+ SKILLS_ROOT = HERE.parent.parent.parent # .../skills
30
+
31
+ CUSTOMER_CLI = SKILLS_ROOT / "insurance-customer-policy" / "src" / "scripts" / "cli.py"
32
+ PRODUCT_CLI = SKILLS_ROOT / "insurance-product-analysis" / "src" / "scripts" / "cli.py"
33
+ SCHEDULE_CLI = SKILLS_ROOT / "insurance-schedule-renewal" / "src" / "scripts" / "cli.py"
34
+ PIPELINE_CLI = SKILLS_ROOT / "insurance-sales-pipeline" / "src" / "scripts" / "cli.py"
35
+ DASHBOARD_ALL = HERE / "dashboard_all.py"
36
+
37
+
38
+ def _clean_env(extra: dict[str, str] | None = None) -> dict[str, str]:
39
+ """Build a minimal env that mimics cloud: no PYTHONPATH, only required vars."""
40
+ env: dict[str, str] = {}
41
+ for var in ("PATH", "SystemRoot", "TEMP", "TMP", "HOME", "USERPROFILE", "windir", "ComSpec"):
42
+ if var in os.environ:
43
+ env[var] = os.environ[var]
44
+ env["PYTHONIOENCODING"] = "utf-8"
45
+ if extra:
46
+ env.update(extra)
47
+ return env
48
+
49
+
50
+ def run(cli: Path, args: list[str], extra_env: dict[str, str] | None = None,
51
+ cwd: Path | None = None) -> dict:
52
+ """Run wrapper CLI with a clean env and arbitrary CWD; return parsed JSON."""
53
+ env = _clean_env(extra_env)
54
+ r = subprocess.run(
55
+ [sys.executable, str(cli), *args],
56
+ cwd=str(cwd) if cwd else None,
57
+ env=env,
58
+ capture_output=True,
59
+ text=True,
60
+ encoding="utf-8",
61
+ timeout=60,
62
+ )
63
+ if r.returncode not in (0, 1):
64
+ raise AssertionError(
65
+ f"\n{cli.name} {' '.join(args)} crashed (rc={r.returncode})\n"
66
+ f"STDOUT:\n{r.stdout}\nSTDERR:\n{r.stderr}"
67
+ )
68
+ out = (r.stdout or "").strip()
69
+ assert out, f"{cli.name} {' '.join(args)} produced no stdout. STDERR:\n{r.stderr}"
70
+ try:
71
+ return json.loads(out)
72
+ except json.JSONDecodeError as e:
73
+ raise AssertionError(f"{cli.name} {' '.join(args)} bad JSON: {e}\nSTDOUT:\n{out}")
74
+
75
+
76
+ def main() -> int:
77
+ print(f"[cloud-smoke] starting; CWD will be a tempdir; env will drop PYTHONPATH")
78
+ print(f"[cloud-smoke] CUSTOMER_CLI = {CUSTOMER_CLI}")
79
+
80
+ assert CUSTOMER_CLI.is_file(), f"missing wrapper: {CUSTOMER_CLI}"
81
+ assert PRODUCT_CLI.is_file(), f"missing wrapper: {PRODUCT_CLI}"
82
+ assert SCHEDULE_CLI.is_file(), f"missing wrapper: {SCHEDULE_CLI}"
83
+ assert PIPELINE_CLI.is_file(), f"missing wrapper: {PIPELINE_CLI}"
84
+ assert DASHBOARD_ALL.is_file(), f"missing aggregator: {DASHBOARD_ALL}"
85
+
86
+ with tempfile.TemporaryDirectory() as raw:
87
+ tmp = Path(raw)
88
+ (tmp / "data").mkdir()
89
+
90
+ # Use isolated shared DB under the tempdir so the test never touches
91
+ # the user's real ~/.config dirs. 四个 skill 共用一库。
92
+ ts = int(time.time() * 1000)
93
+ shared_db = tmp / "data" / f"insurance-{ts}.sqlite3"
94
+
95
+ env_dbs = {
96
+ "INSURANCE_DB_PATH": str(shared_db),
97
+ "INSURANCE_CUSTOMER_DB_PATH": str(shared_db),
98
+ "INSURANCE_PRODUCT_DB_PATH": str(shared_db),
99
+ "INSURANCE_SCHEDULE_DB_PATH": str(shared_db),
100
+ "INSURANCE_PIPELINE_DB_PATH": str(shared_db),
101
+ }
102
+
103
+ # --- 1. customer skill: zero-config write + read ---
104
+ print("\n[1/6] customer skill via wrapper (cwd=tempdir, no PYTHONPATH)...")
105
+ d = run(CUSTOMER_CLI,
106
+ ["customer-add", "--name", "云客户", "--phone", "13900000001",
107
+ "--age", "35", "--income", "300000"],
108
+ extra_env=env_dbs, cwd=tmp)
109
+ assert d.get("ok"), f"customer-add failed: {d}"
110
+ cust_id = d.get("id") or d.get("customer_id")
111
+ assert cust_id, f"customer-add missing id field: {d}"
112
+ d = run(CUSTOMER_CLI, ["customer-query", "--customer-id", cust_id],
113
+ extra_env=env_dbs, cwd=tmp)
114
+ assert d.get("ok") and d.get("count", 0) >= 1, d
115
+ assert d["customers"][0]["name"] == "云客户", d
116
+ print(f" ok customer={cust_id}")
117
+
118
+ # --- 2. customer dashboard ---
119
+ d = run(CUSTOMER_CLI, ["dashboard", "--json"], extra_env=env_dbs, cwd=tmp)
120
+ assert d.get("ok") and d.get("customers_total", 0) >= 1, d
121
+ print(f" ok dashboard customers_total={d['customers_total']}")
122
+
123
+ # --- 3. product skill: zero-config write + read ---
124
+ print("\n[2/6] product skill via wrapper...")
125
+ d = run(PRODUCT_CLI,
126
+ ["product-add", "--name", "云重疾", "--company", "云保险",
127
+ "--type", "重疾险", "--coverage-range", "10万-100万",
128
+ "--premium-range", "3000-15000", "--age-range", "0-55岁"],
129
+ extra_env=env_dbs, cwd=tmp)
130
+ assert d.get("ok"), f"product-add failed: {d}"
131
+ # 共享库模式下,gap-analysis 直接查共享库中的客户/保单数据。
132
+ d = run(PRODUCT_CLI,
133
+ ["gap-analysis", "--customer-id", cust_id],
134
+ extra_env=env_dbs, cwd=tmp)
135
+ assert d.get("ok"), f"product gap-analysis failed: {d}"
136
+ print(f" ok product-add + gap-analysis")
137
+
138
+ # --- 4. schedule skill ---
139
+ print("\n[3/6] schedule skill via wrapper...")
140
+ d = run(SCHEDULE_CLI, ["renewal-query", "--days", "30"],
141
+ extra_env=env_dbs, cwd=tmp)
142
+ assert d.get("ok"), f"schedule renewal-query failed: {d}"
143
+ d = run(SCHEDULE_CLI, ["dashboard", "--json"],
144
+ extra_env=env_dbs, cwd=tmp)
145
+ assert d.get("ok"), f"schedule dashboard failed: {d}"
146
+ print(f" ok schedule renewal-query + dashboard")
147
+
148
+ # --- 5. pipeline skill ---
149
+ print("\n[4/6] pipeline skill via wrapper...")
150
+ d = run(PIPELINE_CLI,
151
+ ["deal-add", "--customer-id", cust_id, "--product", "云重疾",
152
+ "--product-type", "重疾险", "--stage", "在谈",
153
+ "--estimated-premium", "8000"],
154
+ extra_env=env_dbs, cwd=tmp)
155
+ assert d.get("ok"), f"deal-add failed: {d}"
156
+ d = run(PIPELINE_CLI, ["dashboard", "--json"], extra_env=env_dbs, cwd=tmp)
157
+ assert d.get("ok"), f"pipeline dashboard failed: {d}"
158
+ print(f" ok pipeline deal-add + dashboard")
159
+
160
+ # --- 6. cross-skill aggregation via dashboard_all.py ---
161
+ print("\n[5/6] dashboard_all aggregator via direct path...")
162
+ env = _clean_env(env_dbs)
163
+ r = subprocess.run(
164
+ [sys.executable, str(DASHBOARD_ALL)],
165
+ cwd=str(tmp),
166
+ env=env,
167
+ capture_output=True,
168
+ text=True,
169
+ encoding="utf-8",
170
+ timeout=60,
171
+ )
172
+ assert r.returncode == 0, f"dashboard_all rc={r.returncode}\nSTDERR:\n{r.stderr}"
173
+ agg = json.loads(r.stdout)
174
+ assert agg.get("ok"), f"dashboard_all not ok: {agg}"
175
+ assert agg["overview"]["customers_total"] >= 1, agg["overview"]
176
+ assert agg["overview"]["products_total"] >= 1, agg["overview"]
177
+ # 兼容旧版/新版聚合结构:
178
+ # - 旧版 skills: {skill_name: {ok: true, ...}}
179
+ # - 新版 skills: {"source": "shared-db"}
180
+ if isinstance(agg.get("skills"), dict):
181
+ for skill_name, skill_res in agg["skills"].items():
182
+ if isinstance(skill_res, dict):
183
+ assert skill_res.get("ok"), f"{skill_name} sub-result not ok: {skill_res}"
184
+ print(f" ok aggregator: {agg['overview']}")
185
+
186
+ # --- 7. verify default DB path (no env) creates dir under ~/.config ---
187
+ print("\n[6/6] default DB path probe (no env vars)...")
188
+ # Use a customized HOME so we don't pollute the real one.
189
+ fake_home = tmp / "fake_home"
190
+ fake_home.mkdir()
191
+ env_no_db = _clean_env()
192
+ if sys.platform == "win32":
193
+ env_no_db["USERPROFILE"] = str(fake_home)
194
+ env_no_db["HOME"] = str(fake_home)
195
+ r = subprocess.run(
196
+ [sys.executable, str(CUSTOMER_CLI), "customer-query"],
197
+ cwd=str(tmp),
198
+ env=env_no_db,
199
+ capture_output=True,
200
+ text=True,
201
+ encoding="utf-8",
202
+ timeout=30,
203
+ )
204
+ assert r.returncode == 0, f"default DB customer-query failed:\n{r.stderr}"
205
+ out = json.loads(r.stdout)
206
+ assert out.get("ok"), out
207
+ # Default path must have been created under fake_home/.config
208
+ expected_dir = fake_home / ".config" / "insurance"
209
+ assert expected_dir.is_dir(), f"default DB dir not created: {expected_dir}"
210
+ print(f" ok default DB dir auto-created at {expected_dir}")
211
+
212
+ print("\nAll cloud-style zero-configuration smoke tests passed.")
213
+ return 0
214
+
215
+
216
+ if __name__ == "__main__":
217
+ raise SystemExit(main())
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ """Smoke test for dashboard_all.py — seeds tiny data into all 4 skill DBs
3
+ and verifies the aggregated card.
4
+
5
+ Run from anywhere::
6
+
7
+ python3 path/to/insurance-customer-policy/src/scripts/test_dashboard_all.py
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ import tempfile
16
+ from datetime import datetime, timedelta
17
+ from pathlib import Path
18
+
19
+ HERE = Path(__file__).resolve().parent
20
+ SKILLS_ROOT = HERE.parent.parent.parent
21
+
22
+ CUSTOMER_PKG = SKILLS_ROOT / "insurance-customer-policy" / "src" / "src"
23
+ PRODUCT_PKG = SKILLS_ROOT / "insurance-product-analysis" / "src" / "src"
24
+ SCHEDULE_PKG = SKILLS_ROOT / "insurance-schedule-renewal" / "src" / "src"
25
+ PIPELINE_PKG = SKILLS_ROOT / "insurance-sales-pipeline" / "src" / "src"
26
+
27
+
28
+ def call(pkg: Path, module: str, *args: str) -> dict:
29
+ env = {**os.environ, "PYTHONPATH": str(pkg), "PYTHONIOENCODING": "utf-8"}
30
+ r = subprocess.run([sys.executable, "-m", module, *args],
31
+ env=env, capture_output=True, text=True,
32
+ encoding="utf-8", timeout=60)
33
+ if r.returncode not in (0, 1):
34
+ raise SystemExit(f"rc={r.returncode} module={module} args={args}\n{r.stdout}\n{r.stderr}")
35
+ return json.loads(r.stdout.strip() or "{}")
36
+
37
+
38
+ def main() -> int:
39
+ tmp = tempfile.mkdtemp(prefix="ins_dash_smoke_")
40
+ os.environ["INSURANCE_CUSTOMER_DB_PATH"] = str(Path(tmp) / "customer.sqlite3")
41
+ os.environ["INSURANCE_PRODUCT_DB_PATH"] = str(Path(tmp) / "product.sqlite3")
42
+ os.environ["INSURANCE_SCHEDULE_DB_PATH"] = str(Path(tmp) / "reminders.sqlite3")
43
+ os.environ["INSURANCE_PIPELINE_DB_PATH"] = str(Path(tmp) / "pipeline.sqlite3")
44
+
45
+ today = datetime.now()
46
+ in_15 = (today + timedelta(days=15)).strftime("%Y-%m-%d")
47
+
48
+ print("[seed] customers + policies")
49
+ for i, (name, age, income) in enumerate([("张先生", 35, 300000),
50
+ ("李女士", 40, 100000),
51
+ ("王先生", 50, 600000)], 1):
52
+ call(CUSTOMER_PKG, "insurance_customer_cli",
53
+ "customer-add", "--name", name, "--phone", f"138{i:08d}",
54
+ "--age", str(age), "--income", str(income))
55
+ call(CUSTOMER_PKG, "insurance_customer_cli",
56
+ "policy-add", "--customer-id", "C001", "--company", "A", "--product", "重疾",
57
+ "--type", "重疾险", "--coverage", "500000", "--premium", "8000",
58
+ "--effective-date", "2024-01-01", "--expire-date", in_15)
59
+ call(CUSTOMER_PKG, "insurance_customer_cli",
60
+ "policy-add", "--customer-id", "C003", "--company", "B", "--product", "年金",
61
+ "--type", "养老险", "--coverage", "1000000", "--premium", "60000",
62
+ "--effective-date", "2024-01-01")
63
+
64
+ print("[seed] product")
65
+ call(PRODUCT_PKG, "insurance_product_cli",
66
+ "product-add", "--name", "XX重疾2024", "--company", "A", "--type", "重疾险")
67
+
68
+ print("[seed] deal + target")
69
+ call(PIPELINE_PKG, "insurance_pipeline_cli",
70
+ "deal-add", "--customer-id", "C001", "--product-type", "重疾险",
71
+ "--stage", "在谈", "--estimated-premium", "8000")
72
+ month = today.strftime("%Y-%m")
73
+ call(PIPELINE_PKG, "insurance_pipeline_cli",
74
+ "target-track", "--month", month, "--target", "100000")
75
+
76
+ print("[run] dashboard_all.py --json")
77
+ r = subprocess.run([sys.executable, str(HERE / "dashboard_all.py")],
78
+ env={**os.environ, "PYTHONIOENCODING": "utf-8"},
79
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
80
+ if r.returncode != 0:
81
+ raise SystemExit(f"dashboard_all.py rc={r.returncode}\n{r.stdout}\n{r.stderr}")
82
+ data = json.loads(r.stdout)
83
+ assert data["ok"], data
84
+ ov = data["overview"]
85
+ print(f" overview customers={ov['customers_total']} policies={ov['policies_total']} "
86
+ f"premium={ov['total_premium']} products={ov['products_total']} renewal_30d={ov['renewal_30d']} "
87
+ f"active_deals={ov['active_deals']} target={ov['this_month_target']}")
88
+ assert ov["customers_total"] == 3, ov
89
+ assert ov["policies_total"] == 2, ov
90
+ assert ov["total_premium"] == 68000.0, ov
91
+ assert ov["products_total"] == 1, ov
92
+ assert ov["renewal_30d"] >= 1, ov
93
+ assert ov["active_deals"] >= 1, ov
94
+ assert ov["this_month_target"] == 100000, ov
95
+ assert ov["hi_value"] == 1, ov # C003 premium=60000 > 50000
96
+ # markdown sanity
97
+ assert "经营概览" in data["markdown"]
98
+ assert "客户:3" in data["markdown"]
99
+
100
+ print("[run] dashboard_all.py --md")
101
+ r2 = subprocess.run([sys.executable, str(HERE / "dashboard_all.py"), "--md"],
102
+ env={**os.environ, "PYTHONIOENCODING": "utf-8"},
103
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
104
+ assert r2.returncode == 0
105
+ assert "经营概览" in r2.stdout
106
+ assert "保单" in r2.stdout
107
+
108
+ print("ALL OK")
109
+ return 0
110
+
111
+
112
+ if __name__ == "__main__":
113
+ raise SystemExit(main())
@@ -0,0 +1,4 @@
1
+ from insurance_customer_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())