@saluzi/saluzi-edu 0.2.9 → 0.2.11

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 (219) hide show
  1. package/dist/cli.js +336 -333
  2. package/dist/rcs-web/assets/Dashboard-BwotBWlJ.js +1 -0
  3. package/dist/rcs-web/assets/Join-4yrksbdX.js +1 -0
  4. package/dist/rcs-web/assets/Login-Cxm_yEeE.js +1 -0
  5. package/dist/rcs-web/assets/SessionDetail-DNdfPOYy.js +50 -0
  6. package/dist/rcs-web/assets/Setup-BiAAxugt.js +1 -0
  7. package/dist/rcs-web/assets/ShareLanding-CP3Z6ckr.js +1 -0
  8. package/dist/rcs-web/assets/TeamCreate-DgdCwRSb.js +1 -0
  9. package/dist/rcs-web/assets/TeamDetail-CkNTFoMB.js +11 -0
  10. package/dist/rcs-web/assets/TeamList-Bf0sQ5RQ.js +1 -0
  11. package/dist/rcs-web/assets/TeamSettings-BaNAfXU3.js +1 -0
  12. package/dist/rcs-web/assets/UserSettings-CsutQjxG.js +1 -0
  13. package/{packages/remote-control-server/web/dist/assets/arc-D90597o-.js → dist/rcs-web/assets/arc-BxxmNQO6.js} +1 -1
  14. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  15. package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  16. package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  17. package/dist/rcs-web/assets/channel-JsvbMQjW.js +1 -0
  18. package/dist/rcs-web/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  19. package/dist/rcs-web/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  20. package/dist/rcs-web/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  21. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DtnaqsyD.js → dist/rcs-web/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  22. package/dist/rcs-web/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  23. package/dist/rcs-web/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  24. package/dist/rcs-web/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  25. package/dist/rcs-web/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  26. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  27. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  28. package/dist/rcs-web/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  29. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  30. package/dist/rcs-web/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  31. package/dist/rcs-web/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  32. package/dist/rcs-web/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  33. package/dist/rcs-web/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  34. package/dist/rcs-web/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  35. package/dist/rcs-web/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  36. package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-sre_MTeK.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  37. package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  38. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  39. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  40. package/dist/rcs-web/assets/i18n-Dbt-vEQ3.js +1 -0
  41. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  42. package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  43. package/dist/rcs-web/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  44. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  45. package/dist/rcs-web/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  46. package/dist/rcs-web/assets/main-30YqUvTd.js +238 -0
  47. package/dist/rcs-web/assets/main-EP6Lac2m.css +1 -0
  48. package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D_yQ7nAd.js → dist/rcs-web/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  49. package/dist/rcs-web/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  50. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  51. package/{packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → dist/rcs-web/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  52. package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  53. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  54. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  55. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  56. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  57. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  58. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  59. package/dist/rcs-web/assets/useTeams-MDI4djXg.js +1 -0
  60. package/{packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → dist/rcs-web/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  61. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  62. package/dist/rcs-web/assets/workerType-OM09y7Lk.js +1 -0
  63. package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  64. package/dist/rcs-web/index.html +2 -3
  65. package/package.json +2 -1
  66. package/packages/remote-control-server/src/__tests__/acp-ws-handler.test.ts +7 -19
  67. package/packages/remote-control-server/src/__tests__/auth-login.test.ts +561 -0
  68. package/packages/remote-control-server/src/__tests__/auth-security.test.ts +547 -0
  69. package/packages/remote-control-server/src/__tests__/auth.test.ts +7 -18
  70. package/packages/remote-control-server/src/__tests__/bind-replacement.test.ts +251 -0
  71. package/packages/remote-control-server/src/__tests__/cookie.test.ts +155 -0
  72. package/packages/remote-control-server/src/__tests__/data-layer-fixes.test.ts +147 -0
  73. package/packages/remote-control-server/src/__tests__/db.test.ts +582 -0
  74. package/packages/remote-control-server/src/__tests__/design-gaps.test.ts +440 -0
  75. package/packages/remote-control-server/src/__tests__/disconnect-monitor.test.ts +2 -18
  76. package/packages/remote-control-server/src/__tests__/helpers/db.ts +43 -0
  77. package/packages/remote-control-server/src/__tests__/helpers/fixtures.ts +94 -0
  78. package/packages/remote-control-server/src/__tests__/helpers/mock-config.ts +31 -0
  79. package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -112
  80. package/packages/remote-control-server/src/__tests__/password.test.ts +76 -0
  81. package/packages/remote-control-server/src/__tests__/phase4-cleanup.test.ts +290 -0
  82. package/packages/remote-control-server/src/__tests__/phase4-integration.test.ts +224 -0
  83. package/packages/remote-control-server/src/__tests__/routes.test.ts +292 -207
  84. package/packages/remote-control-server/src/__tests__/services.test.ts +2 -18
  85. package/packages/remote-control-server/src/__tests__/session-visibility.test.ts +452 -0
  86. package/packages/remote-control-server/src/__tests__/session.test.ts +216 -0
  87. package/packages/remote-control-server/src/__tests__/shares.test.ts +1419 -0
  88. package/packages/remote-control-server/src/__tests__/sse-writer.test.ts +2 -18
  89. package/packages/remote-control-server/src/__tests__/teams.test.ts +2089 -0
  90. package/packages/remote-control-server/src/__tests__/work-dispatch.test.ts +2 -18
  91. package/packages/remote-control-server/src/__tests__/worker-token-removal.test.ts +487 -0
  92. package/packages/remote-control-server/src/__tests__/ws-handler.test.ts +2 -18
  93. package/packages/remote-control-server/src/auth/constants.ts +13 -0
  94. package/packages/remote-control-server/src/auth/cookie.ts +75 -0
  95. package/packages/remote-control-server/src/auth/middleware.ts +75 -165
  96. package/packages/remote-control-server/src/auth/password.ts +116 -0
  97. package/packages/remote-control-server/src/auth/session.ts +130 -0
  98. package/packages/remote-control-server/src/db/schema.sql +116 -0
  99. package/packages/remote-control-server/src/db/sqlite.ts +99 -0
  100. package/packages/remote-control-server/src/routes/acp/index.ts +6 -11
  101. package/packages/remote-control-server/src/routes/v1/environments.ts +111 -8
  102. package/packages/remote-control-server/src/routes/web/auth-routes.ts +634 -0
  103. package/packages/remote-control-server/src/routes/web/control.ts +5 -5
  104. package/packages/remote-control-server/src/routes/web/environments.ts +5 -27
  105. package/packages/remote-control-server/src/routes/web/sessions.ts +19 -41
  106. package/packages/remote-control-server/src/routes/web/shares.ts +371 -0
  107. package/packages/remote-control-server/src/routes/web/teams.ts +850 -0
  108. package/packages/remote-control-server/src/services/environment.ts +1 -4
  109. package/packages/remote-control-server/src/services/session-visibility.ts +79 -0
  110. package/packages/remote-control-server/src/services/session.ts +0 -13
  111. package/packages/remote-control-server/src/store.ts +150 -56
  112. package/packages/remote-control-server/src/types/api.ts +2 -8
  113. package/packages/remote-control-server/web/dist/assets/Dashboard-BwotBWlJ.js +1 -0
  114. package/packages/remote-control-server/web/dist/assets/Join-4yrksbdX.js +1 -0
  115. package/packages/remote-control-server/web/dist/assets/Login-Cxm_yEeE.js +1 -0
  116. package/packages/remote-control-server/web/dist/assets/SessionDetail-DNdfPOYy.js +50 -0
  117. package/packages/remote-control-server/web/dist/assets/Setup-BiAAxugt.js +1 -0
  118. package/packages/remote-control-server/web/dist/assets/ShareLanding-CP3Z6ckr.js +1 -0
  119. package/packages/remote-control-server/web/dist/assets/TeamCreate-DgdCwRSb.js +1 -0
  120. package/packages/remote-control-server/web/dist/assets/TeamDetail-CkNTFoMB.js +11 -0
  121. package/packages/remote-control-server/web/dist/assets/TeamList-Bf0sQ5RQ.js +1 -0
  122. package/packages/remote-control-server/web/dist/assets/TeamSettings-BaNAfXU3.js +1 -0
  123. package/packages/remote-control-server/web/dist/assets/UserSettings-CsutQjxG.js +1 -0
  124. package/{dist/rcs-web/assets/arc-D90597o-.js → packages/remote-control-server/web/dist/assets/arc-BxxmNQO6.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-DBjkRdbd.js +36 -0
  126. package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-DHd8IbOs.js → blockDiagram-GPEHLZMM-BF6COWlM.js} +2 -2
  127. package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-CjFkQJ9h.js → c4Diagram-AAUBKEIU-DHOf390v.js} +2 -2
  128. package/packages/remote-control-server/web/dist/assets/channel-JsvbMQjW.js +1 -0
  129. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-C21XWnGZ.js → chunk-2J33WTMH-CeJN8yjM.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-B3SXiJlt.js → chunk-4BX2VUAB-jP76_P2A.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-DjImv45F.js → chunk-55IACEB6-BsFA6emB.js} +1 -1
  132. package/{dist/rcs-web/assets/chunk-727SXJPM-DtnaqsyD.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-BF4HrQjZ.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-BVFgH5mS.js → chunk-AQP2D5EJ-DToJ74Kc.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-Bl8jgb57.js → chunk-FMBD7UC4-DVTARgsa.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BpK111zh.js → chunk-ND2GUHAM-C50Gqv9c.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-ruhIHBim.js → chunk-QZHKN3VN-EsIIh6zV.js} +1 -1
  137. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BXn8Uc4c.js +1 -0
  138. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BXn8Uc4c.js +1 -0
  139. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BnufHNTT.js +2 -0
  140. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-M6tNt14I.js +1 -0
  141. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DaTm29qG.js +4 -0
  142. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-fqoeOvq2.js +43 -0
  143. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-6s2CiaGG.js +10 -0
  144. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-Uy3PDP5S.js → diagram-KO2AKTUF-C0aw_N95.js} +2 -2
  145. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-C8_Rv3rB.js +24 -0
  146. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-Dae4u8Uc.js +24 -0
  147. package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-sre_MTeK.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-CosS5VWr.js} +13 -13
  148. package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-DwrWaLoe.js → flowDiagram-I6XJVG4X-D_Mxs46-.js} +2 -2
  149. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-B0jy8Am_.js +292 -0
  150. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-CSdu2HVI.js +106 -0
  151. package/packages/remote-control-server/web/dist/assets/i18n-Dbt-vEQ3.js +1 -0
  152. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-Dcbc9L1S.js +2 -0
  153. package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-SO0nn3--.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BLMjFE4t.js} +2 -2
  154. package/packages/remote-control-server/web/dist/assets/{journeyDiagram-JHISSGLW-D4igFUd4.js → journeyDiagram-JHISSGLW-Dw4nh1xI.js} +2 -2
  155. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-Cm_yKsZC.js → kanban-definition-UN3LZRKU-DkU3j_ly.js} +2 -2
  156. package/packages/remote-control-server/web/dist/assets/{linear-4IMGll_E.js → linear-N5Cify7M.js} +1 -1
  157. package/packages/remote-control-server/web/dist/assets/main-30YqUvTd.js +238 -0
  158. package/packages/remote-control-server/web/dist/assets/main-EP6Lac2m.css +1 -0
  159. package/{dist/rcs-web/assets/mermaid-VLURNSYL-D_yQ7nAd.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-gfiJUpl_.js} +5 -5
  160. package/packages/remote-control-server/web/dist/assets/{mermaid.core-B1qOUdiU.js → mermaid.core-B_Xc65Bf.js} +5 -5
  161. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-yETom5qt.js → mindmap-definition-RKZ34NQL-DUavHfNu.js} +2 -2
  162. package/{dist/rcs-web/assets/pieDiagram-4H26LBE5-KIhqXGT6.js → packages/remote-control-server/web/dist/assets/pieDiagram-4H26LBE5-DSJaToTU.js} +2 -2
  163. package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-DISqmoA6.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-BFp6TnHj.js} +2 -2
  164. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DsgT4ptk.js → requirementDiagram-4Y6WPE33-eil6F3-O.js} +2 -2
  165. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Dn67kbn0.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-DbGhoepK.js} +2 -2
  166. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CtWaXZHl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-B0YrN1ud.js} +14 -14
  167. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-CofFPj3N.js +1 -0
  168. package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-DPlcvPn1.js +1 -0
  169. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-Dk5WUenC.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-Bx4dusy8.js} +2 -2
  170. package/packages/remote-control-server/web/dist/assets/useTeams-MDI4djXg.js +1 -0
  171. package/{dist/rcs-web/assets/vennDiagram-CIIHVFJN-TY8EiRrC.js → packages/remote-control-server/web/dist/assets/vennDiagram-CIIHVFJN-B_-3l-MF.js} +2 -2
  172. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-BK3oi47X.js → wardleyDiagram-YWT4CUSO-DNSn-pGp.js} +2 -2
  173. package/packages/remote-control-server/web/dist/assets/workerType-OM09y7Lk.js +1 -0
  174. package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DhsxqX67.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-TqUbNp0j.js} +2 -2
  175. package/packages/remote-control-server/web/dist/index.html +2 -3
  176. package/dist/rcs-web/assets/Dashboard-Cyj-Bb86.js +0 -2
  177. package/dist/rcs-web/assets/SessionDetail-SA9QBkyj.js +0 -51
  178. package/dist/rcs-web/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  179. package/dist/rcs-web/assets/channel-CQFGglg-.js +0 -1
  180. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  181. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  182. package/dist/rcs-web/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  183. package/dist/rcs-web/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  184. package/dist/rcs-web/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  185. package/dist/rcs-web/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  186. package/dist/rcs-web/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  187. package/dist/rcs-web/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  188. package/dist/rcs-web/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  189. package/dist/rcs-web/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  190. package/dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  191. package/dist/rcs-web/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  192. package/dist/rcs-web/assets/main-0A4a3FDU.css +0 -1
  193. package/dist/rcs-web/assets/main-Dom16YJk.js +0 -283
  194. package/dist/rcs-web/assets/qr-C0jP9HcD.js +0 -8
  195. package/dist/rcs-web/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  196. package/dist/rcs-web/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  197. package/dist/rcs-web/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
  198. package/packages/remote-control-server/web/dist/assets/Dashboard-Cyj-Bb86.js +0 -2
  199. package/packages/remote-control-server/web/dist/assets/SessionDetail-SA9QBkyj.js +0 -51
  200. package/packages/remote-control-server/web/dist/assets/architectureDiagram-3BPJPVTR-CG9q9_bk.js +0 -36
  201. package/packages/remote-control-server/web/dist/assets/channel-CQFGglg-.js +0 -1
  202. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-C5rBdShD.js +0 -1
  203. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-C5rBdShD.js +0 -1
  204. package/packages/remote-control-server/web/dist/assets/code-block-IT6T5CEO-BLGFrBw3.js +0 -2
  205. package/packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-DBKEXjqM.js +0 -1
  206. package/packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-DB_SWlAe.js +0 -4
  207. package/packages/remote-control-server/web/dist/assets/diagram-2AECGRRQ-Gbm8G3Xz.js +0 -43
  208. package/packages/remote-control-server/web/dist/assets/diagram-5GNKFQAL-BBuWqfDc.js +0 -10
  209. package/packages/remote-control-server/web/dist/assets/diagram-LMA3HP47-DyUwNrlg.js +0 -24
  210. package/packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BXpvRwkJ.js +0 -24
  211. package/packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-ByDYviDl.js +0 -292
  212. package/packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BUnOPw74.js +0 -106
  213. package/packages/remote-control-server/web/dist/assets/infoDiagram-5YYISTIA-B1RpwJHG.js +0 -2
  214. package/packages/remote-control-server/web/dist/assets/main-0A4a3FDU.css +0 -1
  215. package/packages/remote-control-server/web/dist/assets/main-Dom16YJk.js +0 -283
  216. package/packages/remote-control-server/web/dist/assets/qr-C0jP9HcD.js +0 -8
  217. package/packages/remote-control-server/web/dist/assets/qr-scanner-worker.min-D85Z9gVD.js +0 -98
  218. package/packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-D6xp64f3.js +0 -1
  219. package/packages/remote-control-server/web/dist/assets/stateDiagram-v2-BHNVJYJU-D76vZCyQ.js +0 -1
