mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
Compare commits
20 Commits
gh-readonl
...
verify-ema
| Author | SHA1 | Date | |
|---|---|---|---|
| 61a222834a | |||
| e8657cc3de | |||
| 33dd82a3dd | |||
| e08c06cbc3 | |||
| 8ca54ddf94 | |||
| 3e073404cc | |||
| 0acabf5f73 | |||
| 1e0e4a8b43 | |||
| f9b2ff59c8 | |||
| 0da31b1a14 | |||
| 10d1904e59 | |||
| 095b436621 | |||
| baaf4e8041 | |||
| bc41371975 | |||
| 9c5c935ed5 | |||
| 559f8263b7 | |||
| 59c5638342 | |||
| 897ffb6b35 | |||
| d367a6b1e1 | |||
| daa9d38788 |
34
.github/workflows/autofix.yml
vendored
34
.github/workflows/autofix.yml
vendored
@ -2,6 +2,9 @@ name: autofix.ci
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: ["main"]
|
||||
permissions:
|
||||
@ -12,9 +15,15 @@ jobs:
|
||||
if: github.repository == 'langgenius/dify'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Complete merge group check
|
||||
if: github.event_name == 'merge_group'
|
||||
run: echo "autofix.ci updates pull request branches, not merge group refs."
|
||||
|
||||
- if: github.event_name != 'merge_group'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Check Docker Compose inputs
|
||||
if: github.event_name != 'merge_group'
|
||||
id: docker-compose-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
@ -24,30 +33,34 @@ jobs:
|
||||
docker/docker-compose-template.yaml
|
||||
docker/docker-compose.yaml
|
||||
- name: Check web inputs
|
||||
if: github.event_name != 'merge_group'
|
||||
id: web-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
web/**
|
||||
- name: Check api inputs
|
||||
if: github.event_name != 'merge_group'
|
||||
id: api-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
- if: github.event_name != 'merge_group'
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
- if: github.event_name != 'merge_group'
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
|
||||
- name: Generate Docker Compose
|
||||
if: steps.docker-compose-changes.outputs.any_changed == 'true'
|
||||
if: github.event_name != 'merge_group' && steps.docker-compose-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
cd docker
|
||||
./generate_docker_compose
|
||||
|
||||
- if: steps.api-changes.outputs.any_changed == 'true'
|
||||
- if: github.event_name != 'merge_group' && steps.api-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
cd api
|
||||
uv sync --dev
|
||||
@ -59,13 +72,13 @@ jobs:
|
||||
uv run ruff format ..
|
||||
|
||||
- name: count migration progress
|
||||
if: steps.api-changes.outputs.any_changed == 'true'
|
||||
if: github.event_name != 'merge_group' && steps.api-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
cd api
|
||||
./cnt_base.sh
|
||||
|
||||
- name: ast-grep
|
||||
if: steps.api-changes.outputs.any_changed == 'true'
|
||||
if: github.event_name != 'merge_group' && steps.api-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
# ast-grep exits 1 if no matches are found; allow idempotent runs.
|
||||
uvx --from ast-grep-cli ast-grep --pattern 'db.session.query($WHATEVER).filter($HERE)' --rewrite 'db.session.query($WHATEVER).where($HERE)' -l py --update-all || true
|
||||
@ -95,13 +108,14 @@ jobs:
|
||||
find . -name "*.py.bak" -type f -delete
|
||||
|
||||
- name: Setup web environment
|
||||
if: steps.web-changes.outputs.any_changed == 'true'
|
||||
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-web
|
||||
|
||||
- name: ESLint autofix
|
||||
if: steps.web-changes.outputs.any_changed == 'true'
|
||||
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
cd web
|
||||
vp exec eslint --concurrency=2 --prune-suppressions --quiet || true
|
||||
|
||||
- uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3
|
||||
- if: github.event_name != 'merge_group'
|
||||
uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3
|
||||
|
||||
3
.github/workflows/main-ci.yml
vendored
3
.github/workflows/main-ci.yml
vendored
@ -3,6 +3,9 @@ name: Main CI Pipeline
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
types: [checks_requested]
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
|
||||
7
.github/workflows/semantic-pull-request.yml
vendored
7
.github/workflows/semantic-pull-request.yml
vendored
@ -7,6 +7,9 @@ on:
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
merge_group:
|
||||
branches: ["main"]
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -15,7 +18,11 @@ jobs:
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Complete merge group check
|
||||
if: github.event_name == 'merge_group'
|
||||
run: echo "Semantic PR title validation is handled on pull requests."
|
||||
- name: Check title
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -134,7 +134,6 @@ class EducationAutocompleteQuery(BaseModel):
|
||||
class ChangeEmailSendPayload(BaseModel):
|
||||
email: EmailStr
|
||||
language: str | None = None
|
||||
phase: str | None = None
|
||||
token: str | None = None
|
||||
|
||||
|
||||
@ -548,13 +547,17 @@ class ChangeEmailSendEmailApi(Resource):
|
||||
account = None
|
||||
user_email = None
|
||||
email_for_sending = args.email.lower()
|
||||
if args.phase is not None and args.phase == "new_email":
|
||||
if args.token is None:
|
||||
raise InvalidTokenError()
|
||||
send_phase = AccountService.CHANGE_EMAIL_PHASE_OLD
|
||||
if args.token is not None:
|
||||
send_phase = AccountService.CHANGE_EMAIL_PHASE_NEW
|
||||
|
||||
reset_data = AccountService.get_change_email_data(args.token)
|
||||
if reset_data is None:
|
||||
raise InvalidTokenError()
|
||||
|
||||
reset_token_phase = reset_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY)
|
||||
if reset_token_phase != AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED:
|
||||
raise InvalidTokenError()
|
||||
user_email = reset_data.get("email", "")
|
||||
|
||||
if user_email.lower() != current_user.email.lower():
|
||||
@ -574,7 +577,7 @@ class ChangeEmailSendEmailApi(Resource):
|
||||
email=email_for_sending,
|
||||
old_email=user_email,
|
||||
language=language,
|
||||
phase=args.phase,
|
||||
phase=send_phase,
|
||||
)
|
||||
return {"result": "success", "data": token}
|
||||
|
||||
@ -609,12 +612,26 @@ class ChangeEmailCheckApi(Resource):
|
||||
AccountService.add_change_email_error_rate_limit(user_email)
|
||||
raise EmailCodeError()
|
||||
|
||||
phase_transitions: dict[str, str] = {
|
||||
AccountService.CHANGE_EMAIL_PHASE_OLD: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED,
|
||||
AccountService.CHANGE_EMAIL_PHASE_NEW: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED,
|
||||
}
|
||||
token_phase = token_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY)
|
||||
if not isinstance(token_phase, str):
|
||||
raise InvalidTokenError()
|
||||
refreshed_phase = phase_transitions.get(token_phase)
|
||||
if refreshed_phase is None:
|
||||
raise InvalidTokenError()
|
||||
|
||||
# Verified, revoke the first token
|
||||
AccountService.revoke_change_email_token(args.token)
|
||||
|
||||
# Refresh token data by generating a new token
|
||||
_, new_token = AccountService.generate_change_email_token(
|
||||
user_email, code=args.code, old_email=token_data.get("old_email"), additional_data={}
|
||||
user_email,
|
||||
code=args.code,
|
||||
old_email=token_data.get("old_email"),
|
||||
additional_data={AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: refreshed_phase},
|
||||
)
|
||||
|
||||
AccountService.reset_change_email_error_rate_limit(user_email)
|
||||
@ -644,13 +661,22 @@ class ChangeEmailResetApi(Resource):
|
||||
if not reset_data:
|
||||
raise InvalidTokenError()
|
||||
|
||||
AccountService.revoke_change_email_token(args.token)
|
||||
token_phase = reset_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY)
|
||||
if token_phase != AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED:
|
||||
raise InvalidTokenError()
|
||||
|
||||
token_email = reset_data.get("email")
|
||||
normalized_token_email = token_email.lower() if isinstance(token_email, str) else token_email
|
||||
if normalized_token_email != normalized_new_email:
|
||||
raise InvalidTokenError()
|
||||
|
||||
old_email = reset_data.get("old_email", "")
|
||||
current_user, _ = current_account_with_tenant()
|
||||
if current_user.email.lower() != old_email.lower():
|
||||
raise AccountNotFound()
|
||||
|
||||
AccountService.revoke_change_email_token(args.token)
|
||||
|
||||
updated_account = AccountService.update_account_email(current_user, email=normalized_new_email)
|
||||
|
||||
AccountService.send_change_email_completed_notify_email(
|
||||
|
||||
@ -4,6 +4,7 @@ import logging
|
||||
import secrets
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from enum import StrEnum
|
||||
from hashlib import sha256
|
||||
from typing import Any, cast
|
||||
|
||||
@ -90,12 +91,25 @@ class TokenPair(BaseModel):
|
||||
csrf_token: str
|
||||
|
||||
|
||||
class ChangeEmailPhase(StrEnum):
|
||||
OLD = "old_email"
|
||||
OLD_VERIFIED = "old_email_verified"
|
||||
NEW = "new_email"
|
||||
NEW_VERIFIED = "new_email_verified"
|
||||
|
||||
|
||||
REFRESH_TOKEN_PREFIX = "refresh_token:"
|
||||
ACCOUNT_REFRESH_TOKEN_PREFIX = "account_refresh_token:"
|
||||
REFRESH_TOKEN_EXPIRY = timedelta(days=dify_config.REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
|
||||
|
||||
class AccountService:
|
||||
CHANGE_EMAIL_TOKEN_PHASE_KEY = "email_change_phase"
|
||||
CHANGE_EMAIL_PHASE_OLD = ChangeEmailPhase.OLD
|
||||
CHANGE_EMAIL_PHASE_OLD_VERIFIED = ChangeEmailPhase.OLD_VERIFIED
|
||||
CHANGE_EMAIL_PHASE_NEW = ChangeEmailPhase.NEW
|
||||
CHANGE_EMAIL_PHASE_NEW_VERIFIED = ChangeEmailPhase.NEW_VERIFIED
|
||||
|
||||
reset_password_rate_limiter = RateLimiter(prefix="reset_password_rate_limit", max_attempts=1, time_window=60 * 1)
|
||||
email_register_rate_limiter = RateLimiter(prefix="email_register_rate_limit", max_attempts=1, time_window=60 * 1)
|
||||
email_code_login_rate_limiter = RateLimiter(
|
||||
@ -552,13 +566,20 @@ class AccountService:
|
||||
raise ValueError("Email must be provided.")
|
||||
if not phase:
|
||||
raise ValueError("phase must be provided.")
|
||||
if phase not in (cls.CHANGE_EMAIL_PHASE_OLD, cls.CHANGE_EMAIL_PHASE_NEW):
|
||||
raise ValueError("phase must be one of old_email or new_email.")
|
||||
|
||||
if cls.change_email_rate_limiter.is_rate_limited(account_email):
|
||||
from controllers.console.auth.error import EmailChangeRateLimitExceededError
|
||||
|
||||
raise EmailChangeRateLimitExceededError(int(cls.change_email_rate_limiter.time_window / 60))
|
||||
|
||||
code, token = cls.generate_change_email_token(account_email, account, old_email=old_email)
|
||||
code, token = cls.generate_change_email_token(
|
||||
account_email,
|
||||
account,
|
||||
old_email=old_email,
|
||||
additional_data={cls.CHANGE_EMAIL_TOKEN_PHASE_KEY: phase},
|
||||
)
|
||||
|
||||
send_change_mail_task.delay(
|
||||
language=language,
|
||||
|
||||
@ -954,6 +954,16 @@ class TestWorkflowAppService:
|
||||
assert result_with_new_email["total"] == 3
|
||||
assert all(log.created_by_role == CreatorUserRole.ACCOUNT for log in result_with_new_email["data"])
|
||||
|
||||
# Create another account in a different tenant using the original email.
|
||||
# Querying by the old email should still fail for this app's tenant.
|
||||
cross_tenant_account = AccountService.create_account(
|
||||
email=original_email,
|
||||
name=fake.name(),
|
||||
interface_language="en-US",
|
||||
password=fake.password(length=12),
|
||||
)
|
||||
TenantService.create_owner_tenant_if_not_exist(cross_tenant_account, name=fake.company())
|
||||
|
||||
# Old email unbound, is unexpected input, should raise ValueError
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
service.get_paginate_workflow_app_logs(
|
||||
|
||||
@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from flask import Flask, g
|
||||
|
||||
from controllers.console.auth.error import InvalidTokenError
|
||||
from controllers.console.workspace.account import (
|
||||
AccountDeleteUpdateFeedbackApi,
|
||||
ChangeEmailCheckApi,
|
||||
@ -52,7 +53,7 @@ class TestChangeEmailSend:
|
||||
@patch("controllers.console.workspace.account.extract_remote_ip", return_value="127.0.0.1")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_normalize_new_email_phase(
|
||||
def test_should_infer_new_email_phase_from_token(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
@ -68,13 +69,16 @@ class TestChangeEmailSend:
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
mock_account = _build_account("current@example.com", "acc1")
|
||||
mock_current_account.return_value = (mock_account, None)
|
||||
mock_get_change_data.return_value = {"email": "current@example.com"}
|
||||
mock_get_change_data.return_value = {
|
||||
"email": "current@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED,
|
||||
}
|
||||
mock_send_email.return_value = "token-abc"
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email",
|
||||
method="POST",
|
||||
json={"email": "New@Example.com", "language": "en-US", "phase": "new_email", "token": "token-123"},
|
||||
json={"email": "New@Example.com", "language": "en-US", "token": "token-123"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
response = ChangeEmailSendEmailApi().post()
|
||||
@ -91,6 +95,107 @@ class TestChangeEmailSend:
|
||||
mock_is_ip_limit.assert_called_once_with("127.0.0.1")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.workspace.account.current_account_with_tenant")
|
||||
@patch("controllers.console.workspace.account.db")
|
||||
@patch("controllers.console.workspace.account.Session")
|
||||
@patch("controllers.console.workspace.account.AccountService.get_account_by_email_with_case_fallback")
|
||||
@patch("controllers.console.workspace.account.AccountService.send_change_email_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.is_email_send_ip_limit", return_value=False)
|
||||
@patch("controllers.console.workspace.account.extract_remote_ip", return_value="127.0.0.1")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_ignore_client_phase_and_use_old_phase_when_token_missing(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
mock_extract_ip,
|
||||
mock_is_ip_limit,
|
||||
mock_send_email,
|
||||
mock_get_account_by_email,
|
||||
mock_session_cls,
|
||||
mock_account_db,
|
||||
mock_current_account,
|
||||
mock_db,
|
||||
app,
|
||||
):
|
||||
_mock_wraps_db(mock_db)
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
mock_current_account.return_value = (_build_account("current@example.com", "current"), None)
|
||||
existing_account = _build_account("old@example.com", "acc-old")
|
||||
mock_get_account_by_email.return_value = existing_account
|
||||
mock_send_email.return_value = "token-legacy"
|
||||
mock_session = MagicMock()
|
||||
mock_session_cm = MagicMock()
|
||||
mock_session_cm.__enter__.return_value = mock_session
|
||||
mock_session_cm.__exit__.return_value = None
|
||||
mock_session_cls.return_value = mock_session_cm
|
||||
mock_account_db.engine = MagicMock()
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email",
|
||||
method="POST",
|
||||
json={"email": "old@example.com", "language": "en-US", "phase": "new_email"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
response = ChangeEmailSendEmailApi().post()
|
||||
|
||||
assert response == {"result": "success", "data": "token-legacy"}
|
||||
mock_get_account_by_email.assert_called_once_with("old@example.com", session=mock_session)
|
||||
mock_send_email.assert_called_once_with(
|
||||
account=existing_account,
|
||||
email="old@example.com",
|
||||
old_email="old@example.com",
|
||||
language="en-US",
|
||||
phase=AccountService.CHANGE_EMAIL_PHASE_OLD,
|
||||
)
|
||||
mock_extract_ip.assert_called_once()
|
||||
mock_is_ip_limit.assert_called_once_with("127.0.0.1")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.workspace.account.current_account_with_tenant")
|
||||
@patch("controllers.console.workspace.account.AccountService.get_change_email_data")
|
||||
@patch("controllers.console.workspace.account.AccountService.send_change_email_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.is_email_send_ip_limit", return_value=False)
|
||||
@patch("controllers.console.workspace.account.extract_remote_ip", return_value="127.0.0.1")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_reject_unverified_old_email_token_for_new_email_phase(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
mock_extract_ip,
|
||||
mock_is_ip_limit,
|
||||
mock_send_email,
|
||||
mock_get_change_data,
|
||||
mock_current_account,
|
||||
mock_db,
|
||||
app,
|
||||
):
|
||||
_mock_wraps_db(mock_db)
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
mock_account = _build_account("current@example.com", "acc1")
|
||||
mock_current_account.return_value = (mock_account, None)
|
||||
mock_get_change_data.return_value = {
|
||||
"email": "current@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD,
|
||||
}
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email",
|
||||
method="POST",
|
||||
json={"email": "New@Example.com", "language": "en-US", "token": "token-123"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
with pytest.raises(InvalidTokenError):
|
||||
ChangeEmailSendEmailApi().post()
|
||||
|
||||
mock_send_email.assert_not_called()
|
||||
mock_extract_ip.assert_called_once()
|
||||
mock_is_ip_limit.assert_called_once_with("127.0.0.1")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
|
||||
class TestChangeEmailValidity:
|
||||
@patch("controllers.console.wraps.db")
|
||||
@ -122,7 +227,12 @@ class TestChangeEmailValidity:
|
||||
mock_account = _build_account("user@example.com", "acc2")
|
||||
mock_current_account.return_value = (mock_account, None)
|
||||
mock_is_rate_limit.return_value = False
|
||||
mock_get_data.return_value = {"email": "user@example.com", "code": "1234", "old_email": "old@example.com"}
|
||||
mock_get_data.return_value = {
|
||||
"email": "user@example.com",
|
||||
"code": "1234",
|
||||
"old_email": "old@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD,
|
||||
}
|
||||
mock_generate_token.return_value = (None, "new-token")
|
||||
|
||||
with app.test_request_context(
|
||||
@ -138,11 +248,76 @@ class TestChangeEmailValidity:
|
||||
mock_add_rate.assert_not_called()
|
||||
mock_revoke_token.assert_called_once_with("token-123")
|
||||
mock_generate_token.assert_called_once_with(
|
||||
"user@example.com", code="1234", old_email="old@example.com", additional_data={}
|
||||
"user@example.com",
|
||||
code="1234",
|
||||
old_email="old@example.com",
|
||||
additional_data={
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED
|
||||
},
|
||||
)
|
||||
mock_reset_rate.assert_called_once_with("user@example.com")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.workspace.account.current_account_with_tenant")
|
||||
@patch("controllers.console.workspace.account.AccountService.reset_change_email_error_rate_limit")
|
||||
@patch("controllers.console.workspace.account.AccountService.generate_change_email_token")
|
||||
@patch("controllers.console.workspace.account.AccountService.revoke_change_email_token")
|
||||
@patch("controllers.console.workspace.account.AccountService.add_change_email_error_rate_limit")
|
||||
@patch("controllers.console.workspace.account.AccountService.get_change_email_data")
|
||||
@patch("controllers.console.workspace.account.AccountService.is_change_email_error_rate_limit")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_refresh_new_email_phase_to_verified(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
mock_is_rate_limit,
|
||||
mock_get_data,
|
||||
mock_add_rate,
|
||||
mock_revoke_token,
|
||||
mock_generate_token,
|
||||
mock_reset_rate,
|
||||
mock_current_account,
|
||||
mock_db,
|
||||
app,
|
||||
):
|
||||
_mock_wraps_db(mock_db)
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
mock_account = _build_account("old@example.com", "acc2")
|
||||
mock_current_account.return_value = (mock_account, None)
|
||||
mock_is_rate_limit.return_value = False
|
||||
mock_get_data.return_value = {
|
||||
"email": "new@example.com",
|
||||
"code": "5678",
|
||||
"old_email": "old@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW,
|
||||
}
|
||||
mock_generate_token.return_value = (None, "new-phase-token")
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email/validity",
|
||||
method="POST",
|
||||
json={"email": "New@Example.com", "code": "5678", "token": "token-456"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
response = ChangeEmailCheckApi().post()
|
||||
|
||||
assert response == {"is_valid": True, "email": "new@example.com", "token": "new-phase-token"}
|
||||
mock_is_rate_limit.assert_called_once_with("new@example.com")
|
||||
mock_add_rate.assert_not_called()
|
||||
mock_revoke_token.assert_called_once_with("token-456")
|
||||
mock_generate_token.assert_called_once_with(
|
||||
"new@example.com",
|
||||
code="5678",
|
||||
old_email="old@example.com",
|
||||
additional_data={
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED
|
||||
},
|
||||
)
|
||||
mock_reset_rate.assert_called_once_with("new@example.com")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
|
||||
class TestChangeEmailReset:
|
||||
@patch("controllers.console.wraps.db")
|
||||
@ -175,7 +350,11 @@ class TestChangeEmailReset:
|
||||
mock_current_account.return_value = (current_user, None)
|
||||
mock_is_freeze.return_value = False
|
||||
mock_check_unique.return_value = True
|
||||
mock_get_data.return_value = {"old_email": "OLD@example.com"}
|
||||
mock_get_data.return_value = {
|
||||
"old_email": "OLD@example.com",
|
||||
"email": "new@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED,
|
||||
}
|
||||
mock_account_after_update = _build_account("new@example.com", "acc3-updated")
|
||||
mock_update_account.return_value = mock_account_after_update
|
||||
|
||||
@ -194,6 +373,106 @@ class TestChangeEmailReset:
|
||||
mock_send_notify.assert_called_once_with(email="new@example.com")
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.workspace.account.current_account_with_tenant")
|
||||
@patch("controllers.console.workspace.account.AccountService.send_change_email_completed_notify_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.update_account_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.revoke_change_email_token")
|
||||
@patch("controllers.console.workspace.account.AccountService.get_change_email_data")
|
||||
@patch("controllers.console.workspace.account.AccountService.check_email_unique")
|
||||
@patch("controllers.console.workspace.account.AccountService.is_account_in_freeze")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_reject_old_phase_token_for_reset(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
mock_is_freeze,
|
||||
mock_check_unique,
|
||||
mock_get_data,
|
||||
mock_revoke_token,
|
||||
mock_update_account,
|
||||
mock_send_notify,
|
||||
mock_current_account,
|
||||
mock_db,
|
||||
app,
|
||||
):
|
||||
_mock_wraps_db(mock_db)
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
current_user = _build_account("old@example.com", "acc3")
|
||||
mock_current_account.return_value = (current_user, None)
|
||||
mock_is_freeze.return_value = False
|
||||
mock_check_unique.return_value = True
|
||||
mock_get_data.return_value = {
|
||||
"old_email": "OLD@example.com",
|
||||
"email": "old@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD,
|
||||
}
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email/reset",
|
||||
method="POST",
|
||||
json={"new_email": "new@example.com", "token": "token-123"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
with pytest.raises(InvalidTokenError):
|
||||
ChangeEmailResetApi().post()
|
||||
|
||||
mock_revoke_token.assert_not_called()
|
||||
mock_update_account.assert_not_called()
|
||||
mock_send_notify.assert_not_called()
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.workspace.account.current_account_with_tenant")
|
||||
@patch("controllers.console.workspace.account.AccountService.send_change_email_completed_notify_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.update_account_email")
|
||||
@patch("controllers.console.workspace.account.AccountService.revoke_change_email_token")
|
||||
@patch("controllers.console.workspace.account.AccountService.get_change_email_data")
|
||||
@patch("controllers.console.workspace.account.AccountService.check_email_unique")
|
||||
@patch("controllers.console.workspace.account.AccountService.is_account_in_freeze")
|
||||
@patch("libs.login.check_csrf_token", return_value=None)
|
||||
@patch("controllers.console.wraps.FeatureService.get_system_features")
|
||||
def test_should_reject_mismatched_new_email_for_verified_token(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
mock_is_freeze,
|
||||
mock_check_unique,
|
||||
mock_get_data,
|
||||
mock_revoke_token,
|
||||
mock_update_account,
|
||||
mock_send_notify,
|
||||
mock_current_account,
|
||||
mock_db,
|
||||
app,
|
||||
):
|
||||
_mock_wraps_db(mock_db)
|
||||
mock_features.return_value = SimpleNamespace(enable_change_email=True)
|
||||
current_user = _build_account("old@example.com", "acc3")
|
||||
mock_current_account.return_value = (current_user, None)
|
||||
mock_is_freeze.return_value = False
|
||||
mock_check_unique.return_value = True
|
||||
mock_get_data.return_value = {
|
||||
"old_email": "OLD@example.com",
|
||||
"email": "another@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED,
|
||||
}
|
||||
|
||||
with app.test_request_context(
|
||||
"/account/change-email/reset",
|
||||
method="POST",
|
||||
json={"new_email": "new@example.com", "token": "token-789"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
with pytest.raises(InvalidTokenError):
|
||||
ChangeEmailResetApi().post()
|
||||
|
||||
mock_revoke_token.assert_not_called()
|
||||
mock_update_account.assert_not_called()
|
||||
mock_send_notify.assert_not_called()
|
||||
mock_csrf.assert_called_once()
|
||||
|
||||
|
||||
class TestAccountDeletionFeedback:
|
||||
@patch("controllers.console.wraps.db")
|
||||
|
||||
@ -10,7 +10,6 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, Mock, create_autospec, patch
|
||||
|
||||
import pytest
|
||||
from dify_graph.model_runtime.entities.model_entities import ModelFeature, ModelType
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
@ -18,6 +17,7 @@ from core.rag.index_processor.constant.built_in_field import BuiltInField
|
||||
from core.rag.index_processor.constant.index_type import IndexStructureType
|
||||
from core.rag.retrieval.retrieval_methods import RetrievalMethod
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from graphon.model_runtime.entities.model_entities import ModelFeature, ModelType
|
||||
from models import Account, TenantAccountRole
|
||||
from models.dataset import (
|
||||
ChildChunk,
|
||||
|
||||
@ -190,7 +190,7 @@ class TestDatasetServiceValidation:
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
DatasetService.check_dataset_model_setting(dataset)
|
||||
|
||||
model_manager_cls.return_value.get_model_instance.assert_called_once_with(
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.assert_called_once_with(
|
||||
tenant_id=dataset.tenant_id,
|
||||
provider=dataset.embedding_model_provider,
|
||||
model_type=ModelType.TEXT_EMBEDDING,
|
||||
@ -201,7 +201,7 @@ class TestDatasetServiceValidation:
|
||||
dataset = DatasetServiceUnitDataFactory.create_dataset_mock(indexing_technique="high_quality")
|
||||
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
|
||||
with pytest.raises(ValueError, match="No Embedding Model available"):
|
||||
DatasetService.check_dataset_model_setting(dataset)
|
||||
@ -210,14 +210,18 @@ class TestDatasetServiceValidation:
|
||||
dataset = DatasetServiceUnitDataFactory.create_dataset_mock(indexing_technique="high_quality")
|
||||
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = ProviderTokenNotInitError("token missing")
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = ProviderTokenNotInitError(
|
||||
"token missing"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="token missing"):
|
||||
with pytest.raises(ValueError, match="The dataset is unavailable, due to: token missing"):
|
||||
DatasetService.check_dataset_model_setting(dataset)
|
||||
|
||||
def test_check_embedding_model_setting_wraps_provider_token_error_description(self):
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = ProviderTokenNotInitError("provider setup")
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = ProviderTokenNotInitError(
|
||||
"provider setup"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="provider setup"):
|
||||
DatasetService.check_embedding_model_setting("tenant-1", "provider", "embedding-model")
|
||||
@ -226,7 +230,7 @@ class TestDatasetServiceValidation:
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
DatasetService.check_reranking_model_setting("tenant-1", "provider", "reranker")
|
||||
|
||||
model_manager_cls.return_value.get_model_instance.assert_called_once_with(
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
provider="provider",
|
||||
model_type=ModelType.RERANK,
|
||||
@ -235,7 +239,7 @@ class TestDatasetServiceValidation:
|
||||
|
||||
def test_check_reranking_model_setting_wraps_bad_request(self):
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
|
||||
with pytest.raises(ValueError, match="No Rerank Model available"):
|
||||
DatasetService.check_reranking_model_setting("tenant-1", "provider", "reranker")
|
||||
@ -251,7 +255,7 @@ class TestDatasetServiceValidation:
|
||||
)
|
||||
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.return_value = model_instance
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = model_instance
|
||||
|
||||
result = DatasetService.check_is_multimodal_model("tenant-1", "provider", "embedding-model")
|
||||
|
||||
@ -268,7 +272,7 @@ class TestDatasetServiceValidation:
|
||||
)
|
||||
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.return_value = model_instance
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = model_instance
|
||||
|
||||
result = DatasetService.check_is_multimodal_model("tenant-1", "provider", "embedding-model")
|
||||
|
||||
@ -284,14 +288,14 @@ class TestDatasetServiceValidation:
|
||||
)
|
||||
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.return_value = model_instance
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = model_instance
|
||||
|
||||
with pytest.raises(ValueError, match="Model schema not found"):
|
||||
DatasetService.check_is_multimodal_model("tenant-1", "provider", "embedding-model")
|
||||
|
||||
def test_check_is_multimodal_model_wraps_bad_request_error(self):
|
||||
with patch("services.dataset_service.ModelManager") as model_manager_cls:
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = LLMBadRequestError()
|
||||
|
||||
with pytest.raises(ValueError, match="No Model available"):
|
||||
DatasetService.check_is_multimodal_model("tenant-1", "provider", "embedding-model")
|
||||
@ -323,7 +327,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
patch.object(DatasetService, "check_embedding_model_setting") as check_embedding,
|
||||
):
|
||||
mock_db.session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
model_manager_cls.return_value.get_default_model_instance.return_value = default_embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.return_value = default_embedding_model
|
||||
|
||||
dataset = DatasetService.create_empty_dataset(
|
||||
tenant_id="tenant-1",
|
||||
@ -337,7 +341,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
assert dataset.embedding_model == "default-embedding"
|
||||
assert dataset.permission == DatasetPermissionEnum.ONLY_ME
|
||||
assert dataset.provider == "vendor"
|
||||
model_manager_cls.return_value.get_default_model_instance.assert_called_once_with(
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
model_type=ModelType.TEXT_EMBEDDING,
|
||||
)
|
||||
@ -365,7 +369,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
patch.object(DatasetService, "check_reranking_model_setting") as check_reranking,
|
||||
):
|
||||
mock_db.session.query.return_value.filter_by.return_value.first.return_value = None
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
dataset = DatasetService.create_empty_dataset(
|
||||
tenant_id="tenant-1",
|
||||
@ -804,7 +808,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
return_value=SimpleNamespace(id="binding-1"),
|
||||
),
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
DatasetService._configure_embedding_model_for_high_quality(
|
||||
{"embedding_model_provider": "provider", "embedding_model": "embedding-model"},
|
||||
@ -836,7 +840,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
patch("services.dataset_service.current_user", current_user),
|
||||
patch("services.dataset_service.ModelManager") as model_manager_cls,
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = error
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = error
|
||||
|
||||
with pytest.raises(ValueError, match=message):
|
||||
DatasetService._configure_embedding_model_for_high_quality(
|
||||
@ -967,7 +971,7 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
return_value=SimpleNamespace(id="binding-2"),
|
||||
),
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.return_value = SimpleNamespace(
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = SimpleNamespace(
|
||||
provider="provider-two",
|
||||
model_name="embedding-model-two",
|
||||
)
|
||||
@ -1002,7 +1006,9 @@ class TestDatasetServiceCreationAndUpdate:
|
||||
patch("services.dataset_service.current_user", current_user),
|
||||
patch("services.dataset_service.ModelManager") as model_manager_cls,
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = ProviderTokenNotInitError("token missing")
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = ProviderTokenNotInitError(
|
||||
"token missing"
|
||||
)
|
||||
|
||||
DatasetService._apply_new_embedding_settings(
|
||||
dataset,
|
||||
@ -1067,7 +1073,7 @@ class TestDatasetServiceRagPipelineSettings:
|
||||
return_value=SimpleNamespace(id="binding-1"),
|
||||
),
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
DatasetService.update_rag_pipeline_dataset_settings(session, dataset, knowledge_configuration)
|
||||
|
||||
@ -1161,7 +1167,7 @@ class TestDatasetServiceRagPipelineSettings:
|
||||
),
|
||||
patch("services.dataset_service.deal_dataset_index_update_task") as update_task,
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
DatasetService.update_rag_pipeline_dataset_settings(
|
||||
session,
|
||||
@ -1204,7 +1210,7 @@ class TestDatasetServiceRagPipelineSettings:
|
||||
),
|
||||
patch("services.dataset_service.deal_dataset_index_update_task") as update_task,
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.return_value = SimpleNamespace(
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = SimpleNamespace(
|
||||
provider="provider-two",
|
||||
model_name="embedding-model-two",
|
||||
)
|
||||
@ -1243,7 +1249,9 @@ class TestDatasetServiceRagPipelineSettings:
|
||||
patch("services.dataset_service.ModelManager") as model_manager_cls,
|
||||
patch("services.dataset_service.deal_dataset_index_update_task") as update_task,
|
||||
):
|
||||
model_manager_cls.return_value.get_model_instance.side_effect = ProviderTokenNotInitError("token missing")
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.side_effect = ProviderTokenNotInitError(
|
||||
"token missing"
|
||||
)
|
||||
|
||||
DatasetService.update_rag_pipeline_dataset_settings(
|
||||
session,
|
||||
|
||||
@ -1828,7 +1828,7 @@ class TestDocumentServiceSaveDocumentAdditionalBranches:
|
||||
) as get_binding,
|
||||
patch.object(DocumentService, "update_document_with_dataset_id", return_value=updated_document),
|
||||
):
|
||||
model_manager_cls.return_value.get_default_model_instance.return_value = SimpleNamespace(
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.return_value = SimpleNamespace(
|
||||
model_name="default-embedding",
|
||||
provider="default-provider",
|
||||
)
|
||||
@ -1880,7 +1880,7 @@ class TestDocumentServiceSaveDocumentAdditionalBranches:
|
||||
):
|
||||
DocumentService.save_document_with_dataset_id(dataset, knowledge_config, account_context)
|
||||
|
||||
model_manager_cls.return_value.get_default_model_instance.assert_not_called()
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.assert_not_called()
|
||||
get_binding.assert_called_once_with("explicit-provider", "explicit-model")
|
||||
assert dataset.embedding_model == "explicit-model"
|
||||
assert dataset.embedding_model_provider == "explicit-provider"
|
||||
|
||||
@ -9,6 +9,7 @@ from .dataset_service_test_helpers import (
|
||||
DocumentSegment,
|
||||
IndexStructureType,
|
||||
MagicMock,
|
||||
ModelType,
|
||||
SegmentService,
|
||||
SegmentUpdateArgs,
|
||||
SimpleNamespace,
|
||||
@ -459,7 +460,7 @@ class TestSegmentServiceMutations:
|
||||
patch("services.dataset_service.naive_utc_now", return_value="now"),
|
||||
):
|
||||
mock_redis.lock.return_value = _make_lock_context()
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
mock_db.session.query.return_value.where.return_value.scalar.return_value = 1
|
||||
vector_service.create_segments_vector.side_effect = RuntimeError("vector failed")
|
||||
|
||||
@ -571,7 +572,7 @@ class TestSegmentServiceMutations:
|
||||
patch("services.summary_index_service.SummaryIndexService.update_summary_for_segment") as update_summary,
|
||||
):
|
||||
mock_redis.get.return_value = None
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model_instance
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model_instance
|
||||
|
||||
processing_rule_query = MagicMock()
|
||||
processing_rule_query.where.return_value.first.return_value = processing_rule
|
||||
@ -618,7 +619,7 @@ class TestSegmentServiceMutations:
|
||||
) as generate_summary,
|
||||
):
|
||||
mock_redis.get.return_value = None
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
summary_query = MagicMock()
|
||||
summary_query.where.return_value.first.return_value = existing_summary
|
||||
@ -661,7 +662,7 @@ class TestSegmentServiceMutations:
|
||||
patch("services.summary_index_service.SummaryIndexService.update_summary_for_segment") as update_summary,
|
||||
):
|
||||
mock_redis.get.return_value = None
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
|
||||
summary_query = MagicMock()
|
||||
summary_query.where.return_value.first.return_value = existing_summary
|
||||
@ -900,7 +901,7 @@ class TestSegmentServiceAdditionalRegenerationBranches:
|
||||
patch("services.dataset_service.naive_utc_now", return_value="now"),
|
||||
):
|
||||
mock_redis.get.return_value = None
|
||||
model_manager_cls.return_value.get_model_instance.return_value = embedding_model
|
||||
model_manager_cls.for_tenant.return_value.get_model_instance.return_value = embedding_model
|
||||
summary_query = MagicMock()
|
||||
summary_query.where.return_value.first.return_value = None
|
||||
refreshed_query = MagicMock()
|
||||
@ -947,7 +948,7 @@ class TestSegmentServiceAdditionalRegenerationBranches:
|
||||
patch("services.summary_index_service.SummaryIndexService.update_summary_for_segment") as update_summary,
|
||||
):
|
||||
mock_redis.get.return_value = None
|
||||
model_manager_cls.return_value.get_default_model_instance.return_value = embedding_model_instance
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.return_value = embedding_model_instance
|
||||
update_summary.side_effect = RuntimeError("summary failed")
|
||||
|
||||
processing_rule_query = MagicMock()
|
||||
@ -966,9 +967,9 @@ class TestSegmentServiceAdditionalRegenerationBranches:
|
||||
)
|
||||
|
||||
assert result is refreshed_segment
|
||||
model_manager_cls.return_value.get_default_model_instance.assert_called_once_with(
|
||||
model_manager_cls.for_tenant.return_value.get_default_model_instance.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
model_type="text-embedding",
|
||||
model_type=ModelType.TEXT_EMBEDDING,
|
||||
)
|
||||
vector_service.generate_child_chunks.assert_called_once_with(
|
||||
segment,
|
||||
|
||||
@ -58,11 +58,10 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const sendEmail = async (email: string, isOrigin: boolean, token?: string) => {
|
||||
const sendEmail = async (email: string, token?: string) => {
|
||||
try {
|
||||
const res = await sendVerifyCode({
|
||||
email,
|
||||
phase: isOrigin ? 'old_email' : 'new_email',
|
||||
token,
|
||||
})
|
||||
startCount()
|
||||
@ -106,7 +105,6 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
|
||||
const sendCodeToOriginEmail = async () => {
|
||||
await sendEmail(
|
||||
email,
|
||||
true,
|
||||
)
|
||||
setStep(STEP.verifyOrigin)
|
||||
}
|
||||
@ -162,7 +160,6 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
|
||||
}
|
||||
await sendEmail(
|
||||
mail,
|
||||
false,
|
||||
stepToken,
|
||||
)
|
||||
setStep(STEP.verifyNew)
|
||||
|
||||
@ -372,7 +372,7 @@ export const submitDeleteAccountFeedback = (body: { feedback: string, email: str
|
||||
export const getDocDownloadUrl = (doc_name: string): Promise<{ url: string }> =>
|
||||
get<{ url: string }>('/compliance/download', { params: { doc_name } }, { silent: true })
|
||||
|
||||
export const sendVerifyCode = (body: { email: string, phase: string, token?: string }): Promise<CommonResponse & { data: string }> =>
|
||||
export const sendVerifyCode = (body: { email: string, token?: string }): Promise<CommonResponse & { data: string }> =>
|
||||
post<CommonResponse & { data: string }>('/account/change-email', { body })
|
||||
|
||||
export const verifyEmail = (body: { email: string, code: string, token: string }): Promise<CommonResponse & { is_valid: boolean, email: string, token: string }> =>
|
||||
|
||||
Reference in New Issue
Block a user