@saluzi/saluzi-edu 0.2.13 → 0.2.15

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 (179) hide show
  1. package/dist/cli.js +79 -72
  2. package/dist/rcs-web/assets/AdminPanel-CQU763nr.js +1 -0
  3. package/dist/rcs-web/assets/Dashboard-B1hQfqI9.js +1 -0
  4. package/dist/rcs-web/assets/Join-7hMl360t.js +1 -0
  5. package/{packages/remote-control-server/web/dist/assets/Login-0VGZs85I.js → dist/rcs-web/assets/Login-BE8mNc0U.js} +1 -1
  6. package/dist/rcs-web/assets/SessionDetail-B5C0o1YT.js +55 -0
  7. package/dist/rcs-web/assets/SessionVisibilityBadge-CQN3bJW4.js +11 -0
  8. package/dist/rcs-web/assets/{Setup-C0cjxXMt.js → Setup-CqZ5gEBQ.js} +1 -1
  9. package/dist/rcs-web/assets/{ShareLanding-DUZrreJ7.js → ShareLanding-DBC9gbJo.js} +1 -1
  10. package/dist/rcs-web/assets/TeamCreate-DSYdTaPe.js +1 -0
  11. package/dist/rcs-web/assets/TeamDetail-CmJGsgOP.js +1 -0
  12. package/dist/rcs-web/assets/TeamList-BMS_Qq8U.js +1 -0
  13. package/dist/rcs-web/assets/{TeamSettings-CNVwYwcp.js → TeamSettings-C0sYgCFT.js} +1 -1
  14. package/dist/rcs-web/assets/UserSettings-CqzeJ5cc.js +1 -0
  15. package/{packages/remote-control-server/web/dist/assets/arc-CJxirXEQ.js → dist/rcs-web/assets/arc-DBPKxYzS.js} +1 -1
  16. package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-BZvIHrLA.js → architectureDiagram-3BPJPVTR-D88rj_js.js} +1 -1
  17. package/dist/rcs-web/assets/{blockDiagram-GPEHLZMM-3p0y3Ija.js → blockDiagram-GPEHLZMM-xzgUpRMn.js} +1 -1
  18. package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-Bmz9A1wn.js → c4Diagram-AAUBKEIU-vpg_EoPb.js} +1 -1
  19. package/dist/rcs-web/assets/channel-Daz3yMSu.js +1 -0
  20. package/dist/rcs-web/assets/{chunk-2J33WTMH-B8gQz22j.js → chunk-2J33WTMH-DXWsXvhS.js} +1 -1
  21. package/dist/rcs-web/assets/{chunk-4BX2VUAB-CgLEZsTU.js → chunk-4BX2VUAB-BtTxwj9K.js} +1 -1
  22. package/dist/rcs-web/assets/{chunk-55IACEB6-0mdE_-IS.js → chunk-55IACEB6-D2-Q95qg.js} +1 -1
  23. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-DdtjnIHR.js → dist/rcs-web/assets/chunk-727SXJPM-Ce3e5x8u.js} +1 -1
  24. package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-BtwFPk50.js → dist/rcs-web/assets/chunk-AQP2D5EJ-C0UbKVtd.js} +1 -1
  25. package/dist/rcs-web/assets/{chunk-FMBD7UC4-BcO9mQ0P.js → chunk-FMBD7UC4-BCwppVqh.js} +1 -1
  26. package/dist/rcs-web/assets/{chunk-ND2GUHAM-BvRq13S_.js → chunk-ND2GUHAM-CnOBwWrm.js} +1 -1
  27. package/dist/rcs-web/assets/{chunk-QZHKN3VN-D7GHkPpq.js → chunk-QZHKN3VN-CDdzB71P.js} +1 -1
  28. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +1 -0
  29. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +1 -0
  30. package/dist/rcs-web/assets/{code-block-IT6T5CEO-B1p-vO-3.js → code-block-IT6T5CEO-C1_H_ztH.js} +1 -1
  31. package/dist/rcs-web/assets/copy-DXU1lQqc.js +6 -0
  32. package/{packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js → dist/rcs-web/assets/cose-bilkent-S5V4N54A-BC6nfjTF.js} +1 -1
  33. package/dist/rcs-web/assets/{dagre-BM42HDAG-CHGuio22.js → dagre-BM42HDAG-CEPR_pRx.js} +1 -1
  34. package/dist/rcs-web/assets/{diagram-2AECGRRQ-DIDRyQk8.js → diagram-2AECGRRQ-fVMGnSYa.js} +1 -1
  35. package/dist/rcs-web/assets/{diagram-5GNKFQAL-CNubKb6P.js → diagram-5GNKFQAL-BPzN2rbK.js} +1 -1
  36. package/dist/rcs-web/assets/{diagram-KO2AKTUF-CzQh664-.js → diagram-KO2AKTUF-Boneg9n-.js} +1 -1
  37. package/dist/rcs-web/assets/{diagram-LMA3HP47-CWBR7-VG.js → diagram-LMA3HP47-B8AtvjO9.js} +1 -1
  38. package/dist/rcs-web/assets/{diagram-OG6HWLK6-BMHdlbcZ.js → diagram-OG6HWLK6-DjY7A8jK.js} +1 -1
  39. package/{packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-OgT8dC-K.js → dist/rcs-web/assets/erDiagram-TEJ5UH35-C1BEfKxk.js} +1 -1
  40. package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-7TbI9MLV.js} +1 -1
  41. package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-DctpzEKN.js → ganttDiagram-6RSMTGT7-CMAV0A01.js} +1 -1
  42. package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-5KpLszm9.js → gitGraphDiagram-PVQCEYII-DAlR8G81.js} +1 -1
  43. package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-BAKD_Tt7.js → infoDiagram-5YYISTIA-0T0DAV4W.js} +1 -1
  44. package/{packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js → dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js} +1 -1
  45. package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-BtGih-ik.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js} +1 -1
  46. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-BsEGZsD1.js → kanban-definition-UN3LZRKU-Qzqw1H5B.js} +1 -1
  47. package/{packages/remote-control-server/web/dist/assets/linear-Dj636PNG.js → dist/rcs-web/assets/linear-D2KIJoCI.js} +1 -1
  48. package/dist/rcs-web/assets/main-J6cPs2y8.js +253 -0
  49. package/dist/rcs-web/assets/{mermaid-VLURNSYL-DutpHHm9.js → mermaid-VLURNSYL-CxWk1wHz.js} +4 -4
  50. package/dist/rcs-web/assets/{mermaid.core-XHHf0pen.js → mermaid.core-C48YvKu4.js} +5 -5
  51. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-BBa8V_3g.js → mindmap-definition-RKZ34NQL-BfmcTuCA.js} +1 -1
  52. package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-BuNEdo9H.js → pieDiagram-4H26LBE5-DVNZpnfb.js} +1 -1
  53. package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-CsJgyQIN.js → quadrantDiagram-W4KKPZXB-COL55Z3i.js} +1 -1
  54. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-DqMJZ914.js → requirementDiagram-4Y6WPE33-CFczYQ0a.js} +1 -1
  55. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js} +1 -1
  56. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js} +1 -1
  57. package/{packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-S-QqQr-D.js → dist/rcs-web/assets/stateDiagram-AJRCARHV-Dw9xJZ-A.js} +1 -1
  58. package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-D77KfDr-.js → stateDiagram-v2-BHNVJYJU-Br0D-weX.js} +1 -1
  59. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-De8bFWnE.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js} +1 -1
  60. package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-Dikwrn1L.js → vennDiagram-CIIHVFJN--7IV65HQ.js} +1 -1
  61. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-DUzpiV6H.js → wardleyDiagram-YWT4CUSO-CVTgWbvT.js} +1 -1
  62. package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-OZNbB32D.js → xychartDiagram-2RQKCTM6-B9om4xAF.js} +1 -1
  63. package/dist/rcs-web/index.html +1 -1
  64. package/package.json +1 -1
  65. package/packages/remote-control-server/src/__tests__/auth-login.test.ts +21 -4
  66. package/packages/remote-control-server/src/__tests__/auth-security.test.ts +12 -6
  67. package/packages/remote-control-server/src/__tests__/data-layer-round2.test.ts +12 -11
  68. package/packages/remote-control-server/src/__tests__/db-isolation.test.ts +61 -0
  69. package/packages/remote-control-server/src/__tests__/low-priority-fixes.test.ts +178 -0
  70. package/packages/remote-control-server/src/__tests__/middleware.test.ts +4 -4
  71. package/packages/remote-control-server/src/__tests__/route-mounting.test.ts +26 -8
  72. package/packages/remote-control-server/src/__tests__/security-fixes.test.ts +32 -27
  73. package/packages/remote-control-server/src/__tests__/security-round3.test.ts +5 -1
  74. package/packages/remote-control-server/src/__tests__/team-filter-api.test.ts +280 -0
  75. package/packages/remote-control-server/src/__tests__/team-session-env.test.ts +169 -0
  76. package/packages/remote-control-server/src/__tests__/teams.test.ts +7 -0
  77. package/packages/remote-control-server/src/auth/csrf.ts +1 -3
  78. package/packages/remote-control-server/src/auth/middleware.ts +13 -4
  79. package/packages/remote-control-server/src/index.ts +11 -8
  80. package/packages/remote-control-server/src/routes/v1/environments.ts +12 -9
  81. package/packages/remote-control-server/src/routes/v1/sessions.ts +1 -1
  82. package/packages/remote-control-server/src/routes/web/auth-routes.ts +3 -1
  83. package/packages/remote-control-server/src/routes/web/auth.ts +2 -1
  84. package/packages/remote-control-server/src/routes/web/environments.ts +22 -4
  85. package/packages/remote-control-server/src/routes/web/sessions.ts +24 -12
  86. package/packages/remote-control-server/src/routes/web/shares.ts +11 -2
  87. package/packages/remote-control-server/src/routes/web/teams.ts +6 -9
  88. package/packages/remote-control-server/src/services/environment.ts +8 -3
  89. package/packages/remote-control-server/src/services/session.ts +56 -45
  90. package/packages/remote-control-server/src/store.ts +128 -7
  91. package/packages/remote-control-server/src/types/api.ts +8 -1
  92. package/packages/remote-control-server/web/dist/assets/AdminPanel-CQU763nr.js +1 -0
  93. package/packages/remote-control-server/web/dist/assets/Dashboard-B1hQfqI9.js +1 -0
  94. package/packages/remote-control-server/web/dist/assets/Join-7hMl360t.js +1 -0
  95. package/{dist/rcs-web/assets/Login-0VGZs85I.js → packages/remote-control-server/web/dist/assets/Login-BE8mNc0U.js} +1 -1
  96. package/packages/remote-control-server/web/dist/assets/SessionDetail-B5C0o1YT.js +55 -0
  97. package/packages/remote-control-server/web/dist/assets/SessionVisibilityBadge-CQN3bJW4.js +11 -0
  98. package/packages/remote-control-server/web/dist/assets/{Setup-C0cjxXMt.js → Setup-CqZ5gEBQ.js} +1 -1
  99. package/packages/remote-control-server/web/dist/assets/{ShareLanding-DUZrreJ7.js → ShareLanding-DBC9gbJo.js} +1 -1
  100. package/packages/remote-control-server/web/dist/assets/TeamCreate-DSYdTaPe.js +1 -0
  101. package/packages/remote-control-server/web/dist/assets/TeamDetail-CmJGsgOP.js +1 -0
  102. package/packages/remote-control-server/web/dist/assets/TeamList-BMS_Qq8U.js +1 -0
  103. package/packages/remote-control-server/web/dist/assets/{TeamSettings-CNVwYwcp.js → TeamSettings-C0sYgCFT.js} +1 -1
  104. package/packages/remote-control-server/web/dist/assets/UserSettings-CqzeJ5cc.js +1 -0
  105. package/{dist/rcs-web/assets/arc-CJxirXEQ.js → packages/remote-control-server/web/dist/assets/arc-DBPKxYzS.js} +1 -1
  106. package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-BZvIHrLA.js → architectureDiagram-3BPJPVTR-D88rj_js.js} +1 -1
  107. package/packages/remote-control-server/web/dist/assets/{blockDiagram-GPEHLZMM-3p0y3Ija.js → blockDiagram-GPEHLZMM-xzgUpRMn.js} +1 -1
  108. package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-Bmz9A1wn.js → c4Diagram-AAUBKEIU-vpg_EoPb.js} +1 -1
  109. package/packages/remote-control-server/web/dist/assets/channel-Daz3yMSu.js +1 -0
  110. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-B8gQz22j.js → chunk-2J33WTMH-DXWsXvhS.js} +1 -1
  111. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-CgLEZsTU.js → chunk-4BX2VUAB-BtTxwj9K.js} +1 -1
  112. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-0mdE_-IS.js → chunk-55IACEB6-D2-Q95qg.js} +1 -1
  113. package/{dist/rcs-web/assets/chunk-727SXJPM-DdtjnIHR.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Ce3e5x8u.js} +1 -1
  114. package/{dist/rcs-web/assets/chunk-AQP2D5EJ-BtwFPk50.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-C0UbKVtd.js} +1 -1
  115. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BcO9mQ0P.js → chunk-FMBD7UC4-BCwppVqh.js} +1 -1
  116. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-BvRq13S_.js → chunk-ND2GUHAM-CnOBwWrm.js} +1 -1
  117. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-D7GHkPpq.js → chunk-QZHKN3VN-CDdzB71P.js} +1 -1
  118. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DufdcV1P.js +1 -0
  119. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DufdcV1P.js +1 -0
  120. package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-B1p-vO-3.js → code-block-IT6T5CEO-C1_H_ztH.js} +1 -1
  121. package/packages/remote-control-server/web/dist/assets/copy-DXU1lQqc.js +6 -0
  122. package/{dist/rcs-web/assets/cose-bilkent-S5V4N54A-L0rUZuH5.js → packages/remote-control-server/web/dist/assets/cose-bilkent-S5V4N54A-BC6nfjTF.js} +1 -1
  123. package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-CHGuio22.js → dagre-BM42HDAG-CEPR_pRx.js} +1 -1
  124. package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-DIDRyQk8.js → diagram-2AECGRRQ-fVMGnSYa.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-CNubKb6P.js → diagram-5GNKFQAL-BPzN2rbK.js} +1 -1
  126. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-CzQh664-.js → diagram-KO2AKTUF-Boneg9n-.js} +1 -1
  127. package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-CWBR7-VG.js → diagram-LMA3HP47-B8AtvjO9.js} +1 -1
  128. package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-BMHdlbcZ.js → diagram-OG6HWLK6-DjY7A8jK.js} +1 -1
  129. package/{dist/rcs-web/assets/erDiagram-TEJ5UH35-OgT8dC-K.js → packages/remote-control-server/web/dist/assets/erDiagram-TEJ5UH35-C1BEfKxk.js} +1 -1
  130. package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-DRrCY2oB.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-7TbI9MLV.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-DctpzEKN.js → ganttDiagram-6RSMTGT7-CMAV0A01.js} +1 -1
  132. package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-5KpLszm9.js → gitGraphDiagram-PVQCEYII-DAlR8G81.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-BAKD_Tt7.js → infoDiagram-5YYISTIA-0T0DAV4W.js} +1 -1
  134. package/{dist/rcs-web/assets/ishikawaDiagram-YF4QCWOH-VHOPG4Nc.js → packages/remote-control-server/web/dist/assets/ishikawaDiagram-YF4QCWOH-BAw8txlx.js} +1 -1
  135. package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-BtGih-ik.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-DWvYKwz-.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-BsEGZsD1.js → kanban-definition-UN3LZRKU-Qzqw1H5B.js} +1 -1
  137. package/{dist/rcs-web/assets/linear-Dj636PNG.js → packages/remote-control-server/web/dist/assets/linear-D2KIJoCI.js} +1 -1
  138. package/packages/remote-control-server/web/dist/assets/main-J6cPs2y8.js +253 -0
  139. package/packages/remote-control-server/web/dist/assets/{mermaid-VLURNSYL-DutpHHm9.js → mermaid-VLURNSYL-CxWk1wHz.js} +4 -4
  140. package/packages/remote-control-server/web/dist/assets/{mermaid.core-XHHf0pen.js → mermaid.core-C48YvKu4.js} +5 -5
  141. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-BBa8V_3g.js → mindmap-definition-RKZ34NQL-BfmcTuCA.js} +1 -1
  142. package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-BuNEdo9H.js → pieDiagram-4H26LBE5-DVNZpnfb.js} +1 -1
  143. package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-CsJgyQIN.js → quadrantDiagram-W4KKPZXB-COL55Z3i.js} +1 -1
  144. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-DqMJZ914.js → requirementDiagram-4Y6WPE33-CFczYQ0a.js} +1 -1
  145. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CVsz09q2.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-BrQEv3WQ.js} +1 -1
  146. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-BRp4Wddn.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CMxtYmQu.js} +1 -1
  147. package/{dist/rcs-web/assets/stateDiagram-AJRCARHV-S-QqQr-D.js → packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-Dw9xJZ-A.js} +1 -1
  148. package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-D77KfDr-.js → stateDiagram-v2-BHNVJYJU-Br0D-weX.js} +1 -1
  149. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-De8bFWnE.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-DuWDwYBt.js} +1 -1
  150. package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-Dikwrn1L.js → vennDiagram-CIIHVFJN--7IV65HQ.js} +1 -1
  151. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-DUzpiV6H.js → wardleyDiagram-YWT4CUSO-CVTgWbvT.js} +1 -1
  152. package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-OZNbB32D.js → xychartDiagram-2RQKCTM6-B9om4xAF.js} +1 -1
  153. package/packages/remote-control-server/web/dist/index.html +1 -1
  154. package/dist/rcs-web/assets/Dashboard-YYwDrKTN.js +0 -1
  155. package/dist/rcs-web/assets/Join-DKCP5X6X.js +0 -1
  156. package/dist/rcs-web/assets/SessionDetail-DmK-DfBQ.js +0 -50
  157. package/dist/rcs-web/assets/TeamCreate-B8ZV6EdY.js +0 -1
  158. package/dist/rcs-web/assets/TeamDetail-DBAsJ__i.js +0 -11
  159. package/dist/rcs-web/assets/TeamList-C-Hte4nV.js +0 -1
  160. package/dist/rcs-web/assets/UserSettings-DRdz9kYE.js +0 -1
  161. package/dist/rcs-web/assets/channel-DaI_n8dp.js +0 -1
  162. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +0 -1
  163. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +0 -1
  164. package/dist/rcs-web/assets/main-CkqtCllF.js +0 -238
  165. package/dist/rcs-web/assets/useTeams-CqJOqEy1.js +0 -1
  166. package/dist/rcs-web/assets/workerType-DOQ0AHrq.js +0 -1
  167. package/packages/remote-control-server/web/dist/assets/Dashboard-YYwDrKTN.js +0 -1
  168. package/packages/remote-control-server/web/dist/assets/Join-DKCP5X6X.js +0 -1
  169. package/packages/remote-control-server/web/dist/assets/SessionDetail-DmK-DfBQ.js +0 -50
  170. package/packages/remote-control-server/web/dist/assets/TeamCreate-B8ZV6EdY.js +0 -1
  171. package/packages/remote-control-server/web/dist/assets/TeamDetail-DBAsJ__i.js +0 -11
  172. package/packages/remote-control-server/web/dist/assets/TeamList-C-Hte4nV.js +0 -1
  173. package/packages/remote-control-server/web/dist/assets/UserSettings-DRdz9kYE.js +0 -1
  174. package/packages/remote-control-server/web/dist/assets/channel-DaI_n8dp.js +0 -1
  175. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-pYllpkOq.js +0 -1
  176. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-pYllpkOq.js +0 -1
  177. package/packages/remote-control-server/web/dist/assets/main-CkqtCllF.js +0 -238
  178. package/packages/remote-control-server/web/dist/assets/useTeams-CqJOqEy1.js +0 -1
  179. package/packages/remote-control-server/web/dist/assets/workerType-DOQ0AHrq.js +0 -1