@@ -0,0 +1,440 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { mockConfigModule } from './helpers/mock-config'
4
+
5
+ mock.module('../config', () => mockConfigModule())
6
+
7
+ import { Database } from 'bun:sqlite'
8
+ import {
9
+ initDatabase,
10
+ resetDbSingleton,
11
+ migrateDatabase,
12
+ getDb,
13
+ } from '../db/sqlite'
14
+ import {
15
+ storeReset,
16
+ storeCreateSession,
17
+ storeGetSession,
18
+ storeUpdateSession,
19
+ storeDeleteSession,
20
+ storeBindSession,
21
+ storeCreateEnvironment,
22
+ storeGetEnvironment,
23
+ storeListSessionsByOwnerUuid,
24
+ } from '../store'
25
+ import {
26
+ resolveOwnedWebSessionId,
27
+ listWebSessionsByOwnerUuid,
28
+ listWebSessionSummariesByOwnerUuid,
29
+ listSessionSummariesByOwnerUuid,
30
+ } from '../services/session'
31
+ import { storeListSessionsVisibleToUser } from '../services/session-visibility'
32
+ import { Hono } from 'hono'
33
+ import { sessionAuth } from '../auth/middleware'
34
+ import { issueToken } from '../auth/token'
35
+
36
+ function freshDb(): Database {
37
+ try {
38
+ resetDbSingleton()
39
+ } catch {}
40
+ const uniquePath = `/tmp/rcs-design-gap-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`
41
+ return initDatabase(uniquePath)
42
+ }
43
+
44
+ function createSqliteUser(
45
+ db: Database,
46
+ opts: { username: string; role: string },
47
+ ): string {
48
+ const id = `usr_${randomUUID().replace(/-/g, '')}`
49
+ const now = new Date().toISOString()
50
+ db.query(
51
+ `INSERT INTO users (id, username, password_hash, role, created_at)
52
+ VALUES ($id, $u, 'hash', $r, $now)`,
53
+ ).run({ $id: id, $u: opts.username, $r: opts.role, $now: now })
54
+ return id
55
+ }
56
+
57
+ function createSqliteTeam(
58
+ db: Database,
59
+ opts: { slug: string; createdBy: string },
60
+ ): string {
61
+ const id = `team_${randomUUID().replace(/-/g, '')}`
62
+ const now = new Date().toISOString()
63
+ db.query(
64
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
65
+ VALUES ($id, $name, $slug, $by, $now, $now)`,
66
+ ).run({
67
+ $id: id,
68
+ $name: opts.slug,
69
+ $slug: opts.slug,
70
+ $by: opts.createdBy,
71
+ $now: now,
72
+ })
73
+ return id
74
+ }
75
+
76
+ function addTeamMember(
77
+ db: Database,
78
+ teamId: string,
79
+ userId: string,
80
+ role: string,
81
+ ): void {
82
+ const now = new Date().toISOString()
83
+ db.query(
84
+ `INSERT OR IGNORE INTO team_members (team_id, user_id, role, added_at, added_by)
85
+ VALUES ($t, $u, $r, $now, $by)`,
86
+ ).run({ $t: teamId, $u: userId, $r: role, $by: userId, $now: now })
87
+ }
88
+
89
+ // ===========================================================================
90
+ // Gap 6: storeCreateSession persists to SQLite sessions table
91
+ // ===========================================================================
92
+ describe('Gap 6: storeCreateSession persists to SQLite', () => {
93
+ let db: Database
94
+
95
+ beforeEach(() => {
96
+ db = freshDb()
97
+ storeReset()
98
+ })
99
+
100
+ test('storeCreateSession writes a row to the SQLite sessions table', () => {
101
+ const session = storeCreateSession({ title: 'hello', source: 'web' })
102
+ const row = db
103
+ .query('SELECT * FROM sessions WHERE id = $id')
104
+ .get({ $id: session.id }) as Record<string, unknown> | null
105
+ expect(row).not.toBeNull()
106
+ expect(row!.id).toBe(session.id)
107
+ expect(row!.title).toBe('hello')
108
+ expect(row!.source).toBe('web')
109
+ expect(row!.status).toBe('idle')
110
+ expect(row!.visibility).toBe('private')
111
+ })
112
+
113
+ test('storeCreateSession with cse_ prefix persists correctly', () => {
114
+ const session = storeCreateSession({ idPrefix: 'cse_', source: 'cli' })
115
+ expect(session.id).toMatch(/^cse_/)
116
+ const row = db
117
+ .query('SELECT * FROM sessions WHERE id = $id')
118
+ .get({ $id: session.id }) as Record<string, unknown> | null
119
+ expect(row).not.toBeNull()
120
+ expect(row!.source).toBe('cli')
121
+ })
122
+
123
+ test('storeUpdateSession updates the SQLite row', () => {
124
+ const session = storeCreateSession({})
125
+ storeUpdateSession(session.id, { title: 'updated', status: 'active' })
126
+ const row = db
127
+ .query('SELECT title, status FROM sessions WHERE id = $id')
128
+ .get({ $id: session.id }) as { title: string; status: string } | null
129
+ expect(row).not.toBeNull()
130
+ expect(row!.title).toBe('updated')
131
+ expect(row!.status).toBe('active')
132
+ })
133
+
134
+ test('storeDeleteSession removes the SQLite row', () => {
135
+ const session = storeCreateSession({})
136
+ expect(storeDeleteSession(session.id)).toBe(true)
137
+ const row = db
138
+ .query('SELECT * FROM sessions WHERE id = $id')
139
+ .get({ $id: session.id })
140
+ expect(row).toBeNull()
141
+ })
142
+
143
+ test('storeReset clears the sessions table', () => {
144
+ storeCreateSession({})
145
+ storeCreateSession({})
146
+ storeReset()
147
+ const count = db.query('SELECT COUNT(*) as cnt FROM sessions').get() as {
148
+ cnt: number
149
+ }
150
+ expect(count.cnt).toBe(0)
151
+ })
152
+ })
153
+
154
+ // ===========================================================================
155
+ // Gap 7: storeCreateEnvironment persists to SQLite environments table
156
+ // ===========================================================================
157
+ describe('Gap 7: storeCreateEnvironment persists to SQLite', () => {
158
+ let db: Database
159
+
160
+ beforeEach(() => {
161
+ db = freshDb()
162
+ storeReset()
163
+ })
164
+
165
+ test('storeCreateEnvironment writes a row to the SQLite environments table', () => {
166
+ const env = storeCreateEnvironment({
167
+ secret: 'my-secret',
168
+ machineName: 'mac1',
169
+ workerType: 'claude_code',
170
+ })
171
+ const row = db
172
+ .query('SELECT * FROM environments WHERE id = $id')
173
+ .get({ $id: env.id }) as Record<string, unknown> | null
174
+ expect(row).not.toBeNull()
175
+ expect(row!.id).toBe(env.id)
176
+ expect(row!.machine_name).toBe('mac1')
177
+ expect(row!.worker_type).toBe('claude_code')
178
+ expect(row!.status).toBe('active')
179
+ })
180
+
181
+ test('storeReset clears the environments table', () => {
182
+ storeCreateEnvironment({ secret: 's1' })
183
+ storeReset()
184
+ const count = db
185
+ .query('SELECT COUNT(*) as cnt FROM environments')
186
+ .get() as { cnt: number }
187
+ expect(count.cnt).toBe(0)
188
+ })
189
+ })
190
+
191
+ // ===========================================================================
192
+ // Gap 3: storeBindSession supports team ownership
193
+ // ===========================================================================
194
+ describe('Gap 3: storeBindSession supports ownerType', () => {
195
+ let db: Database
196
+
197
+ beforeEach(() => {
198
+ db = freshDb()
199
+ storeReset()
200
+ })
201
+
202
+ test('storeBindSession with ownerType=team writes team ownership', () => {
203
+ const session = storeCreateSession({})
204
+ const teamId = 'team_abc123'
205
+ storeBindSession(session.id, teamId, 'team')
206
+ const row = db
207
+ .query(
208
+ 'SELECT owner_type, owner_id FROM session_owners WHERE session_id = $sid',
209
+ )
210
+ .get({ $sid: session.id }) as {
211
+ owner_type: string
212
+ owner_id: string
213
+ } | null
214
+ expect(row).not.toBeNull()
215
+ expect(row!.owner_type).toBe('team')
216
+ expect(row!.owner_id).toBe(teamId)
217
+ })
218
+
219
+ test('storeBindSession defaults to ownerType=user', () => {
220
+ const session = storeCreateSession({})
221
+ storeBindSession(session.id, 'user-1')
222
+ const row = db
223
+ .query('SELECT owner_type FROM session_owners WHERE session_id = $sid')
224
+ .get({ $sid: session.id }) as { owner_type: string } | null
225
+ expect(row!.owner_type).toBe('user')
226
+ })
227
+ })
228
+
229
+ // ===========================================================================
230
+ // Gap 4: sessionAuth admin bypass uses real admin userId
231
+ // ===========================================================================
232
+ describe('Gap 4: sessionAuth admin bypass uses real userId', () => {
233
+ let db: Database
234
+ let app: Hono
235
+
236
+ beforeEach(() => {
237
+ db = freshDb()
238
+ storeReset()
239
+ app = new Hono()
240
+ app.get('/protected', async (c, next) => {
241
+ const result = await sessionAuth(c, next)
242
+ if (result) return result
243
+ return c.json({ userId: c.get('userId'), isAdmin: c.get('isAdmin') })
244
+ })
245
+ })
246
+
247
+ test('admin API key bypass returns __system__ userId (not a real admin)', async () => {
248
+ createSqliteUser(db, { username: 'admin1', role: 'admin' })
249
+ const res = await app.request('/protected', {
250
+ headers: { Authorization: 'Bearer test-api-key' },
251
+ })
252
+ const body = (await res.json()) as Record<string, unknown>
253
+ expect(body.isAdmin).toBe(true)
254
+ expect(body.userId).toBe('__system__')
255
+ })
256
+
257
+ test('admin API key bypass returns __system__ when no admin user exists', async () => {
258
+ const res = await app.request('/protected', {
259
+ headers: { Authorization: 'Bearer test-api-key' },
260
+ })
261
+ const body = (await res.json()) as Record<string, unknown>
262
+ expect(body.isAdmin).toBe(true)
263
+ expect(body.userId).toBe('__system__')
264
+ })
265
+ })
266
+
267
+ // ===========================================================================
268
+ // Gap 1: storeListSessionsByOwnerUuid uses new visibility rules
269
+ // ===========================================================================
270
+ describe('Gap 1: storeListSessionsByOwnerUuid uses visibility rules', () => {
271
+ let db: Database
272
+
273
+ beforeEach(() => {
274
+ db = freshDb()
275
+ storeReset()
276
+ })
277
+
278
+ test('does NOT auto-bind orphan sessions', () => {
279
+ const session = storeCreateSession({})
280
+ // Don't bind it — it's an orphan
281
+ const result = storeListSessionsByOwnerUuid('some-random-user')
282
+ const ids = result.map(s => s.id)
283
+ expect(ids).not.toContain(session.id)
284
+ })
285
+
286
+ test('ACP sessions are NOT globally visible to arbitrary users', () => {
287
+ const acpSession = storeCreateSession({ source: 'acp' })
288
+ // A regular user who has no ownership should NOT see it
289
+ const result = storeListSessionsByOwnerUuid('random-user')
290
+ const ids = result.map(s => s.id)
291
+ expect(ids).not.toContain(acpSession.id)
292
+ })
293
+
294
+ test('ACP sessions are visible to their owner', () => {
295
+ const acpSession = storeCreateSession({ source: 'acp' })
296
+ storeBindSession(acpSession.id, 'acp-user')
297
+ const result = storeListSessionsByOwnerUuid('acp-user')
298
+ const ids = result.map(s => s.id)
299
+ expect(ids).toContain(acpSession.id)
300
+ })
301
+
302
+ test('still returns explicitly owned sessions', () => {
303
+ const s1 = storeCreateSession({})
304
+ const s2 = storeCreateSession({})
305
+ storeBindSession(s1.id, 'uuid-1')
306
+ storeBindSession(s2.id, 'uuid-1')
307
+ const owned = storeListSessionsByOwnerUuid('uuid-1')
308
+ expect(owned).toHaveLength(2)
309
+ })
310
+
311
+ test('public sessions are visible to all users', () => {
312
+ const publicSession = storeCreateSession({})
313
+ // Set visibility to public in SQLite
314
+ db.query("UPDATE sessions SET visibility = 'public' WHERE id = $id").run({
315
+ $id: publicSession.id,
316
+ })
317
+ const result = storeListSessionsByOwnerUuid('stranger')
318
+ const ids = result.map(s => s.id)
319
+ expect(ids).toContain(publicSession.id)
320
+ })
321
+ })
322
+
323
+ // ===========================================================================
324
+ // Gap 2: resolveOwnedWebSessionId removes auto-bind
325
+ // ===========================================================================
326
+ describe('Gap 2: resolveOwnedWebSessionId removes auto-bind', () => {
327
+ beforeEach(() => {
328
+ storeReset()
329
+ try {
330
+ resetDbSingleton()
331
+ } catch {}
332
+ initDatabase(
333
+ `/tmp/rcs-gap2-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
334
+ )
335
+ })
336
+
337
+ test('does NOT auto-bind orphan session to requesting user', () => {
338
+ const session = storeCreateSession({})
339
+ // Session exists but has no owner
340
+ const result = resolveOwnedWebSessionId(session.id, 'random-user')
341
+ expect(result).toBeNull()
342
+ })
343
+
344
+ test('returns session ID when user is the owner', () => {
345
+ const session = storeCreateSession({})
346
+ storeBindSession(session.id, 'owner-user')
347
+ const result = resolveOwnedWebSessionId(session.id, 'owner-user')
348
+ expect(result).toBe(session.id)
349
+ })
350
+
351
+ test('returns null when session is owned by another user', () => {
352
+ const session = storeCreateSession({})
353
+ storeBindSession(session.id, 'other-user')
354
+ const result = resolveOwnedWebSessionId(session.id, 'me')
355
+ expect(result).toBeNull()
356
+ })
357
+ })
358
+
359
+ // ===========================================================================
360
+ // Gap 5: ACP sessions associated with registered user
361
+ // ===========================================================================
362
+ describe('Gap 5: ACP sessions associated with registered user', () => {
363
+ let db: Database
364
+
365
+ beforeEach(() => {
366
+ db = freshDb()
367
+ storeReset()
368
+ })
369
+
370
+ test('ACP session bound to a user is visible only to that user', () => {
371
+ const acpSession = storeCreateSession({ source: 'acp' })
372
+ storeBindSession(acpSession.id, 'acp-owner')
373
+
374
+ // Owner can see it
375
+ const ownerResult = storeListSessionsByOwnerUuid('acp-owner')
376
+ expect(ownerResult.map(s => s.id)).toContain(acpSession.id)
377
+
378
+ // Other user cannot see it
379
+ const otherResult = storeListSessionsByOwnerUuid('other-user')
380
+ expect(otherResult.map(s => s.id)).not.toContain(acpSession.id)
381
+ })
382
+ })
383
+
384
+ // ===========================================================================
385
+ // Gap 8: Web UI session list uses new visibility rules
386
+ // ===========================================================================
387
+ describe('Gap 8: Web UI session list uses visibility rules', () => {
388
+ let db: Database
389
+
390
+ beforeEach(() => {
391
+ db = freshDb()
392
+ storeReset()
393
+ })
394
+
395
+ test('listWebSessionsByOwnerUuid returns only visible sessions', () => {
396
+ // Create an orphan session (no owner) — should NOT appear for regular user
397
+ storeCreateSession({ title: 'orphan' })
398
+
399
+ // Create a session owned by this user
400
+ const mySession = storeCreateSession({ title: 'mine' })
401
+ storeBindSession(mySession.id, 'web-user')
402
+
403
+ const result = listWebSessionsByOwnerUuid('web-user')
404
+ const ids = result.map(s => s.id)
405
+ expect(ids).not.toContain('orphan')
406
+ })
407
+
408
+ test('listWebSessionsByOwnerUuid does not include ACP sessions for non-owners', () => {
409
+ const acpSession = storeCreateSession({ source: 'acp', title: 'acp-agent' })
410
+ // Don't bind it to the web user
411
+
412
+ const result = listWebSessionsByOwnerUuid('web-user')
413
+ const ids = result.map(s => s.id)
414
+ // ACP session ID gets transformed to web format, so check the raw ID isn't there
415
+ expect(result.length).toBe(0)
416
+ })
417
+
418
+ test('listWebSessionSummariesByOwnerUuid does not auto-bind orphans', () => {
419
+ storeCreateSession({ title: 'orphan-summary' })
420
+ const result = listWebSessionSummariesByOwnerUuid('web-user')
421
+ expect(result).toHaveLength(0)
422
+ })
423
+
424
+ test('listSessionSummariesByOwnerUuid does not auto-bind orphans', () => {
425
+ storeCreateSession({ title: 'orphan-summary-2' })
426
+ const result = listSessionSummariesByOwnerUuid('web-user')
427
+ expect(result).toHaveLength(0)
428
+ })
429
+
430
+ test('listWebSessionsByOwnerUuid includes public sessions', () => {
431
+ const publicSession = storeCreateSession({ title: 'public-session' })
432
+ db.query("UPDATE sessions SET visibility = 'public' WHERE id = $id").run({
433
+ $id: publicSession.id,
434
+ })
435
+
436
+ const result = listWebSessionsByOwnerUuid('any-user')
437
+ const ids = result.map(s => s.id)
438
+ expect(ids).toContain(publicSession.id)
439
+ })
440
+ })
@@ -1,24 +1,8 @@
1
1
  import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { mockConfigModule } from './helpers/mock-config'
