chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:08:54 +08:00
commit 4a4a1fed67
721 changed files with 262090 additions and 0 deletions
View File
+141
View File
@@ -0,0 +1,141 @@
import sys
import pytest
from lightrag.api.config import get_default_host, parse_args
pytestmark = pytest.mark.offline
def _clear_bedrock_auth_env(monkeypatch):
for key in (
"LLM_BINDING",
"EMBEDDING_BINDING",
"QUERY_LLM_BINDING",
"QUERY_LLM_BINDING_API_KEY",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_BEARER_TOKEN_BEDROCK",
"QUERY_AWS_ACCESS_KEY_ID",
"QUERY_AWS_SECRET_ACCESS_KEY",
):
monkeypatch.delenv(key, raising=False)
def test_bedrock_default_host_uses_sdk_default_endpoint(monkeypatch):
monkeypatch.delenv("LLM_BINDING_HOST", raising=False)
assert get_default_host("bedrock") == "DEFAULT_BEDROCK_ENDPOINT"
def test_bedrock_custom_host_is_returned(monkeypatch):
monkeypatch.setenv("LLM_BINDING_HOST", "https://proxy.example.com")
assert get_default_host("bedrock") == "https://proxy.example.com"
def test_bedrock_env_binding_alias_is_normalized(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "aws_bedrock")
monkeypatch.setenv("EMBEDDING_BINDING", "aws_bedrock")
monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "absk-test")
args = parse_args()
assert args.llm_binding == "bedrock"
assert args.embedding_binding == "bedrock"
def test_bedrock_cli_binding_alias_is_not_supported(monkeypatch):
monkeypatch.setattr(
sys, "argv", ["lightrag-server", "--llm-binding", "aws_bedrock"]
)
with pytest.raises(SystemExit):
parse_args()
def test_bedrock_role_env_binding_alias_is_normalized(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("QUERY_LLM_BINDING", "aws_bedrock")
monkeypatch.setenv("QUERY_LLM_MODEL", "us.amazon.nova-lite-v1:0")
monkeypatch.setenv("QUERY_AWS_REGION", "us-west-2")
monkeypatch.setenv("QUERY_AWS_ACCESS_KEY_ID", "akid")
monkeypatch.setenv("QUERY_AWS_SECRET_ACCESS_KEY", "secret")
args = parse_args()
assert args.query_llm_binding == "bedrock"
assert args.query_aws_region == "us-west-2"
def test_bedrock_role_api_key_is_rejected(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("QUERY_LLM_BINDING", "bedrock")
monkeypatch.setenv("QUERY_LLM_MODEL", "us.amazon.nova-lite-v1:0")
monkeypatch.setenv("QUERY_LLM_BINDING_API_KEY", "absk-role")
with pytest.raises(SystemExit, match="does not support QUERY_LLM_BINDING_API_KEY"):
parse_args()
def test_bedrock_binding_requires_sigv4_pair_or_bearer_token(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "bedrock")
monkeypatch.setenv("EMBEDDING_BINDING", "ollama")
with pytest.raises(ValueError, match="Bedrock LLM binding requires"):
parse_args()
def test_bedrock_binding_rejects_partial_sigv4_pair(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "bedrock")
monkeypatch.setenv("EMBEDDING_BINDING", "ollama")
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "akid")
with pytest.raises(ValueError, match="Bedrock LLM binding requires"):
parse_args()
def test_bedrock_binding_accepts_sigv4_pair(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "bedrock")
monkeypatch.setenv("EMBEDDING_BINDING", "ollama")
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "akid")
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "secret")
args = parse_args()
assert args.llm_binding == "bedrock"
def test_bedrock_binding_accepts_bearer_token(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "bedrock")
monkeypatch.setenv("EMBEDDING_BINDING", "ollama")
monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "absk-test")
args = parse_args()
assert args.llm_binding == "bedrock"
def test_bedrock_role_requires_complete_role_sigv4_pair(monkeypatch):
_clear_bedrock_auth_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("QUERY_LLM_BINDING", "bedrock")
monkeypatch.setenv("QUERY_LLM_MODEL", "us.amazon.nova-lite-v1:0")
monkeypatch.setenv("QUERY_AWS_ACCESS_KEY_ID", "akid")
with pytest.raises(ValueError, match="Bedrock role 'query' requires"):
parse_args()
@@ -0,0 +1,12 @@
import pytest
from lightrag.api.config import get_default_host
pytestmark = pytest.mark.offline
def test_gemini_default_host_uses_sdk_default_endpoint(monkeypatch):
monkeypatch.delenv("LLM_BINDING_HOST", raising=False)
assert get_default_host("gemini") == "DEFAULT_GEMINI_ENDPOINT"
@@ -0,0 +1,95 @@
import sys
import pytest
from lightrag.api.config import parse_args
from lightrag.constants import DEFAULT_MAX_ASYNC
pytestmark = pytest.mark.offline
ROLE_MAX_ASYNC_ENV_KEYS = (
"MAX_ASYNC_LLM",
"MAX_ASYNC",
"EXTRACT_MAX_ASYNC_LLM",
"KEYWORD_MAX_ASYNC_LLM",
"QUERY_MAX_ASYNC_LLM",
"VLM_MAX_ASYNC_LLM",
)
def _clear_max_async_env(monkeypatch):
for key in ROLE_MAX_ASYNC_ENV_KEYS:
monkeypatch.delenv(key, raising=False)
def test_role_max_async_defaults_none_when_env_unset(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("MAX_ASYNC", "10")
args = parse_args()
assert args.max_async == 10
assert args.extract_llm_max_async is None
assert args.keyword_llm_max_async is None
assert args.query_llm_max_async is None
assert args.vlm_llm_max_async is None
def test_role_max_async_env_override_keeps_other_roles_none(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("MAX_ASYNC", "10")
monkeypatch.setenv("EXTRACT_MAX_ASYNC_LLM", "7")
args = parse_args()
assert args.max_async == 10
assert args.extract_llm_max_async == 7
assert args.keyword_llm_max_async is None
assert args.query_llm_max_async is None
assert args.vlm_llm_max_async is None
def test_role_max_async_literal_none_string_is_preserved(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("MAX_ASYNC", "10")
monkeypatch.setenv("QUERY_MAX_ASYNC_LLM", "None")
args = parse_args()
assert args.max_async == 10
assert args.query_llm_max_async is None
def test_max_async_llm_new_name(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("MAX_ASYNC_LLM", "8")
args = parse_args()
assert args.max_async == 8
def test_max_async_llm_takes_precedence_over_legacy(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("MAX_ASYNC_LLM", "8")
monkeypatch.setenv("MAX_ASYNC", "10")
args = parse_args()
assert args.max_async == 8
def test_max_async_defaults_when_unset(monkeypatch):
_clear_max_async_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
args = parse_args()
assert args.max_async == DEFAULT_MAX_ASYNC
@@ -0,0 +1,111 @@
"""Offline tests for VLM_PROCESS_ENABLE and renamed role timeout vars."""
from __future__ import annotations
import sys
import pytest
from lightrag.api.config import parse_args
pytestmark = pytest.mark.offline
def _reset_vlm_env(monkeypatch):
for key in (
"VLM_PROCESS_ENABLE",
"VLM_LLM_BINDING",
"VLM_LLM_MODEL",
"VLM_LLM_BINDING_HOST",
"VLM_LLM_BINDING_API_KEY",
"VLM_LLM_TIMEOUT",
"EXTRACT_LLM_TIMEOUT",
"KEYWORD_LLM_TIMEOUT",
"QUERY_LLM_TIMEOUT",
"LLM_TIMEOUT_VLM_LLM",
"LLM_TIMEOUT_EXTRACT_LLM",
"LLM_TIMEOUT_KEYWORD_LLM",
"LLM_TIMEOUT_QUERY_LLM",
):
monkeypatch.delenv(key, raising=False)
def test_vlm_process_enable_defaults_to_false(monkeypatch):
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "openai")
args = parse_args()
assert args.vlm_process_enable is False
def test_vlm_process_enable_true_with_openai_passes(monkeypatch):
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "openai")
monkeypatch.setenv("VLM_PROCESS_ENABLE", "true")
args = parse_args()
assert args.vlm_process_enable is True
def test_vlm_process_enable_rejects_lollms_base_binding(monkeypatch):
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "lollms")
monkeypatch.setenv("VLM_PROCESS_ENABLE", "true")
with pytest.raises(SystemExit) as exc:
parse_args()
assert "lollms" in str(exc.value).lower()
def test_vlm_process_enable_rejects_lollms_role_binding(monkeypatch):
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "openai")
monkeypatch.setenv("VLM_PROCESS_ENABLE", "true")
monkeypatch.setenv("VLM_LLM_BINDING", "lollms")
# Cross-provider validation needs model + key; fill them so the lollms
# branch is the only failure path.
monkeypatch.setenv("VLM_LLM_MODEL", "anything")
monkeypatch.setenv("VLM_LLM_BINDING_HOST", "http://localhost:9600")
monkeypatch.setenv("VLM_LLM_BINDING_API_KEY", "placeholder")
with pytest.raises(SystemExit) as exc:
parse_args()
assert "lollms" in str(exc.value).lower()
def test_role_timeout_uses_new_variable_names(monkeypatch):
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "openai")
monkeypatch.setenv("EXTRACT_LLM_TIMEOUT", "240")
monkeypatch.setenv("KEYWORD_LLM_TIMEOUT", "120")
monkeypatch.setenv("QUERY_LLM_TIMEOUT", "60")
monkeypatch.setenv("VLM_LLM_TIMEOUT", "300")
args = parse_args()
assert args.extract_llm_timeout == 240
assert args.keyword_llm_timeout == 120
assert args.query_llm_timeout == 60
assert args.vlm_llm_timeout == 300
def test_role_timeout_legacy_variables_no_longer_have_effect(monkeypatch):
"""The breaking-change migration: legacy LLM_TIMEOUT_{ROLE}_LLM is silently ignored."""
_reset_vlm_env(monkeypatch)
monkeypatch.setattr(sys, "argv", ["lightrag-server"])
monkeypatch.setenv("LLM_BINDING", "openai")
monkeypatch.setenv("LLM_TIMEOUT_EXTRACT_LLM", "999")
monkeypatch.setenv("LLM_TIMEOUT_VLM_LLM", "888")
args = parse_args()
assert args.extract_llm_timeout is None
assert args.vlm_llm_timeout is None
@@ -0,0 +1,80 @@
"""Offline tests for whitelist_exposes_api_routes (GHSA-mmg5-8x8q-v934 banner).
The startup banner uses this helper to warn when WHITELIST_PATHS leaves the
Ollama-compatible /api/* routes unauthenticated on a network-exposed bind. It
must mirror the prefix/exact matching in get_combined_auth_dependency so a
catch-all entry like "/*" is detected, not just literal "/api..." entries.
"""
from __future__ import annotations
import importlib
import sys
from types import SimpleNamespace
import pytest
pytestmark = pytest.mark.offline
@pytest.fixture
def whitelist_exposes_api_routes(monkeypatch):
"""Import utils_api with a stub global_args (consumed at import time)."""
config = importlib.import_module("lightrag.api.config")
monkeypatch.setattr(
config,
"global_args",
SimpleNamespace(
token_secret=None, # -> AuthHandler falls back to DEFAULT_TOKEN_SECRET
jwt_algorithm="HS256",
token_expire_hours=48,
guest_token_expire_hours=24,
auth_accounts="",
whitelist_paths="/health", # consumed by utils_api at import time
token_auto_renew=False,
),
)
sys.modules.pop("lightrag.api.auth", None)
importlib.reload(importlib.import_module("lightrag.api.auth"))
sys.modules.pop("lightrag.api.utils_api", None)
utils_api = importlib.import_module("lightrag.api.utils_api")
try:
yield utils_api.whitelist_exposes_api_routes
finally:
sys.modules.pop("lightrag.api.utils_api", None)
@pytest.mark.parametrize(
"whitelist",
[
"/health,/api/*", # default whitelist
"/api/*",
"/api",
"/api/chat", # exact Ollama route
"/*", # catch-all: empty prefix matches every path
" /* ", # catch-all with surrounding whitespace
"/health,/*",
"/a/*", # prefix that /api also starts with
],
)
def test_exposes_api_routes(whitelist_exposes_api_routes, whitelist: str) -> None:
assert whitelist_exposes_api_routes(whitelist) is True
@pytest.mark.parametrize(
"whitelist",
[
"/health",
"/health,/docs",
"/health/*", # unrelated prefix
"/apiary/*", # prefix under /api... but not /api/ — exempts only /apiary
"/apidocs", # exact path that merely shares the /api substring
"",
" ",
],
)
def test_does_not_expose_api_routes(
whitelist_exposes_api_routes, whitelist: str
) -> None:
assert whitelist_exposes_api_routes(whitelist) is False