@@ -0,0 +1,178 @@
1
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { readFileSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ import { randomUUID } from 'node:crypto'
5
+ import { mockConfigModule } from './helpers/mock-config'
6
+
7
+ mock.module('../config', () => mockConfigModule({ pollTimeout: 1 }))
8
+
9
+ import { Hono } from 'hono'
10
+ import {
11
+ storeReset,
12
+ storeCreateSession,
13
+ storeBindSession,
14
+ storeListSessionsByOwnerUuid,
15
+ } from '../store'
16
+ import { initDatabase, resetDbSingleton } from '../db/sqlite'
17
+ import { issueToken } from '../auth/token'
18
+ import { SYSTEM_USER_ID } from '../auth/constants'
19
+ import { resolveOwnedWebSessionId } from '../services/session'
20
+ import webSessions from '../routes/web/sessions'
21
+
22
+ function createApp() {
23
+ const app = new Hono()
24
+ app.route('/web', webSessions)
25
+ return app
26
+ }
27
+
28
+ function freshDb() {
29
+ try {
30
+ resetDbSingleton()
31
+ } catch {
32
+ // ignore
33
+ }
34
+ const uniquePath = `/tmp/rcs-low-fixes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`
35
+ return initDatabase(uniquePath)
36
+ }
37
+
38
+ // Auth helper: issues a session token (non-admin) for the given user.
39
+ function userAuth(user: string): string {
40
+ return `Bearer ${issueToken(user).token}`
41
+ }
42
+
43
+ // =============================================================================
44
+ // H1: resolveBindUserId isAdmin check
45
+ // =============================================================================
46
+ describe('H1: resolveBindUserId requires isAdmin for X-User-Id', () => {
47
+ let app: Hono
48
+
49
+ beforeEach(() => {
50
+ freshDb()
51
+ storeReset()
52
+ app = createApp()
53
+ })
54
+
55
+ test('admin API key + X-User-Id creates session bound to X-User-Id user', async () => {
56
+ const res = await app.request('/web/sessions', {
57
+ method: 'POST',
58
+ headers: {
59
+ Authorization: 'Bearer test-api-key',
60
+ 'Content-Type': 'application/json',
61
+ 'X-User-Id': 'admin-chosen-user',
62
+ },
63
+ body: JSON.stringify({ title: 'Admin Bound' }),
64
+ })
65
+ expect(res.status).toBe(200)
66
+ const body = (await res.json()) as Record<string, unknown>
67
+ expect(body.id).toMatch(/^session_/)
68
+
69
+ // Verify the session is bound to the X-User-Id user
70
+ const sessions = storeListSessionsByOwnerUuid('admin-chosen-user')
71
+ expect(sessions.length).toBeGreaterThanOrEqual(1)
72
+ expect(sessions.some(s => s.id === body.id)).toBe(true)
73
+ })
74
+
75
+ test('non-admin user token + X-User-Id ignores X-User-Id, binds to token user', async () => {
76
+ const res = await app.request('/web/sessions', {
77
+ method: 'POST',
78
+ headers: {
79
+ Authorization: userAuth('real-user'),
80
+ 'Content-Type': 'application/json',
81
+ 'X-User-Id': 'impersonated-user',
82
+ },
83
+ body: JSON.stringify({ title: 'Impersonation Attempt' }),
84
+ })
85
+ expect(res.status).toBe(200)
86
+ const body = (await res.json()) as Record<string, unknown>
87
+
88
+ // Session should be bound to the real token user, NOT the impersonated user
89
+ const realSessions = storeListSessionsByOwnerUuid('real-user')
90
+ expect(realSessions.some(s => s.id === body.id)).toBe(true)
91
+
92
+ const impersonated = storeListSessionsByOwnerUuid('impersonated-user')
93
+ expect(impersonated.some(s => s.id === body.id)).toBe(false)
94
+ })
95
+
96
+ test('admin API key without X-User-Id creates orphan session (not bound)', async () => {
97
+ const res = await app.request('/web/sessions', {
98
+ method: 'POST',
99
+ headers: {
100
+ Authorization: 'Bearer test-api-key',
101
+ 'Content-Type': 'application/json',
102
+ },
103
+ body: JSON.stringify({ title: 'Orphan Session' }),
104
+ })
105
+ expect(res.status).toBe(200)
106
+ const body = (await res.json()) as Record<string, unknown>
107
+ expect(body.id).toMatch(/^session_/)
108
+
109
+ // No user should own this session
110
+ const systemSessions = storeListSessionsByOwnerUuid(SYSTEM_USER_ID)
111
+ expect(systemSessions.some(s => s.id === body.id)).toBe(false)
112
+ })
113
+
114
+ test('resolveBindUserId source includes isAdmin check', () => {
115
+ const source = readFileSync(
116
+ resolve(import.meta.dir, '../routes/web/sessions.ts'),
117
+ 'utf8',
118
+ )
119
+ // The resolveBindUserId function should check isAdmin when userId is SYSTEM_USER_ID
120
+ const fnMatch = source.match(/function\s+resolveBindUserId[\s\S]*?^}/m)
121
+ expect(fnMatch).not.toBeNull()
122
+ const fnBody = fnMatch![0]
123
+ // Must reference isAdmin
124
+ expect(fnBody).toContain('isAdmin')
125
+ })
126
+ })
127
+
128
+ // =============================================================================
129
+ // M2: resolveOwnedWebSessionId removes redundant storeIsSessionOwner fallback
130
+ // =============================================================================
131
+ describe('M2: resolveOwnedWebSessionId removes redundant fallback', () => {
132
+ beforeEach(() => {
133
+ freshDb()
134
+ storeReset()
135
+ })
136
+
137
+ test('returns session ID for owner (main logic works)', () => {
138
+ const session = storeCreateSession({})
139
+ storeBindSession(session.id, 'owner-user')
140
+ const result = resolveOwnedWebSessionId(session.id, 'owner-user')
141
+ expect(result).toBe(session.id)
142
+ })
143
+
144
+ test('returns null for non-owner', () => {
145
+ const session = storeCreateSession({})
146
+ storeBindSession(session.id, 'other-user')
147
+ const result = resolveOwnedWebSessionId(session.id, 'not-owner')
148
+ expect(result).toBeNull()
149
+ })
150
+
151
+ test('source does not use storeIsSessionOwner as fallback', () => {
152
+ const source = readFileSync(
153
+ resolve(import.meta.dir, '../services/session.ts'),
154
+ 'utf8',
155
+ )
156
+ // The resolveOwnedWebSessionId function should NOT have a catch block
157
+ // that calls storeIsSessionOwner (redundant DB query)
158
+ const fnMatch = source.match(
159
+ /export\s+function\s+resolveOwnedWebSessionId[\s\S]*?^}/m,
160
+ )
161
+ expect(fnMatch).not.toBeNull()
162
+ const fnBody = fnMatch![0]
163
+ // Should not reference storeIsSessionOwner in the catch block
164
+ const catchMatch = fnBody.match(/catch\s*\{[\s\S]*?\}/)
165
+ if (catchMatch) {
166
+ expect(catchMatch[0]).not.toContain('storeIsSessionOwner')
167
+ }
168
+ })
169
+
170
+ test('session.ts does not import storeIsSessionOwner', () => {
171
+ const source = readFileSync(
172
+ resolve(import.meta.dir, '../services/session.ts'),
173
+ 'utf8',
174
+ )
175
+ // storeIsSessionOwner should be removed from imports
176
+ expect(source).not.toMatch(/import\s*\{[^}]*storeIsSessionOwner[^}]*\}/)
177
+ })
178
+ })
@@ -29,7 +29,7 @@ function createTestApp() {
29
29
 
30
30
  // Test route for apiKeyAuth
31
31
  app.get('/api-key-test', apiKeyAuth, c => {
32
- return c.json({ username: c.get('username') || null })
32
+ return c.json({ userId: c.get('userId') || null })
33
33
  })
34
34
 
35
35
  // Test route for sessionIngressAuth
@@ -62,7 +62,7 @@ describe('Auth Middleware', () => {
62
62
  })
63
63
  expect(res.status).toBe(200)
64
64
  const body = await res.json()
65
- expect(body.username).toBe('alice')
65
+ expect(body.userId).toBe('alice')
66
66
  })