2
3
 
3
4
  // Mock config with very short timeout for testing
4
- const mockConfig = {
5
- port: 3000,
6
- host: '0.0.0.0',
7
- apiKeys: ['test-api-key'],
8
- baseUrl: 'http://localhost:3000',
9
- pollTimeout: 8,
10
- heartbeatInterval: 20,
11
- jwtExpiresIn: 3600,
12
- disconnectTimeout: 300,
13
- webCorsOrigins: [],
14
- wsIdleTimeout: 30,
15
- wsKeepaliveInterval: 20,
16
- }
17
-
18
- mock.module('../config', () => ({
19
- config: mockConfig,
20
- getBaseUrl: () => 'http://localhost:3000',
21
- }))
5
+ mock.module('../config', () => mockConfigModule())
22
6
 
23
7
  import {
24
8
  storeReset,
@@ -0,0 +1,43 @@
1
+ import { Database } from 'bun:sqlite'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { existsSync, unlinkSync } from 'node:fs'
4
+ import { tmpdir } from 'node:os'
5
+ import { resolve } from 'node:path'
6
+ import { afterAll } from 'bun:test'
7
+ import { initDatabase, resetDbSingleton } from '../../db/sqlite'
8
+
9
+ const _tempDbs: string[] = []
10
+
11
+ /**
12
+ * Create a fresh database instance for testing.
13
+ * Automatically cleans up temp files in afterAll.
14
+ */
15
+ export function freshDb(): Database {
16
+ try {
17
+ resetDbSingleton()
18
+ } catch {
19
+ // ignore cleanup errors
20
+ }
21
+ const path = resolve(
22
+ tmpdir(),
23
+ `rcs-test-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
24
+ )
25
+ const db = initDatabase(path)
26
+ _tempDbs.push(path)
27
+ return db
28
+ }
29
+
30
+ afterAll(() => {
31
+ for (const path of _tempDbs) {
32
+ for (const suffix of ['', '-wal', '-shm']) {
33
+ try {
34
+ if (existsSync(path + suffix)) {
35
+ unlinkSync(path + suffix)
36
+ }
37
+ } catch {
38
+ // ignore cleanup errors
39
+ }
40
+ }
41
+ }
42
+ _tempDbs.length = 0
43
+ })
@@ -0,0 +1,94 @@
1
+ import { Database } from 'bun:sqlite'
2
+ import { createHash, randomUUID } from 'node:crypto'
3
+
4
+ export interface TestUser {
5
+ id: string
6
+ username: string
7
+ role: string
8
+ accessToken: string
9
+ }
10
+
11
+ /**
12
+ * Insert a user directly into the database (bypasses auth routes).
13
+ */
14
+ export function createUserDirectly(
15
+ db: Database,
16
+ opts: { username: string; role?: string },
17
+ ): TestUser {
18
+ const id = `usr_${randomUUID().replace(/-/g, '')}`
19
+ const role = opts.role || 'member'
20
+ const now = new Date().toISOString()
21
+ const passwordHash =
22
+ '$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHR0ZXN0$' +
23
+ 'dGVzdGhhc2h0ZXN0aGFzaHRlc3RoYXNodGVzdGhhc2g'
24
+ try {
25
+ db.query(
26
+ `INSERT INTO users (id, username, password_hash, role, created_at)
27
+ VALUES ($id, $username, $hash, $role, $now)`,
28
+ ).run({
29
+ $id: id,
30
+ $username: opts.username,
31
+ $hash: passwordHash,
32
+ $role: role,
33
+ $now: now,
34
+ })
35
+ } catch {
36
+ // users table may not exist
37
+ }
38
+ return { id, username: opts.username, role, accessToken: '' }
39
+ }
40
+
41
+ /**
42
+ * Issue a session token for a user.
43
+ * Falls back to manual token insertion if issueSessionToken is unavailable.
44
+ */
45
+ export function issueTokenForUser(db: Database, userId: string): string {
46
+ try {
47
+ const { issueSessionToken } = require('../auth/session') as {
48
+ issueSessionToken: (
49
+ userId: string,
50
+ db: Database,
51
+ ) => { accessToken: string; refreshToken: string }
52
+ }
53
+ const { accessToken } = issueSessionToken(userId, db)
54
+ return accessToken
55
+ } catch {
56
+ const token = `rct_${randomUUID().replace(/-/g, '')}`
57
+ const tokenHash = createHash('sha256').update(token).digest('hex')
58
+ const now = new Date().toISOString()
59
+ const expiresAt = new Date(Date.now() + 3600_000).toISOString()
60
+ try {
61
+ db.query(
62
+ `INSERT INTO session_tokens (token_hash, user_id, kind, expires_at, created_at)
63
+ VALUES ($hash, $userId, 'access', $exp, $now)`,
64
+ ).run({
65
+ $hash: tokenHash,
66
+ $userId: userId,
67
+ $exp: expiresAt,
68
+ $now: now,
69
+ })
70
+ } catch {
71
+ // session_tokens may not exist
72
+ }
73
+ return token
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Build an Authorization header from a token.
79
+ */
80
+ export function authHeader(token: string): Record<string, string> {
81
+ return { Authorization: `Bearer ${token}` }
82
+ }
83
+
84
+ /**
85
+ * Create a user and issue them an access token.
86
+ */
87
+ export function createAndLoginUser(
88
+ db: Database,
89
+ opts: { username: string; role?: string },
90
+ ): TestUser {
91
+ const user = createUserDirectly(db, opts)
92
+ user.accessToken = issueTokenForUser(db, user.id)
93
+ return user
94
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared mock config factory for RCS tests.
3
+ * Replaces the 18 inline `mockConfig` declarations across test files.
4
+ */
5
+ export function createMockConfig(overrides?: Partial<Record<string, unknown>>) {
6
+ return {
7
+ port: 3000,
8
+ host: '0.0.0.0',
9
+ apiKeys: ['test-api-key'],
10
+ baseUrl: 'http://localhost:3000',
11
+ pollTimeout: 8,
12
+ heartbeatInterval: 20,
13
+ jwtExpiresIn: 3600,
14
+ disconnectTimeout: 300,
15
+ webCorsOrigins: [],
16
+ wsIdleTimeout: 30,
17
+ wsKeepaliveInterval: 20,
18
+ ...overrides,
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Build the module shape for `mock.module('../config', ...)`.
24
+ */
25
+ export function mockConfigModule(overrides?: Partial<Record<string, unknown>>) {
26
+ const config = createMockConfig(overrides)
27
+ return {
28
+ config,
29
+ getBaseUrl: () => config.baseUrl || 'http://localhost:3000',
30
+ }
31
+ }