fix: live-validation fixes (list-coercion, .env session loading, smoke summary)

Cleanup after successful live validation (10 real customers scraped+stored):

- app/config.py: add yk_userid / yk_company / yk_cookie Settings fields so
  existing-session creds can be supplied via .env (bypasses programmatic
  login for validation with a live browser session).
- app/storage/repos.py: coerce list values to ';'-joined strings in
  _build_column_values. Fixes a real bug: the live friends-list API returns
  Customer.deptRoute as a LIST, but dept_route is a scalar String(255)
  column. raw_json still preserves the original list (no data loss).
- scripts/smoke_wechat.py:
  * Revert build_client_from_env to read os.environ ONLY (no get_settings
    fallbacks), so the monkeypatch.setenv unit tests stay hermetic and the
    real .env stops leaking in. 3 previously-red tests green again.
  * Add _push_settings_into_environ() (Settings -> os.environ, real env
    wins, only fills gaps) and call it as the first line of main(); so the
    smoke run still honors .env without the pure builder depending on
    Settings. .env -> Settings -> os.environ (once) -> build_client_from_env.
  * Keep the await _print_summary fix from validation.
- tests/test_repos.py: regression test for list dept_route coercion
  (stored joined 'A;B', raw_json keeps the list).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-01 15:23:43 +08:00
parent 46e3880c0b
commit 2508b5aec1
4 changed files with 99 additions and 3 deletions
+6
View File
@@ -22,6 +22,12 @@ class Settings(BaseSettings):
# Fernet key for encrypting stored credentials. Required for real runs;
# empty default lets tests construct Settings without a key.
encryption_key: str = ""
# Existing-session creds (optional; bypasses programmatic login — e.g. for
# validation with a live browser session). Read from .env / environment.
yk_userid: str = ""
yk_company: str = ""
yk_cookie: str = ""
# Shared bearer token for the viewing API. Empty (default) -> auth DISABLED
# (we trust the network boundary; this is a local/intranet tool). When set,
# every API request must present this value via ``Authorization: Bearer
+6
View File
@@ -193,6 +193,12 @@ def _build_column_values(
v = _coerce_bool(v)
elif col_name in _INT_FIELDS:
v = _coerce_int(v)
# Some upstream fields are lists (e.g. Customer.deptRoute is a
# list of dept ids) but map to scalar String columns — join so the
# bind succeeds and the data is still recoverable (raw_json keeps
# the original list).
if isinstance(v, list):
v = ";".join(str(x) for x in v)
values[col_name] = v
# Preserve the verbatim source record. If the scraper already set raw_json
# (it does), that wins; otherwise the input dict itself is the source of