test_app_service.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. from unittest.mock import patch
  2. import pytest
  3. from faker import Faker
  4. from constants.model_template import default_app_templates
  5. from models.model import App, Site
  6. from services.account_service import AccountService, TenantService
  7. from services.app_service import AppService
  8. class TestAppService:
  9. """Integration tests for AppService using testcontainers."""
  10. @pytest.fixture
  11. def mock_external_service_dependencies(self):
  12. """Mock setup for external service dependencies."""
  13. with (
  14. patch("services.app_service.FeatureService") as mock_feature_service,
  15. patch("services.app_service.EnterpriseService") as mock_enterprise_service,
  16. patch("services.app_service.ModelManager") as mock_model_manager,
  17. patch("services.account_service.FeatureService") as mock_account_feature_service,
  18. ):
  19. # Setup default mock returns for app service
  20. mock_feature_service.get_system_features.return_value.webapp_auth.enabled = False
  21. mock_enterprise_service.WebAppAuth.update_app_access_mode.return_value = None
  22. mock_enterprise_service.WebAppAuth.cleanup_webapp.return_value = None
  23. # Setup default mock returns for account service
  24. mock_account_feature_service.get_system_features.return_value.is_allow_register = True
  25. # Mock ModelManager for model configuration
  26. mock_model_instance = mock_model_manager.return_value
  27. mock_model_instance.get_default_model_instance.return_value = None
  28. mock_model_instance.get_default_provider_model_name.return_value = ("openai", "gpt-3.5-turbo")
  29. yield {
  30. "feature_service": mock_feature_service,
  31. "enterprise_service": mock_enterprise_service,
  32. "model_manager": mock_model_manager,
  33. "account_feature_service": mock_account_feature_service,
  34. }
  35. def test_create_app_success(self, db_session_with_containers, mock_external_service_dependencies):
  36. """
  37. Test successful app creation with basic parameters.
  38. """
  39. fake = Faker()
  40. # Create account and tenant first
  41. account = AccountService.create_account(
  42. email=fake.email(),
  43. name=fake.name(),
  44. interface_language="en-US",
  45. password=fake.password(length=12),
  46. )
  47. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  48. tenant = account.current_tenant
  49. # Setup app creation arguments
  50. app_args = {
  51. "name": fake.company(),
  52. "description": fake.text(max_nb_chars=100),
  53. "mode": "chat",
  54. "icon_type": "emoji",
  55. "icon": "📚",
  56. "icon_background": "#FF6B6B",
  57. "api_rph": 100,
  58. "api_rpm": 10,
  59. }
  60. # Create app
  61. app_service = AppService()
  62. app = app_service.create_app(tenant.id, app_args, account)
  63. # Verify app was created correctly
  64. assert app.name == app_args["name"]
  65. assert app.description == app_args["description"]
  66. assert app.mode == app_args["mode"]
  67. assert app.icon_type == app_args["icon_type"]
  68. assert app.icon == app_args["icon"]
  69. assert app.icon_background == app_args["icon_background"]
  70. assert app.tenant_id == tenant.id
  71. assert app.api_rph == app_args["api_rph"]
  72. assert app.api_rpm == app_args["api_rpm"]
  73. assert app.created_by == account.id
  74. assert app.updated_by == account.id
  75. assert app.status == "normal"
  76. assert app.enable_site is True
  77. assert app.enable_api is True
  78. assert app.is_demo is False
  79. assert app.is_public is False
  80. assert app.is_universal is False
  81. def test_create_app_with_different_modes(self, db_session_with_containers, mock_external_service_dependencies):
  82. """
  83. Test app creation with different app modes.
  84. """
  85. fake = Faker()
  86. # Create account and tenant first
  87. account = AccountService.create_account(
  88. email=fake.email(),
  89. name=fake.name(),
  90. interface_language="en-US",
  91. password=fake.password(length=12),
  92. )
  93. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  94. tenant = account.current_tenant
  95. app_service = AppService()
  96. # Test different app modes
  97. # from AppMode enum in default_app_model_template
  98. app_modes = [v.value for v in default_app_templates]
  99. for mode in app_modes:
  100. app_args = {
  101. "name": f"{fake.company()} {mode}",
  102. "description": f"Test app for {mode} mode",
  103. "mode": mode,
  104. "icon_type": "emoji",
  105. "icon": "🚀",
  106. "icon_background": "#4ECDC4",
  107. }
  108. app = app_service.create_app(tenant.id, app_args, account)
  109. # Verify app mode was set correctly
  110. assert app.mode == mode
  111. assert app.name == app_args["name"]
  112. assert app.tenant_id == tenant.id
  113. assert app.created_by == account.id
  114. def test_get_app_success(self, db_session_with_containers, mock_external_service_dependencies):
  115. """
  116. Test successful app retrieval.
  117. """
  118. fake = Faker()
  119. # Create account and tenant first
  120. account = AccountService.create_account(
  121. email=fake.email(),
  122. name=fake.name(),
  123. interface_language="en-US",
  124. password=fake.password(length=12),
  125. )
  126. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  127. tenant = account.current_tenant
  128. # Create app first
  129. app_args = {
  130. "name": fake.company(),
  131. "description": fake.text(max_nb_chars=100),
  132. "mode": "chat",
  133. "icon_type": "emoji",
  134. "icon": "🎯",
  135. "icon_background": "#45B7D1",
  136. }
  137. app_service = AppService()
  138. created_app = app_service.create_app(tenant.id, app_args, account)
  139. # Get app using the service
  140. retrieved_app = app_service.get_app(created_app)
  141. # Verify retrieved app matches created app
  142. assert retrieved_app.id == created_app.id
  143. assert retrieved_app.name == created_app.name
  144. assert retrieved_app.description == created_app.description
  145. assert retrieved_app.mode == created_app.mode
  146. assert retrieved_app.tenant_id == created_app.tenant_id
  147. assert retrieved_app.created_by == created_app.created_by
  148. def test_get_paginate_apps_success(self, db_session_with_containers, mock_external_service_dependencies):
  149. """
  150. Test successful paginated app list retrieval.
  151. """
  152. fake = Faker()
  153. # Create account and tenant first
  154. account = AccountService.create_account(
  155. email=fake.email(),
  156. name=fake.name(),
  157. interface_language="en-US",
  158. password=fake.password(length=12),
  159. )
  160. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  161. tenant = account.current_tenant
  162. app_service = AppService()
  163. # Create multiple apps
  164. app_names = [fake.company() for _ in range(5)]
  165. for name in app_names:
  166. app_args = {
  167. "name": name,
  168. "description": fake.text(max_nb_chars=100),
  169. "mode": "chat",
  170. "icon_type": "emoji",
  171. "icon": "📱",
  172. "icon_background": "#96CEB4",
  173. }
  174. app_service.create_app(tenant.id, app_args, account)
  175. # Get paginated apps
  176. args = {
  177. "page": 1,
  178. "limit": 10,
  179. "mode": "chat",
  180. }
  181. paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
  182. # Verify pagination results
  183. assert paginated_apps is not None
  184. assert len(paginated_apps.items) >= 5 # Should have at least 5 apps
  185. assert paginated_apps.page == 1
  186. assert paginated_apps.per_page == 10
  187. # Verify all apps belong to the correct tenant
  188. for app in paginated_apps.items:
  189. assert app.tenant_id == tenant.id
  190. assert app.mode == "chat"
  191. def test_get_paginate_apps_with_filters(self, db_session_with_containers, mock_external_service_dependencies):
  192. """
  193. Test paginated app list with various filters.
  194. """
  195. fake = Faker()
  196. # Create account and tenant first
  197. account = AccountService.create_account(
  198. email=fake.email(),
  199. name=fake.name(),
  200. interface_language="en-US",
  201. password=fake.password(length=12),
  202. )
  203. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  204. tenant = account.current_tenant
  205. app_service = AppService()
  206. # Create apps with different modes
  207. chat_app_args = {
  208. "name": "Chat App",
  209. "description": "A chat application",
  210. "mode": "chat",
  211. "icon_type": "emoji",
  212. "icon": "💬",
  213. "icon_background": "#FF6B6B",
  214. }
  215. completion_app_args = {
  216. "name": "Completion App",
  217. "description": "A completion application",
  218. "mode": "completion",
  219. "icon_type": "emoji",
  220. "icon": "✍️",
  221. "icon_background": "#4ECDC4",
  222. }
  223. chat_app = app_service.create_app(tenant.id, chat_app_args, account)
  224. completion_app = app_service.create_app(tenant.id, completion_app_args, account)
  225. # Test filter by mode
  226. chat_args = {
  227. "page": 1,
  228. "limit": 10,
  229. "mode": "chat",
  230. }
  231. chat_apps = app_service.get_paginate_apps(account.id, tenant.id, chat_args)
  232. assert len(chat_apps.items) == 1
  233. assert chat_apps.items[0].mode == "chat"
  234. # Test filter by name
  235. name_args = {
  236. "page": 1,
  237. "limit": 10,
  238. "mode": "chat",
  239. "name": "Chat",
  240. }
  241. filtered_apps = app_service.get_paginate_apps(account.id, tenant.id, name_args)
  242. assert len(filtered_apps.items) == 1
  243. assert "Chat" in filtered_apps.items[0].name
  244. # Test filter by created_by_me
  245. created_by_me_args = {
  246. "page": 1,
  247. "limit": 10,
  248. "mode": "completion",
  249. "is_created_by_me": True,
  250. }
  251. my_apps = app_service.get_paginate_apps(account.id, tenant.id, created_by_me_args)
  252. assert len(my_apps.items) == 1
  253. def test_get_paginate_apps_with_tag_filters(self, db_session_with_containers, mock_external_service_dependencies):
  254. """
  255. Test paginated app list with tag filters.
  256. """
  257. fake = Faker()
  258. # Create account and tenant first
  259. account = AccountService.create_account(
  260. email=fake.email(),
  261. name=fake.name(),
  262. interface_language="en-US",
  263. password=fake.password(length=12),
  264. )
  265. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  266. tenant = account.current_tenant
  267. app_service = AppService()
  268. # Create an app
  269. app_args = {
  270. "name": fake.company(),
  271. "description": fake.text(max_nb_chars=100),
  272. "mode": "chat",
  273. "icon_type": "emoji",
  274. "icon": "🏷️",
  275. "icon_background": "#FFEAA7",
  276. }
  277. app = app_service.create_app(tenant.id, app_args, account)
  278. # Mock TagService to return the app ID for tag filtering
  279. with patch("services.app_service.TagService.get_target_ids_by_tag_ids") as mock_tag_service:
  280. mock_tag_service.return_value = [app.id]
  281. # Test with tag filter
  282. args = {
  283. "page": 1,
  284. "limit": 10,
  285. "mode": "chat",
  286. "tag_ids": ["tag1", "tag2"],
  287. }
  288. paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
  289. # Verify tag service was called
  290. mock_tag_service.assert_called_once_with("app", tenant.id, ["tag1", "tag2"])
  291. # Verify results
  292. assert paginated_apps is not None
  293. assert len(paginated_apps.items) == 1
  294. assert paginated_apps.items[0].id == app.id
  295. # Test with tag filter that returns no results
  296. with patch("services.app_service.TagService.get_target_ids_by_tag_ids") as mock_tag_service:
  297. mock_tag_service.return_value = []
  298. args = {
  299. "page": 1,
  300. "limit": 10,
  301. "mode": "chat",
  302. "tag_ids": ["nonexistent_tag"],
  303. }
  304. paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
  305. # Should return None when no apps match tag filter
  306. assert paginated_apps is None
  307. def test_update_app_success(self, db_session_with_containers, mock_external_service_dependencies):
  308. """
  309. Test successful app update with all fields.
  310. """
  311. fake = Faker()
  312. # Create account and tenant first
  313. account = AccountService.create_account(
  314. email=fake.email(),
  315. name=fake.name(),
  316. interface_language="en-US",
  317. password=fake.password(length=12),
  318. )
  319. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  320. tenant = account.current_tenant
  321. # Create app first
  322. app_args = {
  323. "name": fake.company(),
  324. "description": fake.text(max_nb_chars=100),
  325. "mode": "chat",
  326. "icon_type": "emoji",
  327. "icon": "🎯",
  328. "icon_background": "#45B7D1",
  329. }
  330. app_service = AppService()
  331. app = app_service.create_app(tenant.id, app_args, account)
  332. # Store original values
  333. original_name = app.name
  334. original_description = app.description
  335. original_icon = app.icon
  336. original_icon_background = app.icon_background
  337. original_use_icon_as_answer_icon = app.use_icon_as_answer_icon
  338. # Update app
  339. update_args = {
  340. "name": "Updated App Name",
  341. "description": "Updated app description",
  342. "icon_type": "emoji",
  343. "icon": "🔄",
  344. "icon_background": "#FF8C42",
  345. "use_icon_as_answer_icon": True,
  346. }
  347. with patch("flask_login.utils._get_user", return_value=account):
  348. updated_app = app_service.update_app(app, update_args)
  349. # Verify updated fields
  350. assert updated_app.name == update_args["name"]
  351. assert updated_app.description == update_args["description"]
  352. assert updated_app.icon == update_args["icon"]
  353. assert updated_app.icon_background == update_args["icon_background"]
  354. assert updated_app.use_icon_as_answer_icon is True
  355. assert updated_app.updated_by == account.id
  356. # Verify other fields remain unchanged
  357. assert updated_app.mode == app.mode
  358. assert updated_app.tenant_id == app.tenant_id
  359. assert updated_app.created_by == app.created_by
  360. def test_update_app_name_success(self, db_session_with_containers, mock_external_service_dependencies):
  361. """
  362. Test successful app name update.
  363. """
  364. fake = Faker()
  365. # Create account and tenant first
  366. account = AccountService.create_account(
  367. email=fake.email(),
  368. name=fake.name(),
  369. interface_language="en-US",
  370. password=fake.password(length=12),
  371. )
  372. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  373. tenant = account.current_tenant
  374. # Create app first
  375. app_args = {
  376. "name": fake.company(),
  377. "description": fake.text(max_nb_chars=100),
  378. "mode": "chat",
  379. "icon_type": "emoji",
  380. "icon": "🎯",
  381. "icon_background": "#45B7D1",
  382. }
  383. app_service = AppService()
  384. app = app_service.create_app(tenant.id, app_args, account)
  385. # Store original name
  386. original_name = app.name
  387. # Update app name
  388. new_name = "New App Name"
  389. with patch("flask_login.utils._get_user", return_value=account):
  390. updated_app = app_service.update_app_name(app, new_name)
  391. assert updated_app.name == new_name
  392. assert updated_app.updated_by == account.id
  393. # Verify other fields remain unchanged
  394. assert updated_app.description == app.description
  395. assert updated_app.mode == app.mode
  396. assert updated_app.tenant_id == app.tenant_id
  397. assert updated_app.created_by == app.created_by
  398. def test_update_app_icon_success(self, db_session_with_containers, mock_external_service_dependencies):
  399. """
  400. Test successful app icon update.
  401. """
  402. fake = Faker()
  403. # Create account and tenant first
  404. account = AccountService.create_account(
  405. email=fake.email(),
  406. name=fake.name(),
  407. interface_language="en-US",
  408. password=fake.password(length=12),
  409. )
  410. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  411. tenant = account.current_tenant
  412. # Create app first
  413. app_args = {
  414. "name": fake.company(),
  415. "description": fake.text(max_nb_chars=100),
  416. "mode": "chat",
  417. "icon_type": "emoji",
  418. "icon": "🎯",
  419. "icon_background": "#45B7D1",
  420. }
  421. app_service = AppService()
  422. app = app_service.create_app(tenant.id, app_args, account)
  423. # Store original values
  424. original_icon = app.icon
  425. original_icon_background = app.icon_background
  426. # Update app icon
  427. new_icon = "🌟"
  428. new_icon_background = "#FFD93D"
  429. with patch("flask_login.utils._get_user", return_value=account):
  430. updated_app = app_service.update_app_icon(app, new_icon, new_icon_background)
  431. assert updated_app.icon == new_icon
  432. assert updated_app.icon_background == new_icon_background
  433. assert updated_app.updated_by == account.id
  434. # Verify other fields remain unchanged
  435. assert updated_app.name == app.name
  436. assert updated_app.description == app.description
  437. assert updated_app.mode == app.mode
  438. assert updated_app.tenant_id == app.tenant_id
  439. assert updated_app.created_by == app.created_by
  440. def test_update_app_site_status_success(self, db_session_with_containers, mock_external_service_dependencies):
  441. """
  442. Test successful app site status update.
  443. """
  444. fake = Faker()
  445. # Create account and tenant first
  446. account = AccountService.create_account(
  447. email=fake.email(),
  448. name=fake.name(),
  449. interface_language="en-US",
  450. password=fake.password(length=12),
  451. )
  452. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  453. tenant = account.current_tenant
  454. # Create app first
  455. app_args = {
  456. "name": fake.company(),
  457. "description": fake.text(max_nb_chars=100),
  458. "mode": "chat",
  459. "icon_type": "emoji",
  460. "icon": "🌐",
  461. "icon_background": "#74B9FF",
  462. }
  463. app_service = AppService()
  464. app = app_service.create_app(tenant.id, app_args, account)
  465. # Store original site status
  466. original_site_status = app.enable_site
  467. # Update site status to disabled
  468. with patch("flask_login.utils._get_user", return_value=account):
  469. updated_app = app_service.update_app_site_status(app, False)
  470. assert updated_app.enable_site is False
  471. assert updated_app.updated_by == account.id
  472. # Update site status back to enabled
  473. with patch("flask_login.utils._get_user", return_value=account):
  474. updated_app = app_service.update_app_site_status(updated_app, True)
  475. assert updated_app.enable_site is True
  476. assert updated_app.updated_by == account.id
  477. # Verify other fields remain unchanged
  478. assert updated_app.name == app.name
  479. assert updated_app.description == app.description
  480. assert updated_app.mode == app.mode
  481. assert updated_app.tenant_id == app.tenant_id
  482. assert updated_app.created_by == app.created_by
  483. def test_update_app_api_status_success(self, db_session_with_containers, mock_external_service_dependencies):
  484. """
  485. Test successful app API status update.
  486. """
  487. fake = Faker()
  488. # Create account and tenant first
  489. account = AccountService.create_account(
  490. email=fake.email(),
  491. name=fake.name(),
  492. interface_language="en-US",
  493. password=fake.password(length=12),
  494. )
  495. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  496. tenant = account.current_tenant
  497. # Create app first
  498. app_args = {
  499. "name": fake.company(),
  500. "description": fake.text(max_nb_chars=100),
  501. "mode": "chat",
  502. "icon_type": "emoji",
  503. "icon": "🔌",
  504. "icon_background": "#A29BFE",
  505. }
  506. app_service = AppService()
  507. app = app_service.create_app(tenant.id, app_args, account)
  508. # Store original API status
  509. original_api_status = app.enable_api
  510. # Update API status to disabled
  511. with patch("flask_login.utils._get_user", return_value=account):
  512. updated_app = app_service.update_app_api_status(app, False)
  513. assert updated_app.enable_api is False
  514. assert updated_app.updated_by == account.id
  515. # Update API status back to enabled
  516. with patch("flask_login.utils._get_user", return_value=account):
  517. updated_app = app_service.update_app_api_status(updated_app, True)
  518. assert updated_app.enable_api is True
  519. assert updated_app.updated_by == account.id
  520. # Verify other fields remain unchanged
  521. assert updated_app.name == app.name
  522. assert updated_app.description == app.description
  523. assert updated_app.mode == app.mode
  524. assert updated_app.tenant_id == app.tenant_id
  525. assert updated_app.created_by == app.created_by
  526. def test_update_app_site_status_no_change(self, db_session_with_containers, mock_external_service_dependencies):
  527. """
  528. Test app site status update when status doesn't change.
  529. """
  530. fake = Faker()
  531. # Create account and tenant first
  532. account = AccountService.create_account(
  533. email=fake.email(),
  534. name=fake.name(),
  535. interface_language="en-US",
  536. password=fake.password(length=12),
  537. )
  538. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  539. tenant = account.current_tenant
  540. # Create app first
  541. app_args = {
  542. "name": fake.company(),
  543. "description": fake.text(max_nb_chars=100),
  544. "mode": "chat",
  545. "icon_type": "emoji",
  546. "icon": "🔄",
  547. "icon_background": "#FD79A8",
  548. }
  549. app_service = AppService()
  550. app = app_service.create_app(tenant.id, app_args, account)
  551. # Store original values
  552. original_site_status = app.enable_site
  553. original_updated_at = app.updated_at
  554. # Update site status to the same value (no change)
  555. updated_app = app_service.update_app_site_status(app, original_site_status)
  556. # Verify app is returned unchanged
  557. assert updated_app.id == app.id
  558. assert updated_app.enable_site == original_site_status
  559. assert updated_app.updated_at == original_updated_at
  560. # Verify other fields remain unchanged
  561. assert updated_app.name == app.name
  562. assert updated_app.description == app.description
  563. assert updated_app.mode == app.mode
  564. assert updated_app.tenant_id == app.tenant_id
  565. assert updated_app.created_by == app.created_by
  566. def test_delete_app_success(self, db_session_with_containers, mock_external_service_dependencies):
  567. """
  568. Test successful app deletion.
  569. """
  570. fake = Faker()
  571. # Create account and tenant first
  572. account = AccountService.create_account(
  573. email=fake.email(),
  574. name=fake.name(),
  575. interface_language="en-US",
  576. password=fake.password(length=12),
  577. )
  578. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  579. tenant = account.current_tenant
  580. # Create app first
  581. app_args = {
  582. "name": fake.company(),
  583. "description": fake.text(max_nb_chars=100),
  584. "mode": "chat",
  585. "icon_type": "emoji",
  586. "icon": "🗑️",
  587. "icon_background": "#E17055",
  588. }
  589. app_service = AppService()
  590. app = app_service.create_app(tenant.id, app_args, account)
  591. # Store app ID for verification
  592. app_id = app.id
  593. # Mock the async deletion task
  594. with patch("services.app_service.remove_app_and_related_data_task") as mock_delete_task:
  595. mock_delete_task.delay.return_value = None
  596. # Delete app
  597. app_service.delete_app(app)
  598. # Verify async deletion task was called
  599. mock_delete_task.delay.assert_called_once_with(tenant_id=tenant.id, app_id=app_id)
  600. # Verify app was deleted from database
  601. from extensions.ext_database import db
  602. deleted_app = db.session.query(App).filter_by(id=app_id).first()
  603. assert deleted_app is None
  604. def test_delete_app_with_related_data(self, db_session_with_containers, mock_external_service_dependencies):
  605. """
  606. Test app deletion with related data cleanup.
  607. """
  608. fake = Faker()
  609. # Create account and tenant first
  610. account = AccountService.create_account(
  611. email=fake.email(),
  612. name=fake.name(),
  613. interface_language="en-US",
  614. password=fake.password(length=12),
  615. )
  616. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  617. tenant = account.current_tenant
  618. # Create app first
  619. app_args = {
  620. "name": fake.company(),
  621. "description": fake.text(max_nb_chars=100),
  622. "mode": "chat",
  623. "icon_type": "emoji",
  624. "icon": "🧹",
  625. "icon_background": "#00B894",
  626. }
  627. app_service = AppService()
  628. app = app_service.create_app(tenant.id, app_args, account)
  629. # Store app ID for verification
  630. app_id = app.id
  631. # Mock webapp auth cleanup
  632. mock_external_service_dependencies[
  633. "feature_service"
  634. ].get_system_features.return_value.webapp_auth.enabled = True
  635. # Mock the async deletion task
  636. with patch("services.app_service.remove_app_and_related_data_task") as mock_delete_task:
  637. mock_delete_task.delay.return_value = None
  638. # Delete app
  639. app_service.delete_app(app)
  640. # Verify webapp auth cleanup was called
  641. mock_external_service_dependencies["enterprise_service"].WebAppAuth.cleanup_webapp.assert_called_once_with(
  642. app_id
  643. )
  644. # Verify async deletion task was called
  645. mock_delete_task.delay.assert_called_once_with(tenant_id=tenant.id, app_id=app_id)
  646. # Verify app was deleted from database
  647. from extensions.ext_database import db
  648. deleted_app = db.session.query(App).filter_by(id=app_id).first()
  649. assert deleted_app is None
  650. def test_get_app_meta_success(self, db_session_with_containers, mock_external_service_dependencies):
  651. """
  652. Test successful app metadata retrieval.
  653. """
  654. fake = Faker()
  655. # Create account and tenant first
  656. account = AccountService.create_account(
  657. email=fake.email(),
  658. name=fake.name(),
  659. interface_language="en-US",
  660. password=fake.password(length=12),
  661. )
  662. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  663. tenant = account.current_tenant
  664. # Create app first
  665. app_args = {
  666. "name": fake.company(),
  667. "description": fake.text(max_nb_chars=100),
  668. "mode": "chat",
  669. "icon_type": "emoji",
  670. "icon": "📊",
  671. "icon_background": "#6C5CE7",
  672. }
  673. app_service = AppService()
  674. app = app_service.create_app(tenant.id, app_args, account)
  675. # Get app metadata
  676. app_meta = app_service.get_app_meta(app)
  677. # Verify metadata contains expected fields
  678. assert "tool_icons" in app_meta
  679. # Note: get_app_meta currently only returns tool_icons
  680. def test_get_app_code_by_id_success(self, db_session_with_containers, mock_external_service_dependencies):
  681. """
  682. Test successful app code retrieval by app ID.
  683. """
  684. fake = Faker()
  685. # Create account and tenant first
  686. account = AccountService.create_account(
  687. email=fake.email(),
  688. name=fake.name(),
  689. interface_language="en-US",
  690. password=fake.password(length=12),
  691. )
  692. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  693. tenant = account.current_tenant
  694. # Create app first
  695. app_args = {
  696. "name": fake.company(),
  697. "description": fake.text(max_nb_chars=100),
  698. "mode": "chat",
  699. "icon_type": "emoji",
  700. "icon": "🔗",
  701. "icon_background": "#FDCB6E",
  702. }
  703. app_service = AppService()
  704. app = app_service.create_app(tenant.id, app_args, account)
  705. # Get app code by ID
  706. app_code = AppService.get_app_code_by_id(app.id)
  707. # Verify app code was retrieved correctly
  708. # Note: Site would be created when App is created, site.code is auto-generated
  709. assert app_code is not None
  710. assert len(app_code) > 0
  711. def test_get_app_id_by_code_success(self, db_session_with_containers, mock_external_service_dependencies):
  712. """
  713. Test successful app ID retrieval by app code.
  714. """
  715. fake = Faker()
  716. # Create account and tenant first
  717. account = AccountService.create_account(
  718. email=fake.email(),
  719. name=fake.name(),
  720. interface_language="en-US",
  721. password=fake.password(length=12),
  722. )
  723. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  724. tenant = account.current_tenant
  725. # Create app first
  726. app_args = {
  727. "name": fake.company(),
  728. "description": fake.text(max_nb_chars=100),
  729. "mode": "chat",
  730. "icon_type": "emoji",
  731. "icon": "🆔",
  732. "icon_background": "#E84393",
  733. }
  734. app_service = AppService()
  735. app = app_service.create_app(tenant.id, app_args, account)
  736. # Create a site for the app
  737. site = Site()
  738. site.app_id = app.id
  739. site.code = fake.postalcode()
  740. site.title = fake.company()
  741. site.status = "normal"
  742. site.default_language = "en-US"
  743. site.customize_token_strategy = "uuid"
  744. from extensions.ext_database import db
  745. db.session.add(site)
  746. db.session.commit()
  747. # Get app ID by code
  748. app_id = AppService.get_app_id_by_code(site.code)
  749. # Verify app ID was retrieved correctly
  750. assert app_id == app.id
  751. def test_create_app_invalid_mode(self, db_session_with_containers, mock_external_service_dependencies):
  752. """
  753. Test app creation with invalid mode.
  754. """
  755. fake = Faker()
  756. # Create account and tenant first
  757. account = AccountService.create_account(
  758. email=fake.email(),
  759. name=fake.name(),
  760. interface_language="en-US",
  761. password=fake.password(length=12),
  762. )
  763. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  764. tenant = account.current_tenant
  765. # Setup app creation arguments with invalid mode
  766. app_args = {
  767. "name": fake.company(),
  768. "description": fake.text(max_nb_chars=100),
  769. "mode": "invalid_mode", # Invalid mode
  770. "icon_type": "emoji",
  771. "icon": "❌",
  772. "icon_background": "#D63031",
  773. }
  774. app_service = AppService()
  775. # Attempt to create app with invalid mode
  776. with pytest.raises(ValueError, match="invalid mode value"):
  777. app_service.create_app(tenant.id, app_args, account)