@portel/photon 1.4.1 → 1.6.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/README.md +326 -1177
- package/dist/auto-ui/beam.d.ts +14 -0
- package/dist/auto-ui/beam.d.ts.map +1 -0
- package/dist/auto-ui/beam.js +3057 -0
- package/dist/auto-ui/beam.js.map +1 -0
- package/dist/auto-ui/bridge/index.d.ts +37 -0
- package/dist/auto-ui/bridge/index.d.ts.map +1 -0
- package/dist/auto-ui/bridge/index.js +555 -0
- package/dist/auto-ui/bridge/index.js.map +1 -0
- package/dist/auto-ui/bridge/openai-shim.d.ts +20 -0
- package/dist/auto-ui/bridge/openai-shim.d.ts.map +1 -0
- package/dist/auto-ui/bridge/openai-shim.js +231 -0
- package/dist/auto-ui/bridge/openai-shim.js.map +1 -0
- package/dist/auto-ui/bridge/photon-app.d.ts +162 -0
- package/dist/auto-ui/bridge/photon-app.d.ts.map +1 -0
- package/dist/auto-ui/bridge/photon-app.js +460 -0
- package/dist/auto-ui/bridge/photon-app.js.map +1 -0
- package/dist/auto-ui/bridge/types.d.ts +128 -0
- package/dist/auto-ui/bridge/types.d.ts.map +1 -0
- package/dist/auto-ui/bridge/types.js +7 -0
- package/dist/auto-ui/bridge/types.js.map +1 -0
- package/dist/auto-ui/components/card.d.ts +13 -0
- package/dist/auto-ui/components/card.d.ts.map +1 -0
- package/dist/auto-ui/components/card.js +64 -0
- package/dist/auto-ui/components/card.js.map +1 -0
- package/dist/auto-ui/components/form.d.ts +15 -0
- package/dist/auto-ui/components/form.d.ts.map +1 -0
- package/dist/auto-ui/components/form.js +72 -0
- package/dist/auto-ui/components/form.js.map +1 -0
- package/dist/auto-ui/components/list.d.ts +13 -0
- package/dist/auto-ui/components/list.d.ts.map +1 -0
- package/dist/auto-ui/components/list.js +58 -0
- package/dist/auto-ui/components/list.js.map +1 -0
- package/dist/auto-ui/components/progress.d.ts +18 -0
- package/dist/auto-ui/components/progress.d.ts.map +1 -0
- package/dist/auto-ui/components/progress.js +125 -0
- package/dist/auto-ui/components/progress.js.map +1 -0
- package/dist/auto-ui/components/table.d.ts +13 -0
- package/dist/auto-ui/components/table.d.ts.map +1 -0
- package/dist/auto-ui/components/table.js +82 -0
- package/dist/auto-ui/components/table.js.map +1 -0
- package/dist/auto-ui/components/tree.d.ts +13 -0
- package/dist/auto-ui/components/tree.d.ts.map +1 -0
- package/dist/auto-ui/components/tree.js +61 -0
- package/dist/auto-ui/components/tree.js.map +1 -0
- package/dist/auto-ui/daemon-tools.d.ts +45 -0
- package/dist/auto-ui/daemon-tools.d.ts.map +1 -0
- package/dist/auto-ui/daemon-tools.js +580 -0
- package/dist/auto-ui/daemon-tools.js.map +1 -0
- package/dist/auto-ui/design-system/index.d.ts +21 -0
- package/dist/auto-ui/design-system/index.d.ts.map +1 -0
- package/dist/auto-ui/design-system/index.js +27 -0
- package/dist/auto-ui/design-system/index.js.map +1 -0
- package/dist/auto-ui/design-system/tokens.d.ts +9 -0
- package/dist/auto-ui/design-system/tokens.d.ts.map +1 -0
- package/dist/auto-ui/design-system/tokens.js +27 -0
- package/dist/auto-ui/design-system/tokens.js.map +1 -0
- package/dist/auto-ui/design-system/transaction-ui.d.ts +70 -0
- package/dist/auto-ui/design-system/transaction-ui.d.ts.map +1 -0
- package/dist/auto-ui/design-system/transaction-ui.js +982 -0
- package/dist/auto-ui/design-system/transaction-ui.js.map +1 -0
- package/dist/auto-ui/frontend/index.html +84 -0
- package/dist/auto-ui/index.d.ts +23 -0
- package/dist/auto-ui/index.d.ts.map +1 -0
- package/dist/auto-ui/index.js +28 -0
- package/dist/auto-ui/index.js.map +1 -0
- package/dist/auto-ui/openapi-generator.d.ts +71 -0
- package/dist/auto-ui/openapi-generator.d.ts.map +1 -0
- package/dist/auto-ui/openapi-generator.js +223 -0
- package/dist/auto-ui/openapi-generator.js.map +1 -0
- package/dist/auto-ui/photon-bridge.d.ts +159 -0
- package/dist/auto-ui/photon-bridge.d.ts.map +1 -0
- package/dist/auto-ui/photon-bridge.js +262 -0
- package/dist/auto-ui/photon-bridge.js.map +1 -0
- package/dist/auto-ui/photon-host.d.ts +113 -0
- package/dist/auto-ui/photon-host.d.ts.map +1 -0
- package/dist/auto-ui/photon-host.js +284 -0
- package/dist/auto-ui/photon-host.js.map +1 -0
- package/dist/auto-ui/platform-compat.d.ts +71 -0
- package/dist/auto-ui/platform-compat.d.ts.map +1 -0
- package/dist/auto-ui/platform-compat.js +628 -0
- package/dist/auto-ui/platform-compat.js.map +1 -0
- package/dist/auto-ui/playground-html.d.ts +15 -0
- package/dist/auto-ui/playground-html.d.ts.map +1 -0
- package/dist/auto-ui/playground-html.js +1113 -0
- package/dist/auto-ui/playground-html.js.map +1 -0
- package/dist/auto-ui/playground-server.d.ts +7 -0
- package/dist/auto-ui/playground-server.d.ts.map +1 -0
- package/dist/auto-ui/playground-server.js +840 -0
- package/dist/auto-ui/playground-server.js.map +1 -0
- package/dist/auto-ui/registry.d.ts +13 -0
- package/dist/auto-ui/registry.d.ts.map +1 -0
- package/dist/auto-ui/registry.js +62 -0
- package/dist/auto-ui/registry.js.map +1 -0
- package/dist/auto-ui/renderer.d.ts +14 -0
- package/dist/auto-ui/renderer.d.ts.map +1 -0
- package/dist/auto-ui/renderer.js +88 -0
- package/dist/auto-ui/renderer.js.map +1 -0
- package/dist/auto-ui/rendering/components.d.ts +29 -0
- package/dist/auto-ui/rendering/components.d.ts.map +1 -0
- package/dist/auto-ui/rendering/components.js +773 -0
- package/dist/auto-ui/rendering/components.js.map +1 -0
- package/dist/auto-ui/rendering/field-analyzer.d.ts +48 -0
- package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -0
- package/dist/auto-ui/rendering/field-analyzer.js +270 -0
- package/dist/auto-ui/rendering/field-analyzer.js.map +1 -0
- package/dist/auto-ui/rendering/field-renderers.d.ts +64 -0
- package/dist/auto-ui/rendering/field-renderers.d.ts.map +1 -0
- package/dist/auto-ui/rendering/field-renderers.js +317 -0
- package/dist/auto-ui/rendering/field-renderers.js.map +1 -0
- package/dist/auto-ui/rendering/index.d.ts +28 -0
- package/dist/auto-ui/rendering/index.d.ts.map +1 -0
- package/dist/auto-ui/rendering/index.js +60 -0
- package/dist/auto-ui/rendering/index.js.map +1 -0
- package/dist/auto-ui/rendering/layout-selector.d.ts +48 -0
- package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -0
- package/dist/auto-ui/rendering/layout-selector.js +352 -0
- package/dist/auto-ui/rendering/layout-selector.js.map +1 -0
- package/dist/auto-ui/rendering/template-engine.d.ts +41 -0
- package/dist/auto-ui/rendering/template-engine.d.ts.map +1 -0
- package/dist/auto-ui/rendering/template-engine.js +238 -0
- package/dist/auto-ui/rendering/template-engine.js.map +1 -0
- package/dist/auto-ui/streamable-http-transport.d.ts +103 -0
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -0
- package/dist/auto-ui/streamable-http-transport.js +1875 -0
- package/dist/auto-ui/streamable-http-transport.js.map +1 -0
- package/dist/auto-ui/types.d.ts +384 -0
- package/dist/auto-ui/types.d.ts.map +1 -0
- package/dist/auto-ui/types.js +92 -0
- package/dist/auto-ui/types.js.map +1 -0
- package/dist/beam.bundle.js +63137 -0
- package/dist/beam.bundle.js.map +7 -0
- package/dist/claude-code-plugin.d.ts.map +1 -1
- package/dist/claude-code-plugin.js +30 -30
- package/dist/claude-code-plugin.js.map +1 -1
- package/dist/cli/commands/info.d.ts +11 -0
- package/dist/cli/commands/info.d.ts.map +1 -0
- package/dist/cli/commands/info.js +313 -0
- package/dist/cli/commands/info.js.map +1 -0
- package/dist/cli/commands/marketplace.d.ts +11 -0
- package/dist/cli/commands/marketplace.d.ts.map +1 -0
- package/dist/cli/commands/marketplace.js +198 -0
- package/dist/cli/commands/marketplace.js.map +1 -0
- package/dist/cli/commands/package-app.d.ts +9 -0
- package/dist/cli/commands/package-app.d.ts.map +1 -0
- package/dist/cli/commands/package-app.js +191 -0
- package/dist/cli/commands/package-app.js.map +1 -0
- package/dist/cli/commands/package.d.ts +11 -0
- package/dist/cli/commands/package.d.ts.map +1 -0
- package/dist/cli/commands/package.js +573 -0
- package/dist/cli/commands/package.js.map +1 -0
- package/dist/cli-alias.d.ts.map +1 -1
- package/dist/cli-alias.js +30 -28
- package/dist/cli-alias.js.map +1 -1
- package/dist/cli-formatter.d.ts +8 -24
- package/dist/cli-formatter.d.ts.map +1 -1
- package/dist/cli-formatter.js +8 -325
- package/dist/cli-formatter.js.map +1 -1
- package/dist/cli.d.ts +15 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1166 -1131
- package/dist/cli.js.map +1 -1
- package/dist/daemon/client.d.ts +84 -3
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +561 -11
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.d.ts +51 -12
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +122 -61
- package/dist/daemon/manager.js.map +1 -1
- package/dist/daemon/protocol.d.ts +62 -6
- package/dist/daemon/protocol.d.ts.map +1 -1
- package/dist/daemon/protocol.js +76 -1
- package/dist/daemon/protocol.js.map +1 -1
- package/dist/daemon/server.d.ts +6 -6
- package/dist/daemon/server.js +743 -133
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/session-manager.d.ts +8 -1
- package/dist/daemon/session-manager.d.ts.map +1 -1
- package/dist/daemon/session-manager.js +32 -9
- package/dist/daemon/session-manager.js.map +1 -1
- package/dist/deploy/cloudflare.d.ts +12 -0
- package/dist/deploy/cloudflare.d.ts.map +1 -0
- package/dist/deploy/cloudflare.js +216 -0
- package/dist/deploy/cloudflare.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +191 -21
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1186 -319
- package/dist/loader.js.map +1 -1
- package/dist/markdown-utils.d.ts +8 -0
- package/dist/markdown-utils.d.ts.map +1 -0
- package/dist/markdown-utils.js +63 -0
- package/dist/markdown-utils.js.map +1 -0
- package/dist/marketplace-manager.d.ts +10 -0
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +112 -28
- package/dist/marketplace-manager.js.map +1 -1
- package/dist/mcp-client.d.ts +9 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +11 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-elicitation.d.ts +32 -0
- package/dist/mcp-elicitation.d.ts.map +1 -0
- package/dist/mcp-elicitation.js +26 -0
- package/dist/mcp-elicitation.js.map +1 -0
- package/dist/path-resolver.d.ts +9 -12
- package/dist/path-resolver.d.ts.map +1 -1
- package/dist/path-resolver.js +13 -43
- package/dist/path-resolver.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +204 -77
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +89 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +560 -32
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/photons/maker.photon.d.ts +182 -0
- package/dist/photons/maker.photon.d.ts.map +1 -0
- package/dist/photons/maker.photon.js +504 -0
- package/dist/photons/maker.photon.js.map +1 -0
- package/dist/photons/maker.photon.ts +626 -0
- package/dist/photons/marketplace.photon.d.ts +110 -0
- package/dist/photons/marketplace.photon.d.ts.map +1 -0
- package/dist/photons/marketplace.photon.js +260 -0
- package/dist/photons/marketplace.photon.js.map +1 -0
- package/dist/photons/marketplace.photon.ts +378 -0
- package/dist/photons/tunnel.photon.d.ts +80 -0
- package/dist/photons/tunnel.photon.d.ts.map +1 -0
- package/dist/photons/tunnel.photon.js +269 -0
- package/dist/photons/tunnel.photon.js.map +1 -0
- package/dist/photons/tunnel.photon.ts +345 -0
- package/dist/security-scanner.d.ts.map +1 -1
- package/dist/security-scanner.js +18 -15
- package/dist/security-scanner.js.map +1 -1
- package/dist/serv/auth/jwt.d.ts +89 -0
- package/dist/serv/auth/jwt.d.ts.map +1 -0
- package/dist/serv/auth/jwt.js +239 -0
- package/dist/serv/auth/jwt.js.map +1 -0
- package/dist/serv/auth/oauth.d.ts +117 -0
- package/dist/serv/auth/oauth.d.ts.map +1 -0
- package/dist/serv/auth/oauth.js +395 -0
- package/dist/serv/auth/oauth.js.map +1 -0
- package/dist/serv/auth/well-known.d.ts +60 -0
- package/dist/serv/auth/well-known.d.ts.map +1 -0
- package/dist/serv/auth/well-known.js +154 -0
- package/dist/serv/auth/well-known.js.map +1 -0
- package/dist/serv/db/d1-client.d.ts +65 -0
- package/dist/serv/db/d1-client.d.ts.map +1 -0
- package/dist/serv/db/d1-client.js +137 -0
- package/dist/serv/db/d1-client.js.map +1 -0
- package/dist/serv/db/d1-stores.d.ts +62 -0
- package/dist/serv/db/d1-stores.d.ts.map +1 -0
- package/dist/serv/db/d1-stores.js +307 -0
- package/dist/serv/db/d1-stores.js.map +1 -0
- package/dist/serv/index.d.ts +114 -0
- package/dist/serv/index.d.ts.map +1 -0
- package/dist/serv/index.js +172 -0
- package/dist/serv/index.js.map +1 -0
- package/dist/serv/local.d.ts +118 -0
- package/dist/serv/local.d.ts.map +1 -0
- package/dist/serv/local.js +392 -0
- package/dist/serv/local.js.map +1 -0
- package/dist/serv/middleware/auth.d.ts +66 -0
- package/dist/serv/middleware/auth.d.ts.map +1 -0
- package/dist/serv/middleware/auth.js +178 -0
- package/dist/serv/middleware/auth.js.map +1 -0
- package/dist/serv/middleware/tenant.d.ts +94 -0
- package/dist/serv/middleware/tenant.d.ts.map +1 -0
- package/dist/serv/middleware/tenant.js +152 -0
- package/dist/serv/middleware/tenant.js.map +1 -0
- package/dist/serv/runtime/executor.d.ts +76 -0
- package/dist/serv/runtime/executor.d.ts.map +1 -0
- package/dist/serv/runtime/executor.js +105 -0
- package/dist/serv/runtime/executor.js.map +1 -0
- package/dist/serv/runtime/index.d.ts +8 -0
- package/dist/serv/runtime/index.d.ts.map +1 -0
- package/dist/serv/runtime/index.js +10 -0
- package/dist/serv/runtime/index.js.map +1 -0
- package/dist/serv/runtime/oauth-context.d.ts +121 -0
- package/dist/serv/runtime/oauth-context.d.ts.map +1 -0
- package/dist/serv/runtime/oauth-context.js +153 -0
- package/dist/serv/runtime/oauth-context.js.map +1 -0
- package/dist/serv/session/kv-store.d.ts +54 -0
- package/dist/serv/session/kv-store.d.ts.map +1 -0
- package/dist/serv/session/kv-store.js +149 -0
- package/dist/serv/session/kv-store.js.map +1 -0
- package/dist/serv/session/store.d.ts +113 -0
- package/dist/serv/session/store.d.ts.map +1 -0
- package/dist/serv/session/store.js +284 -0
- package/dist/serv/session/store.js.map +1 -0
- package/dist/serv/types/index.d.ts +147 -0
- package/dist/serv/types/index.d.ts.map +1 -0
- package/dist/serv/types/index.js +8 -0
- package/dist/serv/types/index.js.map +1 -0
- package/dist/serv/vault/token-vault.d.ts +102 -0
- package/dist/serv/vault/token-vault.d.ts.map +1 -0
- package/dist/serv/vault/token-vault.js +177 -0
- package/dist/serv/vault/token-vault.js.map +1 -0
- package/dist/server.d.ts +184 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1995 -86
- package/dist/server.js.map +1 -1
- package/dist/shared/cli-sections.d.ts +6 -0
- package/dist/shared/cli-sections.d.ts.map +1 -0
- package/dist/shared/cli-sections.js +16 -0
- package/dist/shared/cli-sections.js.map +1 -0
- package/dist/shared/cli-utils.d.ts +81 -0
- package/dist/shared/cli-utils.d.ts.map +1 -0
- package/dist/shared/cli-utils.js +174 -0
- package/dist/shared/cli-utils.js.map +1 -0
- package/dist/shared/config-docs.d.ts +6 -0
- package/dist/shared/config-docs.d.ts.map +1 -0
- package/dist/shared/config-docs.js +6 -0
- package/dist/shared/config-docs.js.map +1 -0
- package/dist/shared/error-handler.d.ts +128 -0
- package/dist/shared/error-handler.d.ts.map +1 -0
- package/dist/shared/error-handler.js +342 -0
- package/dist/shared/error-handler.js.map +1 -0
- package/dist/shared/logger.d.ts +42 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +123 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/performance.d.ts +65 -0
- package/dist/shared/performance.d.ts.map +1 -0
- package/dist/shared/performance.js +136 -0
- package/dist/shared/performance.js.map +1 -0
- package/dist/shared/task-runner.d.ts +2 -0
- package/dist/shared/task-runner.d.ts.map +1 -0
- package/dist/shared/task-runner.js +16 -0
- package/dist/shared/task-runner.js.map +1 -0
- package/dist/shared/validation.d.ts +6 -0
- package/dist/shared/validation.d.ts.map +1 -0
- package/dist/shared/validation.js +6 -0
- package/dist/shared/validation.js.map +1 -0
- package/dist/shared-utils.d.ts +63 -0
- package/dist/shared-utils.d.ts.map +1 -0
- package/dist/shared-utils.js +123 -0
- package/dist/shared-utils.js.map +1 -0
- package/dist/template-manager.d.ts +23 -2
- package/dist/template-manager.d.ts.map +1 -1
- package/dist/template-manager.js +176 -87
- package/dist/template-manager.js.map +1 -1
- package/dist/test-client.d.ts.map +1 -1
- package/dist/test-client.js +10 -8
- package/dist/test-client.js.map +1 -1
- package/dist/test-runner.d.ts +52 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +785 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/testing.d.ts +103 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +163 -0
- package/dist/testing.js.map +1 -0
- package/dist/version-checker.d.ts.map +1 -1
- package/dist/version-checker.js +2 -2
- package/dist/version-checker.js.map +1 -1
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +21 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher.d.ts +6 -3
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +49 -10
- package/dist/watcher.js.map +1 -1
- package/package.json +57 -7
- package/templates/cloudflare/worker.ts.template +381 -0
- package/templates/cloudflare/wrangler.toml.template +9 -0
- package/dist/base.d.ts +0 -58
- package/dist/base.d.ts.map +0 -1
- package/dist/base.js +0 -92
- package/dist/base.js.map +0 -1
- package/dist/dependency-manager.d.ts +0 -49
- package/dist/dependency-manager.d.ts.map +0 -1
- package/dist/dependency-manager.js +0 -165
- package/dist/dependency-manager.js.map +0 -1
- package/dist/registry-manager.d.ts +0 -76
- package/dist/registry-manager.d.ts.map +0 -1
- package/dist/registry-manager.js +0 -220
- package/dist/registry-manager.js.map +0 -1
- package/dist/schema-extractor.d.ts +0 -110
- package/dist/schema-extractor.d.ts.map +0 -1
- package/dist/schema-extractor.js +0 -727
- package/dist/schema-extractor.js.map +0 -1
- package/dist/test-marketplace-sources.d.ts +0 -5
- package/dist/test-marketplace-sources.d.ts.map +0 -1
- package/dist/test-marketplace-sources.js +0 -53
- package/dist/test-marketplace-sources.js.map +0 -1
- package/dist/types.d.ts +0 -109
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photon Test Runner
|
|
3
|
+
*
|
|
4
|
+
* Discovers and runs test* methods in photons
|
|
5
|
+
* Supports multiple test modes:
|
|
6
|
+
* - direct: Call methods directly on instance (unit tests)
|
|
7
|
+
* - cli: Call methods via CLI subprocess (integration tests)
|
|
8
|
+
* - mcp: Call methods via MCP protocol (integration tests)
|
|
9
|
+
*
|
|
10
|
+
* Usage: photon test [photon] [testName] [--mode direct|cli|all]
|
|
11
|
+
*/
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { PhotonLoader } from './loader.js';
|
|
17
|
+
import { listPhotonMCPs, resolvePhotonPath } from './path-resolver.js';
|
|
18
|
+
import { logger } from './shared/logger.js';
|
|
19
|
+
import { SchemaExtractor } from '@portel/photon-core';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = path.dirname(__filename);
|
|
23
|
+
// Get the path to the CLI binary (either local dev or installed)
|
|
24
|
+
const CLI_PATH = path.resolve(__dirname, 'cli.js');
|
|
25
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
// ISSUE URL GENERATOR
|
|
27
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
const ISSUE_REPO = 'https://github.com/anthropics/photon';
|
|
29
|
+
/**
|
|
30
|
+
* Generate a pre-filled GitHub issue URL for a failed test
|
|
31
|
+
*/
|
|
32
|
+
function generateIssueUrl(result, workingDir) {
|
|
33
|
+
const title = encodeURIComponent(`[Test Failure] ${result.photon}.${result.test} (${result.mode} mode)`);
|
|
34
|
+
const body = encodeURIComponent(`## Test Failure Report
|
|
35
|
+
|
|
36
|
+
**Photon:** \`${result.photon}\`
|
|
37
|
+
**Test:** \`${result.test}\`
|
|
38
|
+
**Mode:** ${result.mode}
|
|
39
|
+
**Duration:** ${result.duration}ms
|
|
40
|
+
|
|
41
|
+
### Error
|
|
42
|
+
\`\`\`
|
|
43
|
+
${result.error || 'No error message'}
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
### Environment
|
|
47
|
+
- Working Directory: \`${workingDir}\`
|
|
48
|
+
- Node Version: \`${process.version}\`
|
|
49
|
+
- Platform: \`${process.platform}\`
|
|
50
|
+
|
|
51
|
+
### Steps to Reproduce
|
|
52
|
+
\`\`\`bash
|
|
53
|
+
photon test ${result.photon} ${result.test} --mode ${result.mode}
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
### Additional Context
|
|
57
|
+
<!-- Add any additional context about the problem here -->
|
|
58
|
+
`);
|
|
59
|
+
return `${ISSUE_REPO}/issues/new?title=${title}&body=${body}&labels=bug,test-failure`;
|
|
60
|
+
}
|
|
61
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
62
|
+
// DIRECT TEST EXECUTION
|
|
63
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
64
|
+
/**
|
|
65
|
+
* Extract test methods from a photon instance
|
|
66
|
+
* Excludes testBeforeAll and testAfterAll (lifecycle hooks)
|
|
67
|
+
*/
|
|
68
|
+
function getTestMethods(instance) {
|
|
69
|
+
const methods = [];
|
|
70
|
+
const proto = Object.getPrototypeOf(instance);
|
|
71
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
72
|
+
if (name.startsWith('test') &&
|
|
73
|
+
typeof instance[name] === 'function' &&
|
|
74
|
+
name !== 'constructor' &&
|
|
75
|
+
name !== 'testBeforeAll' &&
|
|
76
|
+
name !== 'testAfterAll') {
|
|
77
|
+
methods.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return methods.sort();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if photon has lifecycle hooks
|
|
84
|
+
*/
|
|
85
|
+
function hasLifecycleHook(instance, hookName) {
|
|
86
|
+
return typeof instance[hookName] === 'function';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run a single test method directly
|
|
90
|
+
*/
|
|
91
|
+
async function runDirectTest(instance, photonName, testName, workingDir) {
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
try {
|
|
94
|
+
const result = await instance[testName]();
|
|
95
|
+
const duration = Date.now() - start;
|
|
96
|
+
// Check result format
|
|
97
|
+
if (result && typeof result === 'object') {
|
|
98
|
+
// Handle skipped tests
|
|
99
|
+
if (result.skipped === true) {
|
|
100
|
+
return {
|
|
101
|
+
photon: photonName,
|
|
102
|
+
test: testName,
|
|
103
|
+
passed: true,
|
|
104
|
+
skipped: true,
|
|
105
|
+
duration,
|
|
106
|
+
message: result.reason || 'Skipped',
|
|
107
|
+
mode: 'direct',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (result.passed === false) {
|
|
111
|
+
const failResult = {
|
|
112
|
+
photon: photonName,
|
|
113
|
+
test: testName,
|
|
114
|
+
passed: false,
|
|
115
|
+
duration,
|
|
116
|
+
error: result.error || result.message || 'Test returned passed: false',
|
|
117
|
+
mode: 'direct',
|
|
118
|
+
};
|
|
119
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
120
|
+
return failResult;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
photon: photonName,
|
|
124
|
+
test: testName,
|
|
125
|
+
passed: true,
|
|
126
|
+
duration,
|
|
127
|
+
message: result.message,
|
|
128
|
+
mode: 'direct',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// If no explicit result, consider it passed
|
|
132
|
+
return {
|
|
133
|
+
photon: photonName,
|
|
134
|
+
test: testName,
|
|
135
|
+
passed: true,
|
|
136
|
+
duration,
|
|
137
|
+
mode: 'direct',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const duration = Date.now() - start;
|
|
142
|
+
const failResult = {
|
|
143
|
+
photon: photonName,
|
|
144
|
+
test: testName,
|
|
145
|
+
passed: false,
|
|
146
|
+
duration,
|
|
147
|
+
error: error.message || String(error),
|
|
148
|
+
mode: 'direct',
|
|
149
|
+
};
|
|
150
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
151
|
+
return failResult;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
155
|
+
// CLI INTERFACE TEST EXECUTION
|
|
156
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
157
|
+
/**
|
|
158
|
+
* Get public methods with their schemas for interface testing
|
|
159
|
+
*/
|
|
160
|
+
async function getPublicMethods(photonPath) {
|
|
161
|
+
try {
|
|
162
|
+
const extractor = new SchemaExtractor();
|
|
163
|
+
const schemas = await extractor.extractFromFile(photonPath);
|
|
164
|
+
return schemas.map((schema) => ({
|
|
165
|
+
name: schema.name,
|
|
166
|
+
params: schema.inputSchema?.properties
|
|
167
|
+
? Object.entries(schema.inputSchema.properties).map(([name, prop]) => ({
|
|
168
|
+
name,
|
|
169
|
+
type: prop.type || 'string',
|
|
170
|
+
required: schema.inputSchema?.required?.includes(name) || false,
|
|
171
|
+
example: prop.examples?.[0],
|
|
172
|
+
}))
|
|
173
|
+
: [],
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Build example params for a method from its schema
|
|
182
|
+
*/
|
|
183
|
+
function buildExampleParams(method) {
|
|
184
|
+
const params = {};
|
|
185
|
+
for (const param of method.params) {
|
|
186
|
+
if (param.example !== undefined) {
|
|
187
|
+
params[param.name] = param.example;
|
|
188
|
+
}
|
|
189
|
+
else if (param.required) {
|
|
190
|
+
// Generate default values based on type
|
|
191
|
+
switch (param.type) {
|
|
192
|
+
case 'string':
|
|
193
|
+
params[param.name] = 'test';
|
|
194
|
+
break;
|
|
195
|
+
case 'number':
|
|
196
|
+
case 'integer':
|
|
197
|
+
params[param.name] = 1;
|
|
198
|
+
break;
|
|
199
|
+
case 'boolean':
|
|
200
|
+
params[param.name] = true;
|
|
201
|
+
break;
|
|
202
|
+
case 'array':
|
|
203
|
+
params[param.name] = [];
|
|
204
|
+
break;
|
|
205
|
+
case 'object':
|
|
206
|
+
params[param.name] = {};
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return params;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Run a method via CLI subprocess
|
|
215
|
+
*/
|
|
216
|
+
async function runCliTest(photonName, methodName, params, workingDir) {
|
|
217
|
+
const start = Date.now();
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
// Build CLI arguments (use 'cli' command - the implicit run mode)
|
|
220
|
+
const args = ['cli', photonName, methodName, '--json', '--dir', workingDir];
|
|
221
|
+
// Add params as CLI flags
|
|
222
|
+
for (const [key, value] of Object.entries(params)) {
|
|
223
|
+
if (typeof value === 'object') {
|
|
224
|
+
args.push(`--${key}`, JSON.stringify(value));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
args.push(`--${key}`, String(value));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const proc = spawn('node', [CLI_PATH, ...args], {
|
|
231
|
+
cwd: workingDir,
|
|
232
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
233
|
+
timeout: 30000, // 30 second timeout
|
|
234
|
+
});
|
|
235
|
+
let stdout = '';
|
|
236
|
+
let stderr = '';
|
|
237
|
+
proc.stdout?.on('data', (data) => {
|
|
238
|
+
stdout += data.toString();
|
|
239
|
+
});
|
|
240
|
+
proc.stderr?.on('data', (data) => {
|
|
241
|
+
stderr += data.toString();
|
|
242
|
+
});
|
|
243
|
+
proc.on('close', (code) => {
|
|
244
|
+
const duration = Date.now() - start;
|
|
245
|
+
// Interface tests verify the transport layer works, not business logic.
|
|
246
|
+
// A method that returns an error still proves the CLI interface works.
|
|
247
|
+
// Check if we got any output (stdout or stderr) - indicates the CLI ran
|
|
248
|
+
const hasOutput = stdout.trim() || stderr.trim();
|
|
249
|
+
// Check for specific CLI infrastructure errors (not method errors)
|
|
250
|
+
const isInfraError = stderr.includes('Photon not found') ||
|
|
251
|
+
stderr.includes('command not found') ||
|
|
252
|
+
stderr.includes('Cannot find module') ||
|
|
253
|
+
stderr.includes('ENOENT');
|
|
254
|
+
if (hasOutput && !isInfraError) {
|
|
255
|
+
// Got a response - interface test passes
|
|
256
|
+
// Note: method may have returned an error, but CLI transport worked
|
|
257
|
+
resolve({
|
|
258
|
+
photon: photonName,
|
|
259
|
+
test: `cli:${methodName}`,
|
|
260
|
+
passed: true,
|
|
261
|
+
duration,
|
|
262
|
+
mode: 'cli',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// CLI infrastructure failed
|
|
267
|
+
const failResult = {
|
|
268
|
+
photon: photonName,
|
|
269
|
+
test: `cli:${methodName}`,
|
|
270
|
+
passed: false,
|
|
271
|
+
duration,
|
|
272
|
+
error: stderr || `CLI exited with code ${code} (no output)`,
|
|
273
|
+
mode: 'cli',
|
|
274
|
+
};
|
|
275
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
276
|
+
resolve(failResult);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
proc.on('error', (err) => {
|
|
280
|
+
const duration = Date.now() - start;
|
|
281
|
+
const failResult = {
|
|
282
|
+
photon: photonName,
|
|
283
|
+
test: `cli:${methodName}`,
|
|
284
|
+
passed: false,
|
|
285
|
+
duration,
|
|
286
|
+
error: `Failed to spawn CLI: ${err.message}`,
|
|
287
|
+
mode: 'cli',
|
|
288
|
+
};
|
|
289
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
290
|
+
resolve(failResult);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
295
|
+
// MCP INTERFACE TEST EXECUTION
|
|
296
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
297
|
+
/**
|
|
298
|
+
* Run a method via MCP protocol
|
|
299
|
+
* This starts a temporary MCP server and calls the method through it
|
|
300
|
+
*/
|
|
301
|
+
async function runMcpTest(photonPath, photonName, methodName, params, workingDir) {
|
|
302
|
+
const start = Date.now();
|
|
303
|
+
return new Promise((resolve) => {
|
|
304
|
+
// Start MCP server for this photon
|
|
305
|
+
const args = ['mcp', photonName, '--dir', workingDir];
|
|
306
|
+
const proc = spawn('node', [CLI_PATH, ...args], {
|
|
307
|
+
cwd: workingDir,
|
|
308
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
309
|
+
});
|
|
310
|
+
let initialized = false;
|
|
311
|
+
let responseReceived = false;
|
|
312
|
+
const requestId = 1;
|
|
313
|
+
// Send initialize request
|
|
314
|
+
const initRequest = {
|
|
315
|
+
jsonrpc: '2.0',
|
|
316
|
+
id: 0,
|
|
317
|
+
method: 'initialize',
|
|
318
|
+
params: {
|
|
319
|
+
protocolVersion: '2024-11-05',
|
|
320
|
+
capabilities: {},
|
|
321
|
+
clientInfo: { name: 'photon-test', version: '1.0.0' },
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
proc.stdin?.write(JSON.stringify(initRequest) + '\n');
|
|
325
|
+
proc.stdout?.on('data', (data) => {
|
|
326
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
try {
|
|
329
|
+
const response = JSON.parse(line);
|
|
330
|
+
if (response.id === 0 && !initialized) {
|
|
331
|
+
// Initialize response received, send tool call
|
|
332
|
+
initialized = true;
|
|
333
|
+
const toolRequest = {
|
|
334
|
+
jsonrpc: '2.0',
|
|
335
|
+
id: requestId,
|
|
336
|
+
method: 'tools/call',
|
|
337
|
+
params: {
|
|
338
|
+
name: methodName,
|
|
339
|
+
arguments: params,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
proc.stdin?.write(JSON.stringify(toolRequest) + '\n');
|
|
343
|
+
}
|
|
344
|
+
else if (response.id === requestId && !responseReceived) {
|
|
345
|
+
responseReceived = true;
|
|
346
|
+
const duration = Date.now() - start;
|
|
347
|
+
proc.kill();
|
|
348
|
+
// Interface tests verify the transport layer works, not business logic.
|
|
349
|
+
// A response (even an error response) proves the MCP protocol works.
|
|
350
|
+
// Only MCP-level errors (not method errors) should fail the test.
|
|
351
|
+
if (response.error && response.error.code && response.error.code < -32000) {
|
|
352
|
+
// JSON-RPC protocol error (not a method error)
|
|
353
|
+
const failResult = {
|
|
354
|
+
photon: photonName,
|
|
355
|
+
test: `mcp:${methodName}`,
|
|
356
|
+
passed: false,
|
|
357
|
+
duration,
|
|
358
|
+
error: response.error.message || JSON.stringify(response.error),
|
|
359
|
+
mode: 'mcp',
|
|
360
|
+
};
|
|
361
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
362
|
+
resolve(failResult);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Got a valid MCP response - interface test passes
|
|
366
|
+
// Note: method may have returned an error, but MCP transport worked
|
|
367
|
+
resolve({
|
|
368
|
+
photon: photonName,
|
|
369
|
+
test: `mcp:${methodName}`,
|
|
370
|
+
passed: true,
|
|
371
|
+
duration,
|
|
372
|
+
mode: 'mcp',
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// Ignore non-JSON lines
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// Timeout after 30 seconds
|
|
383
|
+
setTimeout(() => {
|
|
384
|
+
if (!responseReceived) {
|
|
385
|
+
proc.kill();
|
|
386
|
+
const duration = Date.now() - start;
|
|
387
|
+
const failResult = {
|
|
388
|
+
photon: photonName,
|
|
389
|
+
test: `mcp:${methodName}`,
|
|
390
|
+
passed: false,
|
|
391
|
+
duration,
|
|
392
|
+
error: 'MCP request timed out after 30 seconds',
|
|
393
|
+
mode: 'mcp',
|
|
394
|
+
};
|
|
395
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
396
|
+
resolve(failResult);
|
|
397
|
+
}
|
|
398
|
+
}, 30000);
|
|
399
|
+
proc.on('error', (err) => {
|
|
400
|
+
const duration = Date.now() - start;
|
|
401
|
+
const failResult = {
|
|
402
|
+
photon: photonName,
|
|
403
|
+
test: `mcp:${methodName}`,
|
|
404
|
+
passed: false,
|
|
405
|
+
duration,
|
|
406
|
+
error: `Failed to start MCP server: ${err.message}`,
|
|
407
|
+
mode: 'mcp',
|
|
408
|
+
};
|
|
409
|
+
failResult.issueUrl = generateIssueUrl(failResult, workingDir);
|
|
410
|
+
resolve(failResult);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
415
|
+
// TEST ORCHESTRATION
|
|
416
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
417
|
+
/**
|
|
418
|
+
* Run tests for a single photon
|
|
419
|
+
*/
|
|
420
|
+
async function runPhotonTests(photonPath, photonName, workingDir, mode, specificTest) {
|
|
421
|
+
const results = [];
|
|
422
|
+
const loader = new PhotonLoader(false);
|
|
423
|
+
try {
|
|
424
|
+
const photon = await loader.loadFile(photonPath);
|
|
425
|
+
const instance = photon.instance;
|
|
426
|
+
if (!instance) {
|
|
427
|
+
return [
|
|
428
|
+
{
|
|
429
|
+
photon: photonName,
|
|
430
|
+
test: '*',
|
|
431
|
+
passed: false,
|
|
432
|
+
duration: 0,
|
|
433
|
+
error: 'Failed to load photon instance',
|
|
434
|
+
mode: 'direct',
|
|
435
|
+
},
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
439
|
+
// DIRECT TESTS (test* methods)
|
|
440
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
441
|
+
if (mode === 'direct' || mode === 'all') {
|
|
442
|
+
const testMethods = getTestMethods(instance);
|
|
443
|
+
if (testMethods.length > 0) {
|
|
444
|
+
// Filter to specific test if requested
|
|
445
|
+
const testsToRun = specificTest
|
|
446
|
+
? testMethods.filter((t) => t === specificTest || t === `test${specificTest}`)
|
|
447
|
+
: testMethods;
|
|
448
|
+
if (specificTest && testsToRun.length === 0 && mode === 'direct') {
|
|
449
|
+
return [
|
|
450
|
+
{
|
|
451
|
+
photon: photonName,
|
|
452
|
+
test: specificTest,
|
|
453
|
+
passed: false,
|
|
454
|
+
duration: 0,
|
|
455
|
+
error: `Test not found: ${specificTest}`,
|
|
456
|
+
mode: 'direct',
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
// Run testBeforeAll if it exists
|
|
461
|
+
if (hasLifecycleHook(instance, 'testBeforeAll')) {
|
|
462
|
+
try {
|
|
463
|
+
await instance.testBeforeAll();
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
return [
|
|
467
|
+
{
|
|
468
|
+
photon: photonName,
|
|
469
|
+
test: 'beforeAll',
|
|
470
|
+
passed: false,
|
|
471
|
+
duration: 0,
|
|
472
|
+
error: `Setup failed: ${error.message}`,
|
|
473
|
+
mode: 'direct',
|
|
474
|
+
},
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Run direct tests
|
|
479
|
+
for (const testName of testsToRun) {
|
|
480
|
+
const result = await runDirectTest(instance, photonName, testName, workingDir);
|
|
481
|
+
results.push(result);
|
|
482
|
+
}
|
|
483
|
+
// Run testAfterAll if it exists
|
|
484
|
+
if (hasLifecycleHook(instance, 'testAfterAll')) {
|
|
485
|
+
try {
|
|
486
|
+
await instance.testAfterAll();
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
results.push({
|
|
490
|
+
photon: photonName,
|
|
491
|
+
test: 'afterAll',
|
|
492
|
+
passed: false,
|
|
493
|
+
duration: 0,
|
|
494
|
+
error: `Teardown failed: ${error.message}`,
|
|
495
|
+
mode: 'direct',
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
502
|
+
// CLI INTERFACE TESTS
|
|
503
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
504
|
+
if (mode === 'cli' || mode === 'all') {
|
|
505
|
+
const methods = await getPublicMethods(photonPath);
|
|
506
|
+
for (const method of methods) {
|
|
507
|
+
// Skip test methods and lifecycle hooks in interface tests
|
|
508
|
+
if (method.name.startsWith('test') || method.name.startsWith('on')) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
// Skip if specific test requested and doesn't match
|
|
512
|
+
if (specificTest && !`cli:${method.name}`.includes(specificTest)) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const params = buildExampleParams(method);
|
|
516
|
+
const result = await runCliTest(photonName, method.name, params, workingDir);
|
|
517
|
+
results.push(result);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
521
|
+
// MCP INTERFACE TESTS
|
|
522
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
523
|
+
if (mode === 'mcp' || mode === 'all') {
|
|
524
|
+
const methods = await getPublicMethods(photonPath);
|
|
525
|
+
for (const method of methods) {
|
|
526
|
+
// Skip test methods and lifecycle hooks in interface tests
|
|
527
|
+
if (method.name.startsWith('test') || method.name.startsWith('on')) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
// Skip if specific test requested and doesn't match
|
|
531
|
+
if (specificTest && !`mcp:${method.name}`.includes(specificTest)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const params = buildExampleParams(method);
|
|
535
|
+
const result = await runMcpTest(photonPath, photonName, method.name, params, workingDir);
|
|
536
|
+
results.push(result);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return results;
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
return [
|
|
543
|
+
{
|
|
544
|
+
photon: photonName,
|
|
545
|
+
test: '*',
|
|
546
|
+
passed: false,
|
|
547
|
+
duration: 0,
|
|
548
|
+
error: `Failed to load photon: ${error.message}`,
|
|
549
|
+
mode: 'direct',
|
|
550
|
+
},
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
555
|
+
// OUTPUT FORMATTING
|
|
556
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
557
|
+
/**
|
|
558
|
+
* Print a single test result
|
|
559
|
+
*/
|
|
560
|
+
function printTestResult(result) {
|
|
561
|
+
let icon;
|
|
562
|
+
if (result.skipped) {
|
|
563
|
+
icon = chalk.yellow('○');
|
|
564
|
+
}
|
|
565
|
+
else if (result.passed) {
|
|
566
|
+
icon = chalk.green('✓');
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
icon = chalk.red('✗');
|
|
570
|
+
}
|
|
571
|
+
// Format test name based on mode
|
|
572
|
+
let displayName;
|
|
573
|
+
if (result.test.startsWith('cli:') || result.test.startsWith('mcp:')) {
|
|
574
|
+
displayName = result.test;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
// Strip 'test' prefix and lowercase first char for direct tests
|
|
578
|
+
const stripped = result.test.replace(/^test/, '');
|
|
579
|
+
displayName = stripped.charAt(0).toLowerCase() + stripped.slice(1);
|
|
580
|
+
}
|
|
581
|
+
const modeTag = chalk.gray(`[${result.mode}]`);
|
|
582
|
+
const name = chalk.gray(`${result.photon}.`) + displayName;
|
|
583
|
+
const time = chalk.gray(`${result.duration}ms`);
|
|
584
|
+
if (result.skipped) {
|
|
585
|
+
console.log(` ${icon} ${modeTag} ${name} ${chalk.yellow('skipped')} ${time}`);
|
|
586
|
+
if (result.message) {
|
|
587
|
+
console.log(chalk.yellow(` ${result.message}`));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
console.log(` ${icon} ${modeTag} ${name} ${time}`);
|
|
592
|
+
if (!result.passed && result.error) {
|
|
593
|
+
console.log(chalk.red(` ${result.error}`));
|
|
594
|
+
if (result.issueUrl) {
|
|
595
|
+
console.log(chalk.gray(` File issue: ${result.issueUrl.substring(0, 80)}...`));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Print test summary
|
|
602
|
+
*/
|
|
603
|
+
function printSummary(summary) {
|
|
604
|
+
console.log('');
|
|
605
|
+
console.log(chalk.bold('─'.repeat(60)));
|
|
606
|
+
console.log('');
|
|
607
|
+
const skippedInfo = summary.skipped > 0 ? chalk.yellow(` (${summary.skipped} skipped)`) : '';
|
|
608
|
+
const modeInfo = chalk.gray(` [mode: ${summary.mode}]`);
|
|
609
|
+
if (summary.failed === 0) {
|
|
610
|
+
console.log(chalk.green.bold(`✓ All ${summary.passed} tests passed`) +
|
|
611
|
+
skippedInfo +
|
|
612
|
+
chalk.gray(` (${summary.duration}ms)`) +
|
|
613
|
+
modeInfo);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
console.log(chalk.red.bold(`✗ ${summary.failed} of ${summary.total} tests failed`) +
|
|
617
|
+
skippedInfo +
|
|
618
|
+
chalk.gray(` (${summary.duration}ms)`) +
|
|
619
|
+
modeInfo);
|
|
620
|
+
// List failed tests
|
|
621
|
+
console.log('');
|
|
622
|
+
console.log(chalk.red('Failed tests:'));
|
|
623
|
+
for (const result of summary.results.filter((r) => !r.passed && !r.skipped)) {
|
|
624
|
+
console.log(chalk.red(` • [${result.mode}] ${result.photon}.${result.test}`));
|
|
625
|
+
if (result.error) {
|
|
626
|
+
console.log(chalk.gray(` ${result.error}`));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Show issue filing hint
|
|
630
|
+
const interfaceFailures = summary.results.filter((r) => !r.passed && !r.skipped && (r.mode === 'cli' || r.mode === 'mcp'));
|
|
631
|
+
if (interfaceFailures.length > 0) {
|
|
632
|
+
console.log('');
|
|
633
|
+
console.log(chalk.yellow('Tip: Interface test failures may indicate MCP protocol issues.'));
|
|
634
|
+
console.log(chalk.yellow(' Run with --json to get issue URLs for bug reports.'));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
console.log('');
|
|
638
|
+
}
|
|
639
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
640
|
+
// PUBLIC API
|
|
641
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
642
|
+
/**
|
|
643
|
+
* Main test runner
|
|
644
|
+
*/
|
|
645
|
+
export async function runTests(workingDir, photonName, testName, options = {}) {
|
|
646
|
+
const startTime = Date.now();
|
|
647
|
+
const results = [];
|
|
648
|
+
const mode = options.mode || 'direct';
|
|
649
|
+
if (!options.json) {
|
|
650
|
+
console.log('');
|
|
651
|
+
console.log(chalk.bold('⚡ Photon Test Runner'));
|
|
652
|
+
console.log(chalk.gray(` ${workingDir}`));
|
|
653
|
+
console.log(chalk.gray(` Mode: ${mode}`));
|
|
654
|
+
console.log('');
|
|
655
|
+
}
|
|
656
|
+
if (photonName) {
|
|
657
|
+
// Run tests for specific photon
|
|
658
|
+
const photonPath = await resolvePhotonPath(photonName, workingDir);
|
|
659
|
+
if (!photonPath) {
|
|
660
|
+
logger.error(`Photon not found: ${photonName}`);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
if (!options.json) {
|
|
664
|
+
console.log(chalk.bold(photonName));
|
|
665
|
+
}
|
|
666
|
+
const photonResults = await runPhotonTests(photonPath, photonName, workingDir, mode, testName);
|
|
667
|
+
results.push(...photonResults);
|
|
668
|
+
// Print progress for each result
|
|
669
|
+
if (!options.json) {
|
|
670
|
+
for (const result of photonResults) {
|
|
671
|
+
printTestResult(result);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
// Run tests for all photons
|
|
677
|
+
const photons = await listPhotonMCPs(workingDir);
|
|
678
|
+
if (photons.length === 0) {
|
|
679
|
+
if (!options.json) {
|
|
680
|
+
console.log(chalk.yellow('No photons found in working directory'));
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
total: 0,
|
|
684
|
+
passed: 0,
|
|
685
|
+
failed: 0,
|
|
686
|
+
skipped: 0,
|
|
687
|
+
duration: 0,
|
|
688
|
+
results: [],
|
|
689
|
+
mode,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
for (const photon of photons) {
|
|
693
|
+
const photonPath = path.join(workingDir, `${photon}.photon.ts`);
|
|
694
|
+
if (!existsSync(photonPath)) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
// Check if photon has any test methods before printing header (for direct mode)
|
|
698
|
+
if (mode === 'direct') {
|
|
699
|
+
const loader = new PhotonLoader(false);
|
|
700
|
+
try {
|
|
701
|
+
const loaded = await loader.loadFile(photonPath);
|
|
702
|
+
const testMethods = getTestMethods(loaded.instance);
|
|
703
|
+
if (testMethods.length === 0) {
|
|
704
|
+
continue; // Skip photons with no tests in direct mode
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (!options.json) {
|
|
712
|
+
console.log(chalk.bold(photon));
|
|
713
|
+
}
|
|
714
|
+
const photonResults = await runPhotonTests(photonPath, photon, workingDir, mode);
|
|
715
|
+
results.push(...photonResults);
|
|
716
|
+
// Print progress for each result
|
|
717
|
+
if (!options.json) {
|
|
718
|
+
for (const result of photonResults) {
|
|
719
|
+
printTestResult(result);
|
|
720
|
+
}
|
|
721
|
+
if (photonResults.length > 0) {
|
|
722
|
+
console.log('');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const duration = Date.now() - startTime;
|
|
728
|
+
const skipped = results.filter((r) => r.skipped).length;
|
|
729
|
+
const passed = results.filter((r) => r.passed && !r.skipped).length;
|
|
730
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
731
|
+
const summary = {
|
|
732
|
+
total: results.length,
|
|
733
|
+
passed,
|
|
734
|
+
failed,
|
|
735
|
+
skipped,
|
|
736
|
+
duration,
|
|
737
|
+
results,
|
|
738
|
+
mode,
|
|
739
|
+
};
|
|
740
|
+
if (options.json) {
|
|
741
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
printSummary(summary);
|
|
745
|
+
}
|
|
746
|
+
return summary;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Check if a photon has test methods (for UI display)
|
|
750
|
+
*/
|
|
751
|
+
export async function hasTests(photonPath) {
|
|
752
|
+
const loader = new PhotonLoader(false);
|
|
753
|
+
try {
|
|
754
|
+
const photon = await loader.loadFile(photonPath);
|
|
755
|
+
const testMethods = getTestMethods(photon.instance);
|
|
756
|
+
return testMethods.length > 0;
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
logger.debug('Failed to check tests:', { error });
|
|
760
|
+
return false; // file unloadable → no tests
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Get list of test methods for a photon (for UI display)
|
|
765
|
+
*/
|
|
766
|
+
export async function getTests(photonPath) {
|
|
767
|
+
const loader = new PhotonLoader(false);
|
|
768
|
+
try {
|
|
769
|
+
const photon = await loader.loadFile(photonPath);
|
|
770
|
+
return getTestMethods(photon.instance);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Get public methods for interface testing (for UI display)
|
|
778
|
+
*/
|
|
779
|
+
export async function getInterfaceTests(photonPath) {
|
|
780
|
+
const methods = await getPublicMethods(photonPath);
|
|
781
|
+
return methods
|
|
782
|
+
.filter((m) => !m.name.startsWith('test') && !m.name.startsWith('on'))
|
|
783
|
+
.map((m) => m.name);
|
|
784
|
+
}
|
|
785
|
+
//# sourceMappingURL=test-runner.js.map
|