Merge remote-tracking branch 'upstream/main' into feat/rag-2

This commit is contained in:
QuantumGhost
2025-09-16 14:59:35 +08:00
791 changed files with 24372 additions and 7085 deletions

View File

@ -13,7 +13,6 @@ from services.account_service import AccountService, RegisterService, TenantServ
from services.errors.account import (
AccountAlreadyInTenantError,
AccountLoginError,
AccountNotFoundError,
AccountPasswordError,
AccountRegisterError,
CurrentPasswordIncorrectError,
@ -91,6 +90,28 @@ class TestAccountService:
assert account.password is None
assert account.password_salt is None
def test_create_account_password_invalid_new_password(
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test account create with invalid new password format.
"""
fake = Faker()
email = fake.email()
name = fake.name()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
# Test with too short password (assuming minimum length validation)
with pytest.raises(ValueError): # Password validation error
AccountService.create_account(
email=email,
name=name,
interface_language="en-US",
password="invalid_new_password",
)
def test_create_account_registration_disabled(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test account creation when registration is disabled.
@ -139,7 +160,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
password = fake.password(length=12)
with pytest.raises(AccountNotFoundError):
with pytest.raises(AccountPasswordError):
AccountService.authenticate(email, password)
def test_authenticate_banned_account(self, db_session_with_containers, mock_external_service_dependencies):
@ -940,7 +961,8 @@ class TestAccountService:
Test getting user through non-existent email.
"""
fake = Faker()
non_existent_email = fake.email()
domain = f"test-{fake.random_letters(10)}.com"
non_existent_email = fake.email(domain=domain)
found_user = AccountService.get_user_through_email(non_existent_email)
assert found_user is None
@ -3278,7 +3300,7 @@ class TestRegisterService:
redis_client.setex(cache_key, 24 * 60 * 60, account_id)
# Execute invitation retrieval
result = RegisterService._get_invitation_by_token(
result = RegisterService.get_invitation_by_token(
token=token,
workspace_id=workspace_id,
email=email,
@ -3316,7 +3338,7 @@ class TestRegisterService:
redis_client.setex(token_key, 24 * 60 * 60, json.dumps(invitation_data))
# Execute invitation retrieval
result = RegisterService._get_invitation_by_token(token=token)
result = RegisterService.get_invitation_by_token(token=token)
# Verify result contains expected data
assert result is not None

View File

@ -42,7 +42,7 @@ class TestAdvancedPromptTemplateService:
# Test data for Baichuan model
args = {
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "baichuan-13b-chat",
"has_context": "true",
@ -77,7 +77,7 @@ class TestAdvancedPromptTemplateService:
# Test data for common model
args = {
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
@ -116,7 +116,7 @@ class TestAdvancedPromptTemplateService:
for model_name in test_cases:
args = {
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": model_name,
"has_context": "true",
@ -144,7 +144,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT.value, "completion", "true")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT, "completion", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -173,7 +173,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT.value, "chat", "true")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT, "chat", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -202,7 +202,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.COMPLETION.value, "completion", "true")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.COMPLETION, "completion", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -230,7 +230,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.COMPLETION.value, "chat", "true")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.COMPLETION, "chat", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -257,7 +257,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT.value, "completion", "false")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT, "completion", "false")
# Assert: Verify the expected outcomes
assert result is not None
@ -303,7 +303,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT.value, "unsupported_mode", "true")
result = AdvancedPromptTemplateService.get_common_prompt(AppMode.CHAT, "unsupported_mode", "true")
# Assert: Verify empty dict is returned
assert result == {}
@ -442,7 +442,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT.value, "completion", "true")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT, "completion", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -473,7 +473,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT.value, "chat", "true")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT, "chat", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -502,7 +502,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.COMPLETION.value, "completion", "true")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.COMPLETION, "completion", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -530,7 +530,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.COMPLETION.value, "chat", "true")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.COMPLETION, "chat", "true")
# Assert: Verify the expected outcomes
assert result is not None
@ -557,7 +557,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT.value, "completion", "false")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT, "completion", "false")
# Assert: Verify the expected outcomes
assert result is not None
@ -603,7 +603,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Act: Execute the method under test
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT.value, "unsupported_mode", "true")
result = AdvancedPromptTemplateService.get_baichuan_prompt(AppMode.CHAT, "unsupported_mode", "true")
# Assert: Verify empty dict is returned
assert result == {}
@ -621,7 +621,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Test all app modes
app_modes = [AppMode.CHAT.value, AppMode.COMPLETION.value]
app_modes = [AppMode.CHAT, AppMode.COMPLETION]
model_modes = ["completion", "chat"]
for app_mode in app_modes:
@ -653,7 +653,7 @@ class TestAdvancedPromptTemplateService:
fake = Faker()
# Test all app modes
app_modes = [AppMode.CHAT.value, AppMode.COMPLETION.value]
app_modes = [AppMode.CHAT, AppMode.COMPLETION]
model_modes = ["completion", "chat"]
for app_mode in app_modes:
@ -686,10 +686,10 @@ class TestAdvancedPromptTemplateService:
# Test edge cases
edge_cases = [
{"app_mode": "", "model_mode": "completion", "model_name": "gpt-3.5-turbo", "has_context": "true"},
{"app_mode": AppMode.CHAT.value, "model_mode": "", "model_name": "gpt-3.5-turbo", "has_context": "true"},
{"app_mode": AppMode.CHAT.value, "model_mode": "completion", "model_name": "", "has_context": "true"},
{"app_mode": AppMode.CHAT, "model_mode": "", "model_name": "gpt-3.5-turbo", "has_context": "true"},
{"app_mode": AppMode.CHAT, "model_mode": "completion", "model_name": "", "has_context": "true"},
{
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "gpt-3.5-turbo",
"has_context": "",
@ -723,7 +723,7 @@ class TestAdvancedPromptTemplateService:
# Test with context
args = {
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
@ -757,7 +757,7 @@ class TestAdvancedPromptTemplateService:
# Test with context
args = {
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "baichuan-13b-chat",
"has_context": "true",
@ -786,25 +786,25 @@ class TestAdvancedPromptTemplateService:
# Test different scenarios
test_scenarios = [
{
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
},
{
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "chat",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
},
{
"app_mode": AppMode.COMPLETION.value,
"app_mode": AppMode.COMPLETION,
"model_mode": "completion",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
},
{
"app_mode": AppMode.COMPLETION.value,
"app_mode": AppMode.COMPLETION,
"model_mode": "chat",
"model_name": "gpt-3.5-turbo",
"has_context": "true",
@ -843,25 +843,25 @@ class TestAdvancedPromptTemplateService:
# Test different scenarios
test_scenarios = [
{
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "completion",
"model_name": "baichuan-13b-chat",
"has_context": "true",
},
{
"app_mode": AppMode.CHAT.value,
"app_mode": AppMode.CHAT,
"model_mode": "chat",
"model_name": "baichuan-13b-chat",
"has_context": "true",
},
{
"app_mode": AppMode.COMPLETION.value,
"app_mode": AppMode.COMPLETION,
"model_mode": "completion",
"model_name": "baichuan-13b-chat",
"has_context": "true",
},
{
"app_mode": AppMode.COMPLETION.value,
"app_mode": AppMode.COMPLETION,
"model_mode": "chat",
"model_name": "baichuan-13b-chat",
"has_context": "true",

View File

@ -255,7 +255,7 @@ class TestMetadataService:
mock_external_service_dependencies["current_user"].id = account.id
# Try to create metadata with built-in field name
built_in_field_name = BuiltInField.document_name.value
built_in_field_name = BuiltInField.document_name
metadata_args = MetadataArgs(type="string", name=built_in_field_name)
# Act & Assert: Verify proper error handling
@ -375,7 +375,7 @@ class TestMetadataService:
metadata = MetadataService.create_metadata(dataset.id, metadata_args)
# Try to update with built-in field name
built_in_field_name = BuiltInField.document_name.value
built_in_field_name = BuiltInField.document_name
with pytest.raises(ValueError, match="Metadata name already exists in Built-in fields."):
MetadataService.update_metadata_name(dataset.id, metadata.id, built_in_field_name)
@ -540,11 +540,11 @@ class TestMetadataService:
field_names = [field["name"] for field in result]
field_types = [field["type"] for field in result]
assert BuiltInField.document_name.value in field_names
assert BuiltInField.uploader.value in field_names
assert BuiltInField.upload_date.value in field_names
assert BuiltInField.last_update_date.value in field_names
assert BuiltInField.source.value in field_names
assert BuiltInField.document_name in field_names
assert BuiltInField.uploader in field_names
assert BuiltInField.upload_date in field_names
assert BuiltInField.last_update_date in field_names
assert BuiltInField.source in field_names
# Verify field types
assert "string" in field_types
@ -682,11 +682,11 @@ class TestMetadataService:
# Set document metadata with built-in fields
document.doc_metadata = {
BuiltInField.document_name.value: document.name,
BuiltInField.uploader.value: "test_uploader",
BuiltInField.upload_date.value: 1234567890.0,
BuiltInField.last_update_date.value: 1234567890.0,
BuiltInField.source.value: "test_source",
BuiltInField.document_name: document.name,
BuiltInField.uploader: "test_uploader",
BuiltInField.upload_date: 1234567890.0,
BuiltInField.last_update_date: 1234567890.0,
BuiltInField.source: "test_source",
}
db.session.add(document)
db.session.commit()

View File

@ -2,6 +2,7 @@ from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
from sqlalchemy import select
from models.account import TenantAccountJoin, TenantAccountRole
from models.model import Account, Tenant
@ -468,7 +469,7 @@ class TestModelLoadBalancingService:
assert load_balancing_config.id is not None
# Verify inherit config was created in database
inherit_configs = (
db.session.query(LoadBalancingModelConfig).where(LoadBalancingModelConfig.name == "__inherit__").all()
)
inherit_configs = db.session.scalars(
select(LoadBalancingModelConfig).where(LoadBalancingModelConfig.name == "__inherit__")
).all()
assert len(inherit_configs) == 1

View File

@ -2,6 +2,7 @@ from unittest.mock import create_autospec, patch
import pytest
from faker import Faker
from sqlalchemy import select
from werkzeug.exceptions import NotFound
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
@ -954,7 +955,9 @@ class TestTagService:
from extensions.ext_database import db
# Verify only one binding exists
bindings = db.session.query(TagBinding).where(TagBinding.tag_id == tag.id, TagBinding.target_id == app.id).all()
bindings = db.session.scalars(
select(TagBinding).where(TagBinding.tag_id == tag.id, TagBinding.target_id == app.id)
).all()
assert len(bindings) == 1
def test_save_tag_binding_invalid_target_type(self, db_session_with_containers, mock_external_service_dependencies):
@ -1064,7 +1067,9 @@ class TestTagService:
# No error should be raised, and database state should remain unchanged
from extensions.ext_database import db
bindings = db.session.query(TagBinding).where(TagBinding.tag_id == tag.id, TagBinding.target_id == app.id).all()
bindings = db.session.scalars(
select(TagBinding).where(TagBinding.tag_id == tag.id, TagBinding.target_id == app.id)
).all()
assert len(bindings) == 0
def test_check_target_exists_knowledge_success(

View File

@ -2,6 +2,7 @@ from unittest.mock import patch
import pytest
from faker import Faker
from sqlalchemy import select
from core.app.entities.app_invoke_entities import InvokeFrom
from models.account import Account
@ -354,16 +355,14 @@ class TestWebConversationService:
# Verify only one pinned conversation record exists
from extensions.ext_database import db
pinned_conversations = (
db.session.query(PinnedConversation)
.where(
pinned_conversations = db.session.scalars(
select(PinnedConversation).where(
PinnedConversation.app_id == app.id,
PinnedConversation.conversation_id == conversation.id,
PinnedConversation.created_by_role == "account",
PinnedConversation.created_by == account.id,
)
.all()
)
).all()
assert len(pinned_conversations) == 1

View File

@ -1,3 +1,5 @@
import time
import uuid
from unittest.mock import patch
import pytest
@ -248,9 +250,15 @@ class TestWebAppAuthService:
- Proper error handling for non-existent accounts
- Correct exception type and message
"""
# Arrange: Use non-existent email
fake = Faker()
non_existent_email = fake.email()
# Arrange: Generate a guaranteed non-existent email
# Use UUID and timestamp to ensure uniqueness
unique_id = str(uuid.uuid4()).replace("-", "")
timestamp = str(int(time.time() * 1000000)) # microseconds
non_existent_email = f"nonexistent_{unique_id}_{timestamp}@test-domain-that-never-exists.invalid"
# Double-check this email doesn't exist in the database
existing_account = db_session_with_containers.query(Account).filter_by(email=non_existent_email).first()
assert existing_account is None, f"Test email {non_existent_email} already exists in database"
# Act & Assert: Verify proper error handling
with pytest.raises(AccountNotFoundError):

View File

@ -96,7 +96,7 @@ class TestWorkflowService:
app.tenant_id = fake.uuid4()
app.name = fake.company()
app.description = fake.text()
app.mode = AppMode.WORKFLOW.value
app.mode = AppMode.WORKFLOW
app.icon_type = "emoji"
app.icon = "🤖"
app.icon_background = "#FFEAD5"
@ -883,7 +883,7 @@ class TestWorkflowService:
# Create chat mode app
app = self._create_test_app(db_session_with_containers, fake)
app.mode = AppMode.CHAT.value
app.mode = AppMode.CHAT
# Create app model config (required for conversion)
from models.model import AppModelConfig
@ -926,7 +926,7 @@ class TestWorkflowService:
# Assert
assert result is not None
assert result.mode == AppMode.ADVANCED_CHAT.value # CHAT mode converts to ADVANCED_CHAT, not WORKFLOW
assert result.mode == AppMode.ADVANCED_CHAT # CHAT mode converts to ADVANCED_CHAT, not WORKFLOW
assert result.name == conversion_args["name"]
assert result.icon == conversion_args["icon"]
assert result.icon_type == conversion_args["icon_type"]
@ -945,7 +945,7 @@ class TestWorkflowService:
# Create completion mode app
app = self._create_test_app(db_session_with_containers, fake)
app.mode = AppMode.COMPLETION.value
app.mode = AppMode.COMPLETION
# Create app model config (required for conversion)
from models.model import AppModelConfig
@ -988,7 +988,7 @@ class TestWorkflowService:
# Assert
assert result is not None
assert result.mode == AppMode.WORKFLOW.value
assert result.mode == AppMode.WORKFLOW
assert result.name == conversion_args["name"]
assert result.icon == conversion_args["icon"]
assert result.icon_type == conversion_args["icon_type"]
@ -1007,7 +1007,7 @@ class TestWorkflowService:
# Create workflow mode app (already in workflow mode)
app = self._create_test_app(db_session_with_containers, fake)
app.mode = AppMode.WORKFLOW.value
app.mode = AppMode.WORKFLOW
from extensions.ext_database import db
@ -1030,7 +1030,7 @@ class TestWorkflowService:
# Arrange
fake = Faker()
app = self._create_test_app(db_session_with_containers, fake)
app.mode = AppMode.ADVANCED_CHAT.value
app.mode = AppMode.ADVANCED_CHAT
from extensions.ext_database import db
@ -1061,7 +1061,7 @@ class TestWorkflowService:
# Arrange
fake = Faker()
app = self._create_test_app(db_session_with_containers, fake)
app.mode = AppMode.WORKFLOW.value
app.mode = AppMode.WORKFLOW
from extensions.ext_database import db

View File

@ -14,6 +14,7 @@ from core.app.app_config.entities import (
VariableEntityType,
)
from core.model_runtime.entities.llm_entities import LLMMode
from core.prompt.utils.prompt_template_parser import PromptTemplateParser
from models.account import Account, Tenant
from models.api_based_extension import APIBasedExtension
from models.model import App, AppMode, AppModelConfig
@ -37,7 +38,7 @@ class TestWorkflowConverter:
# Setup default mock returns
mock_encrypter.decrypt_token.return_value = "decrypted_api_key"
mock_prompt_transform.return_value.get_prompt_template.return_value = {
"prompt_template": type("obj", (object,), {"template": "You are a helpful assistant {{text_input}}"})(),
"prompt_template": PromptTemplateParser(template="You are a helpful assistant {{text_input}}"),
"prompt_rules": {"human_prefix": "Human", "assistant_prefix": "Assistant"},
}
mock_agent_chat_config_manager.get_app_config.return_value = self._create_mock_app_config()