@quenty/studio-bridge 0.21.0 → 0.22.0
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/CHANGELOG.md +12 -0
- package/README.md +109 -17
- package/build-scripts/transform-add-session-attribute.lua +1 -0
- package/deploy.nevermore.json +10 -0
- package/dist/src/bridge/bridge-connection.d.ts +26 -2
- package/dist/src/bridge/bridge-connection.d.ts.map +1 -1
- package/dist/src/bridge/bridge-connection.js +159 -14
- package/dist/src/bridge/bridge-connection.js.map +1 -1
- package/dist/src/bridge/bridge-connection.test.js +81 -0
- package/dist/src/bridge/bridge-connection.test.js.map +1 -1
- package/dist/src/bridge/bridge-session.d.ts +35 -2
- package/dist/src/bridge/bridge-session.d.ts.map +1 -1
- package/dist/src/bridge/bridge-session.js +185 -28
- package/dist/src/bridge/bridge-session.js.map +1 -1
- package/dist/src/bridge/bridge-session.test.js +6 -4
- package/dist/src/bridge/bridge-session.test.js.map +1 -1
- package/dist/src/bridge/index.d.ts +1 -1
- package/dist/src/bridge/index.d.ts.map +1 -1
- package/dist/src/bridge/index.js.map +1 -1
- package/dist/src/bridge/internal/bridge-client.d.ts +4 -0
- package/dist/src/bridge/internal/bridge-client.d.ts.map +1 -1
- package/dist/src/bridge/internal/bridge-client.js +20 -7
- package/dist/src/bridge/internal/bridge-client.js.map +1 -1
- package/dist/src/bridge/internal/bridge-client.test.js +18 -0
- package/dist/src/bridge/internal/bridge-client.test.js.map +1 -1
- package/dist/src/bridge/internal/bridge-host.d.ts +13 -1
- package/dist/src/bridge/internal/bridge-host.d.ts.map +1 -1
- package/dist/src/bridge/internal/bridge-host.js +35 -0
- package/dist/src/bridge/internal/bridge-host.js.map +1 -1
- package/dist/src/bridge/internal/bridge-host.test.js +31 -0
- package/dist/src/bridge/internal/bridge-host.test.js.map +1 -1
- package/dist/src/bridge/internal/hand-off.d.ts.map +1 -1
- package/dist/src/bridge/internal/hand-off.js +4 -1
- package/dist/src/bridge/internal/hand-off.js.map +1 -1
- package/dist/src/bridge/internal/host-protocol.d.ts +8 -0
- package/dist/src/bridge/internal/host-protocol.d.ts.map +1 -1
- package/dist/src/bridge/internal/host-protocol.js +120 -13
- package/dist/src/bridge/internal/host-protocol.js.map +1 -1
- package/dist/src/bridge/internal/host-protocol.test.js +13 -0
- package/dist/src/bridge/internal/host-protocol.test.js.map +1 -1
- package/dist/src/bridge/internal/instance-state.d.ts +9 -0
- package/dist/src/bridge/internal/instance-state.d.ts.map +1 -0
- package/dist/src/bridge/internal/instance-state.js +28 -0
- package/dist/src/bridge/internal/instance-state.js.map +1 -0
- package/dist/src/bridge/internal/transport-client.d.ts.map +1 -1
- package/dist/src/bridge/internal/transport-client.js +2 -1
- package/dist/src/bridge/internal/transport-client.js.map +1 -1
- package/dist/src/bridge/internal/transport-server.d.ts.map +1 -1
- package/dist/src/bridge/internal/transport-server.js +5 -1
- package/dist/src/bridge/internal/transport-server.js.map +1 -1
- package/dist/src/bridge/types.d.ts +43 -4
- package/dist/src/bridge/types.d.ts.map +1 -1
- package/dist/src/bridge/types.js.map +1 -1
- package/dist/src/cli/adapters/cli-command-adapter-handler.test.js +24 -0
- package/dist/src/cli/adapters/cli-command-adapter-handler.test.js.map +1 -1
- package/dist/src/cli/adapters/cli-command-adapter.d.ts +6 -0
- package/dist/src/cli/adapters/cli-command-adapter.d.ts.map +1 -1
- package/dist/src/cli/adapters/cli-command-adapter.js +107 -11
- package/dist/src/cli/adapters/cli-command-adapter.js.map +1 -1
- package/dist/src/cli/adapters/group-builder.d.ts +3 -0
- package/dist/src/cli/adapters/group-builder.d.ts.map +1 -1
- package/dist/src/cli/adapters/group-builder.js +34 -15
- package/dist/src/cli/adapters/group-builder.js.map +1 -1
- package/dist/src/cli/adapters/group-builder.test.js +26 -2
- package/dist/src/cli/adapters/group-builder.test.js.map +1 -1
- package/dist/src/cli/adapters/help-formatter.d.ts.map +1 -1
- package/dist/src/cli/adapters/help-formatter.js +4 -3
- package/dist/src/cli/adapters/help-formatter.js.map +1 -1
- package/dist/src/cli/adapters/help-formatter.test.js +1 -1
- package/dist/src/cli/adapters/help-formatter.test.js.map +1 -1
- package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts +2 -0
- package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts.map +1 -0
- package/dist/src/cli/adapters/playtest-edit-guard.test.js +28 -0
- package/dist/src/cli/adapters/playtest-edit-guard.test.js.map +1 -0
- package/dist/src/cli/args/global-args.d.ts +1 -1
- package/dist/src/cli/args/global-args.d.ts.map +1 -1
- package/dist/src/cli/cli-output-context.d.ts +14 -0
- package/dist/src/cli/cli-output-context.d.ts.map +1 -0
- package/dist/src/cli/cli-output-context.js +39 -0
- package/dist/src/cli/cli-output-context.js.map +1 -0
- package/dist/src/cli/cli-output-context.test.d.ts +2 -0
- package/dist/src/cli/cli-output-context.test.d.ts.map +1 -0
- package/dist/src/cli/cli-output-context.test.js +38 -0
- package/dist/src/cli/cli-output-context.test.js.map +1 -0
- package/dist/src/cli/cli.js +40 -17
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/cli/managed-studio-run.d.ts +21 -0
- package/dist/src/cli/managed-studio-run.d.ts.map +1 -0
- package/dist/src/cli/managed-studio-run.js +75 -0
- package/dist/src/cli/managed-studio-run.js.map +1 -0
- package/dist/src/cli/managed-studio-run.test.d.ts +2 -0
- package/dist/src/cli/managed-studio-run.test.d.ts.map +1 -0
- package/dist/src/cli/managed-studio-run.test.js +52 -0
- package/dist/src/cli/managed-studio-run.test.js.map +1 -0
- package/dist/src/cli/resolve-session.js +1 -1
- package/dist/src/cli/resolve-session.js.map +1 -1
- package/dist/src/cli/resolve-session.test.js +1 -1
- package/dist/src/cli/resolve-session.test.js.map +1 -1
- package/dist/src/cli/script-executor.d.ts +13 -1
- package/dist/src/cli/script-executor.d.ts.map +1 -1
- package/dist/src/cli/script-executor.js +77 -47
- package/dist/src/cli/script-executor.js.map +1 -1
- package/dist/src/commands/console/exec/exec.d.ts +17 -0
- package/dist/src/commands/console/exec/exec.d.ts.map +1 -1
- package/dist/src/commands/console/exec/exec.js +39 -7
- package/dist/src/commands/console/exec/exec.js.map +1 -1
- package/dist/src/commands/console/exec/exec.test.js +62 -3
- package/dist/src/commands/console/exec/exec.test.js.map +1 -1
- package/dist/src/commands/console/logs/logs.d.ts +30 -2
- package/dist/src/commands/console/logs/logs.d.ts.map +1 -1
- package/dist/src/commands/console/logs/logs.js +82 -7
- package/dist/src/commands/console/logs/logs.js.map +1 -1
- package/dist/src/commands/console/logs/logs.test.js +63 -4
- package/dist/src/commands/console/logs/logs.test.js.map +1 -1
- package/dist/src/commands/explorer/export/export.d.ts +19 -0
- package/dist/src/commands/explorer/export/export.d.ts.map +1 -0
- package/dist/src/commands/explorer/export/export.js +37 -0
- package/dist/src/commands/explorer/export/export.js.map +1 -0
- package/dist/src/commands/explorer/export/export.test.d.ts +2 -0
- package/dist/src/commands/explorer/export/export.test.d.ts.map +1 -0
- package/dist/src/commands/explorer/export/export.test.js +25 -0
- package/dist/src/commands/explorer/export/export.test.js.map +1 -0
- package/dist/src/commands/explorer/find/find.d.ts +28 -0
- package/dist/src/commands/explorer/find/find.d.ts.map +1 -0
- package/dist/src/commands/explorer/find/find.js +75 -0
- package/dist/src/commands/explorer/find/find.js.map +1 -0
- package/dist/src/commands/explorer/find/find.test.d.ts +2 -0
- package/dist/src/commands/explorer/find/find.test.d.ts.map +1 -0
- package/dist/src/commands/explorer/find/find.test.js +44 -0
- package/dist/src/commands/explorer/find/find.test.js.map +1 -0
- package/dist/src/commands/explorer/grep/grep.d.ts +24 -0
- package/dist/src/commands/explorer/grep/grep.d.ts.map +1 -0
- package/dist/src/commands/explorer/grep/grep.js +63 -0
- package/dist/src/commands/explorer/grep/grep.js.map +1 -0
- package/dist/src/commands/explorer/grep/grep.test.d.ts +2 -0
- package/dist/src/commands/explorer/grep/grep.test.d.ts.map +1 -0
- package/dist/src/commands/explorer/grep/grep.test.js +42 -0
- package/dist/src/commands/explorer/grep/grep.test.js.map +1 -0
- package/dist/src/commands/explorer/import/import.d.ts +21 -0
- package/dist/src/commands/explorer/import/import.d.ts.map +1 -0
- package/dist/src/commands/explorer/import/import.js +37 -0
- package/dist/src/commands/explorer/import/import.js.map +1 -0
- package/dist/src/commands/explorer/import/import.test.d.ts +2 -0
- package/dist/src/commands/explorer/import/import.test.d.ts.map +1 -0
- package/dist/src/commands/explorer/import/import.test.js +45 -0
- package/dist/src/commands/explorer/import/import.test.js.map +1 -0
- package/dist/src/commands/formatters.test.js +7 -1
- package/dist/src/commands/formatters.test.js.map +1 -1
- package/dist/src/commands/framework/action-loader.d.ts +5 -1
- package/dist/src/commands/framework/action-loader.d.ts.map +1 -1
- package/dist/src/commands/framework/action-loader.js +9 -2
- package/dist/src/commands/framework/action-loader.js.map +1 -1
- package/dist/src/commands/framework/action-loader.test.js +23 -0
- package/dist/src/commands/framework/action-loader.test.js.map +1 -1
- package/dist/src/commands/framework/arg-builder.d.ts +15 -2
- package/dist/src/commands/framework/arg-builder.d.ts.map +1 -1
- package/dist/src/commands/framework/arg-builder.js +22 -2
- package/dist/src/commands/framework/arg-builder.js.map +1 -1
- package/dist/src/commands/framework/define-command.d.ts +19 -3
- package/dist/src/commands/framework/define-command.d.ts.map +1 -1
- package/dist/src/commands/framework/define-command.js.map +1 -1
- package/dist/src/commands/framework/index.d.ts +1 -1
- package/dist/src/commands/framework/index.d.ts.map +1 -1
- package/dist/src/commands/framework/index.js.map +1 -1
- package/dist/src/commands/index.d.ts +11 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +11 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/linux/inject-credentials/inject-credentials.d.ts.map +1 -1
- package/dist/src/commands/linux/inject-credentials/inject-credentials.js +1 -3
- package/dist/src/commands/linux/inject-credentials/inject-credentials.js.map +1 -1
- package/dist/src/commands/linux/setup/setup.d.ts.map +1 -1
- package/dist/src/commands/linux/setup/setup.js +1 -3
- package/dist/src/commands/linux/setup/setup.js.map +1 -1
- package/dist/src/commands/linux/status/status.d.ts +9 -1
- package/dist/src/commands/linux/status/status.d.ts.map +1 -1
- package/dist/src/commands/linux/status/status.js +66 -86
- package/dist/src/commands/linux/status/status.js.map +1 -1
- package/dist/src/commands/playtest/player/add/add.d.ts +17 -0
- package/dist/src/commands/playtest/player/add/add.d.ts.map +1 -0
- package/dist/src/commands/playtest/player/add/add.js +45 -0
- package/dist/src/commands/playtest/player/add/add.js.map +1 -0
- package/dist/src/commands/playtest/player/list/list.d.ts +13 -0
- package/dist/src/commands/playtest/player/list/list.d.ts.map +1 -0
- package/dist/src/commands/playtest/player/list/list.js +33 -0
- package/dist/src/commands/playtest/player/list/list.js.map +1 -0
- package/dist/src/commands/playtest/player/playtest-players.d.ts +36 -0
- package/dist/src/commands/playtest/player/playtest-players.d.ts.map +1 -0
- package/dist/src/commands/playtest/player/playtest-players.js +187 -0
- package/dist/src/commands/playtest/player/playtest-players.js.map +1 -0
- package/dist/src/commands/playtest/player/playtest-players.test.d.ts +2 -0
- package/dist/src/commands/playtest/player/playtest-players.test.d.ts.map +1 -0
- package/dist/src/commands/playtest/player/playtest-players.test.js +89 -0
- package/dist/src/commands/playtest/player/playtest-players.test.js.map +1 -0
- package/dist/src/commands/playtest/player/remove/remove.d.ts +17 -0
- package/dist/src/commands/playtest/player/remove/remove.d.ts.map +1 -0
- package/dist/src/commands/playtest/player/remove/remove.js +42 -0
- package/dist/src/commands/playtest/player/remove/remove.js.map +1 -0
- package/dist/src/commands/playtest/playtest-sessions.d.ts +49 -0
- package/dist/src/commands/playtest/playtest-sessions.d.ts.map +1 -0
- package/dist/src/commands/playtest/playtest-sessions.js +283 -0
- package/dist/src/commands/playtest/playtest-sessions.js.map +1 -0
- package/dist/src/commands/playtest/playtest-sessions.test.d.ts +2 -0
- package/dist/src/commands/playtest/playtest-sessions.test.d.ts.map +1 -0
- package/dist/src/commands/playtest/playtest-sessions.test.js +87 -0
- package/dist/src/commands/playtest/playtest-sessions.test.js.map +1 -0
- package/dist/src/commands/playtest/restart/restart.d.ts +23 -0
- package/dist/src/commands/playtest/restart/restart.d.ts.map +1 -0
- package/dist/src/commands/playtest/restart/restart.js +105 -0
- package/dist/src/commands/playtest/restart/restart.js.map +1 -0
- package/dist/src/commands/playtest/run/run.d.ts +47 -0
- package/dist/src/commands/playtest/run/run.d.ts.map +1 -0
- package/dist/src/commands/playtest/run/run.js +137 -0
- package/dist/src/commands/playtest/run/run.js.map +1 -0
- package/dist/src/commands/playtest/start/start.d.ts +42 -0
- package/dist/src/commands/playtest/start/start.d.ts.map +1 -0
- package/dist/src/commands/playtest/start/start.js +70 -0
- package/dist/src/commands/playtest/start/start.js.map +1 -0
- package/dist/src/commands/playtest/stop/stop.d.ts +15 -0
- package/dist/src/commands/playtest/stop/stop.d.ts.map +1 -0
- package/dist/src/commands/playtest/stop/stop.js +24 -0
- package/dist/src/commands/playtest/stop/stop.js.map +1 -0
- package/dist/src/commands/process/close/close.d.ts +19 -10
- package/dist/src/commands/process/close/close.d.ts.map +1 -1
- package/dist/src/commands/process/close/close.js +118 -15
- package/dist/src/commands/process/close/close.js.map +1 -1
- package/dist/src/commands/process/close/close.test.d.ts +2 -0
- package/dist/src/commands/process/close/close.test.d.ts.map +1 -0
- package/dist/src/commands/process/close/close.test.js +190 -0
- package/dist/src/commands/process/close/close.test.js.map +1 -0
- package/dist/src/commands/process/info/info.d.ts +45 -5
- package/dist/src/commands/process/info/info.d.ts.map +1 -1
- package/dist/src/commands/process/info/info.js +117 -13
- package/dist/src/commands/process/info/info.js.map +1 -1
- package/dist/src/commands/process/info/info.test.js +140 -38
- package/dist/src/commands/process/info/info.test.js.map +1 -1
- package/dist/src/commands/process/launch/launch.d.ts +11 -1
- package/dist/src/commands/process/launch/launch.d.ts.map +1 -1
- package/dist/src/commands/process/launch/launch.js +37 -7
- package/dist/src/commands/process/launch/launch.js.map +1 -1
- package/dist/src/commands/process/launch/launch.test.js +99 -20
- package/dist/src/commands/process/launch/launch.test.js.map +1 -1
- package/dist/src/commands/process/list/list.d.ts +2 -1
- package/dist/src/commands/process/list/list.d.ts.map +1 -1
- package/dist/src/commands/process/list/list.js +9 -6
- package/dist/src/commands/process/list/list.js.map +1 -1
- package/dist/src/commands/process/list/list.test.js +1 -0
- package/dist/src/commands/process/list/list.test.js.map +1 -1
- package/dist/src/commands/process/run/run.d.ts +9 -0
- package/dist/src/commands/process/run/run.d.ts.map +1 -1
- package/dist/src/commands/process/run/run.js +15 -5
- package/dist/src/commands/process/run/run.js.map +1 -1
- package/dist/src/commands/serve/serve.d.ts +2 -0
- package/dist/src/commands/serve/serve.d.ts.map +1 -1
- package/dist/src/commands/serve/serve.js +17 -4
- package/dist/src/commands/serve/serve.js.map +1 -1
- package/dist/src/commands/serve/serve.test.js +9 -2
- package/dist/src/commands/serve/serve.test.js.map +1 -1
- package/dist/src/commands/viewport/screenshot/screenshot.d.ts.map +1 -1
- package/dist/src/commands/viewport/screenshot/screenshot.js +4 -1
- package/dist/src/commands/viewport/screenshot/screenshot.js.map +1 -1
- package/dist/src/commands/viewport/screenshot/screenshot.test.js +5 -9
- package/dist/src/commands/viewport/screenshot/screenshot.test.js.map +1 -1
- package/dist/src/deploy/deploy-places.d.ts +39 -0
- package/dist/src/deploy/deploy-places.d.ts.map +1 -0
- package/dist/src/deploy/deploy-places.js +162 -0
- package/dist/src/deploy/deploy-places.js.map +1 -0
- package/dist/src/deploy/deploy-places.test.d.ts +2 -0
- package/dist/src/deploy/deploy-places.test.d.ts.map +1 -0
- package/dist/src/deploy/deploy-places.test.js +96 -0
- package/dist/src/deploy/deploy-places.test.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/linux/linux-studio-launcher.d.ts.map +1 -1
- package/dist/src/linux/linux-studio-launcher.js +10 -0
- package/dist/src/linux/linux-studio-launcher.js.map +1 -1
- package/dist/src/linux/linux-studio-launcher.test.d.ts +2 -0
- package/dist/src/linux/linux-studio-launcher.test.d.ts.map +1 -0
- package/dist/src/linux/linux-studio-launcher.test.js +68 -0
- package/dist/src/linux/linux-studio-launcher.test.js.map +1 -0
- package/dist/src/plugin/persistent-plugin-installer.d.ts.map +1 -1
- package/dist/src/plugin/persistent-plugin-installer.js +2 -1
- package/dist/src/plugin/persistent-plugin-installer.js.map +1 -1
- package/dist/src/plugin/persistent-plugin-installer.test.js +3 -0
- package/dist/src/plugin/persistent-plugin-installer.test.js.map +1 -1
- package/dist/src/plugin/plugin-build-version.d.ts +7 -0
- package/dist/src/plugin/plugin-build-version.d.ts.map +1 -0
- package/dist/src/plugin/plugin-build-version.js +42 -0
- package/dist/src/plugin/plugin-build-version.js.map +1 -0
- package/dist/src/plugin/plugin-build-version.test.d.ts +2 -0
- package/dist/src/plugin/plugin-build-version.test.d.ts.map +1 -0
- package/dist/src/plugin/plugin-build-version.test.js +41 -0
- package/dist/src/plugin/plugin-build-version.test.js.map +1 -0
- package/dist/src/plugin/plugin-injector.d.ts.map +1 -1
- package/dist/src/plugin/plugin-injector.js +7 -1
- package/dist/src/plugin/plugin-injector.js.map +1 -1
- package/dist/src/process/studio-process-closer.d.ts +35 -0
- package/dist/src/process/studio-process-closer.d.ts.map +1 -0
- package/dist/src/process/studio-process-closer.js +129 -0
- package/dist/src/process/studio-process-closer.js.map +1 -0
- package/dist/src/process/studio-process-closer.test.d.ts +2 -0
- package/dist/src/process/studio-process-closer.test.d.ts.map +1 -0
- package/dist/src/process/studio-process-closer.test.js +80 -0
- package/dist/src/process/studio-process-closer.test.js.map +1 -0
- package/dist/src/process/studio-settings.d.ts +14 -0
- package/dist/src/process/studio-settings.d.ts.map +1 -0
- package/dist/src/process/studio-settings.js +50 -0
- package/dist/src/process/studio-settings.js.map +1 -0
- package/dist/src/process/studio-settings.test.d.ts +2 -0
- package/dist/src/process/studio-settings.test.d.ts.map +1 -0
- package/dist/src/process/studio-settings.test.js +16 -0
- package/dist/src/process/studio-settings.test.js.map +1 -0
- package/dist/src/process/studio-window-state.d.ts +13 -0
- package/dist/src/process/studio-window-state.d.ts.map +1 -0
- package/dist/src/process/studio-window-state.js +48 -0
- package/dist/src/process/studio-window-state.js.map +1 -0
- package/dist/src/server/studio-bridge-server.d.ts +3 -1
- package/dist/src/server/studio-bridge-server.d.ts.map +1 -1
- package/dist/src/server/studio-bridge-server.js +12 -4
- package/dist/src/server/studio-bridge-server.js.map +1 -1
- package/dist/src/server/web-socket-protocol.d.ts +173 -6
- package/dist/src/server/web-socket-protocol.d.ts.map +1 -1
- package/dist/src/server/web-socket-protocol.js +244 -1
- package/dist/src/server/web-socket-protocol.js.map +1 -1
- package/dist/src/server/web-socket-protocol.test.js +48 -0
- package/dist/src/server/web-socket-protocol.test.js.map +1 -1
- package/dist/src/test/e2e/persistent-session.test.js +121 -0
- package/dist/src/test/e2e/persistent-session.test.js.map +1 -1
- package/dist/src/test/e2e/split-server.test.d.ts +3 -5
- package/dist/src/test/e2e/split-server.test.d.ts.map +1 -1
- package/dist/src/test/e2e/split-server.test.js +3 -5
- package/dist/src/test/e2e/split-server.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docker/Dockerfile +3 -0
- package/package.json +8 -6
- package/src/bridge/bridge-connection.test.ts +126 -0
- package/src/bridge/bridge-connection.ts +217 -22
- package/src/bridge/bridge-session.test.ts +6 -4
- package/src/bridge/bridge-session.ts +273 -28
- package/src/bridge/index.ts +3 -0
- package/src/bridge/internal/bridge-client.test.ts +23 -0
- package/src/bridge/internal/bridge-client.ts +25 -7
- package/src/bridge/internal/bridge-host.test.ts +38 -0
- package/src/bridge/internal/bridge-host.ts +47 -0
- package/src/bridge/internal/hand-off.ts +4 -1
- package/src/bridge/internal/host-protocol.test.ts +15 -0
- package/src/bridge/internal/host-protocol.ts +148 -37
- package/src/bridge/internal/instance-state.ts +34 -0
- package/src/bridge/internal/transport-client.ts +2 -1
- package/src/bridge/internal/transport-server.ts +5 -1
- package/src/bridge/types.ts +47 -2
- package/src/cli/adapters/cli-command-adapter-handler.test.ts +28 -0
- package/src/cli/adapters/cli-command-adapter.ts +150 -18
- package/src/cli/adapters/group-builder.test.ts +34 -2
- package/src/cli/adapters/group-builder.ts +52 -20
- package/src/cli/adapters/help-formatter.test.ts +1 -1
- package/src/cli/adapters/help-formatter.ts +4 -3
- package/src/cli/adapters/playtest-edit-guard.test.ts +54 -0
- package/src/cli/args/global-args.ts +1 -1
- package/src/cli/cli-output-context.test.ts +55 -0
- package/src/cli/cli-output-context.ts +50 -0
- package/src/cli/cli.ts +49 -17
- package/src/cli/managed-studio-run.test.ts +71 -0
- package/src/cli/managed-studio-run.ts +135 -0
- package/src/cli/resolve-session.test.ts +1 -1
- package/src/cli/resolve-session.ts +1 -1
- package/src/cli/script-executor.ts +99 -51
- package/src/commands/console/exec/exec.test.ts +96 -3
- package/src/commands/console/exec/exec.ts +59 -6
- package/src/commands/console/exec/execute.lua +356 -240
- package/src/commands/console/exec/execute.spec.lua +286 -0
- package/src/commands/console/logs/logs.test.ts +76 -4
- package/src/commands/console/logs/logs.ts +132 -17
- package/src/commands/console/logs/query-logs.lua +85 -52
- package/src/commands/console/logs/query-logs.spec.lua +132 -0
- package/src/commands/explorer/export/export-instances.lua +108 -0
- package/src/commands/explorer/export/export-instances.spec.lua +87 -0
- package/src/commands/explorer/export/export.test.ts +27 -0
- package/src/commands/explorer/export/export.ts +60 -0
- package/src/commands/explorer/find/find-instances.lua +174 -0
- package/src/commands/explorer/find/find-instances.spec.lua +122 -0
- package/src/commands/explorer/find/find.test.ts +53 -0
- package/src/commands/explorer/find/find.ts +113 -0
- package/src/commands/explorer/grep/grep-scripts.lua +214 -0
- package/src/commands/explorer/grep/grep-scripts.spec.lua +130 -0
- package/src/commands/explorer/grep/grep.test.ts +45 -0
- package/src/commands/explorer/grep/grep.ts +93 -0
- package/src/commands/explorer/import/import-instances.lua +106 -0
- package/src/commands/explorer/import/import-instances.spec.lua +84 -0
- package/src/commands/explorer/import/import.test.ts +60 -0
- package/src/commands/explorer/import/import.ts +66 -0
- package/src/commands/explorer/query/query-data-model.lua +124 -152
- package/src/commands/explorer/query/query-data-model.spec.lua +150 -0
- package/src/commands/formatters.test.ts +7 -1
- package/src/commands/framework/action-loader.test.ts +28 -0
- package/src/commands/framework/action-loader.ts +10 -2
- package/src/commands/framework/arg-builder.ts +26 -3
- package/src/commands/framework/define-command.ts +32 -3
- package/src/commands/framework/index.ts +1 -0
- package/src/commands/index.ts +50 -0
- package/src/commands/linux/inject-credentials/inject-credentials.ts +1 -3
- package/src/commands/linux/setup/setup.ts +1 -3
- package/src/commands/linux/status/status.ts +105 -98
- package/src/commands/playtest/player/add/add.ts +85 -0
- package/src/commands/playtest/player/list/list.ts +57 -0
- package/src/commands/playtest/player/playtest-players.test.ts +146 -0
- package/src/commands/playtest/player/playtest-players.ts +270 -0
- package/src/commands/playtest/player/remove/remove.ts +78 -0
- package/src/commands/playtest/playtest-sessions.test.ts +143 -0
- package/src/commands/playtest/playtest-sessions.ts +417 -0
- package/src/commands/playtest/restart/restart.ts +199 -0
- package/src/commands/playtest/run/playtest-runner.luau +264 -0
- package/src/commands/playtest/run/playtest-runner.spec.lua +71 -0
- package/src/commands/playtest/run/run-playtest-script.lua +139 -0
- package/src/commands/playtest/run/run-playtest-script.spec.lua +43 -0
- package/src/commands/playtest/run/run.ts +239 -0
- package/src/commands/playtest/start/start-playtest.lua +108 -0
- package/src/commands/playtest/start/start.ts +138 -0
- package/src/commands/playtest/stop/end-playtest.lua +47 -0
- package/src/commands/playtest/stop/stop.ts +51 -0
- package/src/commands/process/close/close.test.ts +250 -0
- package/src/commands/process/close/close.ts +183 -17
- package/src/commands/process/info/info.test.ts +198 -44
- package/src/commands/process/info/info.ts +204 -15
- package/src/commands/process/info/query-state.lua +42 -29
- package/src/commands/process/info/query-state.spec.lua +39 -0
- package/src/commands/process/launch/launch.test.ts +138 -21
- package/src/commands/process/launch/launch.ts +69 -9
- package/src/commands/process/list/list.test.ts +1 -0
- package/src/commands/process/list/list.ts +14 -6
- package/src/commands/process/run/run.ts +26 -5
- package/src/commands/serve/serve.test.ts +17 -2
- package/src/commands/serve/serve.ts +25 -6
- package/src/commands/viewport/screenshot/capture-screenshot.lua +175 -182
- package/src/commands/viewport/screenshot/capture-screenshot.spec.lua +129 -0
- package/src/commands/viewport/screenshot/screenshot.test.ts +7 -10
- package/src/commands/viewport/screenshot/screenshot.ts +6 -1
- package/src/deploy/deploy-places.test.ts +127 -0
- package/src/deploy/deploy-places.ts +229 -0
- package/src/index.ts +11 -0
- package/src/linux/linux-studio-launcher.test.ts +89 -0
- package/src/linux/linux-studio-launcher.ts +11 -0
- package/src/plugin/persistent-plugin-installer.test.ts +4 -0
- package/src/plugin/persistent-plugin-installer.ts +2 -1
- package/src/plugin/plugin-build-version.test.ts +55 -0
- package/src/plugin/plugin-build-version.ts +53 -0
- package/src/plugin/plugin-injector.ts +7 -1
- package/src/process/studio-process-closer.test.ts +107 -0
- package/src/process/studio-process-closer.ts +187 -0
- package/src/process/studio-settings.test.ts +18 -0
- package/src/process/studio-settings.ts +61 -0
- package/src/process/studio-window-state.ts +65 -0
- package/src/server/studio-bridge-server.ts +15 -4
- package/src/server/web-socket-protocol.test.ts +55 -0
- package/src/server/web-socket-protocol.ts +468 -8
- package/src/test/e2e/persistent-session.test.ts +137 -0
- package/src/test/e2e/split-server.test.ts +3 -5
- package/templates/studio-bridge-plugin/default.project.json +3 -0
- package/templates/studio-bridge-plugin/src/Shared/ActionRouter.lua +208 -159
- package/templates/studio-bridge-plugin/src/Shared/ActionRouter.spec.lua +418 -0
- package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.lua +94 -0
- package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.spec.lua +166 -0
- package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.lua +118 -137
- package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.spec.lua +228 -0
- package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.lua +80 -109
- package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.spec.lua +116 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.lua +233 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.spec.lua +239 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.lua +53 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.spec.lua +27 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.lua +164 -0
- package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.spec.lua +159 -0
- package/templates/studio-bridge-plugin/src/StudioBridgePlugin.server.lua +266 -368
- package/templates/studio-bridge-plugin/src/Vendor/png.lua +8 -2
- package/test/default.project.json +41 -0
- package/test/jest.config.lua +3 -0
- package/test/scripts/Server/ServerMain.server.lua +10 -0
- package/tsconfig.json +1 -0
- package/templates/studio-bridge-plugin/src/Shared/Protocol.lua +0 -132
- package/templates/studio-bridge-plugin-test/test/actions.test.lua +0 -580
- package/templates/studio-bridge-plugin-test/test/discovery.test.lua +0 -622
- package/templates/studio-bridge-plugin-test/test/execute-handler.test.lua +0 -517
- package/templates/studio-bridge-plugin-test/test/hash-lifecycle.test.lua +0 -814
- package/templates/studio-bridge-plugin-test/test/integration.test.lua +0 -1020
- package/templates/studio-bridge-plugin-test/test/new-actions.test.lua +0 -339
- package/templates/studio-bridge-plugin-test/test/plugin-entry.test.lua +0 -493
- package/templates/studio-bridge-plugin-test/test/protocol.test.lua +0 -506
- package/templates/studio-bridge-plugin-test/test/roblox-mocks.lua +0 -108
- package/templates/studio-bridge-plugin-test/test/test-runner.lua +0 -112
|
@@ -1,1020 +0,0 @@
|
|
|
1
|
-
--[[
|
|
2
|
-
Integration tests for Phase 0.5 plugin modules.
|
|
3
|
-
|
|
4
|
-
Tests that Protocol, ActionRouter, MessageBuffer, and DiscoveryStateMachine
|
|
5
|
-
compose correctly as an integrated system. Each test exercises multiple modules
|
|
6
|
-
together, validating the full data flow rather than individual module behavior.
|
|
7
|
-
]]
|
|
8
|
-
|
|
9
|
-
local ActionRouter = require("../../studio-bridge-plugin/src/Shared/ActionRouter")
|
|
10
|
-
local DiscoveryStateMachine = require("../../studio-bridge-plugin/src/Shared/DiscoveryStateMachine")
|
|
11
|
-
local MessageBuffer = require("../../studio-bridge-plugin/src/Shared/MessageBuffer")
|
|
12
|
-
local Protocol = require("../../studio-bridge-plugin/src/Shared/Protocol")
|
|
13
|
-
|
|
14
|
-
-- ---------------------------------------------------------------------------
|
|
15
|
-
-- Test helpers
|
|
16
|
-
-- ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
local function assertEqual(actual: any, expected: any, label: string?)
|
|
19
|
-
if actual ~= expected then
|
|
20
|
-
error(
|
|
21
|
-
string.format(
|
|
22
|
-
"%sexpected %s, got %s",
|
|
23
|
-
if label then label .. ": " else "",
|
|
24
|
-
tostring(expected),
|
|
25
|
-
tostring(actual)
|
|
26
|
-
)
|
|
27
|
-
)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
local function assertNil(value: any, label: string?)
|
|
32
|
-
if value ~= nil then
|
|
33
|
-
error(string.format("%sexpected nil, got %s", if label then label .. ": " else "", tostring(value)))
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
local function assertNotNil(value: any, label: string?)
|
|
38
|
-
if value == nil then
|
|
39
|
-
error(string.format("%sexpected non-nil value", if label then label .. ": " else ""))
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
local function assertTrue(value: any, label: string?)
|
|
44
|
-
if not value then
|
|
45
|
-
error(string.format("%sexpected truthy value, got %s", if label then label .. ": " else "", tostring(value)))
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
local function assertContains(str: string, substring: string, label: string?)
|
|
50
|
-
if not string.find(str, substring, 1, true) then
|
|
51
|
-
error(
|
|
52
|
-
string.format(
|
|
53
|
-
"%sexpected string to contain '%s', got '%s'",
|
|
54
|
-
if label then label .. ": " else "",
|
|
55
|
-
substring,
|
|
56
|
-
str
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
-- ---------------------------------------------------------------------------
|
|
63
|
-
-- Tests
|
|
64
|
-
-- ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
local tests = {}
|
|
67
|
-
|
|
68
|
-
-- ===========================================================================
|
|
69
|
-
-- 1. Protocol + ActionRouter: encode request, decode, dispatch, encode response
|
|
70
|
-
-- ===========================================================================
|
|
71
|
-
|
|
72
|
-
table.insert(tests, {
|
|
73
|
-
name = "Protocol + ActionRouter: queryState round-trip through dispatch",
|
|
74
|
-
fn = function()
|
|
75
|
-
-- Set up a router with a queryState handler
|
|
76
|
-
local router = ActionRouter.new()
|
|
77
|
-
router:setResponseType("queryState", "stateResult")
|
|
78
|
-
router:register("queryState", function(_payload, _requestId, _sessionId)
|
|
79
|
-
return { state = "Edit", placeId = 12345, placeName = "TestPlace", gameId = 67890 }
|
|
80
|
-
end)
|
|
81
|
-
|
|
82
|
-
-- Encode a queryState request using Protocol
|
|
83
|
-
local encoded = Protocol.encode({
|
|
84
|
-
type = "queryState",
|
|
85
|
-
sessionId = "int-sess-001",
|
|
86
|
-
requestId = "int-req-001",
|
|
87
|
-
payload = {},
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
-- Decode it back
|
|
91
|
-
local decoded, decodeErr = Protocol.decode(encoded)
|
|
92
|
-
assertNil(decodeErr, "decode error")
|
|
93
|
-
assertNotNil(decoded, "decoded message")
|
|
94
|
-
assertEqual(decoded.type, "queryState")
|
|
95
|
-
assertEqual(decoded.sessionId, "int-sess-001")
|
|
96
|
-
assertEqual(decoded.requestId, "int-req-001")
|
|
97
|
-
|
|
98
|
-
-- Dispatch through ActionRouter
|
|
99
|
-
local response = router:dispatch(decoded)
|
|
100
|
-
assertNotNil(response, "dispatch response")
|
|
101
|
-
assertEqual(response.type, "stateResult")
|
|
102
|
-
assertEqual(response.sessionId, "int-sess-001")
|
|
103
|
-
assertEqual(response.requestId, "int-req-001")
|
|
104
|
-
assertEqual(response.payload.state, "Edit")
|
|
105
|
-
assertEqual(response.payload.placeId, 12345)
|
|
106
|
-
|
|
107
|
-
-- Encode the response back through Protocol
|
|
108
|
-
local responseEncoded = Protocol.encode({
|
|
109
|
-
type = response.type,
|
|
110
|
-
sessionId = response.sessionId,
|
|
111
|
-
requestId = response.requestId,
|
|
112
|
-
payload = response.payload,
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
-- Decode the response to verify it round-trips cleanly
|
|
116
|
-
local responseParsed, respErr = Protocol.decode(responseEncoded)
|
|
117
|
-
assertNil(respErr, "response decode error")
|
|
118
|
-
assertNotNil(responseParsed, "response parsed")
|
|
119
|
-
assertEqual(responseParsed.type, "stateResult")
|
|
120
|
-
assertEqual(responseParsed.payload.state, "Edit")
|
|
121
|
-
assertEqual(responseParsed.payload.placeName, "TestPlace")
|
|
122
|
-
end,
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
-- ===========================================================================
|
|
126
|
-
-- 2. Protocol + ActionRouter: execute action with scriptComplete response
|
|
127
|
-
-- ===========================================================================
|
|
128
|
-
|
|
129
|
-
table.insert(tests, {
|
|
130
|
-
name = "Protocol + ActionRouter: execute round-trip produces scriptComplete",
|
|
131
|
-
fn = function()
|
|
132
|
-
local router = ActionRouter.new()
|
|
133
|
-
local capturedScript = nil
|
|
134
|
-
|
|
135
|
-
router:setResponseType("execute", "scriptComplete")
|
|
136
|
-
router:register("execute", function(payload, _requestId, _sessionId)
|
|
137
|
-
capturedScript = payload.script
|
|
138
|
-
return { success = true }
|
|
139
|
-
end)
|
|
140
|
-
|
|
141
|
-
-- Simulate server sending an execute message
|
|
142
|
-
local executeMsg = Protocol.encode({
|
|
143
|
-
type = "execute",
|
|
144
|
-
sessionId = "exec-sess",
|
|
145
|
-
requestId = "exec-req-001",
|
|
146
|
-
payload = { script = "print('hello world')" },
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
-- Decode and dispatch
|
|
150
|
-
local decoded, err = Protocol.decode(executeMsg)
|
|
151
|
-
assertNil(err, "decode error")
|
|
152
|
-
local response = router:dispatch(decoded)
|
|
153
|
-
|
|
154
|
-
-- Verify handler received the script
|
|
155
|
-
assertEqual(capturedScript, "print('hello world')", "handler received script")
|
|
156
|
-
|
|
157
|
-
-- Verify response is scriptComplete
|
|
158
|
-
assertNotNil(response, "response")
|
|
159
|
-
assertEqual(response.type, "scriptComplete")
|
|
160
|
-
assertEqual(response.payload.success, true)
|
|
161
|
-
assertEqual(response.requestId, "exec-req-001")
|
|
162
|
-
|
|
163
|
-
-- Re-encode and verify the scriptComplete round-trips
|
|
164
|
-
local responseJson = Protocol.encode({
|
|
165
|
-
type = response.type,
|
|
166
|
-
sessionId = response.sessionId,
|
|
167
|
-
requestId = response.requestId,
|
|
168
|
-
payload = response.payload,
|
|
169
|
-
})
|
|
170
|
-
local reparsed, rerr = Protocol.decode(responseJson)
|
|
171
|
-
assertNil(rerr, "re-decode error")
|
|
172
|
-
assertEqual(reparsed.type, "scriptComplete")
|
|
173
|
-
assertEqual(reparsed.payload.success, true)
|
|
174
|
-
end,
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
-- ===========================================================================
|
|
178
|
-
-- 3. ActionRouter + MessageBuffer: queryLogs reads from buffer
|
|
179
|
-
-- ===========================================================================
|
|
180
|
-
|
|
181
|
-
table.insert(tests, {
|
|
182
|
-
name = "ActionRouter + MessageBuffer: queryLogs handler reads from buffer",
|
|
183
|
-
fn = function()
|
|
184
|
-
local buffer = MessageBuffer.new(100)
|
|
185
|
-
local router = ActionRouter.new()
|
|
186
|
-
|
|
187
|
-
-- Register a queryLogs handler that reads from the buffer
|
|
188
|
-
router:setResponseType("queryLogs", "logsResult")
|
|
189
|
-
router:register("queryLogs", function(payload, _requestId, _sessionId)
|
|
190
|
-
local direction = payload.direction or "tail"
|
|
191
|
-
local count = payload.count or 50
|
|
192
|
-
return buffer:get(direction, count)
|
|
193
|
-
end)
|
|
194
|
-
|
|
195
|
-
-- Push log entries
|
|
196
|
-
buffer:push({ level = "Print", body = "Starting up...", timestamp = 1000 })
|
|
197
|
-
buffer:push({ level = "Warning", body = "Low memory", timestamp = 2000 })
|
|
198
|
-
buffer:push({ level = "Error", body = "Connection failed", timestamp = 3000 })
|
|
199
|
-
buffer:push({ level = "Print", body = "Retrying...", timestamp = 4000 })
|
|
200
|
-
buffer:push({ level = "Print", body = "Connected!", timestamp = 5000 })
|
|
201
|
-
|
|
202
|
-
-- Dispatch a queryLogs request for tail 3
|
|
203
|
-
local response = router:dispatch({
|
|
204
|
-
type = "queryLogs",
|
|
205
|
-
sessionId = "logs-sess",
|
|
206
|
-
requestId = "logs-req-001",
|
|
207
|
-
payload = { direction = "tail", count = 3 },
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
assertNotNil(response, "response")
|
|
211
|
-
assertEqual(response.type, "logsResult")
|
|
212
|
-
assertEqual(#response.payload.entries, 3)
|
|
213
|
-
assertEqual(response.payload.entries[1].body, "Connection failed")
|
|
214
|
-
assertEqual(response.payload.entries[2].body, "Retrying...")
|
|
215
|
-
assertEqual(response.payload.entries[3].body, "Connected!")
|
|
216
|
-
assertEqual(response.payload.total, 5)
|
|
217
|
-
assertEqual(response.payload.bufferCapacity, 100)
|
|
218
|
-
|
|
219
|
-
-- Now request head 2
|
|
220
|
-
local headResponse = router:dispatch({
|
|
221
|
-
type = "queryLogs",
|
|
222
|
-
sessionId = "logs-sess",
|
|
223
|
-
requestId = "logs-req-002",
|
|
224
|
-
payload = { direction = "head", count = 2 },
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
assertNotNil(headResponse, "head response")
|
|
228
|
-
assertEqual(#headResponse.payload.entries, 2)
|
|
229
|
-
assertEqual(headResponse.payload.entries[1].body, "Starting up...")
|
|
230
|
-
assertEqual(headResponse.payload.entries[2].body, "Low memory")
|
|
231
|
-
end,
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
-- ===========================================================================
|
|
235
|
-
-- 4. Protocol + ActionRouter + MessageBuffer: queryLogs wire round-trip
|
|
236
|
-
-- ===========================================================================
|
|
237
|
-
|
|
238
|
-
table.insert(tests, {
|
|
239
|
-
name = "Protocol + ActionRouter + MessageBuffer: queryLogs full wire round-trip",
|
|
240
|
-
fn = function()
|
|
241
|
-
local buffer = MessageBuffer.new(50)
|
|
242
|
-
local router = ActionRouter.new()
|
|
243
|
-
|
|
244
|
-
router:setResponseType("queryLogs", "logsResult")
|
|
245
|
-
router:register("queryLogs", function(payload, _requestId, _sessionId)
|
|
246
|
-
return buffer:get(payload.direction, payload.count)
|
|
247
|
-
end)
|
|
248
|
-
|
|
249
|
-
-- Push entries
|
|
250
|
-
for i = 1, 10 do
|
|
251
|
-
buffer:push({
|
|
252
|
-
level = if i % 3 == 0 then "Warning" else "Print",
|
|
253
|
-
body = "log entry " .. tostring(i),
|
|
254
|
-
timestamp = i * 1000,
|
|
255
|
-
})
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
-- Encode queryLogs request
|
|
259
|
-
local requestJson = Protocol.encode({
|
|
260
|
-
type = "queryLogs",
|
|
261
|
-
sessionId = "wire-sess",
|
|
262
|
-
requestId = "wire-req-001",
|
|
263
|
-
payload = { direction = "tail", count = 5 },
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
-- Decode request
|
|
267
|
-
local request, decErr = Protocol.decode(requestJson)
|
|
268
|
-
assertNil(decErr, "decode error")
|
|
269
|
-
|
|
270
|
-
-- Dispatch
|
|
271
|
-
local response = router:dispatch(request)
|
|
272
|
-
assertNotNil(response, "response")
|
|
273
|
-
|
|
274
|
-
-- Encode response
|
|
275
|
-
local responseJson = Protocol.encode({
|
|
276
|
-
type = response.type,
|
|
277
|
-
sessionId = response.sessionId,
|
|
278
|
-
requestId = response.requestId,
|
|
279
|
-
payload = response.payload,
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
-- Decode response
|
|
283
|
-
local responseParsed, respErr = Protocol.decode(responseJson)
|
|
284
|
-
assertNil(respErr, "response decode error")
|
|
285
|
-
assertEqual(responseParsed.type, "logsResult")
|
|
286
|
-
assertEqual(#responseParsed.payload.entries, 5)
|
|
287
|
-
assertEqual(responseParsed.payload.entries[5].body, "log entry 10")
|
|
288
|
-
assertEqual(responseParsed.payload.total, 10)
|
|
289
|
-
assertEqual(responseParsed.payload.bufferCapacity, 50)
|
|
290
|
-
end,
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
-- ===========================================================================
|
|
294
|
-
-- 5. DiscoveryStateMachine + Protocol: health check and connection lifecycle
|
|
295
|
-
-- ===========================================================================
|
|
296
|
-
|
|
297
|
-
table.insert(tests, {
|
|
298
|
-
name = "DiscoveryStateMachine + Protocol: full discovery to connected lifecycle",
|
|
299
|
-
fn = function()
|
|
300
|
-
local stateTransitions: { { old: string, new: string } } = {}
|
|
301
|
-
local connectedConnection = nil
|
|
302
|
-
local disconnectedReason = nil
|
|
303
|
-
local targetPort = 38745
|
|
304
|
-
|
|
305
|
-
local sm = DiscoveryStateMachine.new({
|
|
306
|
-
portRange = { min = 38740, max = 38759 },
|
|
307
|
-
pollIntervalSec = 1,
|
|
308
|
-
}, {
|
|
309
|
-
scanPortsAsync = function(ports: { number }, _timeoutSec: number): (number?, string?)
|
|
310
|
-
-- Simulate: only the target port responds. The health body is
|
|
311
|
-
-- opaque to DiscoveryStateMachine, so a stub JSON suffices.
|
|
312
|
-
for _, port in ports do
|
|
313
|
-
if port == targetPort then
|
|
314
|
-
return port, '{"status":"ok"}'
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
return nil, nil
|
|
318
|
-
end,
|
|
319
|
-
connectWebSocket = function(url: string)
|
|
320
|
-
-- Simulate: WebSocket connection succeeds for the target port
|
|
321
|
-
if string.find(url, tostring(targetPort)) then
|
|
322
|
-
return true, { id = "mock-connection" }
|
|
323
|
-
end
|
|
324
|
-
return false, nil
|
|
325
|
-
end,
|
|
326
|
-
onStateChange = function(oldState: string, newState: string)
|
|
327
|
-
table.insert(stateTransitions, { old = oldState, new = newState })
|
|
328
|
-
end,
|
|
329
|
-
onConnected = function(connection: any)
|
|
330
|
-
connectedConnection = connection
|
|
331
|
-
end,
|
|
332
|
-
onDisconnected = function(reason: string?)
|
|
333
|
-
disconnectedReason = reason
|
|
334
|
-
end,
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
-- Start the state machine
|
|
338
|
-
assertEqual(sm:getState(), "idle")
|
|
339
|
-
sm:start()
|
|
340
|
-
assertEqual(sm:getState(), "searching")
|
|
341
|
-
|
|
342
|
-
-- First poll triggers immediate scan (nextPollAt was set to 0)
|
|
343
|
-
sm:pollAsync()
|
|
344
|
-
|
|
345
|
-
-- State machine should have found the port and connected
|
|
346
|
-
assertEqual(sm:getState(), "connected")
|
|
347
|
-
assertNotNil(connectedConnection, "connection object")
|
|
348
|
-
assertEqual(connectedConnection.id, "mock-connection")
|
|
349
|
-
|
|
350
|
-
-- Verify state transitions: idle -> searching -> connecting -> connected
|
|
351
|
-
assertTrue(#stateTransitions >= 3, "at least 3 transitions")
|
|
352
|
-
assertEqual(stateTransitions[1].old, "idle")
|
|
353
|
-
assertEqual(stateTransitions[1].new, "searching")
|
|
354
|
-
assertEqual(stateTransitions[2].old, "searching")
|
|
355
|
-
assertEqual(stateTransitions[2].new, "connecting")
|
|
356
|
-
assertEqual(stateTransitions[3].old, "connecting")
|
|
357
|
-
assertEqual(stateTransitions[3].new, "connected")
|
|
358
|
-
end,
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
-- ===========================================================================
|
|
362
|
-
-- 6. DiscoveryStateMachine: disconnect triggers immediate search and recovers
|
|
363
|
-
-- ===========================================================================
|
|
364
|
-
|
|
365
|
-
table.insert(tests, {
|
|
366
|
-
name = "DiscoveryStateMachine: disconnect searches immediately and reconnects when server returns",
|
|
367
|
-
fn = function()
|
|
368
|
-
local currentState = "idle"
|
|
369
|
-
local connectAttempts = 0
|
|
370
|
-
local scanAttempts = 0
|
|
371
|
-
local serverUp = true
|
|
372
|
-
|
|
373
|
-
local sm = DiscoveryStateMachine.new({
|
|
374
|
-
portRange = { min = 38740, max = 38740 }, -- single port
|
|
375
|
-
pollIntervalSec = 0.1,
|
|
376
|
-
}, {
|
|
377
|
-
scanPortsAsync = function(ports: { number }, _timeoutSec: number): (number?, string?)
|
|
378
|
-
scanAttempts = scanAttempts + 1
|
|
379
|
-
if serverUp then
|
|
380
|
-
return ports[1], "ok"
|
|
381
|
-
end
|
|
382
|
-
return nil, nil
|
|
383
|
-
end,
|
|
384
|
-
connectWebSocket = function(_url: string)
|
|
385
|
-
connectAttempts = connectAttempts + 1
|
|
386
|
-
if serverUp then
|
|
387
|
-
return true, { id = "conn" }
|
|
388
|
-
end
|
|
389
|
-
return false, nil
|
|
390
|
-
end,
|
|
391
|
-
onStateChange = function(_old: string, new: string)
|
|
392
|
-
currentState = new
|
|
393
|
-
end,
|
|
394
|
-
onConnected = function(_conn: any) end,
|
|
395
|
-
onDisconnected = function(_reason: string?) end,
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
-- Start and connect
|
|
399
|
-
sm:start()
|
|
400
|
-
sm:pollAsync()
|
|
401
|
-
assertEqual(currentState, "connected", "initial connection")
|
|
402
|
-
|
|
403
|
-
-- Simulate disconnect with server down
|
|
404
|
-
serverUp = false
|
|
405
|
-
sm:onDisconnect("connection lost")
|
|
406
|
-
-- Transitions straight to searching (no backoff)
|
|
407
|
-
assertEqual(currentState, "searching", "searching immediately after disconnect")
|
|
408
|
-
|
|
409
|
-
-- First poll triggers scan (primed), server still down -> stays searching
|
|
410
|
-
sm:pollAsync()
|
|
411
|
-
assertEqual(currentState, "searching", "stays searching when server is down")
|
|
412
|
-
|
|
413
|
-
-- Next poll should not scan yet (nextPollAt is in the future)
|
|
414
|
-
sm:pollAsync()
|
|
415
|
-
assertEqual(currentState, "searching", "still searching, waiting for poll interval")
|
|
416
|
-
|
|
417
|
-
-- Bring server back up and force next poll by setting _nextPollAt to 0
|
|
418
|
-
serverUp = true
|
|
419
|
-
sm._nextPollAt = 0
|
|
420
|
-
|
|
421
|
-
-- Next poll triggers scan -> server responds -> connects
|
|
422
|
-
sm:pollAsync()
|
|
423
|
-
assertEqual(currentState, "connected", "reconnected after server came back")
|
|
424
|
-
end,
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
-- ===========================================================================
|
|
428
|
-
-- 7. Full message lifecycle: register -> execute -> output -> scriptComplete
|
|
429
|
-
-- ===========================================================================
|
|
430
|
-
|
|
431
|
-
table.insert(tests, {
|
|
432
|
-
name = "Full lifecycle: register -> execute -> output -> scriptComplete",
|
|
433
|
-
fn = function()
|
|
434
|
-
local sessionId = "lifecycle-sess-001"
|
|
435
|
-
local router = ActionRouter.new()
|
|
436
|
-
local outputBuffer = MessageBuffer.new(100)
|
|
437
|
-
|
|
438
|
-
-- Set up execute handler that simulates script execution
|
|
439
|
-
router:setResponseType("execute", "scriptComplete")
|
|
440
|
-
router:register("execute", function(payload, _requestId, _sessionId)
|
|
441
|
-
-- Simulate: script produces output and completes
|
|
442
|
-
outputBuffer:push({
|
|
443
|
-
level = "Print",
|
|
444
|
-
body = "Script output: " .. payload.script,
|
|
445
|
-
timestamp = 5000,
|
|
446
|
-
})
|
|
447
|
-
return { success = true }
|
|
448
|
-
end)
|
|
449
|
-
|
|
450
|
-
-- Step 1: Plugin sends register
|
|
451
|
-
local registerMsg = Protocol.encode({
|
|
452
|
-
type = "register",
|
|
453
|
-
sessionId = sessionId,
|
|
454
|
-
payload = {
|
|
455
|
-
pluginVersion = "1.0.0",
|
|
456
|
-
instanceId = "inst-abc",
|
|
457
|
-
placeName = "TestPlace",
|
|
458
|
-
state = "Edit",
|
|
459
|
-
capabilities = { "execute", "queryState", "queryLogs" },
|
|
460
|
-
},
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
local registerDecoded, regErr = Protocol.decode(registerMsg)
|
|
464
|
-
assertNil(regErr, "register decode error")
|
|
465
|
-
assertEqual(registerDecoded.type, "register")
|
|
466
|
-
assertEqual(registerDecoded.payload.pluginVersion, "1.0.0")
|
|
467
|
-
|
|
468
|
-
-- Step 2: Server sends execute
|
|
469
|
-
local executeMsg = Protocol.encode({
|
|
470
|
-
type = "execute",
|
|
471
|
-
sessionId = sessionId,
|
|
472
|
-
requestId = "req-exec-001",
|
|
473
|
-
payload = { script = "print('hello')" },
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
local executeDecoded, execErr = Protocol.decode(executeMsg)
|
|
477
|
-
assertNil(execErr, "execute decode error")
|
|
478
|
-
|
|
479
|
-
-- Step 4: Plugin dispatches execute through ActionRouter
|
|
480
|
-
local scriptCompleteResponse = router:dispatch(executeDecoded)
|
|
481
|
-
assertNotNil(scriptCompleteResponse, "scriptComplete response")
|
|
482
|
-
assertEqual(scriptCompleteResponse.type, "scriptComplete")
|
|
483
|
-
assertEqual(scriptCompleteResponse.payload.success, true)
|
|
484
|
-
assertEqual(scriptCompleteResponse.requestId, "req-exec-001")
|
|
485
|
-
|
|
486
|
-
-- Step 5: Verify output was buffered
|
|
487
|
-
assertEqual(outputBuffer:size(), 1, "one output entry")
|
|
488
|
-
local logs = outputBuffer:get("tail", 1)
|
|
489
|
-
assertContains(logs.entries[1].body, "print('hello')")
|
|
490
|
-
|
|
491
|
-
-- Step 6: Encode the scriptComplete response (script output rides
|
|
492
|
-
-- inside the scriptComplete payload — there is no separate "output"
|
|
493
|
-
-- protocol message).
|
|
494
|
-
local _ = sessionId -- suppress unused-warning when output step is gone
|
|
495
|
-
local completeMsg = Protocol.encode({
|
|
496
|
-
type = scriptCompleteResponse.type,
|
|
497
|
-
sessionId = scriptCompleteResponse.sessionId,
|
|
498
|
-
requestId = scriptCompleteResponse.requestId,
|
|
499
|
-
payload = scriptCompleteResponse.payload,
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
local completeParsed, compErr = Protocol.decode(completeMsg)
|
|
503
|
-
assertNil(compErr, "scriptComplete decode error")
|
|
504
|
-
assertEqual(completeParsed.type, "scriptComplete")
|
|
505
|
-
assertEqual(completeParsed.payload.success, true)
|
|
506
|
-
end,
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
-- ===========================================================================
|
|
510
|
-
-- 8. ActionRouter error dispatch round-trips through Protocol
|
|
511
|
-
-- ===========================================================================
|
|
512
|
-
|
|
513
|
-
table.insert(tests, {
|
|
514
|
-
name = "Protocol + ActionRouter: error response round-trips through Protocol",
|
|
515
|
-
fn = function()
|
|
516
|
-
local router = ActionRouter.new()
|
|
517
|
-
|
|
518
|
-
-- No handler registered: should produce UNKNOWN_REQUEST error
|
|
519
|
-
local unknownRequest = Protocol.encode({
|
|
520
|
-
type = "captureScreenshot",
|
|
521
|
-
sessionId = "err-sess",
|
|
522
|
-
requestId = "err-req-001",
|
|
523
|
-
payload = {},
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
local decoded, err = Protocol.decode(unknownRequest)
|
|
527
|
-
assertNil(err, "decode error")
|
|
528
|
-
|
|
529
|
-
local errorResponse = router:dispatch(decoded)
|
|
530
|
-
assertNotNil(errorResponse, "error response")
|
|
531
|
-
assertEqual(errorResponse.type, "error")
|
|
532
|
-
assertEqual(errorResponse.payload.code, "UNKNOWN_REQUEST")
|
|
533
|
-
|
|
534
|
-
-- Encode the error response and verify it round-trips
|
|
535
|
-
local errorJson = Protocol.encode({
|
|
536
|
-
type = errorResponse.type,
|
|
537
|
-
sessionId = errorResponse.sessionId,
|
|
538
|
-
requestId = errorResponse.requestId,
|
|
539
|
-
payload = errorResponse.payload,
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
local errorParsed, errParseErr = Protocol.decode(errorJson)
|
|
543
|
-
assertNil(errParseErr, "error parse error")
|
|
544
|
-
assertEqual(errorParsed.type, "error")
|
|
545
|
-
assertEqual(errorParsed.payload.code, "UNKNOWN_REQUEST")
|
|
546
|
-
assertContains(errorParsed.payload.message, "captureScreenshot")
|
|
547
|
-
end,
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
-- ===========================================================================
|
|
551
|
-
-- 9. ActionRouter + MessageBuffer: buffer overflow during queryLogs
|
|
552
|
-
-- ===========================================================================
|
|
553
|
-
|
|
554
|
-
table.insert(tests, {
|
|
555
|
-
name = "ActionRouter + MessageBuffer: queryLogs with buffer overflow returns newest",
|
|
556
|
-
fn = function()
|
|
557
|
-
local buffer = MessageBuffer.new(5) -- small capacity
|
|
558
|
-
local router = ActionRouter.new()
|
|
559
|
-
|
|
560
|
-
router:setResponseType("queryLogs", "logsResult")
|
|
561
|
-
router:register("queryLogs", function(payload, _requestId, _sessionId)
|
|
562
|
-
return buffer:get(payload.direction, payload.count)
|
|
563
|
-
end)
|
|
564
|
-
|
|
565
|
-
-- Push more entries than capacity
|
|
566
|
-
for i = 1, 20 do
|
|
567
|
-
buffer:push({
|
|
568
|
-
level = if i % 2 == 0 then "Warning" else "Print",
|
|
569
|
-
body = "overflow-" .. tostring(i),
|
|
570
|
-
timestamp = i * 100,
|
|
571
|
-
})
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
-- Query tail 3 via dispatch
|
|
575
|
-
local response = router:dispatch({
|
|
576
|
-
type = "queryLogs",
|
|
577
|
-
sessionId = "overflow-sess",
|
|
578
|
-
requestId = "overflow-req",
|
|
579
|
-
payload = { direction = "tail", count = 3 },
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
assertNotNil(response, "response")
|
|
583
|
-
assertEqual(response.type, "logsResult")
|
|
584
|
-
assertEqual(#response.payload.entries, 3)
|
|
585
|
-
-- Should be the last 3 of the most recent 5 (16..20)
|
|
586
|
-
assertEqual(response.payload.entries[1].body, "overflow-18")
|
|
587
|
-
assertEqual(response.payload.entries[2].body, "overflow-19")
|
|
588
|
-
assertEqual(response.payload.entries[3].body, "overflow-20")
|
|
589
|
-
assertEqual(response.payload.total, 5, "total reflects buffer count (capped at capacity)")
|
|
590
|
-
assertEqual(response.payload.bufferCapacity, 5)
|
|
591
|
-
end,
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
-- ===========================================================================
|
|
595
|
-
-- 10. Multiple actions dispatched concurrently through Protocol
|
|
596
|
-
-- ===========================================================================
|
|
597
|
-
|
|
598
|
-
table.insert(tests, {
|
|
599
|
-
name = "Protocol + ActionRouter: multiple concurrent requests maintain isolation",
|
|
600
|
-
fn = function()
|
|
601
|
-
local buffer = MessageBuffer.new(100)
|
|
602
|
-
local router = ActionRouter.new()
|
|
603
|
-
|
|
604
|
-
-- Register multiple handlers
|
|
605
|
-
router:setResponseType("queryState", "stateResult")
|
|
606
|
-
router:setResponseType("execute", "scriptComplete")
|
|
607
|
-
router:setResponseType("queryLogs", "logsResult")
|
|
608
|
-
router:register("queryState", function(_payload, _requestId, _sessionId)
|
|
609
|
-
return { state = "Play", placeId = 111, placeName = "GamePlace", gameId = 222 }
|
|
610
|
-
end)
|
|
611
|
-
|
|
612
|
-
router:register("queryLogs", function(payload, _requestId, _sessionId)
|
|
613
|
-
return buffer:get(payload.direction, payload.count)
|
|
614
|
-
end)
|
|
615
|
-
|
|
616
|
-
router:register("execute", function(payload, _requestId, _sessionId)
|
|
617
|
-
return { success = true }
|
|
618
|
-
end)
|
|
619
|
-
|
|
620
|
-
-- Push some logs
|
|
621
|
-
buffer:push({ level = "Print", body = "test log", timestamp = 1000 })
|
|
622
|
-
|
|
623
|
-
-- Encode three requests with different requestIds
|
|
624
|
-
local requests = {
|
|
625
|
-
Protocol.encode({
|
|
626
|
-
type = "queryState",
|
|
627
|
-
sessionId = "multi-sess",
|
|
628
|
-
requestId = "multi-req-001",
|
|
629
|
-
payload = {},
|
|
630
|
-
}),
|
|
631
|
-
Protocol.encode({
|
|
632
|
-
type = "execute",
|
|
633
|
-
sessionId = "multi-sess",
|
|
634
|
-
requestId = "multi-req-002",
|
|
635
|
-
payload = { script = "game:GetService('Workspace')" },
|
|
636
|
-
}),
|
|
637
|
-
Protocol.encode({
|
|
638
|
-
type = "queryLogs",
|
|
639
|
-
sessionId = "multi-sess",
|
|
640
|
-
requestId = "multi-req-003",
|
|
641
|
-
payload = { direction = "tail", count = 10 },
|
|
642
|
-
}),
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
-- Decode and dispatch all three
|
|
646
|
-
local responses = {}
|
|
647
|
-
for _, reqJson in requests do
|
|
648
|
-
local decoded, err = Protocol.decode(reqJson)
|
|
649
|
-
assertNil(err, "decode error")
|
|
650
|
-
local resp = router:dispatch(decoded)
|
|
651
|
-
assertNotNil(resp, "response")
|
|
652
|
-
table.insert(responses, resp)
|
|
653
|
-
end
|
|
654
|
-
|
|
655
|
-
-- Verify each response has correct type and requestId
|
|
656
|
-
assertEqual(responses[1].type, "stateResult")
|
|
657
|
-
assertEqual(responses[1].requestId, "multi-req-001")
|
|
658
|
-
assertEqual(responses[1].payload.state, "Play")
|
|
659
|
-
|
|
660
|
-
assertEqual(responses[2].type, "scriptComplete")
|
|
661
|
-
assertEqual(responses[2].requestId, "multi-req-002")
|
|
662
|
-
assertEqual(responses[2].payload.success, true)
|
|
663
|
-
|
|
664
|
-
assertEqual(responses[3].type, "logsResult")
|
|
665
|
-
assertEqual(responses[3].requestId, "multi-req-003")
|
|
666
|
-
assertEqual(#responses[3].payload.entries, 1)
|
|
667
|
-
assertEqual(responses[3].payload.entries[1].body, "test log")
|
|
668
|
-
|
|
669
|
-
-- All share the same sessionId
|
|
670
|
-
for _, resp in responses do
|
|
671
|
-
assertEqual(resp.sessionId, "multi-sess")
|
|
672
|
-
end
|
|
673
|
-
end,
|
|
674
|
-
})
|
|
675
|
-
|
|
676
|
-
-- ===========================================================================
|
|
677
|
-
-- 11. DiscoveryStateMachine + Protocol: stop during connection cleans up
|
|
678
|
-
-- ===========================================================================
|
|
679
|
-
|
|
680
|
-
table.insert(tests, {
|
|
681
|
-
name = "DiscoveryStateMachine: stop during connected state fires onDisconnected",
|
|
682
|
-
fn = function()
|
|
683
|
-
local disconnectReason: string? = nil
|
|
684
|
-
local wasConnected = false
|
|
685
|
-
|
|
686
|
-
local sm = DiscoveryStateMachine.new({
|
|
687
|
-
portRange = { min = 38740, max = 38740 },
|
|
688
|
-
pollIntervalSec = 0.1,
|
|
689
|
-
}, {
|
|
690
|
-
scanPortsAsync = function(ports: { number }, _timeoutSec: number): (number?, string?)
|
|
691
|
-
return ports[1], "ok"
|
|
692
|
-
end,
|
|
693
|
-
connectWebSocket = function(_url: string)
|
|
694
|
-
return true, { id = "conn-stop-test" }
|
|
695
|
-
end,
|
|
696
|
-
onStateChange = function(_old: string, _new: string) end,
|
|
697
|
-
onConnected = function(_conn: any)
|
|
698
|
-
wasConnected = true
|
|
699
|
-
end,
|
|
700
|
-
onDisconnected = function(reason: string?)
|
|
701
|
-
disconnectReason = reason
|
|
702
|
-
end,
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
-- Connect
|
|
706
|
-
sm:start()
|
|
707
|
-
sm:pollAsync()
|
|
708
|
-
assertTrue(wasConnected, "should be connected")
|
|
709
|
-
assertEqual(sm:getState(), "connected")
|
|
710
|
-
|
|
711
|
-
-- Stop while connected
|
|
712
|
-
sm:stop()
|
|
713
|
-
assertEqual(sm:getState(), "idle")
|
|
714
|
-
assertEqual(disconnectReason, "stopped", "disconnect reason should be 'stopped'")
|
|
715
|
-
end,
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
-- ===========================================================================
|
|
719
|
-
-- 12. Protocol + ActionRouter: handler error produces encodable error message
|
|
720
|
-
-- ===========================================================================
|
|
721
|
-
|
|
722
|
-
table.insert(tests, {
|
|
723
|
-
name = "Protocol + ActionRouter: handler error encodes as valid Protocol error",
|
|
724
|
-
fn = function()
|
|
725
|
-
local router = ActionRouter.new()
|
|
726
|
-
router:register("queryDataModel", function(_payload, _requestId, _sessionId)
|
|
727
|
-
error("Instance not found: game.Workspace.Missing")
|
|
728
|
-
end)
|
|
729
|
-
|
|
730
|
-
-- Encode request
|
|
731
|
-
local requestJson = Protocol.encode({
|
|
732
|
-
type = "queryDataModel",
|
|
733
|
-
sessionId = "err-handler-sess",
|
|
734
|
-
requestId = "err-handler-req",
|
|
735
|
-
payload = { path = "game.Workspace.Missing", depth = 0 },
|
|
736
|
-
})
|
|
737
|
-
|
|
738
|
-
-- Decode and dispatch
|
|
739
|
-
local decoded, decErr = Protocol.decode(requestJson)
|
|
740
|
-
assertNil(decErr, "decode error")
|
|
741
|
-
|
|
742
|
-
local errorResponse = router:dispatch(decoded)
|
|
743
|
-
assertNotNil(errorResponse, "error response")
|
|
744
|
-
assertEqual(errorResponse.type, "error")
|
|
745
|
-
assertEqual(errorResponse.payload.code, "INTERNAL_ERROR")
|
|
746
|
-
|
|
747
|
-
-- Encode the error through Protocol
|
|
748
|
-
local errorJson = Protocol.encode({
|
|
749
|
-
type = errorResponse.type,
|
|
750
|
-
sessionId = errorResponse.sessionId,
|
|
751
|
-
requestId = errorResponse.requestId,
|
|
752
|
-
payload = errorResponse.payload,
|
|
753
|
-
})
|
|
754
|
-
|
|
755
|
-
-- Verify it decodes cleanly
|
|
756
|
-
local errorParsed, parseErr = Protocol.decode(errorJson)
|
|
757
|
-
assertNil(parseErr, "error decode error")
|
|
758
|
-
assertEqual(errorParsed.type, "error")
|
|
759
|
-
assertEqual(errorParsed.payload.code, "INTERNAL_ERROR")
|
|
760
|
-
assertContains(errorParsed.payload.message, "Instance not found")
|
|
761
|
-
assertEqual(errorParsed.sessionId, "err-handler-sess")
|
|
762
|
-
assertEqual(errorParsed.requestId, "err-handler-req")
|
|
763
|
-
end,
|
|
764
|
-
})
|
|
765
|
-
|
|
766
|
-
-- ===========================================================================
|
|
767
|
-
-- 13. Full system: discovery -> register -> queryLogs with buffered data
|
|
768
|
-
-- ===========================================================================
|
|
769
|
-
|
|
770
|
-
table.insert(tests, {
|
|
771
|
-
name = "Full system: discovery + register + queryLogs with populated buffer",
|
|
772
|
-
fn = function()
|
|
773
|
-
-- Set up the message buffer and router as if a plugin were running
|
|
774
|
-
local buffer = MessageBuffer.new(100)
|
|
775
|
-
local router = ActionRouter.new()
|
|
776
|
-
local discoveredPort: number? = nil
|
|
777
|
-
|
|
778
|
-
router:setResponseType("queryLogs", "logsResult")
|
|
779
|
-
router:register("queryLogs", function(payload, _requestId, _sessionId)
|
|
780
|
-
return buffer:get(payload.direction or "tail", payload.count or 50)
|
|
781
|
-
end)
|
|
782
|
-
|
|
783
|
-
-- Pre-populate the buffer (simulating logs accumulated during startup)
|
|
784
|
-
buffer:push({ level = "Print", body = "[StudioBridge] Plugin loaded", timestamp = 100 })
|
|
785
|
-
buffer:push({ level = "Print", body = "[StudioBridge] Searching for server...", timestamp = 200 })
|
|
786
|
-
buffer:push({ level = "Print", body = "[StudioBridge] Connected to port 38742", timestamp = 500 })
|
|
787
|
-
|
|
788
|
-
-- Discovery finds the server on port 38742
|
|
789
|
-
local sm = DiscoveryStateMachine.new({
|
|
790
|
-
portRange = { min = 38740, max = 38750 },
|
|
791
|
-
pollIntervalSec = 0.5,
|
|
792
|
-
}, {
|
|
793
|
-
scanPortsAsync = function(ports: { number }, _timeoutSec: number): (number?, string?)
|
|
794
|
-
for _, port in ports do
|
|
795
|
-
if port == 38742 then
|
|
796
|
-
return port, '{"status":"ok"}'
|
|
797
|
-
end
|
|
798
|
-
end
|
|
799
|
-
return nil, nil
|
|
800
|
-
end,
|
|
801
|
-
connectWebSocket = function(url: string)
|
|
802
|
-
if string.find(url, "38742") then
|
|
803
|
-
discoveredPort = 38742
|
|
804
|
-
return true, { id = "ws-conn" }
|
|
805
|
-
end
|
|
806
|
-
return false, nil
|
|
807
|
-
end,
|
|
808
|
-
onStateChange = function(_old: string, _new: string) end,
|
|
809
|
-
onConnected = function(_conn: any) end,
|
|
810
|
-
onDisconnected = function(_reason: string?) end,
|
|
811
|
-
})
|
|
812
|
-
|
|
813
|
-
sm:start()
|
|
814
|
-
sm:pollAsync()
|
|
815
|
-
assertEqual(sm:getState(), "connected")
|
|
816
|
-
assertEqual(discoveredPort, 38742)
|
|
817
|
-
|
|
818
|
-
-- Now simulate the register handshake via Protocol
|
|
819
|
-
local registerJson = Protocol.encode({
|
|
820
|
-
type = "register",
|
|
821
|
-
sessionId = "full-sys-sess",
|
|
822
|
-
payload = {
|
|
823
|
-
pluginVersion = "1.0.0",
|
|
824
|
-
instanceId = "inst-full",
|
|
825
|
-
placeName = "FullTestPlace",
|
|
826
|
-
state = "Edit",
|
|
827
|
-
capabilities = { "execute", "queryLogs" },
|
|
828
|
-
},
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
local registerDecoded, regErr = Protocol.decode(registerJson)
|
|
832
|
-
assertNil(regErr, "register decode error")
|
|
833
|
-
assertEqual(registerDecoded.type, "register")
|
|
834
|
-
|
|
835
|
-
-- Server sends queryLogs
|
|
836
|
-
local queryLogsJson = Protocol.encode({
|
|
837
|
-
type = "queryLogs",
|
|
838
|
-
sessionId = "full-sys-sess",
|
|
839
|
-
requestId = "ql-001",
|
|
840
|
-
payload = { direction = "tail", count = 2 },
|
|
841
|
-
})
|
|
842
|
-
|
|
843
|
-
local queryDecoded, qlErr = Protocol.decode(queryLogsJson)
|
|
844
|
-
assertNil(qlErr, "queryLogs decode error")
|
|
845
|
-
|
|
846
|
-
-- Plugin dispatches through router
|
|
847
|
-
local logsResponse = router:dispatch(queryDecoded)
|
|
848
|
-
assertNotNil(logsResponse, "logs response")
|
|
849
|
-
assertEqual(logsResponse.type, "logsResult")
|
|
850
|
-
assertEqual(#logsResponse.payload.entries, 2)
|
|
851
|
-
-- Last 2 entries
|
|
852
|
-
assertContains(logsResponse.payload.entries[1].body, "Searching for server")
|
|
853
|
-
assertContains(logsResponse.payload.entries[2].body, "Connected to port 38742")
|
|
854
|
-
assertEqual(logsResponse.payload.total, 3)
|
|
855
|
-
end,
|
|
856
|
-
})
|
|
857
|
-
|
|
858
|
-
-- ===========================================================================
|
|
859
|
-
-- 14. Protocol preserves register payload fields across encode/decode
|
|
860
|
-
-- ===========================================================================
|
|
861
|
-
|
|
862
|
-
table.insert(tests, {
|
|
863
|
-
name = "Protocol: register preserves payload fields across encode/decode",
|
|
864
|
-
fn = function()
|
|
865
|
-
local capabilities =
|
|
866
|
-
{ "execute", "queryState", "captureScreenshot", "queryDataModel", "queryLogs", "subscribe", "heartbeat" }
|
|
867
|
-
|
|
868
|
-
local registerJson = Protocol.encode({
|
|
869
|
-
type = "register",
|
|
870
|
-
sessionId = "register-test-sess",
|
|
871
|
-
payload = {
|
|
872
|
-
pluginVersion = "1.2.3",
|
|
873
|
-
instanceId = "inst-x",
|
|
874
|
-
placeName = "TestPlace",
|
|
875
|
-
state = "Edit",
|
|
876
|
-
capabilities = capabilities,
|
|
877
|
-
},
|
|
878
|
-
})
|
|
879
|
-
|
|
880
|
-
local registerParsed, regErr = Protocol.decode(registerJson)
|
|
881
|
-
assertNil(regErr, "register error")
|
|
882
|
-
assertEqual(registerParsed.payload.pluginVersion, "1.2.3")
|
|
883
|
-
assertEqual(registerParsed.payload.instanceId, "inst-x")
|
|
884
|
-
assertEqual(#registerParsed.payload.capabilities, 7)
|
|
885
|
-
end,
|
|
886
|
-
})
|
|
887
|
-
|
|
888
|
-
-- ===========================================================================
|
|
889
|
-
-- 15. DiscoveryStateMachine: all ports fail, stays searching
|
|
890
|
-
-- ===========================================================================
|
|
891
|
-
|
|
892
|
-
table.insert(tests, {
|
|
893
|
-
name = "DiscoveryStateMachine: all ports fail stays in searching state",
|
|
894
|
-
fn = function()
|
|
895
|
-
local currentState = "idle"
|
|
896
|
-
local sm = DiscoveryStateMachine.new({
|
|
897
|
-
portRange = { min = 38740, max = 38742 }, -- 3 ports
|
|
898
|
-
pollIntervalSec = 0.5,
|
|
899
|
-
}, {
|
|
900
|
-
scanPortsAsync = function(_ports: { number }, _timeoutSec: number): (number?, string?)
|
|
901
|
-
return nil, nil -- all ports fail
|
|
902
|
-
end,
|
|
903
|
-
connectWebSocket = function(_url: string)
|
|
904
|
-
return false, nil
|
|
905
|
-
end,
|
|
906
|
-
onStateChange = function(_old: string, new: string)
|
|
907
|
-
currentState = new
|
|
908
|
-
end,
|
|
909
|
-
onConnected = function(_conn: any) end,
|
|
910
|
-
onDisconnected = function(_reason: string?) end,
|
|
911
|
-
})
|
|
912
|
-
|
|
913
|
-
sm:start()
|
|
914
|
-
assertEqual(currentState, "searching")
|
|
915
|
-
|
|
916
|
-
-- Poll to trigger scan
|
|
917
|
-
sm:pollAsync()
|
|
918
|
-
-- All ports failed, should stay in searching
|
|
919
|
-
assertEqual(currentState, "searching")
|
|
920
|
-
|
|
921
|
-
-- Next poll should skip (nextPollAt is in the future), then force it
|
|
922
|
-
sm._nextPollAt = 0
|
|
923
|
-
sm:pollAsync()
|
|
924
|
-
assertEqual(currentState, "searching", "still searching after second scan")
|
|
925
|
-
end,
|
|
926
|
-
})
|
|
927
|
-
|
|
928
|
-
-- ===========================================================================
|
|
929
|
-
-- 16. ActionRouter + Protocol: subscribe/unsubscribe flow
|
|
930
|
-
-- ===========================================================================
|
|
931
|
-
|
|
932
|
-
table.insert(tests, {
|
|
933
|
-
name = "Protocol + ActionRouter: subscribe and unsubscribe flow",
|
|
934
|
-
fn = function()
|
|
935
|
-
local router = ActionRouter.new()
|
|
936
|
-
local activeSubscriptions: { string } = {}
|
|
937
|
-
|
|
938
|
-
router:setResponseType("subscribe", "subscribeResult")
|
|
939
|
-
router:setResponseType("unsubscribe", "unsubscribeResult")
|
|
940
|
-
router:register("subscribe", function(payload, _requestId, _sessionId)
|
|
941
|
-
for _, event in payload.events do
|
|
942
|
-
table.insert(activeSubscriptions, event)
|
|
943
|
-
end
|
|
944
|
-
return { events = payload.events }
|
|
945
|
-
end)
|
|
946
|
-
|
|
947
|
-
router:register("unsubscribe", function(payload, _requestId, _sessionId)
|
|
948
|
-
local remaining = {}
|
|
949
|
-
for _, sub in activeSubscriptions do
|
|
950
|
-
local found = false
|
|
951
|
-
for _, unsub in payload.events do
|
|
952
|
-
if sub == unsub then
|
|
953
|
-
found = true
|
|
954
|
-
break
|
|
955
|
-
end
|
|
956
|
-
end
|
|
957
|
-
if not found then
|
|
958
|
-
table.insert(remaining, sub)
|
|
959
|
-
end
|
|
960
|
-
end
|
|
961
|
-
activeSubscriptions = remaining
|
|
962
|
-
return { events = payload.events }
|
|
963
|
-
end)
|
|
964
|
-
|
|
965
|
-
-- Subscribe via Protocol-encoded message
|
|
966
|
-
local subJson = Protocol.encode({
|
|
967
|
-
type = "subscribe",
|
|
968
|
-
sessionId = "sub-sess",
|
|
969
|
-
requestId = "sub-req-001",
|
|
970
|
-
payload = { events = { "stateChange", "logPush" } },
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
local subDecoded, subErr = Protocol.decode(subJson)
|
|
974
|
-
assertNil(subErr, "subscribe decode error")
|
|
975
|
-
local subResponse = router:dispatch(subDecoded)
|
|
976
|
-
|
|
977
|
-
assertNotNil(subResponse, "subscribe response")
|
|
978
|
-
assertEqual(subResponse.type, "subscribeResult")
|
|
979
|
-
assertEqual(#activeSubscriptions, 2)
|
|
980
|
-
|
|
981
|
-
-- Encode the subscribeResult and verify round-trip
|
|
982
|
-
local subResultJson = Protocol.encode({
|
|
983
|
-
type = subResponse.type,
|
|
984
|
-
sessionId = subResponse.sessionId,
|
|
985
|
-
requestId = subResponse.requestId,
|
|
986
|
-
payload = subResponse.payload,
|
|
987
|
-
})
|
|
988
|
-
local subResultParsed, srErr = Protocol.decode(subResultJson)
|
|
989
|
-
assertNil(srErr, "subscribeResult decode error")
|
|
990
|
-
assertEqual(subResultParsed.type, "subscribeResult")
|
|
991
|
-
|
|
992
|
-
-- Unsubscribe from stateChange
|
|
993
|
-
local unsubJson = Protocol.encode({
|
|
994
|
-
type = "unsubscribe",
|
|
995
|
-
sessionId = "sub-sess",
|
|
996
|
-
requestId = "sub-req-002",
|
|
997
|
-
payload = { events = { "stateChange" } },
|
|
998
|
-
})
|
|
999
|
-
|
|
1000
|
-
local unsubDecoded, unsubErr = Protocol.decode(unsubJson)
|
|
1001
|
-
assertNil(unsubErr, "unsubscribe decode error")
|
|
1002
|
-
local unsubResponse = router:dispatch(unsubDecoded)
|
|
1003
|
-
|
|
1004
|
-
assertNotNil(unsubResponse, "unsubscribe response")
|
|
1005
|
-
assertEqual(unsubResponse.type, "unsubscribeResult")
|
|
1006
|
-
assertEqual(#activeSubscriptions, 1)
|
|
1007
|
-
|
|
1008
|
-
-- Verify remaining subscription is logPush (not stateChange)
|
|
1009
|
-
local found = false
|
|
1010
|
-
for _, sub in activeSubscriptions do
|
|
1011
|
-
if sub == "logPush" then
|
|
1012
|
-
found = true
|
|
1013
|
-
end
|
|
1014
|
-
assertTrue(sub ~= "stateChange", "stateChange should be removed")
|
|
1015
|
-
end
|
|
1016
|
-
assertTrue(found, "logPush should still be subscribed")
|
|
1017
|
-
end,
|
|
1018
|
-
})
|
|
1019
|
-
|
|
1020
|
-
return tests
|