create-workframe 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +40 -40
  5. package/bin/workframe.js +329 -329
  6. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
  7. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
  8. package/package.json +3 -6
  9. package/profiles/architect/AGENTS.md +29 -29
  10. package/profiles/architect/SOUL.md +2 -2
  11. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  12. package/profiles/designer/AGENTS.md +26 -26
  13. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  14. package/profiles/dev/AGENTS.md +28 -28
  15. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  16. package/profiles/docs/AGENTS.md +27 -27
  17. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  18. package/profiles/research/AGENTS.md +26 -26
  19. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/visionary/AGENTS.md +25 -25
  21. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  22. package/profiles/workframe-agent/AGENTS.md +37 -37
  23. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  24. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  25. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  26. package/rules/workspace-README.md +5 -5
  27. package/scripts/apply-update-hermes.sh +17 -17
  28. package/scripts/apply-update-workframe.sh +77 -77
  29. package/scripts/bootstrap-workspace-link.sh +8 -8
  30. package/scripts/bundle-workframe-ui.mjs +3 -3
  31. package/scripts/compose-docker-host.sh +37 -37
  32. package/scripts/ensure-compose-host-paths.mjs +51 -51
  33. package/scripts/fix-zk-encryption-key.sh +35 -35
  34. package/scripts/lib/install-identity.mjs +212 -212
  35. package/scripts/restart-gateway-hermes.sh +12 -12
  36. package/scripts/set-compose-public-url.mjs +92 -92
  37. package/scripts/setup-stack-secrets.sh +50 -50
  38. package/scripts/sync-canonical-to-package.mjs +8 -7
  39. package/scripts/verify-public-deploy.sh +105 -105
  40. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  41. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  42. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  43. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  44. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  45. package/workframe-api/README.md +28 -28
  46. package/workframe-api/action_proxy.py +131 -131
  47. package/workframe-api/auth_rate_limit.py +49 -49
  48. package/workframe-api/credential_vault.py +445 -445
  49. package/workframe-api/data/avatar-catalog.json +41 -41
  50. package/workframe-api/email_sender.py +220 -220
  51. package/workframe-api/google_auth.py +90 -90
  52. package/workframe-api/install_api.py +359 -359
  53. package/workframe-api/internal_proxy_auth.py +150 -150
  54. package/workframe-api/llm_proxy.py +277 -277
  55. package/workframe-api/oidc_jwt.py +108 -108
  56. package/workframe-api/package.json +12 -13
  57. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  58. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  59. package/workframe-api/requirements.txt +2 -2
  60. package/workframe-api/site_meta.py +271 -271
  61. package/workframe-api/stack_config.py +427 -427
  62. package/workframe-api/time-bind-chat.py +99 -99
  63. package/workframe-api/turn_credentials.py +226 -226
  64. package/workframe-api/updates.py +417 -417
  65. package/workframe-api/vault_kek.py +159 -159
  66. package/workframe-api/zk_auth.py +633 -633
  67. package/workframe-supervisor/Dockerfile +11 -11
  68. package/workframe-supervisor/server.py +787 -787
  69. package/workframe-ui/docker/nginx.conf +85 -85
  70. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  71. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  72. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  73. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  74. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  75. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  76. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  81. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  84. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  85. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  86. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  87. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  88. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  89. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  90. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  91. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  92. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  93. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  94. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  95. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  96. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  97. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  98. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  99. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  100. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  101. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  102. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  103. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  104. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  105. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  106. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  107. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  108. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  109. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  110. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  111. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  112. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  113. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  114. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  115. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  116. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  117. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  118. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  119. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  120. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  121. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  122. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  123. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  124. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  125. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  126. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  127. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  128. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  129. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  130. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  131. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  132. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  133. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  134. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  135. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  136. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  137. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  138. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  139. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  140. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  141. package/workframe-ui/public/favicon.svg +7 -7
  142. package/workframe-ui/public/index.html +50 -50
  143. package/workframe-ui/public/workframe-config.json +3 -3
  144. package/scripts/security_audit.py +0 -156
  145. package/scripts/test-scaffold.mjs +0 -390
  146. package/workframe-api/tests/__init__.py +0 -0
  147. package/workframe-api/tests/db_setup.py +0 -13
  148. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  149. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  150. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  151. package/workframe-api/tests/test_auth_email.py +0 -222
  152. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  153. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  154. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  155. package/workframe-api/tests/test_child_soul_template.py +0 -71
  156. package/workframe-api/tests/test_credential_canary.py +0 -135
  157. package/workframe-api/tests/test_credential_isolation.py +0 -448
  158. package/workframe-api/tests/test_credential_resolution.py +0 -206
  159. package/workframe-api/tests/test_device_oauth.py +0 -108
  160. package/workframe-api/tests/test_doctor_repair.py +0 -103
  161. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  162. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  163. package/workframe-api/tests/test_install_secure_host.py +0 -39
  164. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  165. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  166. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  167. package/workframe-api/tests/test_llm_proxy.py +0 -155
  168. package/workframe-api/tests/test_login_access_policy.py +0 -183
  169. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  170. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  171. package/workframe-api/tests/test_platform_auth.py +0 -47
  172. package/workframe-api/tests/test_profile_config_path.py +0 -56
  173. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  174. package/workframe-api/tests/test_profile_create.py +0 -72
  175. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  176. package/workframe-api/tests/test_profile_install_health.py +0 -45
  177. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  178. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  179. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  180. package/workframe-api/tests/test_provider_connect.py +0 -54
  181. package/workframe-api/tests/test_room_crud.py +0 -192
  182. package/workframe-api/tests/test_room_tenancy.py +0 -701
  183. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  184. package/workframe-api/tests/test_site_meta.py +0 -81
  185. package/workframe-api/tests/test_soul_stub.py +0 -42
  186. package/workframe-api/tests/test_space_member_sync.py +0 -99
  187. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  188. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  189. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  190. package/workframe-api/tests/test_updates.py +0 -176
  191. package/workframe-api/tests/test_user_cohort.py +0 -113
  192. package/workframe-api/tests/test_vault_envelope.py +0 -110
  193. package/workframe-api/tests/test_workspace_members.py +0 -183
  194. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  195. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  196. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  197. package/workframe-supervisor/tests/test_server_import.py +0 -21
  198. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  199. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  200. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  201. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  202. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  203. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  204. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  205. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  206. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
