mirror of
https://github.com/langgenius/dify.git
synced 2026-04-19 18:27:27 +08:00
Compare commits
7 Commits
verify-ema
...
chore/depe
| Author | SHA1 | Date | |
|---|---|---|---|
| 457914ea84 | |||
| 2cb4d9f59d | |||
| 0eb233eb46 | |||
| 54e11c1a7e | |||
| 49f4ed5cd6 | |||
| 1b71dbed69 | |||
| 496baa9335 |
282
.github/workflows/dependabot-alert-to-feishu.yml
vendored
Normal file
282
.github/workflows/dependabot-alert-to-feishu.yml
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
name: Dependabot Alert to Feishu
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: read
|
||||
|
||||
jobs:
|
||||
notify-feishu:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate webhook secret
|
||||
env:
|
||||
FEISHU_WEBHOOK: ${{ secrets.FEISHU_WEBHOOK }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${FEISHU_WEBHOOK:-}" ]; then
|
||||
echo "FEISHU_WEBHOOK secret is not configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify Feishu from event or API polling
|
||||
env:
|
||||
FEISHU_WEBHOOK: ${{ secrets.FEISHU_WEBHOOK }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
MAX_ITEMS="${MAX_ITEMS:-10}"
|
||||
SUMMARY_MAX_LEN="${SUMMARY_MAX_LEN:-140}"
|
||||
|
||||
build_payload() {
|
||||
local alerts_json="$1"
|
||||
local alert_count critical_count high_count header_template
|
||||
local summary_line summary_line_2 summary_line_3 generated_at
|
||||
local stats_elements visible_items hidden_count table_rows_data
|
||||
|
||||
alert_count="$(echo "${alerts_json}" | jq 'length')"
|
||||
critical_count="$(echo "${alerts_json}" | jq '[.[] | select((.security_advisory.severity // "") == "critical")] | length')"
|
||||
high_count="$(echo "${alerts_json}" | jq '[.[] | select((.security_advisory.severity // "") == "high")] | length')"
|
||||
|
||||
header_template="orange"
|
||||
if [ "${critical_count}" -gt 0 ]; then
|
||||
header_template="red"
|
||||
fi
|
||||
|
||||
summary_line="🚨 嗨,这里是您的 EE&CE 企业级🐮🐴在线打工播报员~"
|
||||
summary_line_2="当前系统雷达已锁定一批**高风险依赖告警**,建议优先处理,不然它们可能比 KPI 先“爆炸”💥"
|
||||
summary_line_3=$'📌 已为您智能筛选:\n仅展示 **未分配负责人的 High / Critical 告警**\n(也就是说——没人背锅,但锅已经烧起来了🔥)\n\n请尽快认领处理,拯救系统于水火之中 🙏'
|
||||
generated_at="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
|
||||
stats_elements="$(jq -n \
|
||||
--argjson total "${alert_count}" \
|
||||
--argjson critical "${critical_count}" \
|
||||
--argjson high "${high_count}" '
|
||||
[
|
||||
{
|
||||
tag: "column_set",
|
||||
flex_mode: "trisect",
|
||||
horizontal_spacing: "small",
|
||||
columns: [
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**待处理告警**\n" + ($total|tostring)}]
|
||||
},
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**🔴 严重风险**\n" + ($critical|tostring)}]
|
||||
},
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**🟠 高风险**\n" + ($high|tostring)}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]')"
|
||||
|
||||
if [ "${alert_count}" -eq 0 ]; then
|
||||
jq -n \
|
||||
--arg title "Dependabot Security Alerts" \
|
||||
--arg subtitle "${REPOSITORY}" \
|
||||
--arg summary "🚨 嗨,这里是您的 EE&CE 企业级🐮🐴在线打工播报员~" \
|
||||
--arg summary2 "当前没有待处理的高风险依赖告警,继续保持,今天可以安心下班。" \
|
||||
--arg summary3 $'📌 已为您智能筛选:\n仅展示 **未分配负责人的 High / Critical 告警**\n(当前结果为空,系统暂时平稳)' \
|
||||
--arg generatedAt "${generated_at}" '
|
||||
{
|
||||
msg_type: "interactive",
|
||||
card: {
|
||||
schema: "2.0",
|
||||
config: {wide_screen_mode: true},
|
||||
header: {
|
||||
template: "green",
|
||||
title: {tag: "plain_text", content: $title},
|
||||
subtitle: {tag: "plain_text", content: $subtitle}
|
||||
},
|
||||
body: {
|
||||
elements: [
|
||||
{tag: "markdown", content: $summary},
|
||||
{tag: "markdown", content: $summary2},
|
||||
{tag: "markdown", content: $summary3},
|
||||
{
|
||||
tag: "column_set",
|
||||
flex_mode: "trisect",
|
||||
horizontal_spacing: "small",
|
||||
columns: [
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**待处理告警**\n0"}]
|
||||
},
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**🔴 严重风险**\n0"}]
|
||||
},
|
||||
{
|
||||
tag: "column",
|
||||
width: "weighted",
|
||||
weight: 1,
|
||||
padding: "8px",
|
||||
background_style: "grey",
|
||||
elements: [{tag: "markdown", content: "**🟠 高风险**\n0"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
text: {
|
||||
tag: "plain_text",
|
||||
content: ("通知时间:" + $generatedAt),
|
||||
text_color: "grey",
|
||||
text_align: "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
visible_items="$(echo "${alerts_json}" | jq --argjson max "${MAX_ITEMS}" '.[:$max]')"
|
||||
hidden_count="$(echo "${alerts_json}" | jq --argjson max "${MAX_ITEMS}" 'if length > $max then length - $max else 0 end')"
|
||||
|
||||
table_rows_data="$(echo "${visible_items}" | jq -c \
|
||||
--argjson maxLen "${SUMMARY_MAX_LEN}" '
|
||||
map(
|
||||
. as $a |
|
||||
($a.number // "unknown") as $number |
|
||||
($a.security_advisory.severity // "unknown") as $severity |
|
||||
($a.dependency.package.name // "unknown") as $package |
|
||||
($a.security_advisory.summary // "N/A") as $summary |
|
||||
($a.html_url // "") as $url |
|
||||
(
|
||||
if ($summary | length) > $maxLen
|
||||
then ($summary[0:$maxLen] + "...")
|
||||
else $summary
|
||||
end
|
||||
) as $summaryShort |
|
||||
{
|
||||
level: (if $severity == "critical" then "🔴 critical" else "🟠 high" end),
|
||||
alert_id: ("#" + ($number|tostring)),
|
||||
package: ("`" + $package + "`"),
|
||||
details: ($summaryShort + "\n[View alert](" + $url + ")")
|
||||
}
|
||||
)')"
|
||||
|
||||
jq -n \
|
||||
--arg title "Dependabot Security Alerts" \
|
||||
--arg subtitle "${REPOSITORY}" \
|
||||
--arg summary "${summary_line}" \
|
||||
--arg summary2 "${summary_line_2}" \
|
||||
--arg summary3 "${summary_line_3}" \
|
||||
--arg generatedAt "${generated_at}" \
|
||||
--arg headerTemplate "${header_template}" \
|
||||
--argjson stats "${stats_elements}" \
|
||||
--argjson tableRows "${table_rows_data}" \
|
||||
--argjson hidden "${hidden_count}" '
|
||||
{
|
||||
msg_type: "interactive",
|
||||
card: {
|
||||
schema: "2.0",
|
||||
config: {wide_screen_mode: true},
|
||||
header: {
|
||||
template: $headerTemplate,
|
||||
title: {tag: "plain_text", content: $title},
|
||||
subtitle: {tag: "plain_text", content: $subtitle}
|
||||
},
|
||||
body: {
|
||||
elements:
|
||||
(
|
||||
[
|
||||
{tag: "markdown", content: $summary},
|
||||
{tag: "markdown", content: $summary2},
|
||||
{tag: "markdown", content: $summary3},
|
||||
{tag: "hr"},
|
||||
$stats[0],
|
||||
{tag: "hr"},
|
||||
{
|
||||
tag: "table",
|
||||
page_size: 10,
|
||||
row_height: "auto",
|
||||
header_style: {
|
||||
text_align: "left",
|
||||
text_size: "normal",
|
||||
text_color: "grey",
|
||||
bold: true,
|
||||
lines: 1
|
||||
},
|
||||
columns: [
|
||||
{name: "level", display_name: "Level", data_type: "text", width: "120px"},
|
||||
{name: "alert_id", display_name: "ID", data_type: "text", width: "90px"},
|
||||
{name: "package", display_name: "Package", data_type: "lark_md", width: "140px"},
|
||||
{name: "details", display_name: "Summary / Link", data_type: "lark_md", width: "auto"}
|
||||
],
|
||||
rows: $tableRows
|
||||
}
|
||||
]
|
||||
+ (
|
||||
if $hidden > 0
|
||||
then [
|
||||
{tag: "hr"},
|
||||
{tag: "markdown", content: ("还有 " + ($hidden|tostring) + " 条告警未展示,请点击仓库安全页查看全部。")}
|
||||
]
|
||||
else []
|
||||
end
|
||||
)
|
||||
+ [
|
||||
{
|
||||
tag: "div",
|
||||
text: {
|
||||
tag: "plain_text",
|
||||
content: ("通知时间:" + $generatedAt),
|
||||
text_color: "grey",
|
||||
text_align: "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
api_url="https://api.github.com/repos/${REPOSITORY}/dependabot/alerts?state=open&severity=high,critical&assignee=none&per_page=100"
|
||||
alerts_json="$(curl -sS -f -L \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "X-GitHub-Api-Version: 2026-03-10" \
|
||||
"$api_url")"
|
||||
|
||||
filtered_json="$(echo "${alerts_json}" | jq '[.[] | select(((.security_advisory.severity // "") == "high" or (.security_advisory.severity // "") == "critical") and ((.assignees | length) == 0))]')"
|
||||
alert_count="$(echo "${filtered_json}" | jq 'length')"
|
||||
echo "Filtered dependabot alerts count: ${alert_count}"
|
||||
|
||||
payload="$(build_payload "${filtered_json}")"
|
||||
curl -sS -f -X POST "${FEISHU_WEBHOOK}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "${payload}"
|
||||
@ -134,6 +134,7 @@ class EducationAutocompleteQuery(BaseModel):
|
||||
class ChangeEmailSendPayload(BaseModel):
|
||||
email: EmailStr
|
||||
language: str | None = None
|
||||
phase: str | None = None
|
||||
token: str | None = None
|
||||
|
||||
|
||||
@ -547,17 +548,13 @@ class ChangeEmailSendEmailApi(Resource):
|
||||
account = None
|
||||
user_email = None
|
||||
email_for_sending = args.email.lower()
|
||||
send_phase = AccountService.CHANGE_EMAIL_PHASE_OLD
|
||||
if args.token is not None:
|
||||
send_phase = AccountService.CHANGE_EMAIL_PHASE_NEW
|
||||
if args.phase is not None and args.phase == "new_email":
|
||||
if args.token is None:
|
||||
raise InvalidTokenError()
|
||||
|
||||
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():
|
||||
@ -577,7 +574,7 @@ class ChangeEmailSendEmailApi(Resource):
|
||||
email=email_for_sending,
|
||||
old_email=user_email,
|
||||
language=language,
|
||||
phase=send_phase,
|
||||
phase=args.phase,
|
||||
)
|
||||
return {"result": "success", "data": token}
|
||||
|
||||
@ -612,26 +609,12 @@ 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={AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: refreshed_phase},
|
||||
user_email, code=args.code, old_email=token_data.get("old_email"), additional_data={}
|
||||
)
|
||||
|
||||
AccountService.reset_change_email_error_rate_limit(user_email)
|
||||
@ -661,22 +644,13 @@ class ChangeEmailResetApi(Resource):
|
||||
if not reset_data:
|
||||
raise InvalidTokenError()
|
||||
|
||||
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()
|
||||
AccountService.revoke_change_email_token(args.token)
|
||||
|
||||
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,7 +4,6 @@ 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
|
||||
|
||||
@ -91,25 +90,12 @@ 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(
|
||||
@ -566,20 +552,13 @@ 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,
|
||||
additional_data={cls.CHANGE_EMAIL_TOKEN_PHASE_KEY: phase},
|
||||
)
|
||||
code, token = cls.generate_change_email_token(account_email, account, old_email=old_email)
|
||||
|
||||
send_change_mail_task.delay(
|
||||
language=language,
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from uuid import uuid4
|
||||
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserAction
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.account import Account, Tenant, TenantAccountJoin
|
||||
from models.enums import ConversationFromSource, InvokeFrom
|
||||
from models.execution_extra_content import HumanInputContent
|
||||
@ -117,7 +118,7 @@ def create_human_input_message_fixture(db_session) -> HumanInputMessageFixture:
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id=action_id, title=action_text)],
|
||||
rendered_content="Rendered block",
|
||||
expiration_time=datetime.utcnow() + timedelta(days=1),
|
||||
expiration_time=naive_utc_now() + timedelta(days=1),
|
||||
node_title=node_title,
|
||||
display_in_ui=True,
|
||||
)
|
||||
@ -129,7 +130,7 @@ def create_human_input_message_fixture(db_session) -> HumanInputMessageFixture:
|
||||
form_definition=form_definition.model_dump_json(),
|
||||
rendered_content="Rendered block",
|
||||
status=HumanInputFormStatus.SUBMITTED,
|
||||
expiration_time=datetime.utcnow() + timedelta(days=1),
|
||||
expiration_time=naive_utc_now() + timedelta(days=1),
|
||||
selected_action_id=action_id,
|
||||
)
|
||||
db_session.add(form)
|
||||
|
||||
@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from uuid import uuid4
|
||||
|
||||
@ -17,6 +17,7 @@ from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserAction
|
||||
from graphon.nodes.human_input.enums import HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
|
||||
from models.enums import ConversationFromSource, InvokeFrom
|
||||
from models.execution_extra_content import ExecutionExtraContent, HumanInputContent
|
||||
@ -174,7 +175,7 @@ def _create_submitted_form(
|
||||
action_title: str = "Approve",
|
||||
node_title: str = "Approval",
|
||||
) -> HumanInputForm:
|
||||
expiration_time = datetime.utcnow() + timedelta(days=1)
|
||||
expiration_time = naive_utc_now() + timedelta(days=1)
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[],
|
||||
@ -207,7 +208,7 @@ def _create_waiting_form(
|
||||
workflow_run_id: str,
|
||||
default_values: dict | None = None,
|
||||
) -> HumanInputForm:
|
||||
expiration_time = datetime.utcnow() + timedelta(days=1)
|
||||
expiration_time = naive_utc_now() + timedelta(days=1)
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[],
|
||||
|
||||
@ -954,16 +954,6 @@ 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(
|
||||
|
||||
@ -26,6 +26,7 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_workflow import (
|
||||
RagPipelineWorkflowLastRunApi,
|
||||
)
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from services.errors.app import IsDraftWorkflowError, WorkflowHashNotEqualError, WorkflowNotFoundError
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
|
||||
@ -372,7 +373,7 @@ class TestPublishedPipelineApis:
|
||||
|
||||
workflow = MagicMock(
|
||||
id="w1",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
session = MagicMock()
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -25,6 +24,7 @@ from controllers.console.datasets.error import (
|
||||
)
|
||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
from core.rag.index_processor.constant.index_type import IndexStructureType
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.dataset import ChildChunk, DocumentSegment
|
||||
from models.model import UploadFile
|
||||
|
||||
@ -54,8 +54,8 @@ def _segment():
|
||||
disabled_by=None,
|
||||
status="normal",
|
||||
created_by="u1",
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
updated_at=naive_utc_now(),
|
||||
updated_by="u1",
|
||||
indexing_at=None,
|
||||
completed_at=None,
|
||||
|
||||
@ -4,7 +4,6 @@ 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,
|
||||
@ -53,7 +52,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_infer_new_email_phase_from_token(
|
||||
def test_should_normalize_new_email_phase(
|
||||
self,
|
||||
mock_features,
|
||||
mock_csrf,
|
||||
@ -69,16 +68,13 @@ 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",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED,
|
||||
}
|
||||
mock_get_change_data.return_value = {"email": "current@example.com"}
|
||||
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", "token": "token-123"},
|
||||
json={"email": "New@Example.com", "language": "en-US", "phase": "new_email", "token": "token-123"},
|
||||
):
|
||||
_set_logged_in_user(_build_account("tester@example.com", "tester"))
|
||||
response = ChangeEmailSendEmailApi().post()
|
||||
@ -95,107 +91,6 @@ 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")
|
||||
@ -227,12 +122,7 @@ 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",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD,
|
||||
}
|
||||
mock_get_data.return_value = {"email": "user@example.com", "code": "1234", "old_email": "old@example.com"}
|
||||
mock_generate_token.return_value = (None, "new-token")
|
||||
|
||||
with app.test_request_context(
|
||||
@ -248,76 +138,11 @@ 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={
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED
|
||||
},
|
||||
"user@example.com", code="1234", old_email="old@example.com", additional_data={}
|
||||
)
|
||||
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")
|
||||
@ -350,11 +175,7 @@ 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",
|
||||
"email": "new@example.com",
|
||||
AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED,
|
||||
}
|
||||
mock_get_data.return_value = {"old_email": "OLD@example.com"}
|
||||
mock_account_after_update = _build_account("new@example.com", "acc3-updated")
|
||||
mock_update_account.return_value = mock_account_after_update
|
||||
|
||||
@ -373,106 +194,6 @@ 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")
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -26,6 +25,7 @@ from controllers.console.workspace.workspace import (
|
||||
WorkspacePermissionApi,
|
||||
)
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.account import TenantStatus
|
||||
|
||||
|
||||
@ -44,13 +44,13 @@ class TestTenantListApi:
|
||||
id="t1",
|
||||
name="Tenant 1",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
tenant2 = MagicMock(
|
||||
id="t2",
|
||||
name="Tenant 2",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
with (
|
||||
@ -97,13 +97,13 @@ class TestTenantListApi:
|
||||
id="t1",
|
||||
name="Tenant 1",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
tenant2 = MagicMock(
|
||||
id="t2",
|
||||
name="Tenant 2",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
features_t2 = MagicMock()
|
||||
@ -152,13 +152,13 @@ class TestTenantListApi:
|
||||
id="t1",
|
||||
name="Tenant 1",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
tenant2 = MagicMock(
|
||||
id="t2",
|
||||
name="Tenant 2",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
features = MagicMock()
|
||||
@ -204,7 +204,7 @@ class TestTenantListApi:
|
||||
id="t1",
|
||||
name="Tenant",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
features = MagicMock()
|
||||
@ -243,13 +243,13 @@ class TestTenantListApi:
|
||||
id="t1",
|
||||
name="Tenant 1",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
tenant2 = MagicMock(
|
||||
id="t2",
|
||||
name="Tenant 2",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
with (
|
||||
@ -305,7 +305,7 @@ class TestWorkspaceListApi:
|
||||
api = WorkspaceListApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
tenant = MagicMock(id="t1", name="T", status="active", created_at=datetime.utcnow())
|
||||
tenant = MagicMock(id="t1", name="T", status="active", created_at=naive_utc_now())
|
||||
|
||||
paginate_result = MagicMock(
|
||||
items=[tenant],
|
||||
@ -331,7 +331,7 @@ class TestWorkspaceListApi:
|
||||
id="t1",
|
||||
name="T",
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
)
|
||||
|
||||
paginate_result = MagicMock(
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@ -45,6 +44,7 @@ from core.base.tts.app_generator_tts_publisher import AudioTrunk
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.enums import MessageStatus
|
||||
from models.model import AppMode, EndUser
|
||||
from tests.workflow_test_utils import build_test_variable_pool
|
||||
@ -76,7 +76,7 @@ def _make_pipeline():
|
||||
message = SimpleNamespace(
|
||||
id="message-id",
|
||||
query="hello",
|
||||
created_at=datetime.utcnow(),
|
||||
created_at=naive_utc_now(),
|
||||
status=MessageStatus.NORMAL,
|
||||
answer="",
|
||||
)
|
||||
@ -257,7 +257,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
iter_next = QueueIterationNextEvent(
|
||||
@ -273,7 +273,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
loop_start = QueueLoopStartEvent(
|
||||
@ -281,7 +281,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
loop_next = QueueLoopNextEvent(
|
||||
@ -297,7 +297,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
|
||||
@ -360,7 +360,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_execution_id="exec",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
@ -370,7 +370,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_execution_id="exec",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
@ -473,7 +473,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="title",
|
||||
expiration_time=datetime.utcnow(),
|
||||
expiration_time=naive_utc_now(),
|
||||
)
|
||||
|
||||
assert list(pipeline._handle_human_input_form_filled_event(filled_event)) == ["filled"]
|
||||
@ -591,7 +591,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_execution_id="exec",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@ -47,6 +46,7 @@ from core.base.tts.app_generator_tts_publisher import AudioTrunk
|
||||
from core.workflow.system_variables import build_system_variables, system_variables_to_mapping
|
||||
from graphon.enums import BuiltinNodeTypes, WorkflowExecutionStatus
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.enums import CreatorUserRole
|
||||
from models.model import AppMode, EndUser
|
||||
from tests.workflow_test_utils import build_test_variable_pool
|
||||
@ -192,7 +192,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_execution_id="exec",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
@ -245,7 +245,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_execution_id="exec",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
@ -303,7 +303,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
iter_next = QueueIterationNextEvent(
|
||||
@ -319,7 +319,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
loop_start = QueueLoopStartEvent(
|
||||
@ -327,7 +327,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
loop_next = QueueLoopNextEvent(
|
||||
@ -343,7 +343,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="LLM",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_index=1,
|
||||
)
|
||||
filled_event = QueueHumanInputFormFilledEvent(
|
||||
@ -359,7 +359,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_title="title",
|
||||
expiration_time=datetime.utcnow(),
|
||||
expiration_time=naive_utc_now(),
|
||||
)
|
||||
agent_event = QueueAgentLogEvent(
|
||||
id="log",
|
||||
@ -648,7 +648,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_title="title",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_run_index=1,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
provider_type="provider",
|
||||
provider_id="provider-id",
|
||||
error="error",
|
||||
@ -660,7 +660,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_title="title",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
node_run_index=1,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
provider_type="provider",
|
||||
provider_id="provider-id",
|
||||
)
|
||||
@ -685,7 +685,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_execution_id="exec-id",
|
||||
node_id="node",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
inputs={},
|
||||
outputs={},
|
||||
process_data={},
|
||||
@ -836,7 +836,7 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
node_id="node-id",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
in_loop_id="loop-id",
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
process_data={"k": "v"},
|
||||
outputs={"out": 1},
|
||||
)
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock
|
||||
|
||||
from core.app.layers.conversation_variable_persist_layer import ConversationVariablePersistenceLayer
|
||||
@ -12,6 +11,7 @@ from graphon.node_events import NodeRunResult
|
||||
from graphon.runtime.graph_runtime_state_protocol import ReadOnlyGraphRuntimeState
|
||||
from graphon.variables import StringVariable
|
||||
from graphon.variables.segments import Segment, StringSegment
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
|
||||
class MockReadOnlyVariablePool:
|
||||
@ -48,7 +48,7 @@ def _build_node_run_succeeded_event() -> NodeRunSucceededEvent:
|
||||
id="node-exec-id",
|
||||
node_id="assigner",
|
||||
node_type=BuiltinNodeTypes.LLM,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_result=NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
outputs={},
|
||||
|
||||
@ -274,7 +274,7 @@ def _make_form_definition() -> str:
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
rendered_content="<p>hello</p>",
|
||||
expiration_time=datetime.utcnow(),
|
||||
expiration_time=naive_utc_now(),
|
||||
).model_dump_json()
|
||||
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import queue
|
||||
from datetime import datetime
|
||||
|
||||
from graphon.enums import BuiltinNodeTypes, WorkflowNodeExecutionStatus
|
||||
from graphon.graph_engine.orchestration.dispatcher import Dispatcher
|
||||
from graphon.graph_events import NodeRunSucceededEvent
|
||||
from graphon.node_events import NodeRunResult
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
|
||||
class StubExecutionCoordinator:
|
||||
@ -52,7 +52,7 @@ def test_dispatcher_drains_events_when_paused() -> None:
|
||||
id="exec-1",
|
||||
node_id="node-1",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
start_at=datetime.utcnow(),
|
||||
start_at=naive_utc_now(),
|
||||
node_run_result=NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED),
|
||||
)
|
||||
event_queue.put(event)
|
||||
|
||||
@ -6,6 +6,7 @@ from typing import Any
|
||||
|
||||
from graphon.nodes.human_input.entities import FormInput
|
||||
from graphon.nodes.human_input.enums import TimeoutUnit
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
|
||||
# Exceptions
|
||||
@ -49,7 +50,7 @@ class HumanInputForm:
|
||||
timeout: int
|
||||
timeout_unit: TimeoutUnit
|
||||
form_token: str | None = None
|
||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||
created_at: datetime = field(default_factory=naive_utc_now)
|
||||
expires_at: datetime | None = None
|
||||
submitted_at: datetime | None = None
|
||||
submitted_data: dict[str, Any] | None = None
|
||||
@ -61,7 +62,7 @@ class HumanInputForm:
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
return self.expires_at is not None and datetime.utcnow() > self.expires_at
|
||||
return self.expires_at is not None and naive_utc_now() > self.expires_at
|
||||
|
||||
@property
|
||||
def is_submitted(self) -> bool:
|
||||
@ -70,7 +71,7 @@ class HumanInputForm:
|
||||
def mark_submitted(self, inputs: dict[str, Any], action: str) -> None:
|
||||
self.submitted_data = inputs
|
||||
self.submitted_action = action
|
||||
self.submitted_at = datetime.utcnow()
|
||||
self.submitted_at = naive_utc_now()
|
||||
|
||||
def submit(self, inputs: dict[str, Any], action: str) -> None:
|
||||
self.mark_submitted(inputs, action)
|
||||
@ -107,7 +108,7 @@ class FormSubmissionData:
|
||||
form_id: str
|
||||
inputs: dict[str, Any]
|
||||
action: str
|
||||
submitted_at: datetime = field(default_factory=datetime.utcnow)
|
||||
submitted_at: datetime = field(default_factory=naive_utc_now)
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, form_id: str, request: FormSubmissionRequest) -> FormSubmissionData: # type: ignore
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
Unit tests for FormService.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@ -142,7 +142,7 @@ class TestFormService:
|
||||
|
||||
# Manually expire the form by modifying expiry time
|
||||
form = form_service.get_form_by_id("form-123")
|
||||
form.expires_at = datetime.utcnow() - timedelta(hours=1)
|
||||
form.expires_at = naive_utc_now() - timedelta(hours=1)
|
||||
form_service.repository.save(form)
|
||||
|
||||
# Should raise FormExpiredError
|
||||
@ -227,7 +227,7 @@ class TestFormService:
|
||||
|
||||
# Manually expire the form
|
||||
form = form_service.get_form_by_id("form-123")
|
||||
form.expires_at = datetime.utcnow() - timedelta(hours=1)
|
||||
form.expires_at = naive_utc_now() - timedelta(hours=1)
|
||||
form_service.repository.save(form)
|
||||
|
||||
# Try to submit expired form
|
||||
|
||||
@ -14,6 +14,7 @@ from graphon.nodes.human_input.enums import (
|
||||
FormInputType,
|
||||
TimeoutUnit,
|
||||
)
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
from .support import FormSubmissionData, FormSubmissionRequest, HumanInputForm
|
||||
|
||||
@ -83,7 +84,7 @@ class TestHumanInputForm:
|
||||
def test_form_expiry_property_expired(self, sample_form_data):
|
||||
"""Test is_expired property for expired form."""
|
||||
# Create form with past expiry
|
||||
past_time = datetime.utcnow() - timedelta(hours=1)
|
||||
past_time = naive_utc_now() - timedelta(hours=1)
|
||||
sample_form_data["created_at"] = past_time
|
||||
|
||||
form = HumanInputForm(**sample_form_data)
|
||||
@ -111,9 +112,9 @@ class TestHumanInputForm:
|
||||
"""Test form submit method."""
|
||||
form = HumanInputForm(**sample_form_data)
|
||||
|
||||
submission_time_before = datetime.utcnow()
|
||||
submission_time_before = naive_utc_now()
|
||||
form.submit({"input": "test value"}, "submit")
|
||||
submission_time_after = datetime.utcnow()
|
||||
submission_time_after = naive_utc_now()
|
||||
|
||||
assert form.is_submitted
|
||||
assert form.submitted_data == {"input": "test value"}
|
||||
@ -213,11 +214,11 @@ class TestFormSubmissionData:
|
||||
|
||||
def test_submission_data_timestamps(self):
|
||||
"""Test submission data timestamp handling."""
|
||||
before_time = datetime.utcnow()
|
||||
before_time = naive_utc_now()
|
||||
|
||||
submission_data = FormSubmissionData(form_id="form-123", inputs={"test": "value"}, action="submit")
|
||||
|
||||
after_time = datetime.utcnow()
|
||||
after_time = naive_utc_now()
|
||||
|
||||
assert before_time <= submission_data.submitted_at <= after_time
|
||||
|
||||
|
||||
@ -6,13 +6,14 @@ Tests are organized by functionality and include edge cases, error handling,
|
||||
and both positive and negative test scenarios.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, Mock, create_autospec, patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import asc, desc
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
from models import Account, ConversationVariable
|
||||
from models.enums import ConversationFromSource
|
||||
@ -122,8 +123,8 @@ class ConversationServiceTestDataFactory:
|
||||
conversation.is_deleted = kwargs.get("is_deleted", False)
|
||||
conversation.name = kwargs.get("name", "Test Conversation")
|
||||
conversation.status = kwargs.get("status", "normal")
|
||||
conversation.created_at = kwargs.get("created_at", datetime.utcnow())
|
||||
conversation.updated_at = kwargs.get("updated_at", datetime.utcnow())
|
||||
conversation.created_at = kwargs.get("created_at", naive_utc_now())
|
||||
conversation.updated_at = kwargs.get("updated_at", naive_utc_now())
|
||||
for key, value in kwargs.items():
|
||||
setattr(conversation, key, value)
|
||||
return conversation
|
||||
@ -152,7 +153,7 @@ class ConversationServiceTestDataFactory:
|
||||
message.conversation_id = conversation_id
|
||||
message.app_id = app_id
|
||||
message.query = kwargs.get("query", "Test message content")
|
||||
message.created_at = kwargs.get("created_at", datetime.utcnow())
|
||||
message.created_at = kwargs.get("created_at", naive_utc_now())
|
||||
for key, value in kwargs.items():
|
||||
setattr(message, key, value)
|
||||
return message
|
||||
@ -181,8 +182,8 @@ class ConversationServiceTestDataFactory:
|
||||
variable.conversation_id = conversation_id
|
||||
variable.app_id = app_id
|
||||
variable.data = {"name": kwargs.get("name", "test_var"), "value": kwargs.get("value", "test_value")}
|
||||
variable.created_at = kwargs.get("created_at", datetime.utcnow())
|
||||
variable.updated_at = kwargs.get("updated_at", datetime.utcnow())
|
||||
variable.created_at = kwargs.get("created_at", naive_utc_now())
|
||||
variable.updated_at = kwargs.get("updated_at", naive_utc_now())
|
||||
|
||||
# Mock to_variable method
|
||||
mock_variable = Mock()
|
||||
@ -302,7 +303,7 @@ class TestConversationServiceHelpers:
|
||||
"""
|
||||
# Arrange
|
||||
mock_conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
mock_conversation.updated_at = datetime.utcnow()
|
||||
mock_conversation.updated_at = naive_utc_now()
|
||||
|
||||
# Act
|
||||
condition = ConversationService._build_filter_condition(
|
||||
@ -323,7 +324,7 @@ class TestConversationServiceHelpers:
|
||||
"""
|
||||
# Arrange
|
||||
mock_conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
mock_conversation.created_at = datetime.utcnow()
|
||||
mock_conversation.created_at = naive_utc_now()
|
||||
|
||||
# Act
|
||||
condition = ConversationService._build_filter_condition(
|
||||
@ -668,9 +669,9 @@ class TestConversationServiceConversationalVariable:
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
last_variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(
|
||||
created_at=datetime.utcnow() - timedelta(hours=1)
|
||||
created_at=naive_utc_now() - timedelta(hours=1)
|
||||
)
|
||||
variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(created_at=datetime.utcnow())
|
||||
variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(created_at=naive_utc_now())
|
||||
|
||||
mock_session.scalar.return_value = last_variable
|
||||
mock_session.scalars.return_value.all.return_value = [variable]
|
||||
|
||||
@ -15,6 +15,7 @@ from graphon.nodes.human_input.entities import (
|
||||
UserAction,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import FormInputType, HumanInputFormKind, HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.human_input import RecipientType
|
||||
from services.human_input_service import (
|
||||
Form,
|
||||
@ -51,11 +52,11 @@ def sample_form_record():
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
rendered_content="<p>hello</p>",
|
||||
expiration_time=datetime.utcnow() + timedelta(hours=1),
|
||||
expiration_time=naive_utc_now() + timedelta(hours=1),
|
||||
),
|
||||
rendered_content="<p>hello</p>",
|
||||
created_at=datetime.utcnow(),
|
||||
expiration_time=datetime.utcnow() + timedelta(hours=1),
|
||||
created_at=naive_utc_now(),
|
||||
expiration_time=naive_utc_now() + timedelta(hours=1),
|
||||
status=HumanInputFormStatus.WAITING,
|
||||
selected_action_id=None,
|
||||
submitted_data=None,
|
||||
@ -101,8 +102,8 @@ def test_ensure_form_active_respects_global_timeout(monkeypatch, sample_form_rec
|
||||
service = HumanInputService(session_factory)
|
||||
expired_record = dataclasses.replace(
|
||||
sample_form_record,
|
||||
created_at=datetime.utcnow() - timedelta(hours=2),
|
||||
expiration_time=datetime.utcnow() + timedelta(hours=2),
|
||||
created_at=naive_utc_now() - timedelta(hours=2),
|
||||
expiration_time=naive_utc_now() + timedelta(hours=2),
|
||||
)
|
||||
monkeypatch.setattr(human_input_service_module.dify_config, "HUMAN_INPUT_GLOBAL_TIMEOUT_SECONDS", 3600)
|
||||
|
||||
@ -391,7 +392,7 @@ def test_ensure_form_active_errors(sample_form_record, mock_session_factory):
|
||||
service = HumanInputService(session_factory)
|
||||
|
||||
# Submitted
|
||||
submitted_record = dataclasses.replace(sample_form_record, submitted_at=datetime.utcnow())
|
||||
submitted_record = dataclasses.replace(sample_form_record, submitted_at=naive_utc_now())
|
||||
with pytest.raises(human_input_service_module.FormSubmittedError):
|
||||
service.ensure_form_active(Form(submitted_record))
|
||||
|
||||
@ -402,7 +403,7 @@ def test_ensure_form_active_errors(sample_form_record, mock_session_factory):
|
||||
|
||||
# Expired time
|
||||
expired_time_record = dataclasses.replace(
|
||||
sample_form_record, expiration_time=datetime.utcnow() - timedelta(minutes=1)
|
||||
sample_form_record, expiration_time=naive_utc_now() - timedelta(minutes=1)
|
||||
)
|
||||
with pytest.raises(FormExpiredError):
|
||||
service.ensure_form_active(Form(expired_time_record))
|
||||
@ -411,7 +412,7 @@ def test_ensure_form_active_errors(sample_form_record, mock_session_factory):
|
||||
def test_ensure_not_submitted_raises(sample_form_record, mock_session_factory):
|
||||
session_factory, _ = mock_session_factory
|
||||
service = HumanInputService(session_factory)
|
||||
submitted_record = dataclasses.replace(sample_form_record, submitted_at=datetime.utcnow())
|
||||
submitted_record = dataclasses.replace(sample_form_record, submitted_at=naive_utc_now())
|
||||
|
||||
with pytest.raises(human_input_service_module.FormSubmittedError):
|
||||
service._ensure_not_submitted(Form(submitted_record))
|
||||
|
||||
@ -58,10 +58,11 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const sendEmail = async (email: string, token?: string) => {
|
||||
const sendEmail = async (email: string, isOrigin: boolean, token?: string) => {
|
||||
try {
|
||||
const res = await sendVerifyCode({
|
||||
email,
|
||||
phase: isOrigin ? 'old_email' : 'new_email',
|
||||
token,
|
||||
})
|
||||
startCount()
|
||||
@ -105,6 +106,7 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
|
||||
const sendCodeToOriginEmail = async () => {
|
||||
await sendEmail(
|
||||
email,
|
||||
true,
|
||||
)
|
||||
setStep(STEP.verifyOrigin)
|
||||
}
|
||||
@ -160,6 +162,7 @@ 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, token?: string }): Promise<CommonResponse & { data: string }> =>
|
||||
export const sendVerifyCode = (body: { email: string, phase: 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