mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-15 07:41:20 +00:00
* fix(web-fetch): stop sending .env.example placeholder keys to providers make config and make setup copy .env.example into .env, and the template set nine search/fetch provider keys to example values such as JINA_API_KEY=your-jina-api-key. The default keyless Jina Reader sent that text as a Bearer key, so every web_fetch on a fresh install failed with 401 while make doctor reported web fetch as configured. - .env.example comments those nine lines out, like every other credential in the template already is. - provider_credentials.resolve_api_key reads a your-...-key/-token/-secret value as unset, so existing .env files that still hold the placeholders recover without editing, for every provider that resolves keys there. * chore: leave the changelog entry to merge time Per CONTRIBUTING.md (#8202), maintainers add changelog entries when they merge a pull request. * fix(web-search): route every provider key and doctor through one placeholder rule Review follow-up for #8206: - provider_credentials exposes is_template_placeholder(), the single definition of an .env.example example value. key/token/secret must be a whole hyphen-separated word, so a real value such as your-monkey or your-passkey is no longer dropped. - Serper, Serply, Sofya, SpecStream, Unbrowse, Webz, Brave, Tencent WSA and You.com resolve config and environment keys through resolve_api_key instead of private strip-and-send copies, so an old .env that still holds the template value no longer sends it. - make doctor asks the same predicate: a placeholder key is reported as still holding the .env.example value instead of "set", and a literal placeholder in config.yaml falls back to the environment key as the runtime does. - A census test keeps credential environment reads under community/ wrapped in resolve_api_key. * fix(web-search): match shipped placeholders exactly and mirror Browserless precedence in doctor Second review follow-up for #8206: - is_template_placeholder() matches exactly the your-... values that .env.example ships instead of a pattern, so a token an operator chose (a self-hosted Browserless TOKEN such as your-review-team-token) is sent again. A test keeps the set equal to the template. - make doctor mirrors Browserless: a configured token field is authoritative, blank or not, and BROWSERLESS_TOKEN is read only when the field is omitted. A blank or placeholder token now warns instead of reporting the variable, cross-checked against the client. - The keyless Jina and You.com providers stay ok but name JINA_API_KEY or YDC_API_KEY when a placeholder there is ignored, and a literal placeholder api_key in config.yaml is named when no env key backs it. * fix(doctor): match each web provider's key precedence; drop the dead Jina api_key from docs Found while re-checking #8206 against every provider's runtime: - Browserless used as web_fetch had no doctor key check; it now gets the same authoritative token handling as web_capture. - InfoQuest reads only INFOQUEST_API_KEY, so doctor no longer counts an api_key field in its tool config as configured. - GroundRoute needs a key for every call but had no doctor entry, so a missing key reported ok. - You.com names a placeholder api_key in config that it ignores. - The Jina web_fetch docs snippet carried api_key: $JINA_API_KEY, which the tool never reads. With JINA_API_KEY now commented out in .env.example, that line made config loading fail on a fresh install. - The provider_credentials docstring now scopes doctor's use of the predicate to web provider keys, and the scripts guide no longer says .env.example ships live placeholder keys.
113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
@pytest.fixture
|
|
def config_checkout(tmp_path: Path) -> Path:
|
|
scripts_dir = tmp_path / "scripts"
|
|
scripts_dir.mkdir()
|
|
(tmp_path / "frontend").mkdir()
|
|
shutil.copyfile(REPO_ROOT / "scripts" / "configure.py", scripts_dir / "configure.py")
|
|
for template in ("config.example.yaml", ".env.example", "frontend/.env.example"):
|
|
(tmp_path / template).write_text(f"template: {template}\n", encoding="utf-8")
|
|
return tmp_path
|
|
|
|
|
|
@pytest.mark.parametrize("encoding", ["ascii", "cp1252", "cp936", "utf-8"])
|
|
def test_configure_succeeds_with_restricted_stdout_encoding(config_checkout: Path, encoding: str):
|
|
result = subprocess.run(
|
|
[sys.executable, str(config_checkout / "scripts" / "configure.py")],
|
|
env={**os.environ, "PYTHONIOENCODING": encoding},
|
|
capture_output=True,
|
|
text=True,
|
|
encoding=encoding,
|
|
check=False,
|
|
timeout=30,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "Configuration files generated" in result.stdout
|
|
assert result.stderr == ""
|
|
for template, generated in (
|
|
("config.example.yaml", "config.yaml"),
|
|
(".env.example", ".env"),
|
|
("frontend/.env.example", "frontend/.env"),
|
|
):
|
|
assert (config_checkout / generated).read_bytes() == (config_checkout / template).read_bytes()
|
|
|
|
|
|
@pytest.mark.parametrize("config_name", ["config.yaml", "config.yml", "configure.yml"])
|
|
def test_configure_preserves_existing_configuration(config_checkout: Path, config_name: str):
|
|
config_path = config_checkout / config_name
|
|
config_path.write_text("existing: true\n", encoding="utf-8")
|
|
|
|
result = subprocess.run(
|
|
[sys.executable, str(config_checkout / "scripts" / "configure.py")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=False,
|
|
timeout=30,
|
|
)
|
|
|
|
assert result.returncode == 1
|
|
assert "configuration file already exists" in result.stdout
|
|
assert config_path.read_text(encoding="utf-8") == "existing: true\n"
|
|
assert not (config_checkout / ".env").exists()
|
|
assert not (config_checkout / "frontend" / ".env").exists()
|
|
|
|
|
|
def test_configure_help_prints_usage_without_generating_files(config_checkout: Path):
|
|
result = subprocess.run(
|
|
[sys.executable, str(config_checkout / "scripts" / "configure.py"), "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=False,
|
|
timeout=30,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "usage: configure.py" in result.stdout
|
|
assert "Cross-platform config bootstrap script for DeerFlow." in result.stdout
|
|
assert not (config_checkout / "config.yaml").exists()
|
|
assert not (config_checkout / ".env").exists()
|
|
assert not (config_checkout / "frontend" / ".env").exists()
|
|
|
|
|
|
def test_configure_rejects_unknown_flags_without_generating_files(config_checkout: Path):
|
|
result = subprocess.run(
|
|
[sys.executable, str(config_checkout / "scripts" / "configure.py"), "--not-a-real-flag"],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=False,
|
|
timeout=30,
|
|
)
|
|
|
|
assert result.returncode == 2
|
|
assert "unrecognized arguments" in result.stderr
|
|
assert not (config_checkout / "config.yaml").exists()
|
|
assert not (config_checkout / ".env").exists()
|
|
assert not (config_checkout / "frontend" / ".env").exists()
|
|
|
|
|
|
def test_env_template_leaves_every_placeholder_credential_commented():
|
|
"""``make config`` and ``make setup`` copy ``.env.example`` verbatim into ``.env``.
|
|
|
|
A live ``JINA_API_KEY=your-jina-api-key`` line made the default keyless
|
|
``web_fetch`` send that text as a key and fail every request with 401, so
|
|
every ``your-...`` example value must stay behind a ``#``.
|
|
"""
|
|
live = [line for line in (REPO_ROOT / ".env.example").read_text(encoding="utf-8").splitlines() if not line.lstrip().startswith("#") and "=" in line and line.split("=", 1)[1].strip().lower().startswith("your-")]
|
|
assert live == []
|