Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ef7c528
fix(api): resolve new pyrefly unreachable-except and unwrap diagnostics
mesrop2008 Oct 14, 2026
4cbf4b0
test(api): cover malformed-input branches touched by the pyrefly fix
mesrop2008 Oct 14, 2026
007d0cf
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 14, 2026
1b0ddcb
Merge branch 'fix/pyrefly-redundant-except-and-unwrap' of https://git…
mesrop2008 Oct 14, 2026
ddeef54
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 14, 2026
8389fe6
Merge remote-tracking branch 'upstream/main' into fix/pyrefly-redunda…
mesrop2008 Oct 14, 2026
3281583
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 14, 2026
30460a4
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 15, 2026
5eed226
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 15, 2026
c76ff9d
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 15, 2026
c74d35b
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 17, 2026
6d00e9d
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 18, 2026
c665fdb
Merge remote-tracking branch 'upstream/main' into fix/pyrefly-redunda…
mesrop2008 Oct 22, 2026
e97969a
Merge remote-tracking branch 'upstream/main' into fix/pyrefly-redunda…
mesrop2008 Oct 29, 2026
6e10ec5
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 29, 2026
3931cc8
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Oct 29, 2026
3442279
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
asukaminato0721 Nov 2, 2026
8635d5c
Merge branch 'main' into fix/pyrefly-redundant-except-and-unwrap
mesrop2008 Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion api/libs/oauth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import base64
import binascii
import json
import logging
import urllib.parse
Expand Down
3 changes: 2 additions & 1 deletion api/libs/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import re
from datetime import UTC, datetime, timedelta
from typing import NoReturn

from flask import Request
from werkzeug.exceptions import Unauthorized
Expand Down Expand Up @@ -200,7 +201,7 @@ def check_csrf_token(request: Request, user_id: str):
if is_admin_api_key_request(request):
return

def _unauthorized():
def _unauthorized() -> NoReturn:
raise Unauthorized("CSRF token is missing or invalid.")

for pattern in CSRF_WHITE_LIST:
Expand Down
27 changes: 0 additions & 27 deletions api/tests/helpers/unit/services/document_service_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1394,33 +1394,6 @@ def test_process_rule_args_validate_missing_pre_processing_rules_error(self):
with pytest.raises(ValueError, match="Process rule pre_processing_rules is required"):
DocumentService.process_rule_args_validate(knowledge_config)

def test_process_rule_args_validate_missing_pre_processing_rule_id_error(self):
"""
Test error when pre_processing_rule id is missing.

Verifies that when a pre_processing_rule has no id, a ValueError
is raised.

This test ensures:
- Missing pre_processing_rule id is rejected
- Error message is clear
- Error type is correct
"""
# Arrange
pre_processing_rules = [
PreProcessingRule.model_construct(id=None, enabled=True) # Missing id
]
process_rule = DocumentValidationTestDataFactory.create_process_rule(
mode="custom", pre_processing_rules=pre_processing_rules
)
knowledge_config = DocumentValidationTestDataFactory.create_knowledge_config(process_rule=process_rule)

# Mock DatasetProcessRule.MODES
with patch.object(DatasetProcessRule, "MODES", ["automatic", "custom", "hierarchical"]):
# Act & Assert
with pytest.raises(ValueError, match="Process rule pre_processing_rules id is required"):
DocumentService.process_rule_args_validate(knowledge_config)

