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