@@ -1,13 +0,0 @@
1
- """Ensure workframe.db schema exists in isolated test temp dirs."""
2
- import server
3
-
4
-
5
- def ensure_workframe_schemas() -> None:
6
- server._ensure_workframe_db_schema()
7
- server._ensure_agent_runs_schema()
8
- server._ensure_invites_schema()
9
- server._ensure_memory_schema()
10
- server._ensure_policies_schema()
11
- server._ensure_budgets_grants_schema()
12
- server._ensure_user_prefs_schema()
13
- server._ensure_runtime_tokens_table()
@@ -1,30 +0,0 @@
1
- import os
2
- import unittest
3
- from unittest import mock
4
-
5
- import updates
6
-
7
-
8
- class AdminUpdatesGatedTests(unittest.TestCase):
9
- def test_apply_update_disabled_without_flag(self) -> None:
10
- with mock.patch.dict(os.environ, {"WORKFRAME_ENABLE_ADMIN_UPDATES": "0"}, clear=False):
11
- with self.assertRaises(ValueError) as ctx:
12
- updates.apply_update("workframe")
13
- self.assertIn("admin_updates_disabled", str(ctx.exception))
14
-
15
- def test_apply_update_enabled_with_flag(self) -> None:
16
- with mock.patch.dict(os.environ, {"WORKFRAME_ENABLE_ADMIN_UPDATES": "1"}, clear=False):
17
- with mock.patch.object(updates.Path, "exists", return_value=False):
18
- with self.assertRaises(ValueError) as ctx:
19
- updates.apply_update("workframe")
20
- self.assertIn("docker_unavailable", str(ctx.exception))
21
-
22
- def test_restart_gateway_disabled_without_flag(self) -> None:
23
- with mock.patch.dict(os.environ, {"WORKFRAME_ENABLE_ADMIN_UPDATES": "0"}, clear=False):
24
- with self.assertRaises(ValueError) as ctx:
25
- updates.restart_gateway()
26
- self.assertIn("admin_updates_disabled", str(ctx.exception))
27
-
28
-
29
- if __name__ == "__main__":
30
- unittest.main()
@@ -1,196 +0,0 @@
1
- """Agent DM lane bootstrap — runtime + room + session parity with install complete."""
2
- import os
3
- import tempfile
4
- import unittest
5
- from pathlib import Path
6
- from unittest import mock
7
-
8
- import server
9
- from db_setup import ensure_workframe_schemas
10
-
11
-
12
- class AgentDmBootstrapTests(unittest.TestCase):
13
- def setUp(self) -> None:
14
- self.tmp = tempfile.TemporaryDirectory()
15
- self.addCleanup(self.tmp.cleanup)
16
- data = Path(self.tmp.name) / "data"
17
- data.mkdir()
18
- self.patches = [
19
- mock.patch.object(server, "DATA_DIR", data),
20
- mock.patch.object(server, "AUTH_DB_PATH", data / "auth.db"),
21
- mock.patch.object(server, "_workframe_db_path", return_value=data / "workframe.db"),
22
- mock.patch.dict(os.environ, {"WORKFRAME_PROJECT": "Workframe"}, clear=False),
23
- ]
24
- for patch in self.patches:
25
- patch.start()
26
- self.addCleanup(patch.stop)
27
- ensure_workframe_schemas()
28
- self.workspace_id = "ws-1"
29
- self.user_id = "user-1"
30
- self.agent_id = "a0000000-0000-4000-8000-000000000001"
31
- self.agent_slug = "dev"
32
- conn = server._workframe_db()
33
- try:
34
- now = "1"
35
- conn.execute(
36
- "INSERT INTO workspaces (id, slug, display_name, owner_id, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
37
- (self.workspace_id, "default", "Workframe", self.user_id, "active", now, now),
38
- )
39
- conn.execute(
40
- """
41
- INSERT INTO agent_profiles (id, workspace_id, slug, display_name, status, created_at, updated_at)
42
- VALUES (?, ?, ?, ?, 'available', ?, ?)
43
- """,
44
- (self.agent_id, self.workspace_id, self.agent_slug, "Dev", now, now),
45
- )
46
- conn.commit()
47
- finally:
48
- conn.close()
49
-
50
- @mock.patch.object(server, "ensure_profile_api", return_value={"ok": True, "api_port": 1})
51
- @mock.patch.object(server, "room_chat_bind", return_value={"ok": True, "session_id": "sid-1"})
52
- @mock.patch.object(server, "_bootstrap_profile_providers", return_value=True)
53
- @mock.patch.object(server, "ensure_runtime_profile")
54
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
55
- def test_bootstrap_creates_agent_profile_row_before_dm_room(
56
- self,
57
- _resolve,
58
- _ensure_runtime,
59
- _bootstrap,
60
- _bind,
61
- _ensure_api,
62
- ) -> None:
63
- conn = server._workframe_db()
64
- try:
65
- conn.execute("DELETE FROM agent_profiles WHERE workspace_id = ?", (self.workspace_id,))
66
- conn.commit()
67
- count = conn.execute(
68
- "SELECT COUNT(*) FROM agent_profiles WHERE workspace_id = ? AND slug = ?",
69
- (self.workspace_id, self.agent_slug),
70
- ).fetchone()[0]
71
- finally:
72
- conn.close()
73
- self.assertEqual(count, 0)
74
-
75
- out = server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
76
- self.assertTrue(out["ok"], out)
77
- self.assertTrue(str(out.get("room_id") or "").strip())
78
-
79
- conn = server._workframe_db()
80
- try:
81
- row = conn.execute(
82
- "SELECT id FROM agent_profiles WHERE workspace_id = ? AND slug = ?",
83
- (self.workspace_id, self.agent_slug),
84
- ).fetchone()
85
- room = conn.execute(
86
- "SELECT id FROM rooms WHERE workspace_id = ? AND room_type = 'direct' AND deleted_at IS NULL",
87
- (self.workspace_id,),
88
- ).fetchone()
89
- finally:
90
- conn.close()
91
- self.assertIsNotNone(row)
92
- self.assertIsNotNone(room)
93
-
94
- @mock.patch.object(server, "ensure_profile_api", return_value={"ok": True, "api_port": 1})
95
- @mock.patch.object(server, "room_chat_bind", return_value={"ok": True, "session_id": "sid-1"})
96
- @mock.patch.object(server, "_create_room", return_value=(201, {"ok": True, "room": {"id": "room-dev", "room_type": "direct"}}))
97
- @mock.patch.object(server, "_bootstrap_profile_providers", return_value=True)
98
- @mock.patch.object(server, "ensure_runtime_profile")
99
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
100
- def test_bootstrap_agent_dm_lane_end_to_end(
101
- self,
102
- _resolve,
103
- ensure_runtime,
104
- _bootstrap,
105
- _create_room,
106
- _bind,
107
- _ensure_api,
108
- ) -> None:
109
- out = server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
110
- self.assertTrue(out["ok"])
111
- self.assertEqual(out["room_id"], "room-dev")
112
- self.assertEqual(out["session_id"], "sid-1")
113
- ensure_runtime.assert_called_once()
114
- _bind.assert_called_once()
115
-
116
- @mock.patch.object(server, "ensure_profile_api", return_value={"ok": True, "api_port": 1})
117
- @mock.patch.object(server, "room_chat_bind", return_value={"ok": True, "session_id": "sid-1"})
118
- @mock.patch.object(server, "_create_room", return_value=(201, {"ok": True, "room": {"id": "room-dev", "room_type": "direct"}}))
119
- @mock.patch.object(server, "_bootstrap_profile_providers", return_value=True)
120
- @mock.patch.object(server, "ensure_runtime_profile")
121
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
122
- def test_bootstrap_skips_second_provider_bootstrap(
123
- self,
124
- _resolve,
125
- _ensure_runtime,
126
- bootstrap,
127
- _create_room,
128
- _bind,
129
- ensure_api,
130
- ) -> None:
131
- server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
132
- bootstrap.assert_called_once()
133
- ensure_api.assert_called_once_with(
134
- mock.ANY,
135
- self.user_id,
136
- self.workspace_id,
137
- bootstrap_providers=False,
138
- )
139
-
140
- @mock.patch.object(server, "ensure_runtime_profile", side_effect=ValueError("gateway down"))
141
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
142
- def test_bootstrap_agent_dm_lane_runtime_failure(self, _resolve, _ensure) -> None:
143
- out = server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
144
- self.assertFalse(out["ok"])
145
- self.assertIn("gateway down", str(out.get("error")))
146
-
147
- @mock.patch.object(
148
- server,
149
- "ensure_profile_api",
150
- side_effect=[
151
- ValueError("profile api did not become healthy: u-user-dev"),
152
- ValueError("profile api did not become healthy: u-user-dev"),
153
- {"ok": True, "api_port": 1},
154
- ],
155
- )
156
- @mock.patch.object(server, "room_chat_bind", return_value={"ok": True, "session_id": "sid-1"})
157
- @mock.patch.object(server, "_create_room", return_value=(201, {"ok": True, "room": {"id": "room-dev", "room_type": "direct"}}))
158
- @mock.patch.object(server, "_bootstrap_profile_providers", return_value=True)
159
- @mock.patch.object(server, "ensure_runtime_profile")
160
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
161
- def test_bootstrap_retries_gateway_start_once(
162
- self,
163
- _resolve,
164
- _ensure_runtime,
165
- _bootstrap,
166
- _create_room,
167
- _bind,
168
- ensure_api,
169
- ) -> None:
170
- out = server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
171
- self.assertTrue(out["ok"])
172
- self.assertEqual(ensure_api.call_count, 3)
173
-
174
- @mock.patch.object(server, "ensure_profile_api", return_value={"ok": True, "api_port": 1})
175
- @mock.patch.object(server, "room_chat_bind", return_value={})
176
- @mock.patch.object(server, "_create_room", return_value=(201, {"ok": True, "room": {"id": "room-dev", "room_type": "direct"}}))
177
- @mock.patch.object(server, "_bootstrap_profile_providers", return_value=True)
178
- @mock.patch.object(server, "ensure_runtime_profile")
179
- @mock.patch.object(server, "resolve_validated_profile", side_effect=lambda slug: slug)
180
- def test_bootstrap_bind_failure_returns_ok_false(
181
- self,
182
- _resolve,
183
- ensure_runtime,
184
- _bootstrap,
185
- _create_room,
186
- _bind,
187
- _ensure_api,
188
- ) -> None:
189
- out = server.bootstrap_agent_dm_lane(self.user_id, self.workspace_id, self.agent_slug)
190
- self.assertFalse(out["ok"])
191
- self.assertEqual(out.get("error"), "room_chat_bind_failed")
192
- ensure_runtime.assert_called_once()
193
-
194
-
195
- if __name__ == "__main__":
196
- unittest.main()
@@ -1,76 +0,0 @@
1
- import tempfile
2
- import unittest
3
- from pathlib import Path
4
- from unittest.mock import patch
5
-
6
- import server
7
- from db_setup import ensure_workframe_schemas
8
-
9
-
10
- class AgentProfileSyncTests(unittest.TestCase):
11
- def setUp(self) -> None:
12
- self._tmp = tempfile.TemporaryDirectory()
13
- self.addCleanup(self._tmp.cleanup)
14
- self._old_data_dir = server.DATA_DIR
15
- self._old_auth_db_path = server.AUTH_DB_PATH
16
- server.DATA_DIR = Path(self._tmp.name)
17
- server.AUTH_DB_PATH = Path(self._tmp.name) / "auth.db"
18
- ensure_workframe_schemas()
19
- conn = server._workframe_db()
20
- try:
21
- now = "1"
22
- conn.execute(
23
- """
24
- INSERT INTO workspaces (id, slug, display_name, owner_id, status, created_at, updated_at)
25
- VALUES (?, ?, ?, ?, ?, ?, ?)
26
- """,
27
- ("ws-1", "default", "Default", "user-1", "active", now, now),
28
- )
29
- conn.execute(
30
- """
31
- INSERT INTO agent_profiles (
32
- id, workspace_id, slug, display_name, status, created_at, updated_at
33
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
34
- """,
35
- ("agent-1", "ws-1", "workframe-agent", "Workframe Agent", "available", now, now),
36
- )
37
- conn.commit()
38
- finally:
39
- conn.close()
40
-
41
- def tearDown(self) -> None:
42
- server.DATA_DIR = self._old_data_dir
43
- server.AUTH_DB_PATH = self._old_auth_db_path
44
-
45
- def test_sync_agent_profile_db_writes_avatar_url(self) -> None:
46
- avatar = "data:image/png;base64,abc"
47
- server._sync_agent_profile_db("workframe-agent", {"avatar_url": avatar, "display_name": "WF"})
48
- conn = server._workframe_db()
49
- try:
50
- row = conn.execute(
51
- "SELECT display_name, avatar_url FROM agent_profiles WHERE slug = ?",
52
- ("workframe-agent",),
53
- ).fetchone()
54
- finally:
55
- conn.close()
56
- self.assertIsNotNone(row)
57
- self.assertEqual(row["display_name"], "WF")
58
- self.assertEqual(row["avatar_url"], avatar)
59
-
60
- @patch.object(server, "route_status_for_profile", return_value="available")
61
- @patch.object(server, "_gateway_platform", return_value="local")
62
- def test_crew_data_reads_registry_by_full_profile_slug(self, *_mocks) -> None:
63
- registry = {
64
- "workframe-agent": {
65
- "profile": "workframe-agent",
66
- "avatar_url": "data:image/png;base64,crew",
67
- },
68
- }
69
- with patch.object(server, "load_agent_registry", return_value=registry):
70
- crew = server.crew_data(["workframe-agent"], "workframe-agent", {"ok": True, "state": "running"})
71
- self.assertEqual(len(crew), 1)
72
- self.assertEqual(crew[0]["avatar_url"], "data:image/png;base64,crew")
73
-
74
-
75
- if __name__ == "__main__":
76
- unittest.main()
@@ -1,222 +0,0 @@
1
- """Auth email / OTP start behavior."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import sqlite3
7
- import tempfile
8
- import unittest
9
- import json
10
- from pathlib import Path
11
- from unittest import mock
12
-
13
- import zk_auth as zk
14
-
15
-
16
- class AuthEmailStartTests(unittest.TestCase):
17
- def setUp(self) -> None:
18
- self._tmpdir = tempfile.TemporaryDirectory()
19
- self.addCleanup(self._tmpdir.cleanup)
20
- os.environ["WORKFRAME_API_DATA_DIR"] = self._tmpdir.name
21
-
22
- @mock.patch("email_sender.send_verification_email")
23
- def test_dev_unsafe_returns_otp_when_smtp_missing(self, send_email: mock.MagicMock) -> None:
24
- send_email.side_effect = RuntimeError("SMTP_HOST not configured")
25
-
26
- result = zk.start_email_verification("user@example.com", dev_local_unsafe=True)
27
-
28
- self.assertFalse(result["email_sent"])
29
- self.assertIn("SMTP_HOST", result.get("email_error", ""))
30
- self.assertEqual(len(result["otp_code"]), 6)
31
- self.assertTrue(result["otp_code"].isdigit())
32
-
33
- @mock.patch("email_sender.send_verification_email")
34
- def test_secure_mode_does_not_return_otp_when_smtp_missing(self, send_email: mock.MagicMock) -> None:
35
- send_email.side_effect = RuntimeError("SMTP_HOST not configured")
36
-
37
- result = zk.start_email_verification("user@example.com", dev_local_unsafe=False)
38
-
39
- self.assertFalse(result["email_sent"])
40
- self.assertNotIn("otp_code", result)
41
-
42
- @mock.patch("email_sender.send_verification_email")
43
- def test_email_sent_does_not_return_otp_even_in_dev(self, send_email: mock.MagicMock) -> None:
44
- send_email.return_value = None
45
-
46
- result = zk.start_email_verification("user@example.com", dev_local_unsafe=True)
47
-
48
- self.assertTrue(result["email_sent"])
49
- self.assertNotIn("otp_code", result)
50
-
51
- @mock.patch("email_sender.send_verification_email")
52
- def test_expose_otp_returns_code_when_email_sent(self, send_email: mock.MagicMock) -> None:
53
- send_email.return_value = None
54
-
55
- result = zk.start_email_verification("user@example.com", expose_otp=True)
56
-
57
- self.assertTrue(result["email_sent"])
58
- self.assertEqual(len(result["otp_code"]), 6)
59
-
60
-
61
- class StackConfigTests(unittest.TestCase):
62
- def setUp(self) -> None:
63
- self._tmpdir = tempfile.TemporaryDirectory()
64
- self.addCleanup(self._tmpdir.cleanup)
65
- os.environ["WORKFRAME_API_DATA_DIR"] = self._tmpdir.name
66
- import importlib
67
-
68
- import stack_config as sc
69
-
70
- self.sc = importlib.reload(sc)
71
-
72
- def test_patch_and_resolve_smtp(self) -> None:
73
- self.sc.patch_stack_config(
74
- {
75
- "deployment_mode": "trusted_team",
76
- "smtp": {
77
- "provider": "gmail",
78
- "host": "smtp.gmail.com",
79
- "port": 587,
80
- "user": "ops@example.com",
81
- "password": "secret",
82
- "from": "ops@example.com",
83
- },
84
- }
85
- )
86
- cfg = self.sc.get_stack_config()
87
- self.assertEqual(cfg["deployment_mode"], "trusted_team")
88
- self.assertTrue(self.sc.smtp_configured())
89
- resolved = self.sc.resolved_smtp()
90
- self.assertEqual(resolved["host"], "smtp.gmail.com")
91
- self.assertEqual(resolved["user"], "ops@example.com")
92
-
93
- def test_env_overrides_stack_file(self) -> None:
94
- self.sc.patch_stack_config({"smtp": {"host": "smtp.file.test", "user": "a", "password": "b"}})
95
- os.environ["SMTP_HOST"] = "smtp.env.test"
96
- os.environ["SMTP_USER"] = "env-user"
97
- resolved = self.sc.resolved_smtp()
98
- self.assertEqual(resolved["source"], "env")
99
- self.assertEqual(resolved["host"], "smtp.env.test")
100
- os.environ.pop("SMTP_HOST", None)
101
- os.environ.pop("SMTP_USER", None)
102
-
103
-
104
- class OtpLockoutTests(unittest.TestCase):
105
- def setUp(self) -> None:
106
- self._tmpdir = tempfile.TemporaryDirectory()
107
- self.addCleanup(self._tmpdir.cleanup)
108
- os.environ["WORKFRAME_API_DATA_DIR"] = self._tmpdir.name
109
-
110
- @mock.patch("email_sender.send_verification_email", return_value=None)
111
- def test_lockout_before_correct_code_after_max_wrong(self, _send: mock.MagicMock) -> None:
112
- started = zk.start_email_verification(
113
- "lock@example.com", dev_local_unsafe=True, expose_otp=True,
114
- )
115
- good = started["otp_code"]
116
- for wrong in ("000000", "111111", "222222", "333333", "444444"):
117
- with self.assertRaises(ValueError) as ctx:
118
- zk.verify_email_code("lock@example.com", wrong)
119
- self.assertEqual(str(ctx.exception), "Invalid or expired verification code.")
120
- with self.assertRaises(ValueError) as ctx:
121
- zk.verify_email_code("lock@example.com", good)
122
- self.assertEqual(str(ctx.exception), "Invalid or expired verification code.")
123
-
124
-
125
- class InstallWindowTests(unittest.TestCase):
126
- def setUp(self) -> None:
127
- self._tmpdir = tempfile.TemporaryDirectory()
128
- self.addCleanup(self._tmpdir.cleanup)
129
- os.environ["WORKFRAME_API_DATA_DIR"] = self._tmpdir.name
130
- import importlib
131
-
132
- import stack_config as sc
133
- import install_api as ia
134
-
135
- self.sc = importlib.reload(sc)
136
- self.ia = importlib.reload(ia)
137
- self.db_path = str(Path(self._tmpdir.name) / "workframe.db")
138
-
139
- def test_window_open_until_install_complete_even_with_users(self) -> None:
140
- Path(self.db_path).write_text("", encoding="utf-8")
141
- conn = sqlite3.connect(self.db_path)
142
- conn.execute(
143
- "CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT, display_name TEXT, role TEXT, status TEXT, created_at TEXT, updated_at TEXT)"
144
- )
145
- conn.execute(
146
- "INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
147
- ("u1", "a@b.com", "A", "owner", "active", "1", "1"),
148
- )
149
- conn.commit()
150
- conn.close()
151
- self.assertTrue(self.ia.install_window_open(self.db_path))
152
- self.sc.patch_stack_config({"install_complete": True})
153
- self.assertFalse(self.ia.install_window_open(self.db_path))
154
-
155
-
156
- class SmtpSecureNormalizeTests(unittest.TestCase):
157
- def test_port_465_uses_ssl_even_when_starttls(self) -> None:
158
- import stack_config as sc
159
-
160
- self.assertEqual(sc.normalize_smtp_secure(465, "starttls"), "ssl")
161
- self.assertEqual(sc.normalize_smtp_secure(465, "true"), "ssl")
162
- self.assertEqual(sc.normalize_smtp_secure(587, "true"), "ssl")
163
- self.assertEqual(sc.normalize_smtp_secure(587, "starttls"), "starttls")
164
- self.assertEqual(sc.normalize_smtp_secure(587, ""), "starttls")
165
-
166
-
167
- class ResolvedSmtpTests(unittest.TestCase):
168
- def setUp(self) -> None:
169
- self._tmpdir = tempfile.TemporaryDirectory()
170
- self.addCleanup(self._tmpdir.cleanup)
171
- os.environ["WORKFRAME_API_DATA_DIR"] = self._tmpdir.name
172
- import importlib
173
-
174
- import stack_config as sc
175
-
176
- self.sc = importlib.reload(sc)
177
-
178
- def test_stack_password_not_redacted_from_public_get(self) -> None:
179
- cfg_path = Path(self._tmpdir.name) / "stack_config.json"
180
- cfg_path.write_text(
181
- json.dumps(
182
- {
183
- "smtp": {
184
- "host": "smtp.test",
185
- "user": "login@test.com",
186
- "password": "secret-pass",
187
- "from": "alias@test.com",
188
- "port": 465,
189
- "secure": "ssl",
190
- }
191
- }
192
- ),
193
- encoding="utf-8",
194
- )
195
- public = self.sc.get_stack_config()["smtp"]
196
- self.assertTrue(public["has_password"])
197
- self.assertNotIn("password", public)
198
- resolved = self.sc.resolved_smtp()
199
- self.assertEqual(resolved["password"], "secret-pass")
200
- self.assertEqual(resolved["from"], "alias@test.com")
201
- self.assertEqual(resolved["user"], "login@test.com")
202
-
203
- def test_env_pass_falls_back_to_stack_password(self) -> None:
204
- cfg_path = Path(self._tmpdir.name) / "stack_config.json"
205
- cfg_path.write_text(
206
- json.dumps({"smtp": {"password": "stack-secret"}}),
207
- encoding="utf-8",
208
- )
209
- os.environ["SMTP_HOST"] = "smtp.env.test"
210
- os.environ["SMTP_USER"] = "env-user@test.com"
211
- os.environ["EMAIL_FROM"] = "alias@test.com"
212
- self.addCleanup(os.environ.pop, "SMTP_HOST", None)
213
- self.addCleanup(os.environ.pop, "SMTP_USER", None)
214
- self.addCleanup(os.environ.pop, "EMAIL_FROM", None)
215
- resolved = self.sc.resolved_smtp()
216
- self.assertEqual(resolved["password"], "stack-secret")
217
- self.assertEqual(resolved["from"], "alias@test.com")
218
- self.assertEqual(resolved["user"], "env-user@test.com")
219
-
220
-
221
- if __name__ == "__main__":
222
- unittest.main()
@@ -1,99 +0,0 @@
1
- """Runnable self-check for the four signup-hole fixes (no pytest fixtures)."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import tempfile
7
- import time
8
- import unittest
9
- from pathlib import Path
10
- from unittest import mock
11
-
12
- import stack_config
13
- from db_setup import ensure_workframe_schemas
14
-
15
-
16
- class AuthHoleFixSelfCheck(unittest.TestCase):
17
- def setUp(self) -> None:
18
- self._tmp = tempfile.TemporaryDirectory()
19
- self.addCleanup(self._tmp.cleanup)
20
- self._env = dict(os.environ)
21
- self.addCleanup(lambda: os.environ.clear() or os.environ.update(self._env))
22
-
23
- import server
24
-
25
- self.server = server
26
- self._old_data = server.DATA_DIR
27
- self._old_auth = server.AUTH_DB_PATH
28
- self._old_mode = server.DEPLOYMENT_MODE
29
- server.DATA_DIR = Path(self._tmp.name) / "data"
30
- server.DATA_DIR.mkdir(parents=True, exist_ok=True)
31
- server.AUTH_DB_PATH = server.DATA_DIR / "auth.db"
32
- stack_config.DATA_DIR = server.DATA_DIR
33
- stack_config.CONFIG_PATH = server.DATA_DIR / "stack_config.json"
34
- server.DEV_LOCAL_UNSAFE = False
35
- ensure_workframe_schemas()
36
- stack_config.patch_stack_config({"install_complete": True, "deployment_mode": "trusted_team"})
37
- conn = server._workframe_db()
38
- try:
39
- now = str(int(time.time()))
40
- conn.execute(
41
- "INSERT INTO users (id, email, display_name, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
42
- ("owner-1", "owner@biz.test", "Owner", "owner", "active", now, now),
43
- )
44
- conn.execute(
45
- "INSERT INTO workspaces (id, slug, display_name, owner_id, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
46
- ("ws-1", "default", "Acme", "owner-1", "active", now, now),
47
- )
48
- conn.execute(
49
- "INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)",
50
- ("wm-1", "ws-1", "owner-1", "owner", "active", now, now),
51
- )
52
- conn.commit()
53
- finally:
54
- conn.close()
55
-
56
- def tearDown(self) -> None:
57
- self.server.DATA_DIR = self._old_data
58
- self.server.AUTH_DB_PATH = self._old_auth
59
- self.server.DEPLOYMENT_MODE = self._old_mode
60
-
61
- def test_env_beats_stale_stack_config(self) -> None:
62
- os.environ["WORKFRAME_DEPLOYMENT_MODE"] = "public_multi_user"
63
- self.assertEqual(stack_config.resolve_deployment_mode("trusted_team"), "public_multi_user")
64
- self.assertEqual(stack_config.effective_deployment_mode("trusted_team"), "public_multi_user")
65
- self.assertEqual(self.server._resolve_deployment_mode(), "public_multi_user")
66
-
67
- def test_invite_trust_not_public_post(self) -> None:
68
- from http.server import BaseHTTPRequestHandler
69
-
70
- handler = mock.Mock(spec=BaseHTTPRequestHandler)
71
- handler.command = "POST"
72
- handler.path = "/api/auth/invite-trust"
73
- handler.headers = {}
74
- with mock.patch.object(self.server, "_session_id_from_request", return_value=""):
75
- self.assertFalse(self.server._auth_check(handler))
76
-
77
- def test_trusted_team_stranger_denied_at_auth_start_policy(self) -> None:
78
- self.server.DEPLOYMENT_MODE = "trusted_team"
79
- with mock.patch.object(self.server, "_install_window_open", return_value=False):
80
- self.assertTrue(self.server._invite_only_login_enforced())
81
- allowed, meta = self.server._email_allowed_to_authenticate("stranger@evil.test")
82
- self.assertFalse(allowed)
83
- self.assertEqual(meta.get("error"), "private_workspace")
84
-
85
- def test_install_url_test_get_allowed_without_session_during_install(self) -> None:
86
- from http.server import BaseHTTPRequestHandler
87
-
88
- stack_config.patch_stack_config({"install_complete": False})
89
- handler = mock.Mock(spec=BaseHTTPRequestHandler)
90
- handler.command = "GET"
91
- handler.path = "/api/install/url/test?url=https://example.com"
92
- handler.headers = {}
93
- with mock.patch.object(self.server, "_install_window_open", return_value=True):
94
- with mock.patch.object(self.server, "_session_id_from_request", return_value=""):
95
- self.assertTrue(self.server._auth_check(handler))
96
-
97
-
98
- if __name__ == "__main__":
99
- unittest.main()
@@ -1,19 +0,0 @@
1
- import unittest
2
-
3
- import auth_rate_limit
4
-
5
-
6
- class AuthRateLimitTests(unittest.TestCase):
7
- def setUp(self) -> None:
8
- auth_rate_limit.reset_for_tests()
9
-
10
- def test_blocks_burst_auth_start(self) -> None:
11
- ip = "198.51.100.9"
12
- email = "user@example.com"
13
- for _ in range(5):
14
- self.assertTrue(auth_rate_limit.allow_auth_request("start", ip, email))
15
- self.assertFalse(auth_rate_limit.allow_auth_request("start", ip, email))
16
-
17
-
18
- if __name__ == "__main__":
19
- unittest.main()