test_feature_service.py 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785
  1. from unittest.mock import patch
  2. import pytest
  3. from faker import Faker
  4. from services.feature_service import FeatureModel, FeatureService, KnowledgeRateLimitModel, SystemFeatureModel
  5. class TestFeatureService:
  6. """Integration tests for FeatureService using testcontainers."""
  7. @pytest.fixture
  8. def mock_external_service_dependencies(self):
  9. """Mock setup for external service dependencies."""
  10. with (
  11. patch("services.feature_service.BillingService") as mock_billing_service,
  12. patch("services.feature_service.EnterpriseService") as mock_enterprise_service,
  13. ):
  14. # Setup default mock returns for BillingService
  15. mock_billing_service.get_info.return_value = {
  16. "enabled": True,
  17. "subscription": {"plan": "pro", "interval": "monthly", "education": True},
  18. "members": {"size": 5, "limit": 10},
  19. "apps": {"size": 3, "limit": 20},
  20. "vector_space": {"size": 2, "limit": 10},
  21. "documents_upload_quota": {"size": 15, "limit": 100},
  22. "annotation_quota_limit": {"size": 8, "limit": 50},
  23. "docs_processing": "enhanced",
  24. "can_replace_logo": True,
  25. "model_load_balancing_enabled": True,
  26. "knowledge_rate_limit": {"limit": 100},
  27. }
  28. mock_billing_service.get_knowledge_rate_limit.return_value = {"limit": 100, "subscription_plan": "pro"}
  29. # Setup default mock returns for EnterpriseService
  30. mock_enterprise_service.get_workspace_info.return_value = {
  31. "WorkspaceMembers": {"used": 5, "limit": 10, "enabled": True}
  32. }
  33. mock_enterprise_service.get_info.return_value = {
  34. "SSOEnforcedForSignin": True,
  35. "SSOEnforcedForSigninProtocol": "saml",
  36. "EnableEmailCodeLogin": True,
  37. "EnableEmailPasswordLogin": False,
  38. "IsAllowRegister": False,
  39. "IsAllowCreateWorkspace": False,
  40. "Branding": {
  41. "applicationTitle": "Test Enterprise",
  42. "loginPageLogo": "https://example.com/logo.png",
  43. "workspaceLogo": "https://example.com/workspace.png",
  44. "favicon": "https://example.com/favicon.ico",
  45. },
  46. "WebAppAuth": {"allowSso": True, "allowEmailCodeLogin": True, "allowEmailPasswordLogin": False},
  47. "SSOEnforcedForWebProtocol": "oidc",
  48. "License": {
  49. "status": "active",
  50. "expiredAt": "2025-12-31",
  51. "workspaces": {"enabled": True, "limit": 5, "used": 2},
  52. },
  53. "PluginInstallationPermission": {
  54. "pluginInstallationScope": "official_only",
  55. "restrictToMarketplaceOnly": True,
  56. },
  57. }
  58. yield {
  59. "billing_service": mock_billing_service,
  60. "enterprise_service": mock_enterprise_service,
  61. }
  62. def _create_test_tenant_id(self):
  63. """Helper method to create a test tenant ID."""
  64. fake = Faker()
  65. return fake.uuid4()
  66. def test_get_features_success(self, db_session_with_containers, mock_external_service_dependencies):
  67. """
  68. Test successful feature retrieval with billing and enterprise enabled.
  69. This test verifies:
  70. - Proper feature model creation with all required fields
  71. - Correct integration with billing service
  72. - Proper enterprise workspace information handling
  73. - Return value correctness and structure
  74. """
  75. # Arrange: Setup test data with proper config mocking
  76. tenant_id = self._create_test_tenant_id()
  77. with patch("services.feature_service.dify_config") as mock_config:
  78. mock_config.BILLING_ENABLED = True
  79. mock_config.ENTERPRISE_ENABLED = True
  80. mock_config.CAN_REPLACE_LOGO = True
  81. mock_config.MODEL_LB_ENABLED = True
  82. mock_config.DATASET_OPERATOR_ENABLED = True
  83. mock_config.EDUCATION_ENABLED = True
  84. # Act: Execute the method under test
  85. result = FeatureService.get_features(tenant_id)
  86. # Assert: Verify the expected outcomes
  87. assert result is not None
  88. assert isinstance(result, FeatureModel)
  89. # Verify billing features
  90. assert result.billing.enabled is True
  91. assert result.billing.subscription.plan == "pro"
  92. assert result.billing.subscription.interval == "monthly"
  93. assert result.education.activated is True
  94. # Verify member limitations
  95. assert result.members.size == 5
  96. assert result.members.limit == 10
  97. # Verify app limitations
  98. assert result.apps.size == 3
  99. assert result.apps.limit == 20
  100. # Verify vector space limitations
  101. assert result.vector_space.size == 2
  102. assert result.vector_space.limit == 10
  103. # Verify document upload quota
  104. assert result.documents_upload_quota.size == 15
  105. assert result.documents_upload_quota.limit == 100
  106. # Verify annotation quota
  107. assert result.annotation_quota_limit.size == 8
  108. assert result.annotation_quota_limit.limit == 50
  109. # Verify other features
  110. assert result.docs_processing == "enhanced"
  111. assert result.can_replace_logo is True
  112. assert result.model_load_balancing_enabled is True
  113. assert result.knowledge_rate_limit == 100
  114. # Verify enterprise features
  115. assert result.workspace_members.enabled is True
  116. assert result.workspace_members.size == 5
  117. assert result.workspace_members.limit == 10
  118. # Verify webapp copyright is enabled for non-sandbox plans
  119. assert result.webapp_copyright_enabled is True
  120. assert result.is_allow_transfer_workspace is True
  121. # Verify mock interactions
  122. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  123. mock_external_service_dependencies["enterprise_service"].get_workspace_info.assert_called_once_with(
  124. tenant_id
  125. )
  126. def test_get_features_sandbox_plan(self, db_session_with_containers, mock_external_service_dependencies):
  127. """
  128. Test feature retrieval for sandbox plan with specific limitations.
  129. This test verifies:
  130. - Proper handling of sandbox plan limitations
  131. - Correct webapp copyright settings for sandbox
  132. - Transfer workspace restrictions for sandbox plans
  133. - Proper billing service integration
  134. """
  135. # Arrange: Setup sandbox plan mock with proper config
  136. tenant_id = self._create_test_tenant_id()
  137. with patch("services.feature_service.dify_config") as mock_config:
  138. mock_config.BILLING_ENABLED = True
  139. mock_config.ENTERPRISE_ENABLED = False
  140. mock_config.CAN_REPLACE_LOGO = False
  141. mock_config.MODEL_LB_ENABLED = False
  142. mock_config.DATASET_OPERATOR_ENABLED = False
  143. mock_config.EDUCATION_ENABLED = False
  144. # Set mock return value inside the patch context
  145. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  146. "enabled": True,
  147. "subscription": {"plan": "sandbox", "interval": "monthly", "education": False},
  148. "members": {"size": 1, "limit": 3},
  149. "apps": {"size": 1, "limit": 5},
  150. "vector_space": {"size": 1, "limit": 2},
  151. "documents_upload_quota": {"size": 5, "limit": 20},
  152. "annotation_quota_limit": {"size": 2, "limit": 10},
  153. "docs_processing": "standard",
  154. "can_replace_logo": False,
  155. "model_load_balancing_enabled": False,
  156. "knowledge_rate_limit": {"limit": 10},
  157. }
  158. # Act: Execute the method under test
  159. result = FeatureService.get_features(tenant_id)
  160. # Assert: Verify sandbox-specific limitations
  161. assert result.billing.subscription.plan == "sandbox"
  162. assert result.education.activated is False
  163. # Verify sandbox limitations
  164. assert result.members.size == 1
  165. assert result.members.limit == 3
  166. assert result.apps.size == 1
  167. assert result.apps.limit == 5
  168. assert result.vector_space.size == 1
  169. assert result.vector_space.limit == 2
  170. assert result.documents_upload_quota.size == 5
  171. assert result.documents_upload_quota.limit == 20
  172. assert result.annotation_quota_limit.size == 2
  173. assert result.annotation_quota_limit.limit == 10
  174. # Verify sandbox-specific restrictions
  175. assert result.webapp_copyright_enabled is False
  176. assert result.is_allow_transfer_workspace is False
  177. assert result.can_replace_logo is False
  178. assert result.model_load_balancing_enabled is False
  179. assert result.docs_processing == "standard"
  180. assert result.knowledge_rate_limit == 10
  181. # Verify mock interactions
  182. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  183. def test_get_knowledge_rate_limit_success(self, db_session_with_containers, mock_external_service_dependencies):
  184. """
  185. Test successful knowledge rate limit retrieval with billing enabled.
  186. This test verifies:
  187. - Proper knowledge rate limit model creation
  188. - Correct integration with billing service
  189. - Proper rate limit configuration
  190. - Return value correctness and structure
  191. """
  192. # Arrange: Setup test data with proper config
  193. tenant_id = self._create_test_tenant_id()
  194. with patch("services.feature_service.dify_config") as mock_config:
  195. mock_config.BILLING_ENABLED = True
  196. # Act: Execute the method under test
  197. result = FeatureService.get_knowledge_rate_limit(tenant_id)
  198. # Assert: Verify the expected outcomes
  199. assert result is not None
  200. assert isinstance(result, KnowledgeRateLimitModel)
  201. # Verify rate limit configuration
  202. assert result.enabled is True
  203. assert result.limit == 100
  204. assert result.subscription_plan == "pro"
  205. # Verify mock interactions
  206. mock_external_service_dependencies["billing_service"].get_knowledge_rate_limit.assert_called_once_with(
  207. tenant_id
  208. )
  209. def test_get_system_features_success(self, db_session_with_containers, mock_external_service_dependencies):
  210. """
  211. Test successful system features retrieval with enterprise and marketplace enabled.
  212. This test verifies:
  213. - Proper system feature model creation
  214. - Correct integration with enterprise service
  215. - Proper marketplace configuration
  216. - Return value correctness and structure
  217. """
  218. # Arrange: Setup test data with proper config
  219. tenant_id = self._create_test_tenant_id()
  220. with patch("services.feature_service.dify_config") as mock_config:
  221. mock_config.ENTERPRISE_ENABLED = True
  222. mock_config.MARKETPLACE_ENABLED = True
  223. mock_config.ENABLE_EMAIL_CODE_LOGIN = True
  224. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  225. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  226. mock_config.ALLOW_REGISTER = False
  227. mock_config.ALLOW_CREATE_WORKSPACE = False
  228. mock_config.MAIL_TYPE = "smtp"
  229. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  230. # Act: Execute the method under test
  231. result = FeatureService.get_system_features()
  232. # Assert: Verify the expected outcomes
  233. assert result is not None
  234. assert isinstance(result, SystemFeatureModel)
  235. # Verify enterprise features
  236. assert result.branding.enabled is True
  237. assert result.webapp_auth.enabled is True
  238. assert result.enable_change_email is False
  239. # Verify SSO configuration
  240. assert result.sso_enforced_for_signin is True
  241. assert result.sso_enforced_for_signin_protocol == "saml"
  242. # Verify authentication settings
  243. assert result.enable_email_code_login is True
  244. assert result.enable_email_password_login is False
  245. assert result.is_allow_register is False
  246. assert result.is_allow_create_workspace is False
  247. # Verify branding configuration
  248. assert result.branding.application_title == "Test Enterprise"
  249. assert result.branding.login_page_logo == "https://example.com/logo.png"
  250. assert result.branding.workspace_logo == "https://example.com/workspace.png"
  251. assert result.branding.favicon == "https://example.com/favicon.ico"
  252. # Verify webapp auth configuration
  253. assert result.webapp_auth.allow_sso is True
  254. assert result.webapp_auth.allow_email_code_login is True
  255. assert result.webapp_auth.allow_email_password_login is False
  256. assert result.webapp_auth.sso_config.protocol == "oidc"
  257. # Verify license configuration
  258. assert result.license.status.value == "active"
  259. assert result.license.expired_at == "2025-12-31"
  260. assert result.license.workspaces.enabled is True
  261. assert result.license.workspaces.limit == 5
  262. assert result.license.workspaces.size == 2
  263. # Verify plugin installation permission
  264. assert result.plugin_installation_permission.plugin_installation_scope == "official_only"
  265. assert result.plugin_installation_permission.restrict_to_marketplace_only is True
  266. # Verify marketplace configuration
  267. assert result.enable_marketplace is True
  268. # Verify mock interactions
  269. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  270. def test_get_system_features_basic_config(self, db_session_with_containers, mock_external_service_dependencies):
  271. """
  272. Test system features retrieval with basic configuration (no enterprise).
  273. This test verifies:
  274. - Proper system feature model creation without enterprise
  275. - Correct environment variable handling
  276. - Default configuration values
  277. - Return value correctness and structure
  278. """
  279. # Arrange: Setup basic config mock (no enterprise)
  280. with patch("services.feature_service.dify_config") as mock_config:
  281. mock_config.ENTERPRISE_ENABLED = False
  282. mock_config.MARKETPLACE_ENABLED = False
  283. mock_config.ENABLE_EMAIL_CODE_LOGIN = True
  284. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  285. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  286. mock_config.ALLOW_REGISTER = True
  287. mock_config.ALLOW_CREATE_WORKSPACE = True
  288. mock_config.MAIL_TYPE = "smtp"
  289. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  290. # Act: Execute the method under test
  291. result = FeatureService.get_system_features()
  292. # Assert: Verify the expected outcomes
  293. assert result is not None
  294. assert isinstance(result, SystemFeatureModel)
  295. # Verify basic configuration
  296. assert result.branding.enabled is False
  297. assert result.webapp_auth.enabled is False
  298. assert result.enable_change_email is True
  299. # Verify authentication settings from config
  300. assert result.enable_email_code_login is True
  301. assert result.enable_email_password_login is True
  302. assert result.enable_social_oauth_login is False
  303. assert result.is_allow_register is True
  304. assert result.is_allow_create_workspace is True
  305. assert result.is_email_setup is True
  306. # Verify marketplace configuration
  307. assert result.enable_marketplace is False
  308. # Verify plugin package size (uses default value from dify_config)
  309. assert result.max_plugin_package_size == 15728640
  310. def test_get_features_billing_disabled(self, db_session_with_containers, mock_external_service_dependencies):
  311. """
  312. Test feature retrieval when billing is disabled.
  313. This test verifies:
  314. - Proper feature model creation without billing
  315. - Correct environment variable handling
  316. - Default configuration values
  317. - Return value correctness and structure
  318. """
  319. # Arrange: Setup billing disabled mock
  320. with patch("services.feature_service.dify_config") as mock_config:
  321. mock_config.BILLING_ENABLED = False
  322. mock_config.ENTERPRISE_ENABLED = False
  323. mock_config.CAN_REPLACE_LOGO = True
  324. mock_config.MODEL_LB_ENABLED = True
  325. mock_config.DATASET_OPERATOR_ENABLED = True
  326. mock_config.EDUCATION_ENABLED = True
  327. tenant_id = self._create_test_tenant_id()
  328. # Act: Execute the method under test
  329. result = FeatureService.get_features(tenant_id)
  330. # Assert: Verify the expected outcomes
  331. assert result is not None
  332. assert isinstance(result, FeatureModel)
  333. # Verify billing is disabled
  334. assert result.billing.enabled is False
  335. # Verify environment-based features
  336. assert result.can_replace_logo is True
  337. assert result.model_load_balancing_enabled is True
  338. assert result.dataset_operator_enabled is True
  339. assert result.education.enabled is True
  340. # Verify default limitations
  341. assert result.members.size == 0
  342. assert result.members.limit == 1
  343. assert result.apps.size == 0
  344. assert result.apps.limit == 10
  345. assert result.vector_space.size == 0
  346. assert result.vector_space.limit == 5
  347. assert result.documents_upload_quota.size == 0
  348. assert result.documents_upload_quota.limit == 50
  349. assert result.annotation_quota_limit.size == 0
  350. assert result.annotation_quota_limit.limit == 10
  351. assert result.knowledge_rate_limit == 10
  352. assert result.docs_processing == "standard"
  353. # Verify no enterprise features
  354. assert result.workspace_members.enabled is False
  355. assert result.webapp_copyright_enabled is False
  356. def test_get_knowledge_rate_limit_billing_disabled(
  357. self, db_session_with_containers, mock_external_service_dependencies
  358. ):
  359. """
  360. Test knowledge rate limit retrieval when billing is disabled.
  361. This test verifies:
  362. - Proper knowledge rate limit model creation without billing
  363. - Default rate limit configuration
  364. - Return value correctness and structure
  365. """
  366. # Arrange: Setup billing disabled mock
  367. with patch("services.feature_service.dify_config") as mock_config:
  368. mock_config.BILLING_ENABLED = False
  369. tenant_id = self._create_test_tenant_id()
  370. # Act: Execute the method under test
  371. result = FeatureService.get_knowledge_rate_limit(tenant_id)
  372. # Assert: Verify the expected outcomes
  373. assert result is not None
  374. assert isinstance(result, KnowledgeRateLimitModel)
  375. # Verify default configuration
  376. assert result.enabled is False
  377. assert result.limit == 10
  378. assert result.subscription_plan == "" # Empty string when billing is disabled
  379. # Verify no billing service calls
  380. mock_external_service_dependencies["billing_service"].get_knowledge_rate_limit.assert_not_called()
  381. def test_get_features_enterprise_only(self, db_session_with_containers, mock_external_service_dependencies):
  382. """
  383. Test feature retrieval with enterprise enabled but billing disabled.
  384. This test verifies:
  385. - Proper feature model creation with enterprise only
  386. - Correct enterprise service integration
  387. - Proper workspace member handling
  388. - Return value correctness and structure
  389. """
  390. # Arrange: Setup enterprise only mock
  391. with patch("services.feature_service.dify_config") as mock_config:
  392. mock_config.BILLING_ENABLED = False
  393. mock_config.ENTERPRISE_ENABLED = True
  394. mock_config.CAN_REPLACE_LOGO = False
  395. mock_config.MODEL_LB_ENABLED = False
  396. mock_config.DATASET_OPERATOR_ENABLED = False
  397. mock_config.EDUCATION_ENABLED = False
  398. tenant_id = self._create_test_tenant_id()
  399. # Act: Execute the method under test
  400. result = FeatureService.get_features(tenant_id)
  401. # Assert: Verify the expected outcomes
  402. assert result is not None
  403. assert isinstance(result, FeatureModel)
  404. # Verify billing is disabled
  405. assert result.billing.enabled is False
  406. # Verify enterprise features
  407. assert result.webapp_copyright_enabled is True
  408. # Verify workspace members from enterprise
  409. assert result.workspace_members.enabled is True
  410. assert result.workspace_members.size == 5
  411. assert result.workspace_members.limit == 10
  412. # Verify environment-based features
  413. assert result.can_replace_logo is False
  414. assert result.model_load_balancing_enabled is False
  415. assert result.dataset_operator_enabled is False
  416. assert result.education.enabled is False
  417. # Verify default limitations
  418. assert result.members.size == 0
  419. assert result.members.limit == 1
  420. assert result.apps.size == 0
  421. assert result.apps.limit == 10
  422. assert result.vector_space.size == 0
  423. assert result.vector_space.limit == 5
  424. # Verify mock interactions
  425. mock_external_service_dependencies["enterprise_service"].get_workspace_info.assert_called_once_with(
  426. tenant_id
  427. )
  428. mock_external_service_dependencies["billing_service"].get_info.assert_not_called()
  429. def test_get_system_features_enterprise_disabled(
  430. self, db_session_with_containers, mock_external_service_dependencies
  431. ):
  432. """
  433. Test system features retrieval when enterprise is disabled.
  434. This test verifies:
  435. - Proper system feature model creation without enterprise
  436. - Correct environment variable handling
  437. - Default configuration values
  438. - Return value correctness and structure
  439. """
  440. # Arrange: Setup enterprise disabled mock
  441. with patch("services.feature_service.dify_config") as mock_config:
  442. mock_config.ENTERPRISE_ENABLED = False
  443. mock_config.MARKETPLACE_ENABLED = True
  444. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  445. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  446. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = True
  447. mock_config.ALLOW_REGISTER = False
  448. mock_config.ALLOW_CREATE_WORKSPACE = False
  449. mock_config.MAIL_TYPE = None
  450. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 50
  451. # Act: Execute the method under test
  452. result = FeatureService.get_system_features()
  453. # Assert: Verify the expected outcomes
  454. assert result is not None
  455. assert isinstance(result, SystemFeatureModel)
  456. # Verify enterprise features are disabled
  457. assert result.branding.enabled is False
  458. assert result.webapp_auth.enabled is False
  459. assert result.enable_change_email is True
  460. # Verify authentication settings from config
  461. assert result.enable_email_code_login is False
  462. assert result.enable_email_password_login is True
  463. assert result.enable_social_oauth_login is True
  464. assert result.is_allow_register is False
  465. assert result.is_allow_create_workspace is False
  466. assert result.is_email_setup is False
  467. # Verify marketplace configuration
  468. assert result.enable_marketplace is True
  469. # Verify plugin package size (uses default value from dify_config)
  470. assert result.max_plugin_package_size == 15728640
  471. # Verify default license status
  472. assert result.license.status.value == "none"
  473. assert result.license.expired_at == ""
  474. assert result.license.workspaces.enabled is False
  475. # Verify no enterprise service calls
  476. mock_external_service_dependencies["enterprise_service"].get_info.assert_not_called()
  477. def test_get_features_no_tenant_id(self, db_session_with_containers, mock_external_service_dependencies):
  478. """
  479. Test feature retrieval without tenant ID (billing disabled).
  480. This test verifies:
  481. - Proper feature model creation without tenant ID
  482. - Correct handling when billing is disabled
  483. - Default configuration values
  484. - Return value correctness and structure
  485. """
  486. # Arrange: Setup no tenant ID scenario
  487. with patch("services.feature_service.dify_config") as mock_config:
  488. mock_config.BILLING_ENABLED = True
  489. mock_config.ENTERPRISE_ENABLED = False
  490. mock_config.CAN_REPLACE_LOGO = True
  491. mock_config.MODEL_LB_ENABLED = False
  492. mock_config.DATASET_OPERATOR_ENABLED = True
  493. mock_config.EDUCATION_ENABLED = False
  494. # Act: Execute the method under test
  495. result = FeatureService.get_features("")
  496. # Assert: Verify the expected outcomes
  497. assert result is not None
  498. assert isinstance(result, FeatureModel)
  499. # Verify billing is disabled due to no tenant ID
  500. assert result.billing.enabled is False
  501. # Verify environment-based features
  502. assert result.can_replace_logo is True
  503. assert result.model_load_balancing_enabled is False
  504. assert result.dataset_operator_enabled is True
  505. assert result.education.enabled is False
  506. # Verify default limitations
  507. assert result.members.size == 0
  508. assert result.members.limit == 1
  509. assert result.apps.size == 0
  510. assert result.apps.limit == 10
  511. assert result.vector_space.size == 0
  512. assert result.vector_space.limit == 5
  513. # Verify no billing service calls
  514. mock_external_service_dependencies["billing_service"].get_info.assert_not_called()
  515. def test_get_features_partial_billing_info(self, db_session_with_containers, mock_external_service_dependencies):
  516. """
  517. Test feature retrieval with partial billing information.
  518. This test verifies:
  519. - Proper handling of partial billing data
  520. - Correct fallback to default values
  521. - Proper billing service integration
  522. - Return value correctness and structure
  523. """
  524. # Arrange: Setup partial billing info mock with proper config
  525. tenant_id = self._create_test_tenant_id()
  526. with patch("services.feature_service.dify_config") as mock_config:
  527. mock_config.BILLING_ENABLED = True
  528. mock_config.ENTERPRISE_ENABLED = False
  529. mock_config.CAN_REPLACE_LOGO = True
  530. mock_config.MODEL_LB_ENABLED = False
  531. mock_config.DATASET_OPERATOR_ENABLED = True
  532. mock_config.EDUCATION_ENABLED = False
  533. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  534. "enabled": True,
  535. "subscription": {"plan": "basic", "interval": "yearly"},
  536. # Missing members, apps, vector_space, etc.
  537. }
  538. # Act: Execute the method under test
  539. result = FeatureService.get_features(tenant_id)
  540. # Assert: Verify the expected outcomes
  541. assert result is not None
  542. assert isinstance(result, FeatureModel)
  543. # Verify billing features
  544. assert result.billing.enabled is True
  545. assert result.billing.subscription.plan == "basic"
  546. assert result.billing.subscription.interval == "yearly"
  547. # Verify default values for missing billing info
  548. assert result.members.size == 0
  549. assert result.members.limit == 1
  550. assert result.apps.size == 0
  551. assert result.apps.limit == 10
  552. assert result.vector_space.size == 0
  553. assert result.vector_space.limit == 5
  554. assert result.documents_upload_quota.size == 0
  555. assert result.documents_upload_quota.limit == 50
  556. assert result.annotation_quota_limit.size == 0
  557. assert result.annotation_quota_limit.limit == 10
  558. assert result.knowledge_rate_limit == 10
  559. assert result.docs_processing == "standard"
  560. # Verify basic plan restrictions (non-sandbox plans have webapp copyright enabled)
  561. assert result.webapp_copyright_enabled is True
  562. assert result.is_allow_transfer_workspace is True
  563. # Verify mock interactions
  564. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  565. def test_get_features_edge_case_vector_space(self, db_session_with_containers, mock_external_service_dependencies):
  566. """
  567. Test feature retrieval with edge case vector space configuration.
  568. This test verifies:
  569. - Proper handling of vector space quota limits
  570. - Correct integration with billing service
  571. - Proper fallback to default values
  572. - Return value correctness and structure
  573. """
  574. # Arrange: Setup edge case vector space mock with proper config
  575. tenant_id = self._create_test_tenant_id()
  576. with patch("services.feature_service.dify_config") as mock_config:
  577. mock_config.BILLING_ENABLED = True
  578. mock_config.ENTERPRISE_ENABLED = False
  579. mock_config.CAN_REPLACE_LOGO = True
  580. mock_config.MODEL_LB_ENABLED = False
  581. mock_config.DATASET_OPERATOR_ENABLED = True
  582. mock_config.EDUCATION_ENABLED = False
  583. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  584. "enabled": True,
  585. "subscription": {"plan": "pro", "interval": "monthly"},
  586. "vector_space": {"size": 0, "limit": 0},
  587. "apps": {"size": 5, "limit": 10},
  588. }
  589. # Act: Execute the method under test
  590. result = FeatureService.get_features(tenant_id)
  591. # Assert: Verify the expected outcomes
  592. assert result is not None
  593. assert isinstance(result, FeatureModel)
  594. # Verify vector space configuration
  595. assert result.vector_space.size == 0
  596. assert result.vector_space.limit == 0
  597. # Verify apps configuration
  598. assert result.apps.size == 5
  599. assert result.apps.limit == 10
  600. # Verify pro plan features
  601. assert result.webapp_copyright_enabled is True
  602. assert result.is_allow_transfer_workspace is True
  603. # Verify default values for missing billing info
  604. assert result.members.size == 0
  605. assert result.members.limit == 1
  606. assert result.documents_upload_quota.size == 0
  607. assert result.documents_upload_quota.limit == 50
  608. assert result.annotation_quota_limit.size == 0
  609. assert result.annotation_quota_limit.limit == 10
  610. assert result.knowledge_rate_limit == 10
  611. assert result.docs_processing == "standard"
  612. # Verify mock interactions
  613. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  614. def test_get_system_features_edge_case_webapp_auth(
  615. self, db_session_with_containers, mock_external_service_dependencies
  616. ):
  617. """
  618. Test system features retrieval with edge case webapp auth configuration.
  619. This test verifies:
  620. - Proper handling of webapp auth configuration
  621. - Correct enterprise service integration
  622. - Proper fallback to default values
  623. - Return value correctness and structure
  624. """
  625. # Arrange: Setup edge case webapp auth mock with proper config
  626. with patch("services.feature_service.dify_config") as mock_config:
  627. mock_config.ENTERPRISE_ENABLED = True
  628. mock_config.MARKETPLACE_ENABLED = False
  629. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  630. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  631. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  632. mock_config.ALLOW_REGISTER = False
  633. mock_config.ALLOW_CREATE_WORKSPACE = False
  634. mock_config.MAIL_TYPE = "smtp"
  635. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  636. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  637. "WebAppAuth": {"allowSso": False, "allowEmailCodeLogin": True, "allowEmailPasswordLogin": False}
  638. }
  639. # Act: Execute the method under test
  640. result = FeatureService.get_system_features()
  641. # Assert: Verify the expected outcomes
  642. assert result is not None
  643. assert isinstance(result, SystemFeatureModel)
  644. # Verify webapp auth configuration
  645. assert result.webapp_auth.allow_sso is False
  646. assert result.webapp_auth.allow_email_code_login is True
  647. assert result.webapp_auth.allow_email_password_login is False
  648. assert result.webapp_auth.sso_config.protocol == ""
  649. # Verify enterprise features
  650. assert result.branding.enabled is True
  651. assert result.webapp_auth.enabled is True
  652. assert result.enable_change_email is False
  653. # Verify default values for missing enterprise info
  654. assert result.sso_enforced_for_signin is False
  655. assert result.sso_enforced_for_signin_protocol == ""
  656. assert result.enable_email_code_login is False
  657. assert result.enable_email_password_login is True
  658. assert result.is_allow_register is False
  659. assert result.is_allow_create_workspace is False
  660. # Verify mock interactions
  661. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  662. def test_get_features_edge_case_members_quota(self, db_session_with_containers, mock_external_service_dependencies):
  663. """
  664. Test feature retrieval with edge case members quota configuration.
  665. This test verifies:
  666. - Proper handling of members quota limits
  667. - Correct integration with billing service
  668. - Proper fallback to default values
  669. - Return value correctness and structure
  670. """
  671. # Arrange: Setup edge case members quota mock with proper config
  672. tenant_id = self._create_test_tenant_id()
  673. with patch("services.feature_service.dify_config") as mock_config:
  674. mock_config.BILLING_ENABLED = True
  675. mock_config.ENTERPRISE_ENABLED = False
  676. mock_config.CAN_REPLACE_LOGO = True
  677. mock_config.MODEL_LB_ENABLED = False
  678. mock_config.DATASET_OPERATOR_ENABLED = True
  679. mock_config.EDUCATION_ENABLED = False
  680. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  681. "enabled": True,
  682. "subscription": {"plan": "basic", "interval": "yearly"},
  683. "members": {"size": 10, "limit": 10},
  684. "vector_space": {"size": 3, "limit": 5},
  685. }
  686. # Act: Execute the method under test
  687. result = FeatureService.get_features(tenant_id)
  688. # Assert: Verify the expected outcomes
  689. assert result is not None
  690. assert isinstance(result, FeatureModel)
  691. # Verify members configuration
  692. assert result.members.size == 10
  693. assert result.members.limit == 10
  694. # Verify vector space configuration
  695. assert result.vector_space.size == 3
  696. assert result.vector_space.limit == 5
  697. # Verify basic plan features (non-sandbox plans have webapp copyright enabled)
  698. assert result.webapp_copyright_enabled is True
  699. assert result.is_allow_transfer_workspace is True
  700. # Verify default values for missing billing info
  701. assert result.apps.size == 0
  702. assert result.apps.limit == 10
  703. assert result.documents_upload_quota.size == 0
  704. assert result.documents_upload_quota.limit == 50
  705. assert result.annotation_quota_limit.size == 0
  706. assert result.annotation_quota_limit.limit == 10
  707. assert result.knowledge_rate_limit == 10
  708. assert result.docs_processing == "standard"
  709. # Verify mock interactions
  710. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  711. def test_plugin_installation_permission_scopes(
  712. self, db_session_with_containers, mock_external_service_dependencies
  713. ):
  714. """
  715. Test system features retrieval with different plugin installation permission scopes.
  716. This test verifies:
  717. - Proper handling of different plugin installation scopes
  718. - Correct enterprise service integration
  719. - Proper permission configuration
  720. - Return value correctness and structure
  721. """
  722. # Test case 1: Official only scope
  723. with patch("services.feature_service.dify_config") as mock_config:
  724. mock_config.ENTERPRISE_ENABLED = True
  725. mock_config.MARKETPLACE_ENABLED = False
  726. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  727. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  728. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  729. mock_config.ALLOW_REGISTER = False
  730. mock_config.ALLOW_CREATE_WORKSPACE = False
  731. mock_config.MAIL_TYPE = "smtp"
  732. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  733. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  734. "PluginInstallationPermission": {
  735. "pluginInstallationScope": "official_only",
  736. "restrictToMarketplaceOnly": True,
  737. }
  738. }
  739. result = FeatureService.get_system_features()
  740. assert result.plugin_installation_permission.plugin_installation_scope == "official_only"
  741. assert result.plugin_installation_permission.restrict_to_marketplace_only is True
  742. # Test case 2: All plugins scope
  743. with patch("services.feature_service.dify_config") as mock_config:
  744. mock_config.ENTERPRISE_ENABLED = True
  745. mock_config.MARKETPLACE_ENABLED = False
  746. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  747. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  748. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  749. mock_config.ALLOW_REGISTER = False
  750. mock_config.ALLOW_CREATE_WORKSPACE = False
  751. mock_config.MAIL_TYPE = "smtp"
  752. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  753. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  754. "PluginInstallationPermission": {"pluginInstallationScope": "all", "restrictToMarketplaceOnly": False}
  755. }
  756. result = FeatureService.get_system_features()
  757. assert result.plugin_installation_permission.plugin_installation_scope == "all"
  758. assert result.plugin_installation_permission.restrict_to_marketplace_only is False
  759. # Test case 3: Specific partners scope
  760. with patch("services.feature_service.dify_config") as mock_config:
  761. mock_config.ENTERPRISE_ENABLED = True
  762. mock_config.MARKETPLACE_ENABLED = False
  763. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  764. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  765. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  766. mock_config.ALLOW_REGISTER = False
  767. mock_config.ALLOW_CREATE_WORKSPACE = False
  768. mock_config.MAIL_TYPE = "smtp"
  769. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  770. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  771. "PluginInstallationPermission": {
  772. "pluginInstallationScope": "official_and_specific_partners",
  773. "restrictToMarketplaceOnly": False,
  774. }
  775. }
  776. result = FeatureService.get_system_features()
  777. assert result.plugin_installation_permission.plugin_installation_scope == "official_and_specific_partners"
  778. assert result.plugin_installation_permission.restrict_to_marketplace_only is False
  779. # Test case 4: None scope
  780. with patch("services.feature_service.dify_config") as mock_config:
  781. mock_config.ENTERPRISE_ENABLED = True
  782. mock_config.MARKETPLACE_ENABLED = False
  783. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  784. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  785. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  786. mock_config.ALLOW_REGISTER = False
  787. mock_config.ALLOW_CREATE_WORKSPACE = False
  788. mock_config.MAIL_TYPE = "smtp"
  789. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  790. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  791. "PluginInstallationPermission": {"pluginInstallationScope": "none", "restrictToMarketplaceOnly": True}
  792. }
  793. result = FeatureService.get_system_features()
  794. assert result.plugin_installation_permission.plugin_installation_scope == "none"
  795. assert result.plugin_installation_permission.restrict_to_marketplace_only is True
  796. def test_get_features_workspace_members_missing(
  797. self, db_session_with_containers, mock_external_service_dependencies
  798. ):
  799. """
  800. Test feature retrieval when workspace members info is missing from enterprise.
  801. This test verifies:
  802. - Proper handling of missing workspace members data
  803. - Correct enterprise service integration
  804. - Proper fallback to default values
  805. - Return value correctness and structure
  806. """
  807. # Arrange: Setup missing workspace members mock
  808. tenant_id = self._create_test_tenant_id()
  809. mock_external_service_dependencies["enterprise_service"].get_workspace_info.return_value = {
  810. # Missing WorkspaceMembers key
  811. }
  812. with patch("services.feature_service.dify_config") as mock_config:
  813. mock_config.BILLING_ENABLED = False
  814. mock_config.ENTERPRISE_ENABLED = True
  815. # Act: Execute the method under test
  816. result = FeatureService.get_features(tenant_id)
  817. # Assert: Verify the expected outcomes
  818. assert result is not None
  819. assert isinstance(result, FeatureModel)
  820. # Verify workspace members use default values
  821. assert result.workspace_members.enabled is False
  822. assert result.workspace_members.size == 0
  823. assert result.workspace_members.limit == 0
  824. # Verify enterprise features
  825. assert result.webapp_copyright_enabled is True
  826. # Verify mock interactions
  827. mock_external_service_dependencies["enterprise_service"].get_workspace_info.assert_called_once_with(
  828. tenant_id
  829. )
  830. def test_get_system_features_license_inactive(self, db_session_with_containers, mock_external_service_dependencies):
  831. """
  832. Test system features retrieval with inactive license.
  833. This test verifies:
  834. - Proper handling of inactive license status
  835. - Correct enterprise service integration
  836. - Proper license status handling
  837. - Return value correctness and structure
  838. """
  839. # Arrange: Setup inactive license mock with proper config
  840. with patch("services.feature_service.dify_config") as mock_config:
  841. mock_config.ENTERPRISE_ENABLED = True
  842. mock_config.MARKETPLACE_ENABLED = False
  843. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  844. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  845. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  846. mock_config.ALLOW_REGISTER = False
  847. mock_config.ALLOW_CREATE_WORKSPACE = False
  848. mock_config.MAIL_TYPE = "smtp"
  849. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  850. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  851. "License": {
  852. "status": "inactive",
  853. "expiredAt": "",
  854. "workspaces": {"enabled": False, "limit": 0, "used": 0},
  855. }
  856. }
  857. # Act: Execute the method under test
  858. result = FeatureService.get_system_features()
  859. # Assert: Verify the expected outcomes
  860. assert result is not None
  861. assert isinstance(result, SystemFeatureModel)
  862. # Verify license status
  863. assert result.license.status == "inactive"
  864. assert result.license.expired_at == ""
  865. assert result.license.workspaces.enabled is False
  866. assert result.license.workspaces.size == 0
  867. assert result.license.workspaces.limit == 0
  868. # Verify enterprise features
  869. assert result.branding.enabled is True
  870. assert result.webapp_auth.enabled is True
  871. assert result.enable_change_email is False
  872. # Verify mock interactions
  873. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  874. def test_get_system_features_partial_enterprise_info(
  875. self, db_session_with_containers, mock_external_service_dependencies
  876. ):
  877. """
  878. Test system features retrieval with partial enterprise information.
  879. This test verifies:
  880. - Proper handling of partial enterprise data
  881. - Correct fallback to default values
  882. - Proper enterprise service integration
  883. - Return value correctness and structure
  884. """
  885. # Arrange: Setup partial enterprise info mock with proper config
  886. with patch("services.feature_service.dify_config") as mock_config:
  887. mock_config.ENTERPRISE_ENABLED = True
  888. mock_config.MARKETPLACE_ENABLED = False
  889. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  890. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  891. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  892. mock_config.ALLOW_REGISTER = False
  893. mock_config.ALLOW_CREATE_WORKSPACE = False
  894. mock_config.MAIL_TYPE = "smtp"
  895. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  896. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  897. "SSOEnforcedForSignin": True,
  898. "Branding": {"applicationTitle": "Partial Enterprise"},
  899. # Missing WebAppAuth, License, PluginInstallationPermission, etc.
  900. }
  901. # Act: Execute the method under test
  902. result = FeatureService.get_system_features()
  903. # Assert: Verify the expected outcomes
  904. assert result is not None
  905. assert isinstance(result, SystemFeatureModel)
  906. # Verify enterprise features
  907. assert result.branding.enabled is True
  908. assert result.webapp_auth.enabled is True
  909. assert result.enable_change_email is False
  910. # Verify SSO configuration
  911. assert result.sso_enforced_for_signin is True
  912. assert result.sso_enforced_for_signin_protocol == ""
  913. # Verify branding configuration (partial)
  914. assert result.branding.application_title == "Partial Enterprise"
  915. assert result.branding.login_page_logo == ""
  916. assert result.branding.workspace_logo == ""
  917. assert result.branding.favicon == ""
  918. # Verify default values for missing enterprise info
  919. assert result.webapp_auth.allow_sso is False
  920. assert result.webapp_auth.allow_email_code_login is False
  921. assert result.webapp_auth.allow_email_password_login is False
  922. assert result.webapp_auth.sso_config.protocol == ""
  923. # Verify default license status
  924. assert result.license.status == "none"
  925. assert result.license.expired_at == ""
  926. assert result.license.workspaces.enabled is False
  927. # Verify default plugin installation permission
  928. assert result.plugin_installation_permission.plugin_installation_scope == "all"
  929. assert result.plugin_installation_permission.restrict_to_marketplace_only is False
  930. # Verify mock interactions
  931. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  932. def test_get_features_edge_case_limits(self, db_session_with_containers, mock_external_service_dependencies):
  933. """
  934. Test feature retrieval with edge case limit values.
  935. This test verifies:
  936. - Proper handling of zero and negative limits
  937. - Correct handling of very large limits
  938. - Proper fallback to default values
  939. - Return value correctness and structure
  940. """
  941. # Arrange: Setup edge case limits mock with proper config
  942. tenant_id = self._create_test_tenant_id()
  943. with patch("services.feature_service.dify_config") as mock_config:
  944. mock_config.BILLING_ENABLED = True
  945. mock_config.ENTERPRISE_ENABLED = False
  946. mock_config.CAN_REPLACE_LOGO = True
  947. mock_config.MODEL_LB_ENABLED = False
  948. mock_config.DATASET_OPERATOR_ENABLED = True
  949. mock_config.EDUCATION_ENABLED = False
  950. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  951. "enabled": True,
  952. "subscription": {"plan": "enterprise", "interval": "yearly"},
  953. "members": {"size": 0, "limit": 0},
  954. "apps": {"size": 0, "limit": -1},
  955. "vector_space": {"size": 0, "limit": 999999},
  956. "documents_upload_quota": {"size": 0, "limit": 0},
  957. "annotation_quota_limit": {"size": 0, "limit": 1},
  958. }
  959. # Act: Execute the method under test
  960. result = FeatureService.get_features(tenant_id)
  961. # Assert: Verify the expected outcomes
  962. assert result is not None
  963. assert isinstance(result, FeatureModel)
  964. # Verify edge case limits
  965. assert result.members.size == 0
  966. assert result.members.limit == 0
  967. assert result.apps.size == 0
  968. assert result.apps.limit == -1
  969. assert result.vector_space.size == 0
  970. assert result.vector_space.limit == 999999
  971. assert result.documents_upload_quota.size == 0
  972. assert result.documents_upload_quota.limit == 0
  973. assert result.annotation_quota_limit.size == 0
  974. assert result.annotation_quota_limit.limit == 1
  975. # Verify enterprise plan features
  976. assert result.webapp_copyright_enabled is True
  977. assert result.is_allow_transfer_workspace is True
  978. # Verify mock interactions
  979. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  980. def test_get_system_features_edge_case_protocols(
  981. self, db_session_with_containers, mock_external_service_dependencies
  982. ):
  983. """
  984. Test system features retrieval with edge case protocol values.
  985. This test verifies:
  986. - Proper handling of empty protocol strings
  987. - Correct handling of special protocol values
  988. - Proper fallback to default values
  989. - Return value correctness and structure
  990. """
  991. # Arrange: Setup edge case protocols mock with proper config
  992. with patch("services.feature_service.dify_config") as mock_config:
  993. mock_config.ENTERPRISE_ENABLED = True
  994. mock_config.MARKETPLACE_ENABLED = False
  995. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  996. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  997. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  998. mock_config.ALLOW_REGISTER = False
  999. mock_config.ALLOW_CREATE_WORKSPACE = False
  1000. mock_config.MAIL_TYPE = "smtp"
  1001. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  1002. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  1003. "SSOEnforcedForSigninProtocol": "",
  1004. "SSOEnforcedForWebProtocol": " ",
  1005. "WebAppAuth": {"allowSso": True, "allowEmailCodeLogin": False, "allowEmailPasswordLogin": True},
  1006. }
  1007. # Act: Execute the method under test
  1008. result = FeatureService.get_system_features()
  1009. # Assert: Verify the expected outcomes
  1010. assert result is not None
  1011. assert isinstance(result, SystemFeatureModel)
  1012. # Verify edge case protocols
  1013. assert result.sso_enforced_for_signin_protocol == ""
  1014. assert result.webapp_auth.sso_config.protocol == " "
  1015. # Verify webapp auth configuration
  1016. assert result.webapp_auth.allow_sso is True
  1017. assert result.webapp_auth.allow_email_code_login is False
  1018. assert result.webapp_auth.allow_email_password_login is True
  1019. # Verify enterprise features
  1020. assert result.branding.enabled is True
  1021. assert result.webapp_auth.enabled is True
  1022. assert result.enable_change_email is False
  1023. # Verify mock interactions
  1024. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  1025. def test_get_features_edge_case_education(self, db_session_with_containers, mock_external_service_dependencies):
  1026. """
  1027. Test feature retrieval with edge case education configuration.
  1028. This test verifies:
  1029. - Proper handling of education feature flags
  1030. - Correct integration with billing service
  1031. - Proper fallback to default values
  1032. - Return value correctness and structure
  1033. """
  1034. # Arrange: Setup edge case education mock
  1035. tenant_id = self._create_test_tenant_id()
  1036. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  1037. "enabled": True,
  1038. "subscription": {"plan": "education", "interval": "semester", "education": True},
  1039. "members": {"size": 100, "limit": 200},
  1040. "apps": {"size": 50, "limit": 100},
  1041. "vector_space": {"size": 20, "limit": 50},
  1042. "documents_upload_quota": {"size": 500, "limit": 1000},
  1043. "annotation_quota_limit": {"size": 200, "limit": 500},
  1044. }
  1045. with patch("services.feature_service.dify_config") as mock_config:
  1046. mock_config.EDUCATION_ENABLED = True
  1047. # Act: Execute the method under test
  1048. result = FeatureService.get_features(tenant_id)
  1049. # Assert: Verify the expected outcomes
  1050. assert result is not None
  1051. assert isinstance(result, FeatureModel)
  1052. # Verify education features
  1053. assert result.education.enabled is True
  1054. assert result.education.activated is True
  1055. # Verify education plan limits
  1056. assert result.members.size == 100
  1057. assert result.members.limit == 200
  1058. assert result.apps.size == 50
  1059. assert result.apps.limit == 100
  1060. assert result.vector_space.size == 20
  1061. assert result.vector_space.limit == 50
  1062. assert result.documents_upload_quota.size == 500
  1063. assert result.documents_upload_quota.limit == 1000
  1064. assert result.annotation_quota_limit.size == 200
  1065. assert result.annotation_quota_limit.limit == 500
  1066. # Verify education plan features
  1067. assert result.webapp_copyright_enabled is True
  1068. assert result.is_allow_transfer_workspace is True
  1069. # Verify mock interactions
  1070. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  1071. def test_license_limitation_model_is_available(
  1072. self, db_session_with_containers, mock_external_service_dependencies
  1073. ):
  1074. """
  1075. Test LicenseLimitationModel.is_available method with various scenarios.
  1076. This test verifies:
  1077. - Proper quota availability calculation
  1078. - Correct handling of unlimited limits
  1079. - Proper handling of disabled limits
  1080. - Return value correctness for different scenarios
  1081. """
  1082. from services.feature_service import LicenseLimitationModel
  1083. # Test case 1: Limit disabled
  1084. disabled_limit = LicenseLimitationModel(enabled=False, size=5, limit=10)
  1085. assert disabled_limit.is_available(3) is True
  1086. assert disabled_limit.is_available(10) is True
  1087. # Test case 2: Unlimited limit
  1088. unlimited_limit = LicenseLimitationModel(enabled=True, size=5, limit=0)
  1089. assert unlimited_limit.is_available(3) is True
  1090. assert unlimited_limit.is_available(100) is True
  1091. # Test case 3: Available quota
  1092. available_limit = LicenseLimitationModel(enabled=True, size=5, limit=10)
  1093. assert available_limit.is_available(3) is True
  1094. assert available_limit.is_available(5) is True
  1095. assert available_limit.is_available(1) is True
  1096. # Test case 4: Insufficient quota
  1097. insufficient_limit = LicenseLimitationModel(enabled=True, size=8, limit=10)
  1098. assert insufficient_limit.is_available(3) is False
  1099. assert insufficient_limit.is_available(2) is True
  1100. assert insufficient_limit.is_available(1) is True
  1101. # Test case 5: Exact quota usage
  1102. exact_limit = LicenseLimitationModel(enabled=True, size=7, limit=10)
  1103. assert exact_limit.is_available(3) is True
  1104. assert exact_limit.is_available(3) is True
  1105. def test_get_features_workspace_members_disabled(
  1106. self, db_session_with_containers, mock_external_service_dependencies
  1107. ):
  1108. """
  1109. Test feature retrieval when workspace members are disabled in enterprise.
  1110. This test verifies:
  1111. - Proper handling of disabled workspace members
  1112. - Correct enterprise service integration
  1113. - Proper fallback to default values
  1114. - Return value correctness and structure
  1115. """
  1116. # Arrange: Setup workspace members disabled mock
  1117. tenant_id = self._create_test_tenant_id()
  1118. mock_external_service_dependencies["enterprise_service"].get_workspace_info.return_value = {
  1119. "WorkspaceMembers": {"used": 0, "limit": 0, "enabled": False}
  1120. }
  1121. with patch("services.feature_service.dify_config") as mock_config:
  1122. mock_config.BILLING_ENABLED = False
  1123. mock_config.ENTERPRISE_ENABLED = True
  1124. # Act: Execute the method under test
  1125. result = FeatureService.get_features(tenant_id)
  1126. # Assert: Verify the expected outcomes
  1127. assert result is not None
  1128. assert isinstance(result, FeatureModel)
  1129. # Verify workspace members are disabled
  1130. assert result.workspace_members.enabled is False
  1131. assert result.workspace_members.size == 0
  1132. assert result.workspace_members.limit == 0
  1133. # Verify enterprise features
  1134. assert result.webapp_copyright_enabled is True
  1135. # Verify mock interactions
  1136. mock_external_service_dependencies["enterprise_service"].get_workspace_info.assert_called_once_with(tenant_id)
  1137. def test_get_system_features_license_expired(self, db_session_with_containers, mock_external_service_dependencies):
  1138. """
  1139. Test system features retrieval with expired license.
  1140. This test verifies:
  1141. - Proper handling of expired license status
  1142. - Correct enterprise service integration
  1143. - Proper license status handling
  1144. - Return value correctness and structure
  1145. """
  1146. # Arrange: Setup expired license mock with proper config
  1147. with patch("services.feature_service.dify_config") as mock_config:
  1148. mock_config.ENTERPRISE_ENABLED = True
  1149. mock_config.MARKETPLACE_ENABLED = False
  1150. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  1151. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  1152. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  1153. mock_config.ALLOW_REGISTER = False
  1154. mock_config.ALLOW_CREATE_WORKSPACE = False
  1155. mock_config.MAIL_TYPE = "smtp"
  1156. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  1157. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  1158. "License": {
  1159. "status": "expired",
  1160. "expiredAt": "2023-12-31",
  1161. "workspaces": {"enabled": False, "limit": 0, "used": 0},
  1162. }
  1163. }
  1164. # Act: Execute the method under test
  1165. result = FeatureService.get_system_features()
  1166. # Assert: Verify the expected outcomes
  1167. assert result is not None
  1168. assert isinstance(result, SystemFeatureModel)
  1169. # Verify license status
  1170. assert result.license.status == "expired"
  1171. assert result.license.expired_at == "2023-12-31"
  1172. assert result.license.workspaces.enabled is False
  1173. assert result.license.workspaces.size == 0
  1174. assert result.license.workspaces.limit == 0
  1175. # Verify enterprise features
  1176. assert result.branding.enabled is True
  1177. assert result.webapp_auth.enabled is True
  1178. assert result.enable_change_email is False
  1179. # Verify mock interactions
  1180. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  1181. def test_get_features_edge_case_docs_processing(
  1182. self, db_session_with_containers, mock_external_service_dependencies
  1183. ):
  1184. """
  1185. Test feature retrieval with edge case document processing configuration.
  1186. This test verifies:
  1187. - Proper handling of different document processing modes
  1188. - Correct integration with billing service
  1189. - Proper fallback to default values
  1190. - Return value correctness and structure
  1191. """
  1192. # Arrange: Setup edge case docs processing mock with proper config
  1193. tenant_id = self._create_test_tenant_id()
  1194. with patch("services.feature_service.dify_config") as mock_config:
  1195. mock_config.BILLING_ENABLED = True
  1196. mock_config.ENTERPRISE_ENABLED = False
  1197. mock_config.CAN_REPLACE_LOGO = True
  1198. mock_config.MODEL_LB_ENABLED = True
  1199. mock_config.DATASET_OPERATOR_ENABLED = True
  1200. mock_config.EDUCATION_ENABLED = False
  1201. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  1202. "enabled": True,
  1203. "subscription": {"plan": "premium", "interval": "monthly"},
  1204. "docs_processing": "advanced",
  1205. "can_replace_logo": True,
  1206. "model_load_balancing_enabled": True,
  1207. }
  1208. # Act: Execute the method under test
  1209. result = FeatureService.get_features(tenant_id)
  1210. # Assert: Verify the expected outcomes
  1211. assert result is not None
  1212. assert isinstance(result, FeatureModel)
  1213. # Verify docs processing configuration
  1214. assert result.docs_processing == "advanced"
  1215. assert result.can_replace_logo is True
  1216. assert result.model_load_balancing_enabled is True
  1217. # Verify premium plan features
  1218. assert result.webapp_copyright_enabled is True
  1219. assert result.is_allow_transfer_workspace is True
  1220. # Verify default limitations (no specific billing info)
  1221. assert result.members.size == 0
  1222. assert result.members.limit == 1
  1223. assert result.apps.size == 0
  1224. assert result.apps.limit == 10
  1225. assert result.vector_space.size == 0
  1226. assert result.vector_space.limit == 5
  1227. # Verify mock interactions
  1228. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  1229. def test_get_system_features_edge_case_branding(
  1230. self, db_session_with_containers, mock_external_service_dependencies
  1231. ):
  1232. """
  1233. Test system features retrieval with edge case branding configuration.
  1234. This test verifies:
  1235. - Proper handling of partial branding information
  1236. - Correct enterprise service integration
  1237. - Proper fallback to default values
  1238. - Return value correctness and structure
  1239. """
  1240. # Arrange: Setup edge case branding mock with proper config
  1241. with patch("services.feature_service.dify_config") as mock_config:
  1242. mock_config.ENTERPRISE_ENABLED = True
  1243. mock_config.MARKETPLACE_ENABLED = False
  1244. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  1245. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  1246. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  1247. mock_config.ALLOW_REGISTER = False
  1248. mock_config.ALLOW_CREATE_WORKSPACE = False
  1249. mock_config.MAIL_TYPE = "smtp"
  1250. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  1251. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  1252. "Branding": {
  1253. "applicationTitle": "Edge Case App",
  1254. "loginPageLogo": None,
  1255. "workspaceLogo": "",
  1256. "favicon": "https://example.com/favicon.ico",
  1257. }
  1258. }
  1259. # Act: Execute the method under test
  1260. result = FeatureService.get_system_features()
  1261. # Assert: Verify the expected outcomes
  1262. assert result is not None
  1263. assert isinstance(result, SystemFeatureModel)
  1264. # Verify branding configuration (edge cases)
  1265. assert result.branding.application_title == "Edge Case App"
  1266. assert result.branding.login_page_logo is None # None value from mock
  1267. assert result.branding.workspace_logo == ""
  1268. assert result.branding.favicon == "https://example.com/favicon.ico"
  1269. # Verify enterprise features
  1270. assert result.branding.enabled is True
  1271. assert result.webapp_auth.enabled is True
  1272. assert result.enable_change_email is False
  1273. # Verify default values for missing enterprise info
  1274. assert result.sso_enforced_for_signin is False
  1275. assert result.sso_enforced_for_signin_protocol == ""
  1276. assert result.enable_email_code_login is False
  1277. assert result.enable_email_password_login is True
  1278. assert result.is_allow_register is False
  1279. assert result.is_allow_create_workspace is False
  1280. # Verify mock interactions
  1281. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  1282. def test_get_features_edge_case_annotation_quota(
  1283. self, db_session_with_containers, mock_external_service_dependencies
  1284. ):
  1285. """
  1286. Test feature retrieval with edge case annotation quota configuration.
  1287. This test verifies:
  1288. - Proper handling of annotation quota limits
  1289. - Correct integration with billing service
  1290. - Proper fallback to default values
  1291. - Return value correctness and structure
  1292. """
  1293. # Arrange: Setup edge case annotation quota mock with proper config
  1294. tenant_id = self._create_test_tenant_id()
  1295. with patch("services.feature_service.dify_config") as mock_config:
  1296. mock_config.BILLING_ENABLED = True
  1297. mock_config.ENTERPRISE_ENABLED = False
  1298. mock_config.CAN_REPLACE_LOGO = True
  1299. mock_config.MODEL_LB_ENABLED = False
  1300. mock_config.DATASET_OPERATOR_ENABLED = True
  1301. mock_config.EDUCATION_ENABLED = False
  1302. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  1303. "enabled": True,
  1304. "subscription": {"plan": "enterprise", "interval": "yearly"},
  1305. "annotation_quota_limit": {"size": 999, "limit": 1000},
  1306. "knowledge_rate_limit": {"limit": 500},
  1307. }
  1308. # Act: Execute the method under test
  1309. result = FeatureService.get_features(tenant_id)
  1310. # Assert: Verify the expected outcomes
  1311. assert result is not None
  1312. assert isinstance(result, FeatureModel)
  1313. # Verify annotation quota configuration
  1314. assert result.annotation_quota_limit.size == 999
  1315. assert result.annotation_quota_limit.limit == 1000
  1316. # Verify knowledge rate limit
  1317. assert result.knowledge_rate_limit == 500
  1318. # Verify enterprise plan features
  1319. assert result.webapp_copyright_enabled is True
  1320. assert result.is_allow_transfer_workspace is True
  1321. # Verify default values for missing billing info
  1322. assert result.members.size == 0
  1323. assert result.members.limit == 1
  1324. assert result.apps.size == 0
  1325. assert result.apps.limit == 10
  1326. assert result.vector_space.size == 0
  1327. assert result.vector_space.limit == 5
  1328. assert result.documents_upload_quota.size == 0
  1329. assert result.documents_upload_quota.limit == 50
  1330. assert result.docs_processing == "standard"
  1331. # Verify mock interactions
  1332. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  1333. def test_get_features_edge_case_documents_upload(
  1334. self, db_session_with_containers, mock_external_service_dependencies
  1335. ):
  1336. """
  1337. Test feature retrieval with edge case documents upload settings.
  1338. This test verifies:
  1339. - Proper handling of edge case documents upload configuration
  1340. - Correct integration with billing service
  1341. - Proper fallback to default values
  1342. - Return value correctness and structure
  1343. """
  1344. # Arrange: Setup edge case documents upload mock with proper config
  1345. tenant_id = self._create_test_tenant_id()
  1346. with patch("services.feature_service.dify_config") as mock_config:
  1347. mock_config.BILLING_ENABLED = True
  1348. mock_config.ENTERPRISE_ENABLED = False
  1349. mock_config.CAN_REPLACE_LOGO = True
  1350. mock_config.MODEL_LB_ENABLED = False
  1351. mock_config.DATASET_OPERATOR_ENABLED = True
  1352. mock_config.EDUCATION_ENABLED = False
  1353. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  1354. "enabled": True,
  1355. "subscription": {"plan": "pro", "interval": "monthly"},
  1356. "documents_upload_quota": {
  1357. "size": 0, # Edge case: zero current size
  1358. "limit": 0, # Edge case: zero limit
  1359. },
  1360. "knowledge_rate_limit": {"limit": 100},
  1361. }
  1362. # Act: Execute the method under test
  1363. result = FeatureService.get_features(tenant_id)
  1364. # Assert: Verify the expected outcomes
  1365. assert result is not None
  1366. assert isinstance(result, FeatureModel)
  1367. # Verify documents upload quota configuration (edge cases)
  1368. assert result.documents_upload_quota.size == 0
  1369. assert result.documents_upload_quota.limit == 0
  1370. # Verify knowledge rate limit
  1371. assert result.knowledge_rate_limit == 100
  1372. # Verify pro plan features
  1373. assert result.webapp_copyright_enabled is True
  1374. assert result.is_allow_transfer_workspace is True
  1375. # Verify default values for missing billing info
  1376. assert result.members.size == 0
  1377. assert result.members.limit == 1
  1378. assert result.apps.size == 0
  1379. assert result.apps.limit == 10
  1380. assert result.vector_space.size == 0
  1381. assert result.vector_space.limit == 5
  1382. assert result.annotation_quota_limit.size == 0
  1383. assert result.annotation_quota_limit.limit == 10 # Default value when not provided
  1384. assert result.docs_processing == "standard"
  1385. # Verify mock interactions
  1386. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)
  1387. def test_get_system_features_edge_case_license_lost(
  1388. self, db_session_with_containers, mock_external_service_dependencies
  1389. ):
  1390. """
  1391. Test system features with lost license status.
  1392. This test verifies:
  1393. - Proper handling of lost license status
  1394. - Correct enterprise service integration
  1395. - Proper fallback to default values
  1396. - Return value correctness and structure
  1397. """
  1398. # Arrange: Setup lost license mock with proper config
  1399. with patch("services.feature_service.dify_config") as mock_config:
  1400. mock_config.ENTERPRISE_ENABLED = True
  1401. mock_config.MARKETPLACE_ENABLED = False
  1402. mock_config.ENABLE_EMAIL_CODE_LOGIN = False
  1403. mock_config.ENABLE_EMAIL_PASSWORD_LOGIN = True
  1404. mock_config.ENABLE_SOCIAL_OAUTH_LOGIN = False
  1405. mock_config.ALLOW_REGISTER = False
  1406. mock_config.ALLOW_CREATE_WORKSPACE = False
  1407. mock_config.MAIL_TYPE = "smtp"
  1408. mock_config.PLUGIN_MAX_PACKAGE_SIZE = 100
  1409. mock_external_service_dependencies["enterprise_service"].get_info.return_value = {
  1410. "license": {"status": "lost", "expired_at": None, "plan": None}
  1411. }
  1412. # Act: Execute the method under test
  1413. result = FeatureService.get_system_features()
  1414. # Assert: Verify the expected outcomes
  1415. assert result is not None
  1416. assert isinstance(result, SystemFeatureModel)
  1417. # Verify enterprise features
  1418. assert result.branding.enabled is True
  1419. assert result.webapp_auth.enabled is True
  1420. assert result.enable_change_email is False
  1421. # Verify default values for missing enterprise info
  1422. assert result.sso_enforced_for_signin is False
  1423. assert result.sso_enforced_for_signin_protocol == ""
  1424. assert result.enable_email_code_login is False
  1425. assert result.enable_email_password_login is True
  1426. assert result.is_allow_register is False
  1427. assert result.is_allow_create_workspace is False
  1428. # Verify mock interactions
  1429. mock_external_service_dependencies["enterprise_service"].get_info.assert_called_once()
  1430. def test_get_features_edge_case_education_disabled(
  1431. self, db_session_with_containers, mock_external_service_dependencies
  1432. ):
  1433. """
  1434. Test feature retrieval with education feature disabled.
  1435. This test verifies:
  1436. - Proper handling of disabled education features
  1437. - Correct integration with billing service
  1438. - Proper fallback to default values
  1439. - Return value correctness and structure
  1440. """
  1441. # Arrange: Setup education disabled mock with proper config
  1442. tenant_id = self._create_test_tenant_id()
  1443. with patch("services.feature_service.dify_config") as mock_config:
  1444. mock_config.BILLING_ENABLED = True
  1445. mock_config.ENTERPRISE_ENABLED = False
  1446. mock_config.CAN_REPLACE_LOGO = True
  1447. mock_config.MODEL_LB_ENABLED = False
  1448. mock_config.DATASET_OPERATOR_ENABLED = True
  1449. mock_config.EDUCATION_ENABLED = False
  1450. mock_external_service_dependencies["billing_service"].get_info.return_value = {
  1451. "enabled": True,
  1452. "subscription": {
  1453. "plan": "pro",
  1454. "interval": "monthly",
  1455. "education": False, # Education explicitly disabled
  1456. },
  1457. "knowledge_rate_limit": {"limit": 100},
  1458. }
  1459. # Act: Execute the method under test
  1460. result = FeatureService.get_features(tenant_id)
  1461. # Assert: Verify the expected outcomes
  1462. assert result is not None
  1463. assert isinstance(result, FeatureModel)
  1464. # Verify education configuration
  1465. assert result.education.activated is False
  1466. # Verify knowledge rate limit
  1467. assert result.knowledge_rate_limit == 100
  1468. # Verify pro plan features
  1469. assert result.webapp_copyright_enabled is True
  1470. assert result.is_allow_transfer_workspace is True
  1471. # Verify default values for missing billing info
  1472. assert result.members.size == 0
  1473. assert result.members.limit == 1
  1474. assert result.apps.size == 0
  1475. assert result.apps.limit == 10
  1476. assert result.vector_space.size == 0
  1477. assert result.vector_space.limit == 5
  1478. assert result.documents_upload_quota.size == 0
  1479. assert result.documents_upload_quota.limit == 50
  1480. assert result.annotation_quota_limit.size == 0
  1481. assert result.annotation_quota_limit.limit == 10 # Default value when not provided
  1482. assert result.docs_processing == "standard"
  1483. # Verify mock interactions
  1484. mock_external_service_dependencies["billing_service"].get_info.assert_called_once_with(tenant_id)