@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,582 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
2
+ import { Database } from 'bun:sqlite'
3
+ import { unlinkSync, existsSync } from 'node:fs'
4
+
5
+ // Dynamic import — module may not exist yet (TDD)
6
+ let migrateDatabase: ((db: Database) => void) | undefined
7
+ let getDb: (() => Database) | undefined
8
+ let initDatabase: ((path?: string) => Database) | undefined
9
+ let resetDbSingleton: (() => void) | undefined
10
+
11
+ try {
12
+ const mod = await import('../db/sqlite')
13
+ migrateDatabase = mod.migrateDatabase
14
+ getDb = mod.getDb
15
+ initDatabase = mod.initDatabase
16
+ resetDbSingleton = mod.resetDbSingleton
17
+ } catch {
18
+ // Module not implemented yet — tests will fail on assertions
19
+ }
20
+
21
+ // Helper: create a fresh in-memory database
22
+ function freshDb(): Database {
23
+ return new Database(':memory:')
24
+ }
25
+
26
+ // Helper: list table names in a database
27
+ function listTables(db: Database): string[] {
28
+ const rows = db
29
+ .query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
30
+ .all() as Array<{ name: string }>
31
+ return rows.map(r => r.name)
32
+ }
33
+
34
+ // Helper: get column info for a table
35
+ function tableColumns(
36
+ db: Database,
37
+ tableName: string,
38
+ ): Array<{ name: string; type: string; notnull: number }> {
39
+ return db.query(`PRAGMA table_info(${tableName})`).all() as Array<{
40
+ name: string
41
+ type: string
42
+ notnull: number
43
+ }>
44
+ }
45
+
46
+ // Helper: get foreign keys for a table
47
+ function tableForeignKeys(
48
+ db: Database,
49
+ tableName: string,
50
+ ): Array<{ from: string; table: string }> {
51
+ return db.query(`PRAGMA foreign_key_list(${tableName})`).all() as Array<{
52
+ from: string
53
+ table: string
54
+ }>
55
+ }
56
+
57
+ describe('migrateDatabase', () => {
58
+ test('creates all required tables on first run', () => {
59
+ expect(migrateDatabase).toBeDefined()
60
+ const db = freshDb()
61
+ migrateDatabase!(db)
62
+ const tables = listTables(db)
63
+ expect(tables).toContain('users')
64
+ expect(tables).toContain('session_tokens')
65
+ expect(tables).toContain('invitations')
66
+ })
67
+
68
+ test('is idempotent — running twice does not error', () => {
69
+ expect(migrateDatabase).toBeDefined()
70
+ const db = freshDb()
71
+ migrateDatabase!(db)
72
+ // Second run should not throw
73
+ expect(() => migrateDatabase!(db)).not.toThrow()
74
+ const tables = listTables(db)
75
+ // Still exactly one of each (no duplicates)
76
+ const userCount = tables.filter(t => t === 'users').length
77
+ expect(userCount).toBe(1)
78
+ })
79
+
80
+ test('users table has correct schema', () => {
81
+ expect(migrateDatabase).toBeDefined()
82
+ const db = freshDb()
83
+ migrateDatabase!(db)
84
+ const cols = tableColumns(db, 'users')
85
+ const colNames = cols.map(c => c.name)
86
+ expect(colNames).toContain('id')
87
+ expect(colNames).toContain('username')
88
+ expect(colNames).toContain('password_hash')
89
+ expect(colNames).toContain('role')
90
+ expect(colNames).toContain('created_at')
91
+ // id should be primary key (notnull=1)
92
+ const idCol = cols.find(c => c.name === 'id')
93
+ expect(idCol?.notnull).toBe(1)
94
+ // username should be not null
95
+ const usernameCol = cols.find(c => c.name === 'username')
96
+ expect(usernameCol?.notnull).toBe(1)
97
+ // password_hash should be not null
98
+ const pwCol = cols.find(c => c.name === 'password_hash')
99
+ expect(pwCol?.notnull).toBe(1)
100
+ // role should be not null
101
+ const roleCol = cols.find(c => c.name === 'role')
102
+ expect(roleCol?.notnull).toBe(1)
103
+ })
104
+
105
+ test('session_tokens table has correct schema', () => {
106
+ expect(migrateDatabase).toBeDefined()
107
+ const db = freshDb()
108
+ migrateDatabase!(db)
109
+ const cols = tableColumns(db, 'session_tokens')
110
+ const colNames = cols.map(c => c.name)
111
+ expect(colNames).toContain('token_hash')
112
+ expect(colNames).toContain('user_id')
113
+ expect(colNames).toContain('kind')
114
+ expect(colNames).toContain('expires_at')
115
+ expect(colNames).toContain('created_at')
116
+ // foreign key to users
117
+ const fks = tableForeignKeys(db, 'session_tokens')
118
+ const userIdFk = fks.find(fk => fk.from === 'user_id')
119
+ expect(userIdFk).toBeDefined()
120
+ expect(userIdFk?.table).toBe('users')
121
+ })
122
+
123
+ test('invitations table has correct schema', () => {
124
+ expect(migrateDatabase).toBeDefined()
125
+ const db = freshDb()
126
+ migrateDatabase!(db)
127
+ const cols = tableColumns(db, 'invitations')
128
+ const colNames = cols.map(c => c.name)
129
+ expect(colNames).toContain('token_hash')
130
+ expect(colNames).toContain('role')
131
+ expect(colNames).toContain('expires_at')
132
+ expect(colNames).toContain('created_by')
133
+ })
134
+ })
135
+
136
+ describe('getDb', () => {
137
+ afterEach(() => {
138
+ // Reset singleton between tests if possible
139
+ if (resetDbSingleton) resetDbSingleton()
140
+ })
141
+
142
+ test('returns a Database instance', () => {
143
+ expect(getDb).toBeDefined()
144
+ const db = getDb!()
145
+ expect(db).toBeInstanceOf(Database)
146
+ })
147
+
148
+ test('returns the same connection (singleton)', () => {
149
+ expect(getDb).toBeDefined()
150
+ const db1 = getDb!()
151
+ const db2 = getDb!()
152
+ expect(db1).toBe(db2)
153
+ })
154
+
155
+ test('WAL mode is enabled', () => {
156
+ expect(getDb).toBeDefined()
157
+ const db = getDb!()
158
+ const result = db.query('PRAGMA journal_mode').get() as {
159
+ journal_mode: string
160
+ }
161
+ expect(result.journal_mode).toBe('wal')
162
+ })
163
+ })
164
+
165
+ // ===========================================================================
166
+ // Phase 2 tables (team system + session isolation)
167
+ // ===========================================================================
168
+
169
+ describe('session_owners table', () => {
170
+ test('table exists after migration', () => {
171
+ expect(migrateDatabase).toBeDefined()
172
+ const db = freshDb()
173
+ migrateDatabase!(db)
174
+ const tables = listTables(db)
175
+ expect(tables).toContain('session_owners')
176
+ })
177
+
178
+ test('owner_type CHECK constraint (user/team)', () => {
179
+ expect(migrateDatabase).toBeDefined()
180
+ const db = freshDb()
181
+ migrateDatabase!(db)
182
+
183
+ // Create a user for FK
184
+ db.exec(
185
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
186
+ )
187
+
188
+ // Valid: user type
189
+ expect(() => {
190
+ db.exec(
191
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'user', 'u1')",
192
+ )
193
+ }).not.toThrow()
194
+
195
+ // Valid: team type (need a team)
196
+ try {
197
+ db.exec(
198
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
199
+ )
200
+ expect(() => {
201
+ db.exec(
202
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s2', 'team', 't1')",
203
+ )
204
+ }).not.toThrow()
205
+ } catch {
206
+ // teams table may not exist yet
207
+ }
208
+
209
+ // Invalid: bad owner_type
210
+ expect(() => {
211
+ db.exec(
212
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s3', 'invalid', 'u1')",
213
+ )
214
+ }).toThrow()
215
+ })
216
+
217
+ test('composite primary key (session_id, owner_type, owner_id)', () => {
218
+ expect(migrateDatabase).toBeDefined()
219
+ const db = freshDb()
220
+ migrateDatabase!(db)
221
+
222
+ db.exec(
223
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
224
+ )
225
+
226
+ // Insert first owner
227
+ db.exec(
228
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'user', 'u1')",
229
+ )
230
+
231
+ // Duplicate should fail (PK violation)
232
+ expect(() => {
233
+ db.exec(
234
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'user', 'u1')",
235
+ )
236
+ }).toThrow()
237
+ })
238
+
239
+ test('can insert user type owner', () => {
240
+ expect(migrateDatabase).toBeDefined()
241
+ const db = freshDb()
242
+ migrateDatabase!(db)
243
+
244
+ db.exec(
245
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
246
+ )
247
+
248
+ expect(() => {
249
+ db.exec(
250
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'user', 'u1')",
251
+ )
252
+ }).not.toThrow()
253
+
254
+ const row = db
255
+ .query('SELECT * FROM session_owners WHERE session_id = ?')
256
+ .get('s1') as Record<string, unknown> | null
257
+ expect(row).not.toBeNull()
258
+ expect(row?.owner_type).toBe('user')
259
+ })
260
+
261
+ test('can insert team type owner', () => {
262
+ expect(migrateDatabase).toBeDefined()
263
+ const db = freshDb()
264
+ migrateDatabase!(db)
265
+
266
+ db.exec(
267
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
268
+ )
269
+
270
+ try {
271
+ db.exec(
272
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
273
+ )
274
+
275
+ expect(() => {
276
+ db.exec(
277
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'team', 't1')",
278
+ )
279
+ }).not.toThrow()
280
+ } catch {
281
+ // teams table may not exist
282
+ }
283
+ })
284
+
285
+ test('same session can have multiple owners (user + team mixed)', () => {
286
+ expect(migrateDatabase).toBeDefined()
287
+ const db = freshDb()
288
+ migrateDatabase!(db)
289
+
290
+ db.exec(
291
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
292
+ )
293
+
294
+ // User owner
295
+ db.exec(
296
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'user', 'u1')",
297
+ )
298
+
299
+ // Team owner on the same session
300
+ try {
301
+ db.exec(
302
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
303
+ )
304
+ db.exec(
305
+ "INSERT INTO session_owners (session_id, owner_type, owner_id) VALUES ('s1', 'team', 't1')",
306
+ )
307
+
308
+ const rows = db
309
+ .query('SELECT * FROM session_owners WHERE session_id = ?')
310
+ .all('s1') as Array<Record<string, unknown>>
311
+ expect(rows.length).toBe(2)
312
+ } catch {
313
+ // teams table may not exist — partial pass
314
+ }
315
+ })
316
+ })
317
+
318
+ describe('session_shares table', () => {
319
+ test('table exists after migration', () => {
320
+ expect(migrateDatabase).toBeDefined()
321
+ const db = freshDb()
322
+ migrateDatabase!(db)
323
+ const tables = listTables(db)
324
+ expect(tables).toContain('session_shares')
325
+ })
326
+
327
+ test('permission CHECK constraint (read/write)', () => {
328
+ expect(migrateDatabase).toBeDefined()
329
+ const db = freshDb()
330
+ migrateDatabase!(db)
331
+
332
+ db.exec(
333
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
334
+ )
335
+
336
+ // Valid: read
337
+ expect(() => {
338
+ db.exec(
339
+ "INSERT INTO session_shares (id, session_id, granted_to_user, permission, granted_by, granted_at) VALUES ('sh1', 's1', 'u1', 'read', 'u1', '2026-01-01T00:00:00Z')",
340
+ )
341
+ }).not.toThrow()
342
+
343
+ // Valid: write
344
+ expect(() => {
345
+ db.exec(
346
+ "INSERT INTO session_shares (id, session_id, granted_to_user, permission, granted_by, granted_at) VALUES ('sh2', 's1', 'u1', 'write', 'u1', '2026-01-01T00:00:00Z')",
347
+ )
348
+ }).not.toThrow()
349
+
350
+ // Invalid: bad permission
351
+ expect(() => {
352
+ db.exec(
353
+ "INSERT INTO session_shares (id, session_id, granted_to_user, permission, granted_by, granted_at) VALUES ('sh3', 's1', 'u1', 'admin', 'u1', '2026-01-01T00:00:00Z')",
354
+ )
355
+ }).toThrow()
356
+ })
357
+
358
+ test('can set expiry time', () => {
359
+ expect(migrateDatabase).toBeDefined()
360
+ const db = freshDb()
361
+ migrateDatabase!(db)
362
+
363
+ db.exec(
364
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
365
+ )
366
+
367
+ const futureExpiry = '2027-01-01T00:00:00Z'
368
+ expect(() => {
369
+ db.exec(
370
+ `INSERT INTO session_shares (id, session_id, granted_to_user, permission, expires_at, granted_by, granted_at) VALUES ('sh1', 's1', 'u1', 'read', '${futureExpiry}', 'u1', '2026-01-01T00:00:00Z')`,
371
+ )
372
+ }).not.toThrow()
373
+
374
+ const row = db
375
+ .query('SELECT expires_at FROM session_shares WHERE id = ?')
376
+ .get('sh1') as { expires_at: string } | null
377
+ expect(row?.expires_at).toBe(futureExpiry)
378
+ })
379
+
380
+ test('can associate with user or team', () => {
381
+ expect(migrateDatabase).toBeDefined()
382
+ const db = freshDb()
383
+ migrateDatabase!(db)
384
+
385
+ db.exec(
386
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
387
+ )
388
+
389
+ // User share
390
+ expect(() => {
391
+ db.exec(
392
+ "INSERT INTO session_shares (id, session_id, granted_to_user, permission, granted_by, granted_at) VALUES ('sh1', 's1', 'u1', 'read', 'u1', '2026-01-01T00:00:00Z')",
393
+ )
394
+ }).not.toThrow()
395
+
396
+ // Team share
397
+ try {
398
+ db.exec(
399
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
400
+ )
401
+ expect(() => {
402
+ db.exec(
403
+ "INSERT INTO session_shares (id, session_id, granted_to_team, permission, granted_by, granted_at) VALUES ('sh2', 's1', 't1', 'write', 'u1', '2026-01-01T00:00:00Z')",
404
+ )
405
+ }).not.toThrow()
406
+ } catch {
407
+ // teams table may not exist
408
+ }
409
+ })
410
+ })
411
+
412
+ describe('teams / team_members / team_environments tables', () => {
413
+ test('all three tables exist after migration', () => {
414
+ expect(migrateDatabase).toBeDefined()
415
+ const db = freshDb()
416
+ migrateDatabase!(db)
417
+ const tables = listTables(db)
418
+ expect(tables).toContain('teams')
419
+ expect(tables).toContain('team_members')
420
+ expect(tables).toContain('team_environments')
421
+ })
422
+
423
+ test('teams table has correct schema', () => {
424
+ expect(migrateDatabase).toBeDefined()
425
+ const db = freshDb()
426
+ migrateDatabase!(db)
427
+ const cols = tableColumns(db, 'teams')
428
+ const colNames = cols.map(c => c.name)
429
+ expect(colNames).toContain('id')
430
+ expect(colNames).toContain('name')
431
+ expect(colNames).toContain('slug')
432
+ expect(colNames).toContain('created_by')
433
+ expect(colNames).toContain('created_at')
434
+ expect(colNames).toContain('updated_at')
435
+ expect(colNames).toContain('deleted_at')
436
+ })
437
+
438
+ test('team_members role CHECK constraint (owner/admin/member)', () => {
439
+ expect(migrateDatabase).toBeDefined()
440
+ const db = freshDb()
441
+ migrateDatabase!(db)
442
+
443
+ // Create prerequisite data
444
+ db.exec(
445
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
446
+ )
447
+ db.exec(
448
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
449
+ )
450
+
451
+ // Valid roles
452
+ expect(() => {
453
+ db.exec(
454
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u1', 'owner', '2026-01-01T00:00:00Z', 'u1')",
455
+ )
456
+ }).not.toThrow()
457
+
458
+ db.exec(
459
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u2', 'testuser2', 'hash', 'member', '2026-01-01T00:00:00Z')",
460
+ )
461
+ expect(() => {
462
+ db.exec(
463
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u2', 'admin', '2026-01-01T00:00:00Z', 'u1')",
464
+ )
465
+ }).not.toThrow()
466
+
467
+ db.exec(
468
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u3', 'testuser3', 'hash', 'member', '2026-01-01T00:00:00Z')",
469
+ )
470
+ expect(() => {
471
+ db.exec(
472
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u3', 'member', '2026-01-01T00:00:00Z', 'u1')",
473
+ )
474
+ }).not.toThrow()
475
+
476
+ // Invalid role
477
+ db.exec(
478
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u4', 'testuser4', 'hash', 'member', '2026-01-01T00:00:00Z')",
479
+ )
480
+ expect(() => {
481
+ db.exec(
482
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u4', 'superadmin', '2026-01-01T00:00:00Z', 'u1')",
483
+ )
484
+ }).toThrow()
485
+ })
486
+
487
+ test('foreign key constraints', () => {
488
+ expect(migrateDatabase).toBeDefined()
489
+ const db = freshDb()
490
+ migrateDatabase!(db)
491
+
492
+ // team_members FK to teams
493
+ expect(() => {
494
+ db.exec(
495
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('nonexistent', 'u1', 'owner', '2026-01-01T00:00:00Z', 'u1')",
496
+ )
497
+ }).toThrow()
498
+
499
+ // team_environments FK to teams
500
+ expect(() => {
501
+ db.exec(
502
+ "INSERT INTO team_environments (team_id, environment_id) VALUES ('nonexistent', 'e1')",
503
+ )
504
+ }).toThrow()
505
+ })
506
+
507
+ test('teams.deleted_at soft delete field exists', () => {
508
+ expect(migrateDatabase).toBeDefined()
509
+ const db = freshDb()
510
+ migrateDatabase!(db)
511
+ const cols = tableColumns(db, 'teams')
512
+ const deletedAt = cols.find(c => c.name === 'deleted_at')
513
+ expect(deletedAt).toBeDefined()
514
+ // deleted_at should be nullable (notnull = 0)
515
+ expect(deletedAt?.notnull).toBe(0)
516
+ })
517
+
518
+ test('team_members composite primary key (team_id, user_id)', () => {
519
+ expect(migrateDatabase).toBeDefined()
520
+ const db = freshDb()
521
+ migrateDatabase!(db)
522
+
523
+ db.exec(
524
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
525
+ )
526
+ db.exec(
527
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
528
+ )
529
+ db.exec(
530
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u1', 'owner', '2026-01-01T00:00:00Z', 'u1')",
531
+ )
532
+
533
+ // Duplicate should fail
534
+ expect(() => {
535
+ db.exec(
536
+ "INSERT INTO team_members (team_id, user_id, role, added_at, added_by) VALUES ('t1', 'u1', 'member', '2026-01-01T00:00:00Z', 'u1')",
537
+ )
538
+ }).toThrow()
539
+ })
540
+
541
+ test('team_environments composite primary key (team_id, environment_id)', () => {
542
+ expect(migrateDatabase).toBeDefined()
543
+ const db = freshDb()
544
+ migrateDatabase!(db)
545
+
546
+ db.exec(
547
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
548
+ )
549
+ db.exec(
550
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team', 'team', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
551
+ )
552
+ db.exec(
553
+ "INSERT INTO team_environments (team_id, environment_id) VALUES ('t1', 'e1')",
554
+ )
555
+
556
+ // Duplicate should fail
557
+ expect(() => {
558
+ db.exec(
559
+ "INSERT INTO team_environments (team_id, environment_id) VALUES ('t1', 'e1')",
560
+ )
561
+ }).toThrow()
562
+ })
563
+
564
+ test('slug uniqueness enforced', () => {
565
+ expect(migrateDatabase).toBeDefined()
566
+ const db = freshDb()
567
+ migrateDatabase!(db)
568
+
569
+ db.exec(
570
+ "INSERT INTO users (id, username, password_hash, role, created_at) VALUES ('u1', 'testuser', 'hash', 'member', '2026-01-01T00:00:00Z')",
571
+ )
572
+ db.exec(
573
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t1', 'Team One', 'same-slug', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
574
+ )
575
+
576
+ expect(() => {
577
+ db.exec(
578
+ "INSERT INTO teams (id, name, slug, created_by, created_at, updated_at) VALUES ('t2', 'Team Two', 'same-slug', 'u1', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')",
579
+ )
580
+ }).toThrow()
581
+ })
582
+ })