chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user