Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion backend/apps/agent_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,15 @@ async def list_published_agents_api(
"""
try:
user_id, tenant_id, _ = get_current_user_info(authorization, request)
return await list_published_agents_impl(tenant_id=tenant_id, user_id=user_id)
agent_list = await list_published_agents_impl(
tenant_id=tenant_id, user_id=user_id
)
if tenant_id != ASSET_OWNER_TENANT_ID:
asset_agent_list = await list_published_agents_impl(
tenant_id=ASSET_OWNER_TENANT_ID, user_id=user_id
)
return agent_list + asset_agent_list
return agent_list
except Exception as e:
logger.error(f"Published agents list error: {str(e)}")
raise HTTPException(
Expand Down
12 changes: 9 additions & 3 deletions backend/apps/user_management_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from supabase_auth.errors import AuthApiError, AuthWeakPasswordError

from consts.const import ASSET_OWNER_SIGNUP_USE_OAUTH_DETAIL
from consts.model import UserSignInRequest, UserSignUpRequest, UpdatePasswordRequest
from consts.exceptions import (
NoInviteCodeException,
Expand Down Expand Up @@ -70,9 +71,14 @@ async def signup(request: UserSignUpRequest):
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="INVITE_CODE_INVALID")
except ValidationError as e:
logging.warning(
f"User registration rejected by feature flag: {str(e)}")
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
detail = str(e)
if detail == ASSET_OWNER_SIGNUP_USE_OAUTH_DETAIL:
logging.warning(
"User registration rejected: asset owner invite requires OAuth")
else:
logging.warning(
f"User registration rejected by validation: {detail}")
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=detail)
except UserRegistrationException as e:
logging.error(
f"User registration failed by registration service: {str(e)}")
Expand Down
3 changes: 3 additions & 0 deletions backend/consts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ class VectorDatabaseType(str, Enum):
ENABLE_ASSET_OWNER_ROLE = os.getenv(
"ENABLE_ASSET_OWNER_ROLE", "false").lower() == "true"

# HTTP detail key: asset owner must register via OAuth, not email/password signup.
ASSET_OWNER_SIGNUP_USE_OAUTH_DETAIL = "ASSET_OWNER_USE_OAUTH"

# Roles that can edit all resources within a tenant (permission = EDIT).
# Keep this centralized to avoid drifting role logic across modules.
CAN_EDIT_ALL_USER_ROLES = {"SU", "ADMIN", "SPEED", "ASSET_OWNER"}
Expand Down
15 changes: 13 additions & 2 deletions backend/services/oauth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
from pydantic import EmailStr, TypeAdapter, ValidationError as PydanticValidationError

from consts.const import (
ASSET_OWNER_INVITE_CODE_TYPE,
ASSET_OWNER_ROLE,
ASSET_OWNER_TENANT_ID,
DEFAULT_TENANT_ID,
OAUTH_CALLBACK_BASE_URL,
OAUTH_SSL_VERIFY,
OAUTH_CA_BUNDLE,
SUPABASE_JWT_SECRET,
)
from consts.exceptions import OAuthLinkError, OAuthProviderError
from services.asset_owner_visibility import require_asset_owner_enabled
from consts.oauth_providers import (
get_all_provider_definitions,
get_provider_definition,
Expand Down Expand Up @@ -359,10 +363,13 @@
return "ADMIN"
if code_type == "DEV_INVITE":
return "DEV"
if code_type == ASSET_OWNER_INVITE_CODE_TYPE:
require_asset_owner_enabled()
return ASSET_OWNER_ROLE
return "USER"


async def complete_pending_oauth_account(

Check failure on line 372 in backend/services/oauth_service.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf50WUdCtJVJ0V8c0&open=AZ5yf50WUdCtJVJ0V8c0&pullRequest=3086
pending_token: str,
password: str,
invite_code: str,
Expand Down Expand Up @@ -431,7 +438,10 @@
supabase_user_id = create_resp.user.id

tenant_id = invitation_info["tenant_id"]
if invitation_info.get("code_type") == ASSET_OWNER_INVITE_CODE_TYPE:
tenant_id = ASSET_OWNER_TENANT_ID
user_role = _role_from_invitation_type(invitation_info.get("code_type", "USER_INVITE"))
is_asset_owner_registration = user_role == ASSET_OWNER_ROLE

insert_user_tenant(
user_id=supabase_user_id,
Expand All @@ -446,12 +456,13 @@
from utils.str_utils import convert_string_to_list

group_ids = convert_string_to_list(group_ids)
if group_ids:
if group_ids and not is_asset_owner_registration:
add_user_to_groups(supabase_user_id, group_ids, supabase_user_id)

if user_role == "ADMIN":
await generate_tts_stt_4_admin(tenant_id, supabase_user_id)
await init_tool_list_for_tenant(tenant_id, supabase_user_id)
if not is_asset_owner_registration:
await init_tool_list_for_tenant(tenant_id, supabase_user_id)

create_or_update_oauth_account(
user_id=supabase_user_id,
Expand Down
14 changes: 11 additions & 3 deletions backend/services/user_management_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@
ASSET_OWNER_TENANT_ID,
ASSET_OWNER_INVITE_CODE_TYPE,
ASSET_OWNER_ROLE,
ASSET_OWNER_SIGNUP_USE_OAUTH_DETAIL,
)

from services.asset_owner_visibility import (
filter_accessible_routes_for_asset_owner_feature,
require_asset_owner_enabled,
)
from consts.const import INVITE_CODE, SUPABASE_URL, SUPABASE_KEY, DEFAULT_TENANT_ID
from consts.exceptions import NoInviteCodeException, IncorrectInviteCodeException, UserRegistrationException, UnauthorizedError
from consts.exceptions import (
NoInviteCodeException,
IncorrectInviteCodeException,
UserRegistrationException,
UnauthorizedError,
ValidationError,
)
from consts.error_code import ErrorCode
from consts.exceptions import AppException

Expand Down Expand Up @@ -189,13 +195,15 @@ async def signup_user_with_invitation(email: EmailStr,
user_role = "DEV"
elif code_type == ASSET_OWNER_INVITE_CODE_TYPE:
require_asset_owner_enabled()
user_role = ASSET_OWNER_ROLE
raise ValidationError(ASSET_OWNER_SIGNUP_USE_OAUTH_DETAIL)

logging.info(
f"Invitation code {invite_code} validated successfully, will assign role: {user_role}")

except IncorrectInviteCodeException:
raise
except ValidationError:
raise
except Exception as e:
logging.error(
f"Invitation code {invite_code} validation failed: {str(e)}")
Expand Down
53 changes: 53 additions & 0 deletions docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-- Migration: ASSET_OWNER role permissions and invitation type comment
-- Date: 2026-05-29
-- Description: Add ASSET_OWNER role permissions, SU asset-owner invite permissions,
-- update invitation code_type comment, and ensure ag_skill_info_t.tenant_id exists
-- Source: commit 15cece97692db2372a978cbdf21b5d5316e79f30 (init.sql)

SET search_path TO nexent;

BEGIN;

COMMENT ON COLUMN nexent.tenant_invitation_code_t.code_type IS
'Invitation code type: ADMIN_INVITE, DEV_INVITE, USER_INVITE, ASSET_OWNER_INVITE';

INSERT INTO nexent.role_permission_t
(role_permission_id, user_role, permission_category, permission_type, permission_subtype)
VALUES
(188, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'CREATE'),

Check failure on line 17 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 25 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cs&open=AZ5yf5oMUdCtJVJ0V8cs&pullRequest=3086

Check failure on line 17 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 6 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cq&open=AZ5yf5oMUdCtJVJ0V8cq&pullRequest=3086

Check failure on line 17 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cz&open=AZ5yf5oMUdCtJVJ0V8cz&pullRequest=3086
(189, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'READ'),
(190, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'UPDATE'),

Check failure on line 19 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 6 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cy&open=AZ5yf5oMUdCtJVJ0V8cy&pullRequest=3086
(191, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'DELETE'),

Check failure on line 20 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 6 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cv&open=AZ5yf5oMUdCtJVJ0V8cv&pullRequest=3086
(192, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/'),

Check failure on line 21 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 29 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cp&open=AZ5yf5oMUdCtJVJ0V8cp&pullRequest=3086

Check failure on line 21 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 9 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cw&open=AZ5yf5oMUdCtJVJ0V8cw&pullRequest=3086

Check failure on line 21 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 9 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cx&open=AZ5yf5oMUdCtJVJ0V8cx&pullRequest=3086
(193, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/agents'),
(194, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/knowledges'),
(195, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/chat'),
(196, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/space'),
(197, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/market'),
(198, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/models'),
(199, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'CREATE'),

Check failure on line 28 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8ct&open=AZ5yf5oMUdCtJVJ0V8ct&pullRequest=3086
(200, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'READ'),
(201, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'UPDATE'),
(202, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'DELETE'),
(203, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'CREATE'),

Check failure on line 32 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cr&open=AZ5yf5oMUdCtJVJ0V8cr&pullRequest=3086
(204, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'READ'),
(205, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'UPDATE'),
(206, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'DELETE'),
(207, 'ASSET_OWNER', 'RESOURCE', 'KB', 'CREATE'),
(208, 'ASSET_OWNER', 'RESOURCE', 'KB', 'READ'),
(209, 'ASSET_OWNER', 'RESOURCE', 'KB', 'UPDATE'),
(210, 'ASSET_OWNER', 'RESOURCE', 'KB', 'DELETE'),
(211, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'CREATE'),
(212, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'READ'),
(213, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'UPDATE'),
(214, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'DELETE'),
(215, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'CREATE'),

Check failure on line 44 in docker/sql/v2.2.0_0529_add_asset_owner_role_permissions.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ5yf5oMUdCtJVJ0V8cu&open=AZ5yf5oMUdCtJVJ0V8cu&pullRequest=3086
(216, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'READ'),
(217, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'UPDATE'),
(218, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'DELETE'),
(219, 'ASSET_OWNER', 'RESOURCE', 'USER.ROLE', 'READ'),
(220, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/users'),
(221, 'SU', 'VISIBILITY', 'LEFT_NAV_MENU', '/asset-owner-resources')
ON CONFLICT (role_permission_id) DO NOTHING;

COMMIT;
10 changes: 10 additions & 0 deletions frontend/components/auth/registerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,16 @@ export function RegisterModal() {
value: values.inviteCode,
},
]);
} else if (errorType === "ASSET_OWNER_USE_OAUTH") {
const errorMsg = t("auth.assetOwnerUseOAuth");
message.error(errorMsg);
form.setFields([
{
name: "inviteCode",
errors: [errorMsg],
value: values.inviteCode,
},
]);
}
// Invalid email format
else if (errorType === "INVALID_EMAIL_FORMAT") {
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@
"auth.registerAdmin": "Register Administrator Account",
"auth.inviteCodeNotConfigured": "Admin invite code is not configured yet. Please contact system admin for help.",
"auth.inviteCodeInvalid": "Invalid administrator invite code, please check and try again",
"auth.assetOwnerUseOAuth": "Asset owner accounts cannot be registered with email and password. Please sign in with GitHub, WeChat, or another OAuth provider and enter your asset owner invitation code to complete registration.",
"auth.emailAlreadyExists": "This email is already registered, please use another email or try logging in",
"auth.weakPassword": "Password is too weak, please set a more secure password",
"auth.invalidEmailFormat": "Invalid email format, please check and try again",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@
"auth.registerAdmin": "注册管理员账号",
"auth.inviteCodeNotConfigured": "管理员注册功能暂未开放,请联系系统管理员配置邀请码",
"auth.inviteCodeInvalid": "管理员邀请码错误,请检查后重新输入",
"auth.assetOwnerUseOAuth": "资产管理员账号不支持邮箱密码注册。请使用 GitHub、微信等 OAuth 方式登录,并填写资产管理员邀请码完成注册。",
"auth.emailAlreadyExists": "该邮箱已被注册,请使用其他邮箱地址或尝试登录现有账号",
"auth.weakPassword": "密码强度不够,请设置更安全的密码",
"auth.invalidEmailFormat": "邮箱格式不正确,请检查后重新输入",
Expand Down
3 changes: 2 additions & 1 deletion frontend/services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ export const authService = {
if (!response.ok) {
return {
error: {
message: data.message || "Registration failed",
message:
data.detail || data.message || "Registration failed",
code: response.status,
data: data.data || null,
},
Expand Down
78 changes: 73 additions & 5 deletions test/backend/app/test_agent_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from fastapi.responses import StreamingResponse
from fastapi.testclient import TestClient

from consts.const import ASSET_OWNER_TENANT_ID
from consts.const import AGENT_PROMPTS_HIDDEN_FLAG, ASSET_OWNER_TENANT_ID

# Filter out deprecation warnings from third-party libraries
warnings.filterwarnings(
Expand Down Expand Up @@ -431,6 +431,34 @@ def test_search_agent_info_api_with_version_no(mocker, mock_auth_header):
123, "auth_tenant_id", 2, "user_id")


def test_search_agent_info_api_masks_asset_owner_prompts(mocker, mock_auth_header):
"""Non-asset-owner callers see masked prompts for asset-owner-scoped agents."""
mock_get_user_id = mocker.patch("apps.agent_app.get_current_user_id")
mock_get_agent_info = mocker.patch(
"apps.agent_app.get_agent_info_impl", new_callable=AsyncMock)
mock_get_user_id.return_value = ("user_id", "regular_tenant")
mock_get_agent_info.return_value = {
"agent_id": 1,
"tenant_id": ASSET_OWNER_TENANT_ID,
"duty_prompt": "secret duty",
"constraint_prompt": "secret constraint",
"few_shots_prompt": "secret few",
}

response = config_client.post(
"/agent/search_info",
json={"agent_id": 1},
headers=mock_auth_header,
)

assert response.status_code == 200
body = response.json()
assert body["duty_prompt"] is None
assert body["constraint_prompt"] is None
assert body["few_shots_prompt"] is None
assert body[AGENT_PROMPTS_HIDDEN_FLAG] is True


# get_agent_by_name_api Tests
# ---------------------------------------------------------------------------

Expand Down Expand Up @@ -925,6 +953,23 @@ def test_list_all_agent_info_api_with_explicit_tenant_id(mocker, mock_auth_heade
tenant_id=ASSET_OWNER_TENANT_ID, user_id="test_user")


def test_list_all_agent_info_api_asset_owner_tenant_single_query(mocker, mock_auth_header):
"""Asset-owner tenant callers only query their own tenant (no merge)."""
mock_get_user_info = mocker.patch("apps.agent_app.get_current_user_info")
mock_list_all_agent = mocker.patch(
"apps.agent_app.list_all_agent_info_impl", new_callable=AsyncMock)
mock_get_user_info.return_value = ("ao_user", ASSET_OWNER_TENANT_ID, "en")
mock_list_all_agent.return_value = [{"agent_id": 1, "name": "AO Agent"}]

response = config_client.get("/agent/list", headers=mock_auth_header)

assert response.status_code == 200
mock_list_all_agent.assert_called_once_with(
tenant_id=ASSET_OWNER_TENANT_ID, user_id="ao_user"
)
assert len(response.json()) == 1


def test_list_all_agent_info_api_exception(mocker, mock_auth_header):
"""Test list_all_agent_info_api exception handling."""
mock_get_user_info = mocker.patch("apps.agent_app.get_current_user_info")
Expand Down Expand Up @@ -1925,9 +1970,9 @@ def test_list_published_agents_api_success(mocker, mock_auth_header):
"apps.agent_app.list_published_agents_impl", new_callable=AsyncMock)

mock_get_user_info.return_value = ("test_user_id", "test_tenant_id", "en")
mock_list_published_agents.return_value = [
{"agent_id": 1, "name": "Agent 1", "published_version_no": 1},
{"agent_id": 2, "name": "Agent 2", "published_version_no": 2}
mock_list_published_agents.side_effect = [
[{"agent_id": 1, "name": "Agent 1", "published_version_no": 1}],
[{"agent_id": 2, "name": "Asset Agent", "published_version_no": 1}],
]

response = config_client.get(
Expand All @@ -1936,12 +1981,35 @@ def test_list_published_agents_api_success(mocker, mock_auth_header):
)

assert response.status_code == 200
mock_list_published_agents.assert_called_once_with(
assert mock_list_published_agents.call_count == 2
mock_list_published_agents.assert_any_call(
tenant_id="test_tenant_id", user_id="test_user_id"
)
mock_list_published_agents.assert_any_call(
tenant_id=ASSET_OWNER_TENANT_ID, user_id="test_user_id"
)
assert len(response.json()) == 2


def test_list_published_agents_api_asset_owner_tenant_single_query(mocker, mock_auth_header):
"""Asset-owner tenant callers only query published agents once (no merge)."""
mock_get_user_info = mocker.patch("apps.agent_app.get_current_user_info")
mock_list_published_agents = mocker.patch(
"apps.agent_app.list_published_agents_impl", new_callable=AsyncMock)
mock_get_user_info.return_value = ("ao_user", ASSET_OWNER_TENANT_ID, "en")
mock_list_published_agents.return_value = [
{"agent_id": 1, "name": "AO Agent", "published_version_no": 1},
]

response = config_client.get("/agent/published_list", headers=mock_auth_header)

assert response.status_code == 200
mock_list_published_agents.assert_called_once_with(
tenant_id=ASSET_OWNER_TENANT_ID, user_id="ao_user"
)
assert len(response.json()) == 1


def test_list_published_agents_api_exception(mocker, mock_auth_header):
"""Test list_published_agents_api with exception."""
mock_get_user_info = mocker.patch("apps.agent_app.get_current_user_info")
Expand Down
Loading
Loading