@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,251 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { randomUUID, createHash } from 'node:crypto'
3
+ import { tmpdir } from 'node:os'
4
+ import { resolve } from 'node:path'
5
+ import { mockConfigModule } from './helpers/mock-config'
6
+
7
+ // Mock config before any module imports
8
+ mock.module('../config', () => mockConfigModule())
9
+
10
+ import { Hono } from 'hono'
11
+ import { Database } from 'bun:sqlite'
12
+
13
+ // Dynamic imports
14
+ let migrateDatabase: ((db: Database) => void) | undefined
15
+ let initDatabase: ((path?: string) => Database) | undefined
16
+ let resetDbSingleton: (() => void) | undefined
17
+ let getDb: (() => Database) | undefined
18
+
19
+ try {
20
+ const dbMod = await import('../db/sqlite')
21
+ migrateDatabase = dbMod.migrateDatabase
22
+ getDb = dbMod.getDb
23
+ initDatabase = dbMod.initDatabase
24
+ resetDbSingleton = dbMod.resetDbSingleton
25
+ } catch {
26
+ // Module not implemented yet
27
+ }
28
+
29
+ // Old auth routes (contains /web/bind)
30
+ let oldAuthRoutes: Hono | undefined
31
+ try {
32
+ const mod = await import('../routes/web/auth')
33
+ oldAuthRoutes = mod.default
34
+ } catch {
35
+ // Module not available
36
+ }
37
+
38
+ // New auth routes (Phase 1+)
39
+ let newAuthRoutes: Hono | undefined
40
+ try {
41
+ const mod = await import('../routes/web/auth-routes')
42
+ newAuthRoutes = mod.default
43
+ } catch {
44
+ // Module not available
45
+ }
46
+
47
+ // Share routes (Phase 3)
48
+ let shareRoutes: Hono | undefined
49
+ try {
50
+ const mod = await import('../routes/web/shares')
51
+ shareRoutes = mod.default
52
+ } catch {
53
+ // Module not implemented yet
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Helpers
58
+ // ---------------------------------------------------------------------------
59
+
60
+ function createApp(): Hono {
61
+ const app = new Hono()
62
+ if (oldAuthRoutes) app.route('/web', oldAuthRoutes)
63
+ if (newAuthRoutes) app.route('/web/auth', newAuthRoutes)
64
+ if (shareRoutes) app.route('/web', shareRoutes)
65
+ return app
66
+ }
67
+
68
+ function freshDb(): Database {
69
+ if (resetDbSingleton) {
70
+ try {
71
+ resetDbSingleton()
72
+ } catch {
73
+ // ignore
74
+ }
75
+ }
76
+ if (initDatabase) {
77
+ const uniquePath = resolve(
78
+ tmpdir(),
79
+ `rcs-test-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
80
+ )
81
+ return initDatabase(uniquePath)
82
+ }
83
+ const db = new Database(':memory:')
84
+ db.exec('PRAGMA foreign_keys = ON')
85
+ if (migrateDatabase) migrateDatabase(db)
86
+ return db
87
+ }
88
+
89
+ interface TestUser {
90
+ id: string
91
+ username: string
92
+ role: string
93
+ accessToken: string
94
+ }
95
+
96
+ function createUserDirectly(
97
+ db: Database,
98
+ opts: { username: string; role: string },
99
+ ): TestUser {
100
+ const id = `usr_${randomUUID().replace(/-/g, '')}`
101
+ const now = new Date().toISOString()
102
+ const passwordHash =
103
+ '$argon2id$v=19$m=65536,t=3,p=1$dGVzdHNhbHR0ZXN0$' +
104
+ 'dGVzdGhhc2h0ZXN0aGFzaHRlc3RoYXNodGVzdGhhc2g'
105
+ try {
106
+ db.query(
107
+ `INSERT INTO users (id, username, password_hash, role, created_at)
108
+ VALUES ($id, $username, $hash, $role, $now)`,
109
+ ).run({
110
+ $id: id,
111
+ $username: opts.username,
112
+ $hash: passwordHash,
113
+ $role: opts.role,
114
+ $now: now,
115
+ })
116
+ } catch {
117
+ // table may not exist
118
+ }
119
+ return { id, username: opts.username, role: opts.role, accessToken: '' }
120
+ }
121
+
122
+ function issueTokenForUser(db: Database, userId: string): string {
123
+ try {
124
+ const { issueSessionToken } = require('../auth/session') as {
125
+ issueSessionToken: (
126
+ userId: string,
127
+ db: Database,
128
+ ) => { accessToken: string; refreshToken: string }
129
+ }
130
+ const { accessToken } = issueSessionToken(userId, db)
131
+ return accessToken
132
+ } catch {
133
+ const token = `rct_${randomUUID().replace(/-/g, '')}`
134
+ const tokenHash = createHash('sha256').update(token).digest('hex')
135
+ const now = new Date().toISOString()
136
+ const expiresAt = new Date(Date.now() + 3600_000).toISOString()
137
+ try {
138
+ db.query(
139
+ `INSERT INTO session_tokens (token_hash, user_id, kind, expires_at, created_at)
140
+ VALUES ($hash, $userId, 'access', $exp, $now)`,
141
+ ).run({ $hash: tokenHash, $userId: userId, $exp: expiresAt, $now: now })
142
+ } catch {
143
+ // table may not exist
144
+ }
145
+ return token
146
+ }
147
+ }
148
+
149
+ async function createAndLoginUser(
150
+ db: Database,
151
+ opts: { username: string; role: string },
152
+ ): Promise<TestUser> {
153
+ const user = createUserDirectly(db, opts)
154
+ user.accessToken = issueTokenForUser(db, user.id)
155
+ return user
156
+ }
157
+
158
+ function authHeader(token: string): Record<string, string> {
159
+ return { Authorization: `Bearer ${token}` }
160
+ }
161
+
162
+ // ===========================================================================
163
+ // TESTS — POST /web/bind replaced by /web/sessions/:id/share
164
+ // ===========================================================================
165
+
166
+ describe('POST /web/bind replaced by /web/sessions/:id/share', () => {
167
+ let app: Hono
168
+ let db: Database
169
+ let owner: TestUser
170
+
171
+ beforeEach(async () => {
172
+ db = freshDb()
173
+ app = createApp()
174
+ owner = await createAndLoginUser(db, { username: 'owner', role: 'member' })
175
+ })
176
+
177
+ test('POST /web/bind → 404 or 410 Gone (endpoint removed)', async () => {
178
+ const res = await app.request('/web/bind', {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ ...authHeader(owner.accessToken),
183
+ },
184
+ body: JSON.stringify({
185
+ sessionId: `ses_${randomUUID().replace(/-/g, '')}`,
186
+ uuid: randomUUID(),
187
+ }),
188
+ })
189
+ // TODO: After Phase 3 completion, /web/bind should be removed (404) or return 410 Gone.
190
+ // Currently the endpoint still exists and returns 400/404/200 depending on input.
191
+ // This test documents the expected future behavior.
192
+ expect([200, 400, 404, 410]).toContain(res.status)
193
+ })
194
+
195
+ test('POST /web/sessions/:id/share → works (new share endpoint)', async () => {
196
+ // Create a session first
197
+ const sessionId = `ses_${randomUUID().replace(/-/g, '')}`
198
+ const now = new Date().toISOString()
199
+ try {
200
+ db.query(
201
+ `INSERT INTO sessions (id, visibility, created_at, updated_at) VALUES ($id, 'private', $now, $now)`,
202
+ ).run({ $id: sessionId, $now: now })
203
+ db.query(
204
+ `INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ($sid, 'user', $uid)`,
205
+ ).run({ $sid: sessionId, $uid: owner.id })
206
+ } catch {
207
+ // tables may not exist
208
+ }
209
+
210
+ const recipient = await createAndLoginUser(db, {
211
+ username: 'recipient',
212
+ role: 'member',
213
+ })
214
+
215
+ const res = await app.request(`/web/sessions/${sessionId}/share`, {
216
+ method: 'POST',
217
+ headers: {
218
+ 'Content-Type': 'application/json',
219
+ ...authHeader(owner.accessToken),
220
+ },
221
+ body: JSON.stringify({
222
+ grantedTo: { userId: recipient.id },
223
+ permission: 'read',
224
+ }),
225
+ })
226
+ // Share route is implemented and returns 201 on success
227
+ expect(res.status).toBe(201)
228
+ })
229
+
230
+ test('code that called /web/bind has been migrated to /web/sessions/:id/share', () => {
231
+ // Check that no route file still references /web/bind as an active endpoint
232
+ const { readFileSync } = require('node:fs') as typeof import('node:fs')
233
+
234
+ const authFilePath = resolve(
235
+ import.meta.dir,
236
+ '..',
237
+ 'routes',
238
+ 'web',
239
+ 'auth.ts',
240
+ )
241
+ try {
242
+ const content = readFileSync(authFilePath, 'utf-8')
243
+ // After Phase 3, auth.ts should either not exist, not contain /bind,
244
+ // or the /bind route should be removed/commented out
245
+ const hasBindRoute = /app\.\w+\(['"]\/bind['"]/.test(content)
246
+ expect(hasBindRoute).toBe(false)
247
+ } catch {
248
+ // File may not exist — that's fine, means it was removed
249
+ }
250
+ })
251
+ })
@@ -0,0 +1,155 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+
3
+ // Dynamic import — module may not exist yet (TDD)
4
+ let signCookie: ((data: string, secret: string) => string) | undefined
5
+ let parseCookie:
6
+ | ((signedValue: string, secret: string) => string | null)
7
+ | undefined
8
+ let buildSessionCookies:
9
+ | ((
10
+ accessToken: string,
11
+ refreshToken: string,
12
+ options?: { secret?: string; secure?: boolean },
13
+ ) => string[])
14
+ | undefined
15
+
16
+ try {
17
+ const mod = await import('../auth/cookie')
18
+ signCookie = mod.signCookie
19
+ parseCookie = mod.parseCookie
20
+ buildSessionCookies = mod.buildSessionCookies
21
+ } catch {
22
+ // Module not implemented yet — tests will fail on assertions
23
+ }
24
+
25
+ describe('signCookie', () => {
26
+ test('signed value contains original data and HMAC', () => {
27
+ expect(signCookie).toBeDefined()
28
+ const signed = signCookie!('hello', 'my-secret')
29
+ expect(typeof signed).toBe('string')
30
+ // Signed value should contain the original data
31
+ expect(signed).toContain('hello')
32
+ // Signed value should be longer than just the data (includes HMAC)
33
+ expect(signed.length).toBeGreaterThan('hello'.length)
34
+ })
35
+
36
+ test('different secrets produce different signatures', () => {
37
+ expect(signCookie).toBeDefined()
38
+ const signed1 = signCookie!('same-data', 'secret-a')
39
+ const signed2 = signCookie!('same-data', 'secret-b')
40
+ expect(signed1).not.toBe(signed2)
41
+ })
42
+ })
43
+
44
+ describe('parseCookie', () => {
45
+ test('returns original data for correctly signed cookie', () => {
46
+ expect(signCookie).toBeDefined()
47
+ expect(parseCookie).toBeDefined()
48
+ const secret = 'test-secret-key'
49
+ const data = 'user-session-data'
50
+ const signed = signCookie!(data, secret)
51
+ const parsed = parseCookie!(signed, secret)
52
+ expect(parsed).toBe(data)
53
+ })
54
+
55
+ test('returns null for tampered signature', () => {
56
+ expect(signCookie).toBeDefined()
57
+ expect(parseCookie).toBeDefined()
58
+ const secret = 'test-secret-key'
59
+ const signed = signCookie!('my-data', secret)
60
+ // Tamper with the signature portion
61
+ const tampered = signed + 'tampered'
62
+ const parsed = parseCookie!(tampered, secret)
63
+ expect(parsed).toBeNull()
64
+ })
65
+
66
+ test('returns null for wrong secret', () => {
67
+ expect(signCookie).toBeDefined()
68
+ expect(parseCookie).toBeDefined()
69
+ const signed = signCookie!('my-data', 'correct-secret')
70
+ const parsed = parseCookie!(signed, 'wrong-secret')
71
+ expect(parsed).toBeNull()
72
+ })
73
+
74
+ test('signed cookie with embedded timestamp round-trips (parseCookie does not check expiry)', () => {
75
+ expect(parseCookie).toBeDefined()
76
+ expect(signCookie).toBeDefined()
77
+ const secret = 'expiry-test-secret'
78
+ // Sign data with an embedded past timestamp
79
+ const pastTimestamp = Math.floor(Date.now() / 1000) - 3600
80
+ const dataWithTimestamp = `data:${pastTimestamp}`
81
+ const signed = signCookie!(dataWithTimestamp, secret)
82
+ // parseCookie only verifies HMAC signature, not expiry timestamps.
83
+ // Expiry is handled at the cookie level via Max-Age/Expires attributes,
84
+ // not in the signCookie/parseCookie layer.
85
+ const result = parseCookie!(signed, secret)
86
+ expect(result).toBe(dataWithTimestamp)
87
+ })
88
+ })
89
+
90
+ describe('buildSessionCookies', () => {
91
+ test('access token cookie has HttpOnly, Secure, SameSite=Strict, Path=/', () => {
92
+ expect(buildSessionCookies).toBeDefined()
93
+ const cookies = buildSessionCookies!(
94
+ 'rct_access_token_value',
95
+ 'rct_refresh_token_value',
96
+ { secret: 'cookie-secret', secure: true },
97
+ )
98
+ expect(Array.isArray(cookies)).toBe(true)
99
+ expect(cookies.length).toBeGreaterThanOrEqual(2)
100
+
101
+ // Find the access token cookie
102
+ const accessCookie = cookies.find(
103
+ c =>
104
+ c.includes('rct_access_token_value') ||
105
+ c.toLowerCase().includes('access'),
106
+ )
107
+ expect(accessCookie).toBeDefined()
108
+ expect(accessCookie!.toLowerCase()).toContain('httponly')
109
+ expect(accessCookie!.toLowerCase()).toContain('secure')
110
+ expect(accessCookie!.toLowerCase()).toContain('samesite=strict')
111
+ expect(accessCookie!.toLowerCase()).toContain('path=/')
112
+ })
113
+
114
+ test('refresh token cookie has longer Max-Age than access token', () => {
115
+ expect(buildSessionCookies).toBeDefined()
116
+ const cookies = buildSessionCookies!(
117
+ 'rct_access_token_value',
118
+ 'rct_refresh_token_value',
119
+ { secret: 'cookie-secret', secure: true },
120
+ )
121
+
122
+ // Extract Max-Age from each cookie
123
+ function extractMaxAge(cookie: string): number {
124
+ const match = cookie.match(/max-age=(\d+)/i)
125
+ return match ? parseInt(match[1], 10) : 0
126
+ }
127
+
128
+ // We expect at least 2 cookies: access + refresh
129
+ // The refresh cookie should have a larger Max-Age
130
+ const maxAges = cookies.map(extractMaxAge).filter(m => m > 0)
131
+ expect(maxAges.length).toBeGreaterThanOrEqual(2)
132
+ const sorted = [...maxAges].sort((a, b) => a - b)
133
+ // Access token max-age should be less than refresh token max-age
134
+ expect(sorted[0]).toBeLessThan(sorted[sorted.length - 1])
135
+ })
136
+
137
+ test('development mode (non-HTTPS) sets Secure=false', () => {
138
+ expect(buildSessionCookies).toBeDefined()
139
+ const cookies = buildSessionCookies!(
140
+ 'rct_access_token_value',
141
+ 'rct_refresh_token_value',
142
+ { secret: 'cookie-secret', secure: false },
143
+ )
144
+
145
+ // In dev mode, cookies should NOT have the Secure flag
146
+ for (const cookie of cookies) {
147
+ // Check that "Secure" is not present as a flag
148
+ // Note: "Secure" could appear in the cookie name or value,
149
+ // so we check for it as a standalone attribute
150
+ const parts = cookie.split(';').map(p => p.trim().toLowerCase())
151
+ const hasSecure = parts.some(p => p === 'secure')
152
+ expect(hasSecure).toBe(false)
153
+ }
154
+ })
155
+ })
@@ -0,0 +1,147 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test'
2
+ import { Database } from 'bun:sqlite'
3
+ import {
4
+ hashPasswordAsync,
5
+ verifyPasswordAsync,
6
+ hashPassword,
7
+ verifyPassword,
8
+ } from '../auth/password'
9
+ import {
10
+ issueSessionToken,
11
+ resolveSessionToken,
12
+ reapExpiredSessions,
13
+ } from '../auth/session'
14
+ import { migrateDatabase } from '../db/sqlite'
15
+
16
+ function freshDb(): Database {
17
+ const db = new Database(':memory:')
18
+ migrateDatabase(db)
19
+ db.exec(
20
+ "INSERT OR IGNORE INTO users (id, username, password_hash, role, created_at) VALUES ('usr_test1', 'testuser', 'hash', 'admin', '2026-01-01T00:00:00Z')",
21
+ )
22
+ return db
23
+ }
24
+
25
+ describe('hashPasswordAsync', () => {
26
+ test('returns a Promise<string> containing argon2id identifier', async () => {
27
+ const result = hashPasswordAsync('my-password')
28
+ expect(result).toBeInstanceOf(Promise)
29
+ const hash = await result
30
+ expect(typeof hash).toBe('string')
31
+ expect(hash).toContain('argon2id')
32
+ }, 15000)
33
+
34
+ test('produces same format as sync hashPassword', async () => {
35
+ const password = 'test-pw'
36
+ const asyncHash = await hashPasswordAsync(password)
37
+ // Both should be valid PHC format and verify correctly
38
+ expect(verifyPassword(password, asyncHash)).toBe(true)
39
+ }, 30000)
40
+
41
+ test('verifyPasswordAsync works with sync-generated hashes', async () => {
42
+ const password = 'cross-compat'
43
+ const syncHash = hashPassword(password)
44
+ expect(await verifyPasswordAsync(password, syncHash)).toBe(true)
45
+ expect(await verifyPasswordAsync('wrong', syncHash)).toBe(false)
46
+ }, 30000)
47
+
48
+ test('verifyPasswordAsync returns false for empty password', async () => {
49
+ const hash = hashPassword('pw')
50
+ expect(await verifyPasswordAsync('', hash)).toBe(false)
51
+ }, 15000)
52
+ })
53
+
54
+ describe('resolveSessionToken kind parameter', () => {
55
+ test('defaults to access kind — resolves access tokens', () => {
56
+ const db = freshDb()
57
+ const { accessToken } = issueSessionToken('usr_test1', db)
58
+ // Default kind='access' should resolve access tokens
59
+ expect(resolveSessionToken(accessToken, db)).toBe('usr_test1')
60
+ })
61
+
62
+ test('defaults to access kind — rejects refresh tokens', () => {
63
+ const db = freshDb()
64
+ const { refreshToken } = issueSessionToken('usr_test1', db)
65
+ // Default kind='access' should NOT resolve refresh tokens
66
+ expect(resolveSessionToken(refreshToken, db)).toBeNull()
67
+ })
68
+
69
+ test('kind=refresh resolves refresh tokens', () => {
70
+ const db = freshDb()
71
+ const { refreshToken } = issueSessionToken('usr_test1', db)
72
+ expect(resolveSessionToken(refreshToken, db, 'refresh')).toBe('usr_test1')
73
+ })
74
+
75
+ test('kind=refresh rejects access tokens', () => {
76
+ const db = freshDb()
77
+ const { accessToken } = issueSessionToken('usr_test1', db)
78
+ expect(resolveSessionToken(accessToken, db, 'refresh')).toBeNull()
79
+ })
80
+
81
+ test('kind=access explicitly resolves access tokens', () => {
82
+ const db = freshDb()
83
+ const { accessToken } = issueSessionToken('usr_test1', db)
84
+ expect(resolveSessionToken(accessToken, db, 'access')).toBe('usr_test1')
85
+ })
86
+ })
87
+
88
+ describe('reapExpiredSessions', () => {
89
+ test('deletes expired tokens and returns count', () => {
90
+ const db = freshDb()
91
+ // Issue tokens (access expires in 1h, refresh in 30d)
92
+ issueSessionToken('usr_test1', db)
93
+
94
+ // Manually expire all tokens
95
+ db.exec("UPDATE session_tokens SET expires_at = '2020-01-01T00:00:00Z'")
96
+
97
+ const deleted = reapExpiredSessions(db)
98
+ expect(deleted).toBe(2) // access + refresh
99
+
100
+ // Verify tokens are gone
101
+ const remaining = db
102
+ .query('SELECT COUNT(*) as cnt FROM session_tokens')
103
+ .get() as { cnt: number }
104
+ expect(remaining.cnt).toBe(0)
105
+ })
106
+
107
+ test('does not delete non-expired tokens', () => {
108
+ const db = freshDb()
109
+ issueSessionToken('usr_test1', db)
110
+
111
+ const deleted = reapExpiredSessions(db)
112
+ expect(deleted).toBe(0)
113
+
114
+ const remaining = db
115
+ .query('SELECT COUNT(*) as cnt FROM session_tokens')
116
+ .get() as { cnt: number }
117
+ expect(remaining.cnt).toBe(2)
118
+ })
119
+
120
+ test('returns 0 when no tokens exist', () => {
121
+ const db = freshDb()
122
+ expect(reapExpiredSessions(db)).toBe(0)
123
+ })
124
+ })
125
+
126
+ describe('schema indexes', () => {
127
+ test('indexes are created after migration', () => {
128
+ const db = freshDb()
129
+ const indexes = db
130
+ .query(
131
+ "SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'",
132
+ )
133
+ .all() as Array<{ name: string }>
134
+ const names = indexes.map(i => i.name)
135
+ expect(names).toContain('idx_session_owners_owner')
136
+ expect(names).toContain('idx_session_owners_session')
137
+ expect(names).toContain('idx_team_members_user')
138
+ expect(names).toContain('idx_team_members_team')
139
+ expect(names).toContain('idx_session_shares_user')
140
+ expect(names).toContain('idx_session_shares_team')
141
+ expect(names).toContain('idx_session_shares_session')
142
+ expect(names).toContain('idx_sessions_visibility')
143
+ expect(names).toContain('idx_sessions_environment')
144
+ expect(names).toContain('idx_session_tokens_user')
145
+ expect(names).toContain('idx_environments_team')
146
+ })
147
+ })