67
67
 
68
68
  test('accepts valid API key with username query param', async () => {
@@ -71,7 +71,7 @@ describe('Auth Middleware', () => {
71
71
  })
72
72
  expect(res.status).toBe(200)
73
73
  const body = await res.json()
74
- expect(body.username).toBe('bob')
74
+ expect(body.userId).toBe('bob')
75
75
  })
76
76
 
77
77
  test('accepts valid session token', async () => {
@@ -82,7 +82,7 @@ describe('Auth Middleware', () => {
82
82
  })
83
83
  expect(res.status).toBe(200)
84
84
  const body = await res.json()
85
- expect(body.username).toBe('charlie')
85
+ expect(body.userId).toBe('charlie')
86
86
  })
87
87
 
88
88
  test('rejects invalid token', async () => {
@@ -1,4 +1,7 @@
1
1
  import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { tmpdir } from 'node:os'
4
+ import { resolve } from 'node:path'
2
5
  import { mockConfigModule } from './helpers/mock-config'
3
6
 
4
7
  // Mock config before any imports
@@ -6,7 +9,7 @@ mock.module('../config', () => mockConfigModule())
6
9
 
7
10
  import { Hono } from 'hono'
8
11
  import type { MiddlewareHandler } from 'hono/types'
9
- import { resetDbSingleton, getDb } from '../db/sqlite'
12
+ import { initDatabase, resetDbSingleton } from '../db/sqlite'
10
13
 
11
14
  // Dummy serveStatic — returns a middleware.
12
15
  // When `path: 'index.html'` is set, serves SPA fallback HTML.
@@ -16,7 +19,7 @@ const serveStatic: (opts: Record<string, unknown>) => MiddlewareHandler = (
16
19
  ) => {
17
20
  const path = opts.path as string | undefined
18
21
  if (path === 'index.html') {
19
- return c => c.html('<!DOCTYPE html><html><body>SPA</body></html>')
22
+ return async c => c.html('<!DOCTYPE html><html><body>SPA</body></html>')
20
23
  }
21
24
  return async (_c, next) => {
22
25
  await next()
@@ -25,9 +28,11 @@ const serveStatic: (opts: Record<string, unknown>) => MiddlewareHandler = (
25
28
 
26
29
  // Dummy upgradeWebSocket
27
30
  const upgradeWebSocket = (() => {
28
- return () => (c: import('hono').Context, next: import('hono').Next) =>
29
- next()
30
- }) as import('hono/ws').UpgradeWebSocket<unknown>
31
+ return () =>
32
+ async (_c: import('hono').Context, next: import('hono').Next) => {
33
+ await next()
34
+ }
35
+ }) as unknown as import('hono/ws').UpgradeWebSocket<unknown>
31
36
 
32
37
  import { registerRoutes } from '../registerRoutes'
33
38
 
@@ -38,9 +43,22 @@ function createApp(): Hono {
38
43
  }
39
44
 
40
45
  function setupTestDb(): void {
41
- resetDbSingleton()
42
- // Force init with an in-memory-like temp path
43
- getDb()
46
+ // Initialize to a unique temp file per test file so the singleton never
47
+ // resolves to the production default (tmpdir()/rcs-dev.db). We don't
48
+ // reset between tests within this file because route handlers share the
49
+ // singleton — closing it mid-test would break parallel requests. But
50
+ // each test file gets its own module graph under --isolate, so a
51
+ // file-scoped path is safe.
52
+ try {
53
+ resetDbSingleton()
54
+ } catch {
55
+ // ignore
56
+ }
57
+ const uniquePath = resolve(
58
+ tmpdir(),
59
+ `rcs-routes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
60
+ )
61
+ initDatabase(uniquePath)
44
62
  }
45
63
 
46
64
  describe('Route mounting integration', () => {
@@ -1,5 +1,7 @@
1
1
  import { describe, test, expect, beforeEach, mock } from 'bun:test'
2
2
  import { randomUUID, createHash } from 'node:crypto'
3
+ import { tmpdir } from 'node:os'
4
+ import { resolve } from 'node:path'
3
5
  import { mockConfigModule } from './helpers/mock-config'
4
6
 
5
7
  // Mock config before imports
@@ -10,11 +12,13 @@ import { Hono } from 'hono'
10
12
  let authRoutes: Hono | undefined
11
13
  let webAuth: Hono | undefined
12
14
  let resetDbSingleton: (() => void) | undefined
15
+ let initDatabase: ((path?: string) => import('bun:sqlite').Database) | undefined
13
16
  let getDb: (() => import('bun:sqlite').Database) | undefined
14
17
  let resetRateLimits: (() => void) | undefined
15
18
 
16
19
  try {
17
20
  const dbMod = await import('../db/sqlite')
21
+ initDatabase = dbMod.initDatabase
18
22
  getDb = dbMod.getDb
19
23
  resetDbSingleton = dbMod.resetDbSingleton
20
24
  } catch {
@@ -44,8 +48,15 @@ function freshDb() {
44
48
  // ignore
45
49
  }
46
50
  }
47
- if (getDb) {
48
- const db = getDb()
51
+ if (initDatabase) {
52
+ // Use a unique temp file per test to avoid polluting the production
53
+ // default DB path (tmpdir()/rcs-dev.db). Without this, getDb() would
54
+ // resolve to that file and tests would write real users into it.
55
+ const uniquePath = resolve(
56
+ tmpdir(),
57
+ `rcs-sec-fixes-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
58
+ )
59
+ const db = initDatabase(uniquePath)
49
60
  try {
50
61
  db.exec('DELETE FROM session_tokens')
51
62
  db.exec('DELETE FROM users')
@@ -54,7 +65,7 @@ function freshDb() {
54
65
  }
55
66
  return db
56
67
  }
57
- throw new Error('getDb not available')
68
+ throw new Error('initDatabase not available')
58
69
  }
59
70
 
60
71
  function createApp(): Hono {
@@ -275,17 +286,17 @@ describe('Security: consumed_at semantics for multi-use invitations', () => {
275
286
 
276
287
  // Verify 4 users exist: admin + 3 joined
277
288
  const db = getDb!()
278
- const count = db
279
- .query('SELECT COUNT(*) as count FROM users')
280
- .get() as { count: number }
289
+ const count = db.query('SELECT COUNT(*) as count FROM users').get() as {
290
+ count: number
291
+ }
281
292
  expect(count.count).toBe(4)
282
293
 
283
294
  // Verify invitation uses count
284
- const tokenHash = createHash('sha256')
285
- .update(inviteToken)
286
- .digest('hex')
295
+ const tokenHash = createHash('sha256').update(inviteToken).digest('hex')
287
296
  const inv = db
288
- .query('SELECT uses, max_uses, consumed_at FROM invitations WHERE token_hash = $hash')
297
+ .query(
298
+ 'SELECT uses, max_uses, consumed_at FROM invitations WHERE token_hash = $hash',
299
+ )
289
300
  .get({ $hash: tokenHash }) as {
290
301
  uses: number
291
302
  max_uses: number
@@ -493,15 +504,12 @@ describe('Security: CSRF middleware', () => {
493
504
  app.use('/web/*', csrfCheck)
494
505
  app.delete('/web/resource/:id', c => c.json({ ok: true }))
495
506
 
496
- const res = await app.request(
497
- 'http://localhost:3000/web/resource/abc',
498
- {
499
- method: 'DELETE',
500
- headers: {
501
- Origin: 'http://localhost:3000',
502
- },
507
+ const res = await app.request('http://localhost:3000/web/resource/abc', {
508
+ method: 'DELETE',
509
+ headers: {
510
+ Origin: 'http://localhost:3000',
503
511
  },
504
- )
512
+ })
505
513
  expect(res.status).toBe(200)
506
514
  })
507
515
 
@@ -511,16 +519,13 @@ describe('Security: CSRF middleware', () => {
511
519
  app.use('/web/*', csrfCheck)
512
520
  app.put('/web/resource/:id', c => c.json({ ok: true }))
513
521
 
514
- const res = await app.request(
515
- 'http://localhost:3000/web/resource/abc',
516
- {
517
- method: 'PUT',
518
- headers: {
519
- Origin: 'https://evil.example',
520
- 'Content-Type': 'application/json',
521
- },
522
+ const res = await app.request('http://localhost:3000/web/resource/abc', {
523
+ method: 'PUT',
524
+ headers: {
525
+ Origin: 'https://evil.example',
526
+ 'Content-Type': 'application/json',
522
527
  },
523
- )
528
+ })
524
529
  expect(res.status).toBe(403)
525
530
  })
526
531
  })
@@ -58,7 +58,11 @@ let issueSessionToken:
58
58
  ) => { accessToken: string; refreshToken: string })
59
59
  | undefined
60
60
  let resolveSessionToken:
61
- | ((token: string, db: Database, kind?: string) => string | null)
61
+ | ((
62
+ token: string,
63
+ db: Database,
64
+ kind?: 'access' | 'refresh',
65
+ ) => string | null)
62
66
  | undefined
63
67
 
64
68
  try {
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Tests for team-filter API fields:
3
+ * - GET /web/environments returns team_id + team_name
4
+ * - GET /web/sessions returns owner_type + owner_id
5
+ * - GET /web/sessions/all returns owner_type + owner_id
6
+ *
7
+ * TDD: written before implementation.
8
+ */
9
+ import { describe, test, expect, beforeEach, mock } from 'bun:test'
10
+ import { randomUUID } from 'node:crypto'
11
+ import { tmpdir } from 'node:os'
12
+ import { resolve } from 'node:path'
13
+ import { mockConfigModule } from './helpers/mock-config'
14
+
15
+ // Mock config before importing modules
16
+ mock.module('../config', () => mockConfigModule())
17
+
18
+ import { storeReset, storeCreateEnvironment, storeBindSession } from '../store'
19
+ import { initDatabase, resetDbSingleton, getDb } from '../db/sqlite'
20
+ import { listActiveEnvironmentsResponse } from '../services/environment'
21
+ import {
22
+ createSession,
23
+ listWebSessionsByOwnerUuid,
24
+ listWebSessionSummariesByOwnerUuid,
25
+ } from '../services/session'
26
+
27
+ describe('Team filter API fields', () => {
28
+ beforeEach(() => {
29
+ // Reset and re-init to a unique temp file so the singleton never
30
+ // resolves to the production default (tmpdir()/rcs-dev.db). Without
31
+ // this, getDb() below would write test data into that file and
32
+ // poison real server startups.
33
+ try {
34
+ resetDbSingleton()
35
+ } catch {
36
+ // ignore
37
+ }
38
+ const uniquePath = resolve(
39
+ tmpdir(),
40
+ `rcs-team-filter-${randomUUID().replace(/-/g, '').slice(0, 8)}.db`,
41
+ )
42
+ initDatabase(uniquePath)
43
+ storeReset()
44
+ })
45
+
46
+ describe('EnvironmentResponse team fields', () => {
47
+ test('returns team_id=null and team_name=null when env has no team', () => {
48
+ storeCreateEnvironment({ secret: 's', machineName: 'solo-env' })
49
+ const envs = listActiveEnvironmentsResponse()
50
+ expect(envs.length).toBe(1)
51
+ expect(envs[0]!.team_id).toBeNull()
52
+ expect(envs[0]!.team_name).toBeNull()
53
+ })
54
+
55
+ test('returns team_id and team_name when env belongs to a team', () => {
56
+ const env = storeCreateEnvironment({
57
+ secret: 's',
58
+ machineName: 'team-env',
59
+ })
60
+ const db = getDb()
61
+
62
+ // Create a user (FK constraint for teams.created_by)
63
+ db.query(
64
+ `INSERT INTO users (id, username, password_hash, role, created_at)
65
+ VALUES ($id, $u, $ph, 'admin', $ca)`,
66
+ ).run({
67
+ $id: 'user_admin',
68
+ $u: 'admin',
69
+ $ph: 'hash',
70
+ $ca: new Date().toISOString(),
71
+ })
72
+
73
+ // Create a team
74
+ const teamId = 'team_001'
75
+ db.query(
76
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
77
+ VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
78
+ ).run({
79
+ $id: teamId,
80
+ $name: 'Engineering',
81
+ $slug: 'engineering',
82
+ $cb: 'user_admin',
83
+ $ca: new Date().toISOString(),
84
+ $ua: new Date().toISOString(),
85
+ })
86
+
87
+ // Associate env with team
88
+ db.query(
89
+ `INSERT INTO team_environments (team_id, environment_id)
90
+ VALUES ($tid, $eid)`,
91
+ ).run({ $tid: teamId, $eid: env.id })
92
+
93
+ const envs = listActiveEnvironmentsResponse()
94
+ expect(envs.length).toBe(1)
95
+ expect(envs[0]!.team_id).toBe(teamId)
96
+ expect(envs[0]!.team_name).toBe('Engineering')
97
+ })
98
+
99
+ test('returns team_id=null when team is soft-deleted', () => {
100
+ const env = storeCreateEnvironment({
101
+ secret: 's',
102
+ machineName: 'orphan-env',
103
+ })
104
+ const db = getDb()
105
+
106
+ db.query(
107
+ `INSERT INTO users (id, username, password_hash, role, created_at)
108
+ VALUES ($id, $u, $ph, 'admin', $ca)`,
109
+ ).run({
110
+ $id: 'user_admin',
111
+ $u: 'admin',
112
+ $ph: 'hash',
113
+ $ca: new Date().toISOString(),
114
+ })
115
+
116
+ const teamId = 'team_deleted'
117
+ db.query(
118
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at, deleted_at)
119
+ VALUES ($id, $name, $slug, $cb, $ca, $ua, $da)`,
120
+ ).run({
121
+ $id: teamId,
122
+ $name: 'DeletedTeam',
123
+ $slug: 'deleted-team',
124
+ $cb: 'user_admin',
125
+ $ca: new Date().toISOString(),
126
+ $ua: new Date().toISOString(),
127
+ $da: new Date().toISOString(),
128
+ })
129
+
130
+ db.query(
131
+ `INSERT INTO team_environments (team_id, environment_id)
132
+ VALUES ($tid, $eid)`,
133
+ ).run({ $tid: teamId, $eid: env.id })
134
+
135
+ const envs = listActiveEnvironmentsResponse()
136
+ expect(envs.length).toBe(1)
137
+ // Soft-deleted team should not show up
138
+ expect(envs[0]!.team_id).toBeNull()
139
+ expect(envs[0]!.team_name).toBeNull()
140
+ })
141
+ })
142
+
143
+ describe('SessionResponse owner fields', () => {
144
+ test('returns owner_type and owner_id for user-owned session', () => {
145
+ const userId = 'user_alice'
146
+ const session = createSession({ title: 'Alice session' })
147
+ storeBindSession(session.id, userId, 'user')
148
+
149
+ const sessions = listWebSessionsByOwnerUuid(userId)
150
+ expect(sessions.length).toBeGreaterThanOrEqual(1)
151
+ const found = sessions.find(s => s.id === session.id)
152
+ expect(found).toBeDefined()
153
+ expect(found!.owner_type).toBe('user')
154
+ expect(found!.owner_id).toBe(userId)
155
+ })
156
+
157
+ test('returns owner_type=team for team-owned session', () => {
158
+ const db = getDb()
159
+ const userId = 'user_bob'
160
+ const teamId = 'team_bob'
161
+
162
+ // Create user + team
163
+ db.query(
164
+ `INSERT INTO users (id, username, password_hash, role, created_at)
165
+ VALUES ($id, $u, $ph, 'member', $ca)`,
166
+ ).run({
167
+ $id: userId,
168
+ $u: 'bob',
169
+ $ph: 'hash',
170
+ $ca: new Date().toISOString(),
171
+ })
172
+
173
+ db.query(
174
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
175
+ VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
176
+ ).run({
177
+ $id: teamId,
178
+ $name: 'BobTeam',
179
+ $slug: 'bobteam',
180
+ $cb: userId,
181
+ $ca: new Date().toISOString(),
182
+ $ua: new Date().toISOString(),
183
+ })
184
+
185
+ // Add bob as team member so storeListSessionsByOwnerUuid includes team sessions
186
+ db.query(
187
+ `INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
188
+ VALUES ($tid, $uid, 'owner', $aa, $ab)`,
189
+ ).run({
190
+ $tid: teamId,
191
+ $uid: userId,
192
+ $aa: new Date().toISOString(),
193
+ $ab: userId,
194
+ })
195
+
196
+ const session = createSession({ title: 'Team session' })
197
+ storeBindSession(session.id, teamId, 'team')
198
+
199
+ const sessions = listWebSessionsByOwnerUuid(userId)
200
+ const found = sessions.find(s => s.id === session.id)
201
+ expect(found).toBeDefined()
202
+ expect(found!.owner_type).toBe('team')
203
+ expect(found!.owner_id).toBe(teamId)
204
+ })
205
+
206
+ test('returns null owner when session has no owner binding', () => {
207
+ const userId = 'user_charlie'
208
+ // Create a session bound to charlie via session_owners
209
+ const session = createSession({ title: 'Charlie session' })
210
+ storeBindSession(session.id, userId, 'user')
211
+
212
+ const sessions = listWebSessionsByOwnerUuid(userId)
213
+ const found = sessions.find(s => s.id === session.id)
214
+ expect(found).toBeDefined()
215
+ expect(found!.owner_type).toBe('user')
216
+ expect(found!.owner_id).toBe(userId)
217
+ })
218
+ })
219
+
220
+ describe('SessionSummaryResponse owner fields', () => {
221
+ test('returns owner_type and owner_id in /sessions/all summary', () => {
222
+ const userId = 'user_dave'
223
+ const session = createSession({ title: 'Dave session' })
224
+ storeBindSession(session.id, userId, 'user')
225
+
226
+ const summaries = listWebSessionSummariesByOwnerUuid(userId)
227
+ const found = summaries.find(s => s.id === session.id)
228
+ expect(found).toBeDefined()
229
+ expect(found!.owner_type).toBe('user')
230
+ expect(found!.owner_id).toBe(userId)
231
+ })
232
+
233
+ test('returns team owner in summary for team-owned session', () => {
234
+ const db = getDb()
235
+ const userId = 'user_eve'
236
+ const teamId = 'team_eve'
237
+
238
+ db.query(
239
+ `INSERT INTO users (id, username, password_hash, role, created_at)
240
+ VALUES ($id, $u, $ph, 'member', $ca)`,
241
+ ).run({
242
+ $id: userId,
243
+ $u: 'eve',
244
+ $ph: 'hash',
245
+ $ca: new Date().toISOString(),
246
+ })
247
+
248
+ db.query(
249
+ `INSERT INTO teams (id, name, slug, created_by, created_at, updated_at)
250
+ VALUES ($id, $name, $slug, $cb, $ca, $ua)`,
251
+ ).run({
252
+ $id: teamId,
253
+ $name: 'EveTeam',
254
+ $slug: 'eveteam',
255
+ $cb: userId,
256
+ $ca: new Date().toISOString(),
257
+ $ua: new Date().toISOString(),
258
+ })
259
+
260
+ db.query(
261
+ `INSERT INTO team_members (team_id, user_id, role, added_at, added_by)
262
+ VALUES ($tid, $uid, 'owner', $aa, $ab)`,
263
+ ).run({
264
+ $tid: teamId,
265
+ $uid: userId,
266
+ $aa: new Date().toISOString(),
267
+ $ab: userId,
268
+ })
269
+
270
+ const session = createSession({ title: 'Eve team session' })
271
+ storeBindSession(session.id, teamId, 'team')
272
+
273
+ const summaries = listWebSessionSummariesByOwnerUuid(userId)
274
+ const found = summaries.find(s => s.id === session.id)
275
+ expect(found).toBeDefined()
276
+ expect(found!.owner_type).toBe('team')
277
+ expect(found!.owner_id).toBe(teamId)
278
+ })
279
+ })
280
+ })