create-workframe 0.1.0 → 0.1.2

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 (198) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +38 -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/bundle-workframe-ui.mjs +3 -3
  28. package/scripts/ensure-compose-host-paths.mjs +51 -51
  29. package/scripts/lib/install-identity.mjs +212 -212
  30. package/scripts/set-compose-public-url.mjs +92 -92
  31. package/scripts/sync-canonical-to-package.mjs +27 -9
  32. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  33. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  34. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  35. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  36. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  37. package/workframe-api/README.md +26 -28
  38. package/workframe-api/action_proxy.py +131 -131
  39. package/workframe-api/auth_rate_limit.py +49 -49
  40. package/workframe-api/credential_vault.py +445 -445
  41. package/workframe-api/data/avatar-catalog.json +41 -41
  42. package/workframe-api/email_sender.py +220 -220
  43. package/workframe-api/google_auth.py +90 -90
  44. package/workframe-api/install_api.py +359 -359
  45. package/workframe-api/internal_proxy_auth.py +150 -150
  46. package/workframe-api/llm_proxy.py +277 -277
  47. package/workframe-api/oidc_jwt.py +108 -108
  48. package/workframe-api/package.json +12 -13
  49. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  50. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  51. package/workframe-api/requirements.txt +2 -2
  52. package/workframe-api/site_meta.py +271 -271
  53. package/workframe-api/stack_config.py +427 -427
  54. package/workframe-api/time-bind-chat.py +99 -99
  55. package/workframe-api/turn_credentials.py +226 -226
  56. package/workframe-api/updates.py +417 -417
  57. package/workframe-api/vault_kek.py +159 -159
  58. package/workframe-api/zk_auth.py +633 -633
  59. package/workframe-supervisor/Dockerfile +11 -11
  60. package/workframe-supervisor/server.py +787 -787
  61. package/workframe-ui/docker/nginx.conf +85 -85
  62. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  63. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  64. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  65. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  66. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  67. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  68. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  71. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  73. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  74. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  75. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  76. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  81. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  84. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  85. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  86. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  87. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  88. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  89. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  90. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  91. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  92. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  93. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  94. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  95. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  96. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  97. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  98. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  99. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  100. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  101. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  102. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  103. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  104. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  105. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  106. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  107. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  108. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  109. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  110. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  111. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  112. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  113. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  114. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  115. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  116. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  117. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  118. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  119. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  120. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  121. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  122. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  123. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  124. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  125. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  126. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  127. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  128. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  129. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  130. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  131. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  132. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  133. package/workframe-ui/public/favicon.svg +7 -7
  134. package/workframe-ui/public/index.html +50 -50
  135. package/workframe-ui/public/workframe-config.json +3 -3
  136. package/scripts/security_audit.py +0 -156
  137. package/scripts/test-scaffold.mjs +0 -390
  138. package/workframe-api/tests/__init__.py +0 -0
  139. package/workframe-api/tests/db_setup.py +0 -13
  140. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  141. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  142. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  143. package/workframe-api/tests/test_auth_email.py +0 -222
  144. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  145. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  146. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  147. package/workframe-api/tests/test_child_soul_template.py +0 -71
  148. package/workframe-api/tests/test_credential_canary.py +0 -135
  149. package/workframe-api/tests/test_credential_isolation.py +0 -448
  150. package/workframe-api/tests/test_credential_resolution.py +0 -206
  151. package/workframe-api/tests/test_device_oauth.py +0 -108
  152. package/workframe-api/tests/test_doctor_repair.py +0 -103
  153. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  154. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  155. package/workframe-api/tests/test_install_secure_host.py +0 -39
  156. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  157. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  158. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  159. package/workframe-api/tests/test_llm_proxy.py +0 -155
  160. package/workframe-api/tests/test_login_access_policy.py +0 -183
  161. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  162. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  163. package/workframe-api/tests/test_platform_auth.py +0 -47
  164. package/workframe-api/tests/test_profile_config_path.py +0 -56
  165. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  166. package/workframe-api/tests/test_profile_create.py +0 -72
  167. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  168. package/workframe-api/tests/test_profile_install_health.py +0 -45
  169. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  170. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  171. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  172. package/workframe-api/tests/test_provider_connect.py +0 -54
  173. package/workframe-api/tests/test_room_crud.py +0 -192
  174. package/workframe-api/tests/test_room_tenancy.py +0 -701
  175. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  176. package/workframe-api/tests/test_site_meta.py +0 -81
  177. package/workframe-api/tests/test_soul_stub.py +0 -42
  178. package/workframe-api/tests/test_space_member_sync.py +0 -99
  179. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  180. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  181. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  182. package/workframe-api/tests/test_updates.py +0 -176
  183. package/workframe-api/tests/test_user_cohort.py +0 -113
  184. package/workframe-api/tests/test_vault_envelope.py +0 -110
  185. package/workframe-api/tests/test_workspace_members.py +0 -183
  186. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  187. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  188. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  189. package/workframe-supervisor/tests/test_server_import.py +0 -21
  190. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  191. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  192. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  193. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  194. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  195. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  196. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  197. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  198. 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()