Files
.pouch/tests/test_ack_feishu_intake_unit.py
T

233 lines
11 KiB
Python

from __future__ import annotations
import contextlib
import io
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
ACK_SCRIPTS = REPO_ROOT / "skills" / "ack" / "scripts"
sys.path.insert(0, str(ACK_SCRIPTS))
import feishu_bug_intake # noqa: E402
BOARD = """version: 1
project:
name: demo
bugIntake:
provider: feishu-base
profile: tenant-b
baseToken: bascnDemo
tableId: tblDemo
viewId: vewReady
fields:
title: fldTitle
actual: fldActual
expected: fldExpected
stepsToReproduce: fldSteps
acceptance: fldAcceptance
priority: fldPriority
attachments: fldAttachments
updatedAt: fldUpdated
tasks: []
"""
class FeishuBugIntakeUnitTests(unittest.TestCase):
def make_fake_cli(self, root: Path) -> tuple[Path, Path]:
log_path = root / "calls.jsonl"
fake = root / "lark-cli"
fake.write_text(
"#!" + sys.executable + "\n"
"import json, pathlib, sys\n"
f"log = pathlib.Path({str(log_path)!r})\n"
"args = sys.argv[1:]\n"
"with log.open('a') as f: f.write(json.dumps(args) + '\\n')\n"
"if args[:2] == ['profile', 'list']:\n"
" if '--format' in args: raise SystemExit(8)\n"
" print(json.dumps([{'name':'tenant-a','appId':'cli_a','brand':'feishu','active':True},{'name':'tenant-b','appId':'cli_b','brand':'feishu','active':False}]))\n"
"elif args[:2] == ['base', '+record-list']:\n"
" print(json.dumps({'code': 0, 'data': {'fields': ['fldTitle','fldActual','fldExpected','fldSteps','fldAcceptance','fldPriority','fldAttachments','fldUpdated'], 'record_id_list': ['recA'], 'data': [[' Bug\\n title ', ' actual ', 'expected', ['one', 'two'], 'accept', 'P1', [{'file_token':'fileA','name':'shot.png','type':'image/png','size':3}], '2026-08-01']]}}))\n"
"elif args[:2] == ['base', '+record-download-attachment']:\n"
" out = pathlib.Path(args[args.index('--output') + 1]); out.mkdir(parents=True, exist_ok=True); (out / 'shot.png').write_bytes(b'png'); print(json.dumps({'ok': True, 'data': {}}))\n"
"else: raise SystemExit(2)\n",
encoding="utf-8",
)
fake.chmod(0o755)
return fake, log_path
def invoke(self, argv: list[str], executable: Path) -> tuple[int, str, str]:
stdout, stderr = io.StringIO(), io.StringIO()
with mock.patch.object(feishu_bug_intake, "resolve_lark_cli", return_value=executable), contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
code = feishu_bug_intake.main(argv)
return code, stdout.getvalue(), stderr.getvalue()
def test_reader_exposes_check_and_fetch_commands(self) -> None:
with self.assertRaises(SystemExit) as exited, contextlib.redirect_stdout(io.StringIO()):
feishu_bug_intake.main(["--help"])
self.assertEqual(exited.exception.code, 0)
def test_fetch_uses_mocked_trusted_executable_and_official_wire_shapes(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp = Path(temp_dir)
board_path = temp / "tasks.yaml"
board_path.write_text(BOARD, encoding="utf-8")
fake, log_path = self.make_fake_cli(temp)
hostile = temp / "hostile"
hostile.mkdir()
(hostile / "lark-cli").write_text("#!/bin/sh\nexit 99\n", encoding="utf-8")
(hostile / "lark-cli").chmod(0o755)
with mock.patch.dict(os.environ, {"PATH": str(hostile)}, clear=False):
code, output, error = self.invoke(["fetch", str(board_path), "--output-dir", str(temp / "downloads")], fake)
self.assertEqual(code, 0, error)
payload = json.loads(output)
self.assertEqual(payload["records"][0]["title"], "Bug title")
self.assertEqual(payload["records"][0]["steps"], "one\ntwo")
self.assertRegex(payload["records"][0]["sourceRef"], r"^feishu-base:sha256:[0-9a-f]{64}$")
self.assertNotIn("bascnDemo", output)
self.assertNotIn("fileA", output)
self.assertNotIn("token", payload["records"][0]["attachments"][0])
self.assertTrue(Path(payload["records"][0]["attachments"][0]["localPath"]).is_file())
calls = [json.loads(line) for line in log_path.read_text(encoding="utf-8").splitlines()]
self.assertEqual(calls[0], ["profile", "list"])
for call in calls:
if call[:1] == ["base"]:
self.assertEqual(call[call.index("--profile") + 1], "tenant-b")
def test_resolver_ignores_hostile_path_and_rejects_unsafe_target(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp = Path(temp_dir)
trusted, hostile = temp / "trusted", temp / "hostile"
trusted.mkdir()
hostile.mkdir()
safe = trusted / "lark-cli"
safe.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
safe.chmod(0o755)
(hostile / "lark-cli").write_text("#!/bin/sh\nexit 99\n", encoding="utf-8")
(hostile / "lark-cli").chmod(0o755)
with mock.patch.object(feishu_bug_intake, "trusted_lark_cli_dirs", return_value=[trusted]), mock.patch.dict(os.environ, {"PATH": str(hostile)}, clear=False):
self.assertEqual(feishu_bug_intake.resolve_lark_cli(), safe)
safe.chmod(0o775)
with mock.patch.object(feishu_bug_intake, "trusted_lark_cli_dirs", return_value=[trusted]):
with self.assertRaisesRegex(feishu_bug_intake.IntakeError, "trusted"):
feishu_bug_intake.resolve_lark_cli()
def test_resolver_accepts_only_the_official_npm_wrapper_shape(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp = Path(temp_dir)
trusted = temp / "trusted"
scripts = temp / "node_modules" / "@larksuite" / "cli" / "scripts"
trusted.mkdir()
scripts.mkdir(parents=True)
wrapper = scripts / "run.js"
wrapper.write_text(
"#!" + sys.executable + "\nimport json\nprint(json.dumps({'ok': True}))\n",
encoding="utf-8",
)
wrapper.chmod(0o755)
manifest = scripts.parent / "package.json"
manifest.write_text(json.dumps({
"name": "@larksuite/cli",
"bin": {"lark-cli": "scripts/run.js"},
}), encoding="utf-8")
native = scripts.parent / "bin" / "lark-cli"
native.parent.mkdir()
native.write_text(
"#!" + sys.executable + "\nimport json\nprint(json.dumps({'ok': True}))\n",
encoding="utf-8",
)
native.chmod(0o755)
(trusted / "lark-cli").symlink_to(wrapper)
with mock.patch.object(feishu_bug_intake, "trusted_lark_cli_dirs", return_value=[trusted]):
self.assertEqual(feishu_bug_intake.resolve_lark_cli(), native)
self.assertEqual(feishu_bug_intake.run_cli(["probe"]), {"ok": True})
manifest.write_text(json.dumps({
"name": "lookalike",
"bin": {"lark-cli": "scripts/run.js"},
}), encoding="utf-8")
with mock.patch.object(feishu_bug_intake, "trusted_lark_cli_dirs", return_value=[trusted]):
with self.assertRaises(feishu_bug_intake.IntakeError):
feishu_bug_intake.resolve_lark_cli()
def test_cli_environment_drops_credential_and_runtime_overrides(self) -> None:
hostile = {
"LARKSUITE_CLI_APP_ID": "wrong-app",
"LARKSUITE_CLI_APP_SECRET": "wrong-secret",
"LARKSUITE_CLI_CONFIG_DIR": "/tmp/wrong-config",
"LARKSUITE_CLI_BRAND": "lark",
"FEISHU_APP_SECRET": "wrong-feishu-secret",
"NODE_OPTIONS": "--require=/tmp/inject.js",
"PYTHONPATH": "/tmp/inject",
}
with mock.patch.dict(os.environ, hostile, clear=False):
environment = feishu_bug_intake.cli_environment()
with tempfile.TemporaryDirectory() as temp_dir:
fake = Path(temp_dir) / "lark-cli"
fake.write_text(
"#!" + sys.executable + "\n"
"import json, os\n"
"keys = ['LARKSUITE_CLI_APP_ID','LARKSUITE_CLI_APP_SECRET','LARKSUITE_CLI_CONFIG_DIR','LARKSUITE_CLI_BRAND','FEISHU_APP_SECRET','NODE_OPTIONS','PYTHONPATH']\n"
"print(json.dumps({'ok': True, 'data': {key: os.environ.get(key) for key in keys}}))\n",
encoding="utf-8",
)
fake.chmod(0o755)
with mock.patch.object(feishu_bug_intake, "resolve_lark_cli", return_value=fake):
child = feishu_bug_intake.run_cli(["probe"])
self.assertEqual(set(environment) - {"HOME", "PATH"}, set(environment) & {"LANG", "LC_ALL", "LC_CTYPE"})
for name in hostile:
self.assertNotIn(name, environment)
self.assertIsNone(child["data"][name])
def test_run_cli_rejects_error_and_ambiguous_envelopes(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp = Path(temp_dir)
fake = temp / "lark-cli"
fake.write_text(
"#!" + sys.executable + "\nimport json, sys\nprint(sys.argv[1])\n",
encoding="utf-8",
)
fake.chmod(0o755)
with mock.patch.object(feishu_bug_intake, "resolve_lark_cli", return_value=fake):
for response in ('{"ok":false}', '{"code":7}', '{"data":{}}'):
with self.assertRaises(feishu_bug_intake.IntakeError):
feishu_bug_intake.run_cli([response])
def test_matrix_accepts_official_ok_envelope_and_data_rows(self) -> None:
fields = ["fldTitle"]
response = {
"ok": True,
"data": {"fields": fields, "record_id_list": ["recA"], "data": [["Bug"]]},
}
self.assertEqual(feishu_bug_intake.matrix_from_response(response, fields), (["recA"], [["Bug"]]))
def test_attachments_and_required_text_are_resource_bounded_and_strict(self) -> None:
with self.assertRaises(feishu_bug_intake.IntakeError):
feishu_bug_intake.attachment_items([{
"file_token": "fileA",
"name": "huge.png",
"type": "image/png",
"size": feishu_bug_intake.MAX_ATTACHMENT_BYTES + 1,
}])
too_many = [
{"file_token": f"file{index}", "name": f"{index}.png", "size": 1}
for index in range(feishu_bug_intake.MAX_ATTACHMENTS_PER_RECORD + 1)
]
with self.assertRaises(feishu_bug_intake.IntakeError):
feishu_bug_intake.attachment_items(too_many)
with self.assertRaises(feishu_bug_intake.IntakeError):
feishu_bug_intake.text({"unexpected": "value"})
if __name__ == "__main__":
unittest.main()