def test_process_rule_args_validate_invalid_pre_processing_rule_enabled_error(self):
"""
Test error when pre_processing_rule enabled is not boolean.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import types
from collections.abc import Callable
from inspect import unwrap
from typing import cast
from unittest.mock import MagicMock, patch

import pytest
from flask import Flask
from pydantic import ValidationError

import controllers.files.plugin_file_upload as module
from controllers.common.schema import JsonResponseWithStatus
from controllers.files import bp as files_blueprint
from core.workflow.file_reference import build_file_reference
from enums import DeploymentEdition
Expand Down Expand Up @@ -68,6 +70,11 @@ def _valid_args() -> dict[str, object]:
}


def _post_upload() -> JsonResponseWithStatus:
api = module.PluginUploadFileApi()
return unwrap(cast(Callable[..., object], api.post))(api)


@pytest.fixture
def files_app(config_overrides: Callable[..., None]) -> Flask:
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from collections.abc import Callable, Iterator
from pathlib import Path
from types import SimpleNamespace
from typing import cast
from unittest.mock import patch

import jwt
Expand Down
78 changes: 78 additions & 0 deletions api/tests/unit_tests/core/test_provider_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,84 @@ def test__to_model_settings_lb_disabled(mock_provider_entity, provider_db: Sessi
assert len(result[0].load_balancing_configs) == 0


def test__to_model_settings_skips_lb_config_with_malformed_credentials(mock_provider_entity, provider_db: Session):
ps = ProviderModelSetting(
tenant_id="tenant_id",
provider_name="openai",
model_name="gpt-4",
model_type="llm",
enabled=True,
load_balancing_enabled=True,
)
ps.id = "id"
load_balancing_model_configs = [
LoadBalancingModelConfig(
tenant_id="tenant_id",
provider_name="openai",
model_name="gpt-4",
model_type="llm",
name="__inherit__",
encrypted_config=None,
enabled=True,
),
LoadBalancingModelConfig(
tenant_id="tenant_id",
provider_name="openai",
model_name="gpt-4",
model_type="llm",
name="malformed",
encrypted_config="{not-json",
enabled=True,
),
LoadBalancingModelConfig(
tenant_id="tenant_id",
provider_name="openai",
model_name="gpt-4",
model_type="llm",
name="first",
encrypted_config='{"openai_api_key": "fake_key"}',
enabled=True,
),
]
for index, config in enumerate(load_balancing_model_configs, start=1):
config.id = f"id{index}"
provider_model_settings, load_balancing_model_configs = _persist_model_configuration(
provider_db, ps, load_balancing_model_configs
)
provider_manager = _build_provider_manager()

with (
patch("core.helper.model_provider_cache.ProviderCredentialsCache.get", return_value=None),
patch("core.helper.model_provider_cache.ProviderCredentialsCache.set") as cache_set,
patch.object(provider_manager, "_get_decoding_context", return_value=None),
):
result = provider_manager._to_model_settings(
provider_entity=mock_provider_entity,
provider_model_settings=provider_model_settings,
load_balancing_model_configs=load_balancing_model_configs,
)

assert len(result) == 1
assert [config.name for config in result[0].load_balancing_configs] == ["__inherit__", "first"]
cache_set.assert_called_once_with(credentials={"openai_api_key": "fake_key"})


def test_get_and_decrypt_credentials_returns_empty_dict_for_malformed_config():
provider_manager = _build_provider_manager()

with patch("core.helper.model_provider_cache.ProviderCredentialsCache.get", return_value=None):
credentials = provider_manager._get_and_decrypt_credentials(
tenant_id="tenant_id",
record_id="record_id",
encrypted_config="{not-json",
secret_variables=["openai_api_key"],
cache_type=ProviderCredentialsCacheType.PROVIDER,
is_provider=True,
)

assert credentials == {}


def test_get_default_model_uses_injected_runtime_for_existing_default_record(provider_db: Session):
existing_default_model = TenantDefaultModel(
tenant_id="tenant-id",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,29 @@ def test_get_for_stored_document_rejects_deleted_or_different_stored_credential(
)
is None
)


def test_get_for_stored_document_rejects_malformed_stored_source_info(
sqlite_session_factory: sessionmaker[Session],
) -> None:
repository = SQLAlchemyDatasourceCredentialRepository(session_factory=sqlite_session_factory)
with sqlite_session_factory.begin() as session:
credential = _provider()
session.add(credential)
session.flush()
dataset, document = _notion_source(workspace_id="workspace-1", credential_id=credential.id)
document.data_source_info = "{not-json"
session.add_all([dataset, document])
credential_id = credential.id

assert (
repository.get_for_stored_document(
workspace_id="workspace-1",
dataset_id="dataset-1",
document_id="document-1",
credential_id=credential_id,
provider="notion_datasource",
plugin_id="langgenius/notion_datasource",
)
is None
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
from models.model import AppMode
from repositories.app.console_repository import ConsoleAppRepository
from services.enterprise import rbac_service
from services.errors.document import DocumentIndexingError
from services.knowledge.dataset_access import DatasetNotFoundError
from services.knowledge.datasets.adapters import SQLAlchemyDatasetOperations
from services.knowledge.datasets.application import DatasetListFilter
from services.knowledge.documents.adapters import SQLAlchemyDocumentOperations
from services.knowledge.documents.application import DocumentInvalidActionError
from services.knowledge.entities.document_creation import DocumentIndexingJobs
from services.knowledge.entities.knowledge_entities import KnowledgeConfig
from services.knowledge.resource_scope import DatasetRef
Expand Down Expand Up @@ -456,3 +458,24 @@ def test_queries_and_related_apps_materialize_after_session_close(
assert related["total"] == 1
assert related["data"][0]["id"] == "app"
assert related["data"][0]["mode"] == "chat"


@pytest.mark.usefixtures("operations")
@pytest.mark.parametrize(
"error",
[ValueError("Invalid action"), DocumentIndexingError("Document is being indexed")],
ids=["value-error", "document-indexing-error"],
)
def test_change_status_reports_rejected_status_updates_as_invalid_actions(
sqlite_session_factory: sessionmaker[Session], error: ValueError
) -> None:
with (
patch(
"services.knowledge.documents.adapters.DocumentService.batch_update_document_status",
side_effect=error,
),
pytest.raises(DocumentInvalidActionError, match=str(error)) as error_info,
):
SQLAlchemyDocumentOperations(session_factory=sqlite_session_factory).change_status(CONTEXT, REF, [], "enable")

assert error_info.value.__cause__ is error
Loading