@vybestack/llxprt-code-core 0.1.18-nightly.250811.b0db22c6 → 0.1.18-nightly.250812.12fa8ad2
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 +49 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/src/auth/anthropic-device-flow.d.ts +53 -0
- package/dist/src/auth/anthropic-device-flow.js +208 -0
- package/dist/src/auth/anthropic-device-flow.js.map +1 -0
- package/dist/src/auth/token-store.js +11 -7
- package/dist/src/auth/token-store.js.map +1 -1
- package/dist/src/config/config.d.ts +7 -1
- package/dist/src/config/config.js +42 -4
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/profileManager.d.ts +14 -4
- package/dist/src/config/profileManager.js +90 -11
- package/dist/src/config/profileManager.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +2 -1
- package/dist/src/core/contentGenerator.js +1 -0
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/providers/BaseProvider.d.ts +51 -0
- package/dist/src/providers/BaseProvider.js +136 -1
- package/dist/src/providers/BaseProvider.js.map +1 -1
- package/dist/src/providers/IProvider.d.ts +1 -0
- package/dist/src/providers/LoggingProviderWrapper.d.ts +1 -0
- package/dist/src/providers/LoggingProviderWrapper.js +3 -0
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderManager.d.ts +0 -1
- package/dist/src/providers/ProviderManager.js +23 -14
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +20 -6
- package/dist/src/providers/anthropic/AnthropicProvider.js +172 -26
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.d.ts +4 -0
- package/dist/src/providers/gemini/GeminiProvider.js +33 -1
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.d.ts +32 -0
- package/dist/src/providers/openai/OpenAIProvider.js +200 -13
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/RESPONSES_API_MODELS.d.ts +1 -1
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js +1 -0
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js.map +1 -1
- package/dist/src/providers/openai/syntheticToolResponses.d.ts +52 -0
- package/dist/src/providers/openai/syntheticToolResponses.js +129 -0
- package/dist/src/providers/openai/syntheticToolResponses.js.map +1 -0
- package/dist/src/settings/SettingsService.d.ts +32 -0
- package/dist/src/settings/SettingsService.js +204 -0
- package/dist/src/settings/SettingsService.js.map +1 -0
- package/dist/src/settings/settingsServiceInstance.d.ts +12 -0
- package/dist/src/settings/settingsServiceInstance.js +24 -0
- package/dist/src/settings/settingsServiceInstance.js.map +1 -0
- package/dist/src/settings/types.d.ts +141 -0
- package/dist/src/settings/types.js +5 -0
- package/dist/src/settings/types.js.map +1 -0
- package/package.json +4 -2
- package/dist/src/auth/auth-integration.spec.d.ts +0 -6
- package/dist/src/auth/auth-integration.spec.js +0 -384
- package/dist/src/auth/auth-integration.spec.js.map +0 -1
- package/dist/src/auth/precedence.test.d.ts +0 -6
- package/dist/src/auth/precedence.test.js +0 -374
- package/dist/src/auth/precedence.test.js.map +0 -1
- package/dist/src/auth/qwen-device-flow.spec.d.ts +0 -6
- package/dist/src/auth/qwen-device-flow.spec.js +0 -793
- package/dist/src/auth/qwen-device-flow.spec.js.map +0 -1
- package/dist/src/auth/token-store.spec.d.ts +0 -6
- package/dist/src/auth/token-store.spec.js +0 -405
- package/dist/src/auth/token-store.spec.js.map +0 -1
- package/dist/src/code_assist/converter.test.d.ts +0 -6
- package/dist/src/code_assist/converter.test.js +0 -279
- package/dist/src/code_assist/converter.test.js.map +0 -1
- package/dist/src/code_assist/oauth2.test.d.ts +0 -6
- package/dist/src/code_assist/oauth2.test.js +0 -370
- package/dist/src/code_assist/oauth2.test.js.map +0 -1
- package/dist/src/code_assist/server.test.d.ts +0 -6
- package/dist/src/code_assist/server.test.js +0 -134
- package/dist/src/code_assist/server.test.js.map +0 -1
- package/dist/src/code_assist/setup.test.d.ts +0 -6
- package/dist/src/code_assist/setup.test.js +0 -65
- package/dist/src/code_assist/setup.test.js.map +0 -1
- package/dist/src/config/config.alwaysAllow.test.d.ts +0 -6
- package/dist/src/config/config.alwaysAllow.test.js +0 -84
- package/dist/src/config/config.alwaysAllow.test.js.map +0 -1
- package/dist/src/config/config.ephemeral.test.d.ts +0 -6
- package/dist/src/config/config.ephemeral.test.js +0 -152
- package/dist/src/config/config.ephemeral.test.js.map +0 -1
- package/dist/src/config/config.test.d.ts +0 -6
- package/dist/src/config/config.test.js +0 -369
- package/dist/src/config/config.test.js.map +0 -1
- package/dist/src/config/endpoints.test.d.ts +0 -6
- package/dist/src/config/endpoints.test.js +0 -196
- package/dist/src/config/endpoints.test.js.map +0 -1
- package/dist/src/config/flashFallback.test.d.ts +0 -6
- package/dist/src/config/flashFallback.test.js +0 -91
- package/dist/src/config/flashFallback.test.js.map +0 -1
- package/dist/src/core/client.test.d.ts +0 -6
- package/dist/src/core/client.test.js +0 -1323
- package/dist/src/core/client.test.js.map +0 -1
- package/dist/src/core/contentGenerator.test.d.ts +0 -6
- package/dist/src/core/contentGenerator.test.js +0 -103
- package/dist/src/core/contentGenerator.test.js.map +0 -1
- package/dist/src/core/coreToolScheduler.test.d.ts +0 -6
- package/dist/src/core/coreToolScheduler.test.js +0 -637
- package/dist/src/core/coreToolScheduler.test.js.map +0 -1
- package/dist/src/core/geminiChat.test.d.ts +0 -6
- package/dist/src/core/geminiChat.test.js +0 -425
- package/dist/src/core/geminiChat.test.js.map +0 -1
- package/dist/src/core/googleGenAIWrapper.test.d.ts +0 -6
- package/dist/src/core/googleGenAIWrapper.test.js +0 -104
- package/dist/src/core/googleGenAIWrapper.test.js.map +0 -1
- package/dist/src/core/logger.test.d.ts +0 -6
- package/dist/src/core/logger.test.js +0 -467
- package/dist/src/core/logger.test.js.map +0 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.d.ts +0 -6
- package/dist/src/core/nonInteractiveToolExecutor.test.js +0 -165
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +0 -1
- package/dist/src/core/prompts-async.test.d.ts +0 -6
- package/dist/src/core/prompts-async.test.js +0 -115
- package/dist/src/core/prompts-async.test.js.map +0 -1
- package/dist/src/core/prompts.test.d.ts +0 -6
- package/dist/src/core/prompts.test.js +0 -68
- package/dist/src/core/prompts.test.js.map +0 -1
- package/dist/src/core/subagent.test.d.ts +0 -6
- package/dist/src/core/subagent.test.js +0 -510
- package/dist/src/core/subagent.test.js.map +0 -1
- package/dist/src/core/tokenLimits.test.d.ts +0 -6
- package/dist/src/core/tokenLimits.test.js +0 -66
- package/dist/src/core/tokenLimits.test.js.map +0 -1
- package/dist/src/core/turn.test.d.ts +0 -6
- package/dist/src/core/turn.test.js +0 -366
- package/dist/src/core/turn.test.js.map +0 -1
- package/dist/src/hooks/tool-render-suppression-hook.test.d.ts +0 -6
- package/dist/src/hooks/tool-render-suppression-hook.test.js +0 -59
- package/dist/src/hooks/tool-render-suppression-hook.test.js.map +0 -1
- package/dist/src/ide/ide-installer.test.d.ts +0 -6
- package/dist/src/ide/ide-installer.test.js +0 -55
- package/dist/src/ide/ide-installer.test.js.map +0 -1
- package/dist/src/ide/ideContext.test.d.ts +0 -6
- package/dist/src/ide/ideContext.test.js +0 -265
- package/dist/src/ide/ideContext.test.js.map +0 -1
- package/dist/src/index.test.d.ts +0 -6
- package/dist/src/index.test.js +0 -12
- package/dist/src/index.test.js.map +0 -1
- package/dist/src/integration-tests/oauth-integration.spec.d.ts +0 -6
- package/dist/src/integration-tests/oauth-integration.spec.js +0 -518
- package/dist/src/integration-tests/oauth-integration.spec.js.map +0 -1
- package/dist/src/integration-tests/oauth-simple-test.spec.d.ts +0 -1
- package/dist/src/integration-tests/oauth-simple-test.spec.js +0 -88
- package/dist/src/integration-tests/oauth-simple-test.spec.js.map +0 -1
- package/dist/src/integration-tests/todo-system.test.d.ts +0 -6
- package/dist/src/integration-tests/todo-system.test.js +0 -46
- package/dist/src/integration-tests/todo-system.test.js.map +0 -1
- package/dist/src/mcp/google-auth-provider.test.d.ts +0 -6
- package/dist/src/mcp/google-auth-provider.test.js +0 -54
- package/dist/src/mcp/google-auth-provider.test.js.map +0 -1
- package/dist/src/mcp/oauth-provider.test.d.ts +0 -6
- package/dist/src/mcp/oauth-provider.test.js +0 -602
- package/dist/src/mcp/oauth-provider.test.js.map +0 -1
- package/dist/src/mcp/oauth-token-storage.test.d.ts +0 -6
- package/dist/src/mcp/oauth-token-storage.test.js +0 -205
- package/dist/src/mcp/oauth-token-storage.test.js.map +0 -1
- package/dist/src/mcp/oauth-utils.test.d.ts +0 -6
- package/dist/src/mcp/oauth-utils.test.js +0 -144
- package/dist/src/mcp/oauth-utils.test.js.map +0 -1
- package/dist/src/parsers/TextToolCallParser.multibyte.test.d.ts +0 -1
- package/dist/src/parsers/TextToolCallParser.multibyte.test.js +0 -42
- package/dist/src/parsers/TextToolCallParser.multibyte.test.js.map +0 -1
- package/dist/src/parsers/TextToolCallParser.test.d.ts +0 -1
- package/dist/src/parsers/TextToolCallParser.test.js +0 -225
- package/dist/src/parsers/TextToolCallParser.test.js.map +0 -1
- package/dist/src/prompt-config/TemplateEngine.test.d.ts +0 -1
- package/dist/src/prompt-config/TemplateEngine.test.js +0 -494
- package/dist/src/prompt-config/TemplateEngine.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-cache.test.d.ts +0 -6
- package/dist/src/prompt-config/prompt-cache.test.js +0 -437
- package/dist/src/prompt-config/prompt-cache.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-installer.test.d.ts +0 -7
- package/dist/src/prompt-config/prompt-installer.test.js +0 -503
- package/dist/src/prompt-config/prompt-installer.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-loader.test.d.ts +0 -5
- package/dist/src/prompt-config/prompt-loader.test.js +0 -413
- package/dist/src/prompt-config/prompt-loader.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-resolver.test.d.ts +0 -1
- package/dist/src/prompt-config/prompt-resolver.test.js +0 -529
- package/dist/src/prompt-config/prompt-resolver.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-service.test.d.ts +0 -1
- package/dist/src/prompt-config/prompt-service.test.js +0 -811
- package/dist/src/prompt-config/prompt-service.test.js.map +0 -1
- package/dist/src/providers/BaseProvider.test.d.ts +0 -6
- package/dist/src/providers/BaseProvider.test.js +0 -472
- package/dist/src/providers/BaseProvider.test.js.map +0 -1
- package/dist/src/providers/ProviderManager.gemini-switch.test.d.ts +0 -6
- package/dist/src/providers/ProviderManager.gemini-switch.test.js +0 -57
- package/dist/src/providers/ProviderManager.gemini-switch.test.js.map +0 -1
- package/dist/src/providers/ProviderManager.test.d.ts +0 -6
- package/dist/src/providers/ProviderManager.test.js +0 -284
- package/dist/src/providers/ProviderManager.test.js.map +0 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.d.ts +0 -6
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js +0 -273
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js.map +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.d.ts +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.js +0 -48
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.js.map +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.test.d.ts +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.test.js +0 -487
- package/dist/src/providers/anthropic/AnthropicProvider.test.js.map +0 -1
- package/dist/src/providers/gemini/GeminiProvider.integration.test.d.ts +0 -6
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js +0 -126
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js.map +0 -1
- package/dist/src/providers/gemini/GeminiProvider.test.d.ts +0 -6
- package/dist/src/providers/gemini/GeminiProvider.test.js +0 -136
- package/dist/src/providers/gemini/GeminiProvider.test.js.map +0 -1
- package/dist/src/providers/integration/multi-provider.integration.test.d.ts +0 -6
- package/dist/src/providers/integration/multi-provider.integration.test.js +0 -308
- package/dist/src/providers/integration/multi-provider.integration.test.js.map +0 -1
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.d.ts +0 -1
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js +0 -97
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js.map +0 -1
- package/dist/src/providers/openai/ConversationCache.test.d.ts +0 -1
- package/dist/src/providers/openai/ConversationCache.test.js +0 -113
- package/dist/src/providers/openai/ConversationCache.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js +0 -189
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.integration.test.d.ts +0 -6
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js +0 -125
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responses.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js +0 -350
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js +0 -213
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js +0 -59
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.d.ts +0 -6
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js +0 -105
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.switch.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js +0 -256
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.test.d.ts +0 -16
- package/dist/src/providers/openai/OpenAIProvider.test.js +0 -621
- package/dist/src/providers/openai/OpenAIProvider.test.js.map +0 -1
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.d.ts +0 -1
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js +0 -210
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js.map +0 -1
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.d.ts +0 -1
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js +0 -65
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js +0 -129
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.test.js +0 -406
- package/dist/src/providers/openai/buildResponsesRequest.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js +0 -50
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js.map +0 -1
- package/dist/src/providers/openai/estimateRemoteTokens.test.d.ts +0 -1
- package/dist/src/providers/openai/estimateRemoteTokens.test.js +0 -125
- package/dist/src/providers/openai/estimateRemoteTokens.test.js.map +0 -1
- package/dist/src/providers/openai/openai-oauth.spec.d.ts +0 -16
- package/dist/src/providers/openai/openai-oauth.spec.js +0 -544
- package/dist/src/providers/openai/openai-oauth.spec.js.map +0 -1
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.d.ts +0 -1
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js +0 -192
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js.map +0 -1
- package/dist/src/providers/openai/parseResponsesStream.test.d.ts +0 -1
- package/dist/src/providers/openai/parseResponsesStream.test.js +0 -151
- package/dist/src/providers/openai/parseResponsesStream.test.js.map +0 -1
- package/dist/src/services/fileDiscoveryService.test.d.ts +0 -6
- package/dist/src/services/fileDiscoveryService.test.js +0 -143
- package/dist/src/services/fileDiscoveryService.test.js.map +0 -1
- package/dist/src/services/gitService.test.d.ts +0 -6
- package/dist/src/services/gitService.test.js +0 -209
- package/dist/src/services/gitService.test.js.map +0 -1
- package/dist/src/services/loopDetectionService.test.d.ts +0 -6
- package/dist/src/services/loopDetectionService.test.js +0 -484
- package/dist/src/services/loopDetectionService.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.multibyte.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.multibyte.test.js +0 -72
- package/dist/src/services/shellExecutionService.multibyte.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.test.js +0 -272
- package/dist/src/services/shellExecutionService.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.windows.multibyte.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.windows.multibyte.test.js +0 -98
- package/dist/src/services/shellExecutionService.windows.multibyte.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.windows.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.windows.test.js +0 -79
- package/dist/src/services/shellExecutionService.windows.test.js.map +0 -1
- package/dist/src/services/tool-call-tracker-service.test.d.ts +0 -6
- package/dist/src/services/tool-call-tracker-service.test.js +0 -99
- package/dist/src/services/tool-call-tracker-service.test.js.map +0 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +0 -6
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +0 -187
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +0 -1
- package/dist/src/telemetry/loggers.test.d.ts +0 -6
- package/dist/src/telemetry/loggers.test.js +0 -573
- package/dist/src/telemetry/loggers.test.js.map +0 -1
- package/dist/src/telemetry/metrics.test.d.ts +0 -6
- package/dist/src/telemetry/metrics.test.js +0 -212
- package/dist/src/telemetry/metrics.test.js.map +0 -1
- package/dist/src/telemetry/telemetry.test.d.ts +0 -6
- package/dist/src/telemetry/telemetry.test.js +0 -54
- package/dist/src/telemetry/telemetry.test.js.map +0 -1
- package/dist/src/telemetry/uiTelemetry.test.d.ts +0 -6
- package/dist/src/telemetry/uiTelemetry.test.js +0 -518
- package/dist/src/telemetry/uiTelemetry.test.js.map +0 -1
- package/dist/src/tools/ToolFormatter.test.d.ts +0 -16
- package/dist/src/tools/ToolFormatter.test.js +0 -349
- package/dist/src/tools/ToolFormatter.test.js.map +0 -1
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.d.ts +0 -1
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js +0 -241
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js.map +0 -1
- package/dist/src/tools/diffOptions.test.d.ts +0 -6
- package/dist/src/tools/diffOptions.test.js +0 -119
- package/dist/src/tools/diffOptions.test.js.map +0 -1
- package/dist/src/tools/edit.test.d.ts +0 -6
- package/dist/src/tools/edit.test.js +0 -689
- package/dist/src/tools/edit.test.js.map +0 -1
- package/dist/src/tools/glob.test.d.ts +0 -6
- package/dist/src/tools/glob.test.js +0 -332
- package/dist/src/tools/glob.test.js.map +0 -1
- package/dist/src/tools/grep.test.d.ts +0 -6
- package/dist/src/tools/grep.test.js +0 -272
- package/dist/src/tools/grep.test.js.map +0 -1
- package/dist/src/tools/ls.test.d.ts +0 -6
- package/dist/src/tools/ls.test.js +0 -357
- package/dist/src/tools/ls.test.js.map +0 -1
- package/dist/src/tools/mcp-client.test.d.ts +0 -6
- package/dist/src/tools/mcp-client.test.js +0 -617
- package/dist/src/tools/mcp-client.test.js.map +0 -1
- package/dist/src/tools/mcp-tool.test.d.ts +0 -6
- package/dist/src/tools/mcp-tool.test.js +0 -501
- package/dist/src/tools/mcp-tool.test.js.map +0 -1
- package/dist/src/tools/memoryTool.test.d.ts +0 -6
- package/dist/src/tools/memoryTool.test.js +0 -266
- package/dist/src/tools/memoryTool.test.js.map +0 -1
- package/dist/src/tools/modifiable-tool.test.d.ts +0 -6
- package/dist/src/tools/modifiable-tool.test.js +0 -193
- package/dist/src/tools/modifiable-tool.test.js.map +0 -1
- package/dist/src/tools/read-file.test.d.ts +0 -6
- package/dist/src/tools/read-file.test.js +0 -319
- package/dist/src/tools/read-file.test.js.map +0 -1
- package/dist/src/tools/read-many-files.test.d.ts +0 -6
- package/dist/src/tools/read-many-files.test.js +0 -644
- package/dist/src/tools/read-many-files.test.js.map +0 -1
- package/dist/src/tools/shell.multibyte.test.d.ts +0 -6
- package/dist/src/tools/shell.multibyte.test.js +0 -75
- package/dist/src/tools/shell.multibyte.test.js.map +0 -1
- package/dist/src/tools/shell.test.d.ts +0 -6
- package/dist/src/tools/shell.test.js +0 -367
- package/dist/src/tools/shell.test.js.map +0 -1
- package/dist/src/tools/todo-pause.spec.d.ts +0 -6
- package/dist/src/tools/todo-pause.spec.js +0 -287
- package/dist/src/tools/todo-pause.spec.js.map +0 -1
- package/dist/src/tools/todo-read.test.d.ts +0 -6
- package/dist/src/tools/todo-read.test.js +0 -162
- package/dist/src/tools/todo-read.test.js.map +0 -1
- package/dist/src/tools/todo-schemas.test.d.ts +0 -6
- package/dist/src/tools/todo-schemas.test.js +0 -341
- package/dist/src/tools/todo-schemas.test.js.map +0 -1
- package/dist/src/tools/todo-store.test.d.ts +0 -6
- package/dist/src/tools/todo-store.test.js +0 -169
- package/dist/src/tools/todo-store.test.js.map +0 -1
- package/dist/src/tools/todo-write.test.d.ts +0 -6
- package/dist/src/tools/todo-write.test.js +0 -226
- package/dist/src/tools/todo-write.test.js.map +0 -1
- package/dist/src/tools/tool-registry.test.d.ts +0 -6
- package/dist/src/tools/tool-registry.test.js +0 -468
- package/dist/src/tools/tool-registry.test.js.map +0 -1
- package/dist/src/tools/tools.test.d.ts +0 -6
- package/dist/src/tools/tools.test.js +0 -117
- package/dist/src/tools/tools.test.js.map +0 -1
- package/dist/src/tools/web-fetch.integration.test.d.ts +0 -6
- package/dist/src/tools/web-fetch.integration.test.js +0 -532
- package/dist/src/tools/web-fetch.integration.test.js.map +0 -1
- package/dist/src/tools/web-search.test.d.ts +0 -6
- package/dist/src/tools/web-search.test.js +0 -230
- package/dist/src/tools/web-search.test.js.map +0 -1
- package/dist/src/tools/write-file.test.d.ts +0 -6
- package/dist/src/tools/write-file.test.js +0 -465
- package/dist/src/tools/write-file.test.js.map +0 -1
- package/dist/src/utils/bfsFileSearch.test.d.ts +0 -6
- package/dist/src/utils/bfsFileSearch.test.js +0 -191
- package/dist/src/utils/bfsFileSearch.test.js.map +0 -1
- package/dist/src/utils/editCorrector.test.d.ts +0 -6
- package/dist/src/utils/editCorrector.test.js +0 -564
- package/dist/src/utils/editCorrector.test.js.map +0 -1
- package/dist/src/utils/editor.test.d.ts +0 -6
- package/dist/src/utils/editor.test.js +0 -445
- package/dist/src/utils/editor.test.js.map +0 -1
- package/dist/src/utils/environmentContext.test.d.ts +0 -6
- package/dist/src/utils/environmentContext.test.js +0 -139
- package/dist/src/utils/environmentContext.test.js.map +0 -1
- package/dist/src/utils/errorReporting.test.d.ts +0 -6
- package/dist/src/utils/errorReporting.test.js +0 -130
- package/dist/src/utils/errorReporting.test.js.map +0 -1
- package/dist/src/utils/fileUtils.test.d.ts +0 -6
- package/dist/src/utils/fileUtils.test.js +0 -363
- package/dist/src/utils/fileUtils.test.js.map +0 -1
- package/dist/src/utils/filesearch/crawlCache.test.d.ts +0 -6
- package/dist/src/utils/filesearch/crawlCache.test.js +0 -103
- package/dist/src/utils/filesearch/crawlCache.test.js.map +0 -1
- package/dist/src/utils/filesearch/fileSearch.test.d.ts +0 -6
- package/dist/src/utils/filesearch/fileSearch.test.js +0 -654
- package/dist/src/utils/filesearch/fileSearch.test.js.map +0 -1
- package/dist/src/utils/filesearch/ignore.test.d.ts +0 -6
- package/dist/src/utils/filesearch/ignore.test.js +0 -57
- package/dist/src/utils/filesearch/ignore.test.js.map +0 -1
- package/dist/src/utils/filesearch/result-cache.test.d.ts +0 -6
- package/dist/src/utils/filesearch/result-cache.test.js +0 -47
- package/dist/src/utils/filesearch/result-cache.test.js.map +0 -1
- package/dist/src/utils/flashFallback.integration.test.d.ts +0 -6
- package/dist/src/utils/flashFallback.integration.test.js +0 -120
- package/dist/src/utils/flashFallback.integration.test.js.map +0 -1
- package/dist/src/utils/generateContentResponseUtilities.test.d.ts +0 -6
- package/dist/src/utils/generateContentResponseUtilities.test.js +0 -273
- package/dist/src/utils/generateContentResponseUtilities.test.js.map +0 -1
- package/dist/src/utils/getFolderStructure.test.d.ts +0 -6
- package/dist/src/utils/getFolderStructure.test.js +0 -282
- package/dist/src/utils/getFolderStructure.test.js.map +0 -1
- package/dist/src/utils/gitIgnoreParser.test.d.ts +0 -6
- package/dist/src/utils/gitIgnoreParser.test.js +0 -154
- package/dist/src/utils/gitIgnoreParser.test.js.map +0 -1
- package/dist/src/utils/memoryDiscovery.test.d.ts +0 -6
- package/dist/src/utils/memoryDiscovery.test.js +0 -181
- package/dist/src/utils/memoryDiscovery.test.js.map +0 -1
- package/dist/src/utils/memoryImportProcessor.test.d.ts +0 -6
- package/dist/src/utils/memoryImportProcessor.test.js +0 -715
- package/dist/src/utils/memoryImportProcessor.test.js.map +0 -1
- package/dist/src/utils/nextSpeakerChecker.test.d.ts +0 -6
- package/dist/src/utils/nextSpeakerChecker.test.js +0 -172
- package/dist/src/utils/nextSpeakerChecker.test.js.map +0 -1
- package/dist/src/utils/partUtils.test.d.ts +0 -6
- package/dist/src/utils/partUtils.test.js +0 -130
- package/dist/src/utils/partUtils.test.js.map +0 -1
- package/dist/src/utils/paths.test.d.ts +0 -6
- package/dist/src/utils/paths.test.js +0 -153
- package/dist/src/utils/paths.test.js.map +0 -1
- package/dist/src/utils/retry.test.d.ts +0 -6
- package/dist/src/utils/retry.test.js +0 -322
- package/dist/src/utils/retry.test.js.map +0 -1
- package/dist/src/utils/safeJsonStringify.test.d.ts +0 -6
- package/dist/src/utils/safeJsonStringify.test.js +0 -61
- package/dist/src/utils/safeJsonStringify.test.js.map +0 -1
- package/dist/src/utils/sanitization.test.d.ts +0 -6
- package/dist/src/utils/sanitization.test.js +0 -81
- package/dist/src/utils/sanitization.test.js.map +0 -1
- package/dist/src/utils/schemaValidator.test.d.ts +0 -6
- package/dist/src/utils/schemaValidator.test.js +0 -146
- package/dist/src/utils/schemaValidator.test.js.map +0 -1
- package/dist/src/utils/secure-browser-launcher.test.d.ts +0 -6
- package/dist/src/utils/secure-browser-launcher.test.js +0 -149
- package/dist/src/utils/secure-browser-launcher.test.js.map +0 -1
- package/dist/src/utils/shell-utils.shellReplacement.test.d.ts +0 -6
- package/dist/src/utils/shell-utils.shellReplacement.test.js +0 -149
- package/dist/src/utils/shell-utils.shellReplacement.test.js.map +0 -1
- package/dist/src/utils/shell-utils.test.d.ts +0 -6
- package/dist/src/utils/shell-utils.test.js +0 -200
- package/dist/src/utils/shell-utils.test.js.map +0 -1
- package/dist/src/utils/summarizer.test.d.ts +0 -6
- package/dist/src/utils/summarizer.test.js +0 -131
- package/dist/src/utils/summarizer.test.js.map +0 -1
- package/dist/src/utils/systemEncoding.test.d.ts +0 -6
- package/dist/src/utils/systemEncoding.test.js +0 -368
- package/dist/src/utils/systemEncoding.test.js.map +0 -1
- package/dist/src/utils/toolOutputLimiter.test.d.ts +0 -6
- package/dist/src/utils/toolOutputLimiter.test.js +0 -164
- package/dist/src/utils/toolOutputLimiter.test.js.map +0 -1
- package/dist/src/utils/unicodeUtils.test.d.ts +0 -6
- package/dist/src/utils/unicodeUtils.test.js +0 -120
- package/dist/src/utils/unicodeUtils.test.js.map +0 -1
- package/dist/src/utils/user_account.test.d.ts +0 -6
- package/dist/src/utils/user_account.test.js +0 -153
- package/dist/src/utils/user_account.test.js.map +0 -1
- package/dist/src/utils/user_id.test.d.ts +0 -6
- package/dist/src/utils/user_id.test.js +0 -21
- package/dist/src/utils/user_id.test.js.map +0 -1
- package/dist/src/utils/workspaceContext.test.d.ts +0 -6
- package/dist/src/utils/workspaceContext.test.js +0 -209
- package/dist/src/utils/workspaceContext.test.js.map +0 -1
|
@@ -1,793 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Vybestack LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { createServer } from 'http';
|
|
8
|
-
import { randomBytes, createHash } from 'crypto';
|
|
9
|
-
import { QwenDeviceFlow } from './qwen-device-flow.js';
|
|
10
|
-
describe('QwenDeviceFlow - Behavioral Tests', () => {
|
|
11
|
-
let testServer;
|
|
12
|
-
let serverPort;
|
|
13
|
-
let deviceFlow;
|
|
14
|
-
let config;
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
// Start test HTTP server
|
|
17
|
-
testServer = createServer();
|
|
18
|
-
await new Promise((resolve) => {
|
|
19
|
-
testServer.listen(0, () => {
|
|
20
|
-
serverPort = testServer.address().port;
|
|
21
|
-
resolve();
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
// Configure device flow with test server endpoints
|
|
25
|
-
config = {
|
|
26
|
-
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
|
|
27
|
-
authorizationEndpoint: `http://localhost:${serverPort}/api/v1/oauth2/device/code`,
|
|
28
|
-
tokenEndpoint: `http://localhost:${serverPort}/api/v1/oauth2/token`,
|
|
29
|
-
scopes: ['read', 'write'],
|
|
30
|
-
};
|
|
31
|
-
deviceFlow = new QwenDeviceFlow(config);
|
|
32
|
-
});
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
if (testServer) {
|
|
35
|
-
await new Promise((resolve) => {
|
|
36
|
-
testServer.close(() => resolve());
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
describe('Device Flow Initiation', () => {
|
|
41
|
-
/**
|
|
42
|
-
* @requirement REQ-002.1
|
|
43
|
-
* @scenario Initiate device authorization
|
|
44
|
-
* @given Valid Qwen OAuth config
|
|
45
|
-
* @when initiateDeviceFlow() is called
|
|
46
|
-
* @then Returns device code and verification URI
|
|
47
|
-
* @and Response includes user code for display
|
|
48
|
-
*/
|
|
49
|
-
it('should initiate device flow and return required fields', async () => {
|
|
50
|
-
const mockResponse = {
|
|
51
|
-
device_code: 'GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS',
|
|
52
|
-
user_code: 'WDJB-MJHT',
|
|
53
|
-
verification_uri: 'https://chat.qwen.ai/activate',
|
|
54
|
-
verification_uri_complete: 'https://chat.qwen.ai/activate?user_code=WDJB-MJHT',
|
|
55
|
-
expires_in: 900, // 15 minutes
|
|
56
|
-
interval: 5, // 5 seconds
|
|
57
|
-
};
|
|
58
|
-
testServer.removeAllListeners('request');
|
|
59
|
-
testServer.on('request', (req, res) => {
|
|
60
|
-
expect(req.method).toBe('POST');
|
|
61
|
-
expect(req.url).toBe('/api/v1/oauth2/device/code');
|
|
62
|
-
let body = '';
|
|
63
|
-
req.on('data', (chunk) => {
|
|
64
|
-
body += chunk;
|
|
65
|
-
});
|
|
66
|
-
req.on('end', () => {
|
|
67
|
-
const params = new URLSearchParams(body);
|
|
68
|
-
expect(params.get('client_id')).toBe('f0304373b74a44d2b584a3fb70ca9e56');
|
|
69
|
-
expect(params.get('scope')).toBe('read write');
|
|
70
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
71
|
-
res.end(JSON.stringify(mockResponse));
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
const result = await deviceFlow.initiateDeviceFlow();
|
|
75
|
-
expect(result).toMatchObject(mockResponse);
|
|
76
|
-
});
|
|
77
|
-
/**
|
|
78
|
-
* @requirement REQ-002.3
|
|
79
|
-
* @scenario Correct authorization endpoint
|
|
80
|
-
* @given Qwen device flow instance
|
|
81
|
-
* @when initiateDeviceFlow() makes request
|
|
82
|
-
* @then Uses https://chat.qwen.ai/api/v1/oauth2/device/code
|
|
83
|
-
*/
|
|
84
|
-
it('should use correct Qwen authorization endpoint', async () => {
|
|
85
|
-
const realConfig = {
|
|
86
|
-
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
|
|
87
|
-
authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
|
|
88
|
-
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
|
|
89
|
-
scopes: ['read'],
|
|
90
|
-
};
|
|
91
|
-
const realDeviceFlow = new QwenDeviceFlow(realConfig);
|
|
92
|
-
// This will fail with a real network request, which is expected
|
|
93
|
-
await expect(realDeviceFlow.initiateDeviceFlow()).rejects.toThrow('HTTP 400: Bad Request');
|
|
94
|
-
// Verify the configuration contains the correct endpoint
|
|
95
|
-
expect(realConfig.authorizationEndpoint).toBe('https://chat.qwen.ai/api/v1/oauth2/device/code');
|
|
96
|
-
});
|
|
97
|
-
/**
|
|
98
|
-
* @requirement REQ-002.4
|
|
99
|
-
* @scenario Uses correct client ID
|
|
100
|
-
* @given Device flow request
|
|
101
|
-
* @when sent to Qwen
|
|
102
|
-
* @then Includes client_id: f0304373b74a44d2b584a3fb70ca9e56
|
|
103
|
-
*/
|
|
104
|
-
it('should include correct Qwen client ID in request', async () => {
|
|
105
|
-
testServer.removeAllListeners('request');
|
|
106
|
-
testServer.on('request', (req, res) => {
|
|
107
|
-
let body = '';
|
|
108
|
-
req.on('data', (chunk) => {
|
|
109
|
-
body += chunk;
|
|
110
|
-
});
|
|
111
|
-
req.on('end', () => {
|
|
112
|
-
const params = new URLSearchParams(body);
|
|
113
|
-
expect(params.get('client_id')).toBe('f0304373b74a44d2b584a3fb70ca9e56');
|
|
114
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
115
|
-
res.end(JSON.stringify({
|
|
116
|
-
device_code: 'test',
|
|
117
|
-
user_code: 'TEST',
|
|
118
|
-
verification_uri: 'https://test',
|
|
119
|
-
expires_in: 900,
|
|
120
|
-
interval: 5,
|
|
121
|
-
}));
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
const result = await deviceFlow.initiateDeviceFlow();
|
|
125
|
-
expect(result).toMatchObject({
|
|
126
|
-
device_code: 'test',
|
|
127
|
-
user_code: 'TEST',
|
|
128
|
-
verification_uri: 'https://test',
|
|
129
|
-
expires_in: 900,
|
|
130
|
-
interval: 5,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
/**
|
|
134
|
-
* @requirement REQ-002.1
|
|
135
|
-
* @scenario Device code response validation
|
|
136
|
-
* @given Response from authorization endpoint
|
|
137
|
-
* @when parsing response
|
|
138
|
-
* @then Validates all required fields present
|
|
139
|
-
*/
|
|
140
|
-
it('should validate device code response contains all required fields', async () => {
|
|
141
|
-
const incompleteResponse = {
|
|
142
|
-
device_code: 'test_device_code',
|
|
143
|
-
// Missing user_code, verification_uri, expires_in, interval
|
|
144
|
-
};
|
|
145
|
-
testServer.removeAllListeners('request');
|
|
146
|
-
testServer.on('request', (req, res) => {
|
|
147
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
148
|
-
res.end(JSON.stringify(incompleteResponse));
|
|
149
|
-
});
|
|
150
|
-
// Should throw validation error due to missing required fields
|
|
151
|
-
await expect(deviceFlow.initiateDeviceFlow()).rejects.toThrow();
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
describe('PKCE Security', () => {
|
|
155
|
-
/**
|
|
156
|
-
* @requirement REQ-002.2
|
|
157
|
-
* @scenario PKCE code challenge generation
|
|
158
|
-
* @given Device flow initiation
|
|
159
|
-
* @when PKCE is generated
|
|
160
|
-
* @then Creates SHA-256 challenge from verifier
|
|
161
|
-
* @and Verifier is cryptographically random
|
|
162
|
-
*/
|
|
163
|
-
it('should generate cryptographically random PKCE verifier and SHA-256 challenge', async () => {
|
|
164
|
-
// Test the cryptographic operations directly since implementation is not ready
|
|
165
|
-
const verifier1 = randomBytes(32).toString('base64url');
|
|
166
|
-
const verifier2 = randomBytes(32).toString('base64url');
|
|
167
|
-
// Verifiers should be different (random)
|
|
168
|
-
expect(verifier1).not.toBe(verifier2);
|
|
169
|
-
expect(verifier1).toHaveLength(43); // 32 bytes base64url = 43 chars
|
|
170
|
-
// Challenge should be SHA-256 of verifier
|
|
171
|
-
const challenge1 = createHash('sha256')
|
|
172
|
-
.update(verifier1)
|
|
173
|
-
.digest('base64url');
|
|
174
|
-
const challenge2 = createHash('sha256')
|
|
175
|
-
.update(verifier2)
|
|
176
|
-
.digest('base64url');
|
|
177
|
-
expect(challenge1).not.toBe(challenge2);
|
|
178
|
-
expect(challenge1).toHaveLength(43); // SHA-256 base64url = 43 chars
|
|
179
|
-
// Verify reproducible challenge generation
|
|
180
|
-
const sameChallengeAgain = createHash('sha256')
|
|
181
|
-
.update(verifier1)
|
|
182
|
-
.digest('base64url');
|
|
183
|
-
expect(challenge1).toBe(sameChallengeAgain);
|
|
184
|
-
// The PKCE generation logic is tested above with real crypto functions.
|
|
185
|
-
// The actual implementation works correctly.
|
|
186
|
-
expect(challenge1).toHaveLength(43); // This was already tested above
|
|
187
|
-
});
|
|
188
|
-
/**
|
|
189
|
-
* @requirement REQ-002.2
|
|
190
|
-
* @scenario PKCE parameters in device request
|
|
191
|
-
* @given Device flow initiation with PKCE
|
|
192
|
-
* @when request is made
|
|
193
|
-
* @then Includes code_challenge and code_challenge_method=S256
|
|
194
|
-
*/
|
|
195
|
-
it('should include PKCE parameters in device authorization request', async () => {
|
|
196
|
-
testServer.removeAllListeners('request');
|
|
197
|
-
testServer.on('request', (req, res) => {
|
|
198
|
-
let body = '';
|
|
199
|
-
req.on('data', (chunk) => {
|
|
200
|
-
body += chunk;
|
|
201
|
-
});
|
|
202
|
-
req.on('end', () => {
|
|
203
|
-
const params = new URLSearchParams(body);
|
|
204
|
-
expect(params.get('code_challenge')).toBeDefined();
|
|
205
|
-
expect(params.get('code_challenge_method')).toBe('S256');
|
|
206
|
-
expect(params.get('code_challenge')).toHaveLength(43); // SHA-256 base64url length
|
|
207
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
208
|
-
res.end(JSON.stringify({
|
|
209
|
-
device_code: 'test',
|
|
210
|
-
user_code: 'TEST',
|
|
211
|
-
verification_uri: 'https://test',
|
|
212
|
-
expires_in: 900,
|
|
213
|
-
interval: 5,
|
|
214
|
-
}));
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
const result = await deviceFlow.initiateDeviceFlow();
|
|
218
|
-
expect(result).toMatchObject({
|
|
219
|
-
device_code: 'test',
|
|
220
|
-
user_code: 'TEST',
|
|
221
|
-
verification_uri: 'https://test',
|
|
222
|
-
expires_in: 900,
|
|
223
|
-
interval: 5,
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
/**
|
|
227
|
-
* @requirement REQ-002.2
|
|
228
|
-
* @scenario PKCE verifier storage
|
|
229
|
-
* @given Device flow initiated with PKCE
|
|
230
|
-
* @when polling for token
|
|
231
|
-
* @then Uses same verifier for token exchange
|
|
232
|
-
*/
|
|
233
|
-
it('should store PKCE verifier for later token exchange', async () => {
|
|
234
|
-
testServer.removeAllListeners('request');
|
|
235
|
-
let storedChallenge;
|
|
236
|
-
testServer.on('request', (req, res) => {
|
|
237
|
-
if (req.url?.includes('device/code')) {
|
|
238
|
-
let body = '';
|
|
239
|
-
req.on('data', (chunk) => {
|
|
240
|
-
body += chunk;
|
|
241
|
-
});
|
|
242
|
-
req.on('end', () => {
|
|
243
|
-
const params = new URLSearchParams(body);
|
|
244
|
-
storedChallenge = params.get('code_challenge') || undefined;
|
|
245
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
246
|
-
res.end(JSON.stringify({
|
|
247
|
-
device_code: 'test_device',
|
|
248
|
-
user_code: 'TEST',
|
|
249
|
-
verification_uri: 'https://test',
|
|
250
|
-
expires_in: 900,
|
|
251
|
-
interval: 5,
|
|
252
|
-
}));
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
else if (req.url?.includes('token')) {
|
|
256
|
-
let body = '';
|
|
257
|
-
req.on('data', (chunk) => {
|
|
258
|
-
body += chunk;
|
|
259
|
-
});
|
|
260
|
-
req.on('end', () => {
|
|
261
|
-
const params = new URLSearchParams(body);
|
|
262
|
-
const verifier = params.get('code_verifier');
|
|
263
|
-
// Verify that the verifier produces the same challenge
|
|
264
|
-
if (verifier && storedChallenge) {
|
|
265
|
-
const expectedChallenge = createHash('sha256')
|
|
266
|
-
.update(verifier)
|
|
267
|
-
.digest('base64url');
|
|
268
|
-
expect(expectedChallenge).toBe(storedChallenge);
|
|
269
|
-
}
|
|
270
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
271
|
-
res.end(JSON.stringify({
|
|
272
|
-
access_token: 'test_token',
|
|
273
|
-
token_type: 'Bearer',
|
|
274
|
-
expires_in: 3600,
|
|
275
|
-
}));
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
// Test the requirement by initiating the flow then polling
|
|
280
|
-
const deviceResult = await deviceFlow.initiateDeviceFlow();
|
|
281
|
-
expect(deviceResult.device_code).toBe('test_device');
|
|
282
|
-
// The verifier verification happens in the mock server above
|
|
283
|
-
// This will timeout eventually, but the verifier matching is tested in the mock
|
|
284
|
-
try {
|
|
285
|
-
await deviceFlow.pollForToken('test_device');
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
// Expected to timeout or get a token - both are valid outcomes
|
|
289
|
-
expect(error).toBeDefined();
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
describe('Token Polling', () => {
|
|
294
|
-
/**
|
|
295
|
-
* @requirement REQ-002.1
|
|
296
|
-
* @scenario Poll for authorization completion
|
|
297
|
-
* @given Device code from initiation
|
|
298
|
-
* @when pollForToken() called repeatedly
|
|
299
|
-
* @then Continues until user authorizes
|
|
300
|
-
* @and Returns access token on success
|
|
301
|
-
*/
|
|
302
|
-
it('should poll for token until authorization completes', { timeout: 20000 }, async () => {
|
|
303
|
-
let pollCount = 0;
|
|
304
|
-
const mockToken = {
|
|
305
|
-
access_token: 'qwen_access_token_12345',
|
|
306
|
-
token_type: 'Bearer',
|
|
307
|
-
expiry: Math.floor(Date.now() / 1000) + 3600,
|
|
308
|
-
refresh_token: 'qwen_refresh_token_67890',
|
|
309
|
-
scope: 'read write',
|
|
310
|
-
};
|
|
311
|
-
testServer.removeAllListeners('request');
|
|
312
|
-
testServer.on('request', (req, res) => {
|
|
313
|
-
if (req.url?.includes('token')) {
|
|
314
|
-
pollCount++;
|
|
315
|
-
let body = '';
|
|
316
|
-
req.on('data', (chunk) => {
|
|
317
|
-
body += chunk;
|
|
318
|
-
});
|
|
319
|
-
req.on('end', () => {
|
|
320
|
-
const params = new URLSearchParams(body);
|
|
321
|
-
expect(params.get('grant_type')).toBe('urn:ietf:params:oauth:grant-type:device_code');
|
|
322
|
-
expect(params.get('device_code')).toBe('test_device_code');
|
|
323
|
-
expect(params.get('client_id')).toBe('f0304373b74a44d2b584a3fb70ca9e56');
|
|
324
|
-
if (pollCount < 3) {
|
|
325
|
-
// First few attempts return pending
|
|
326
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
327
|
-
res.end(JSON.stringify({ error: 'authorization_pending' }));
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
// Eventually return success
|
|
331
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
332
|
-
res.end(JSON.stringify({
|
|
333
|
-
access_token: mockToken.access_token,
|
|
334
|
-
token_type: mockToken.token_type,
|
|
335
|
-
expires_in: 3600,
|
|
336
|
-
refresh_token: mockToken.refresh_token,
|
|
337
|
-
scope: mockToken.scope,
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
// This will actually poll and succeed after the third attempt
|
|
344
|
-
const result = await deviceFlow.pollForToken('test_device_code');
|
|
345
|
-
expect(result).toMatchObject({
|
|
346
|
-
access_token: mockToken.access_token,
|
|
347
|
-
token_type: 'Bearer',
|
|
348
|
-
scope: mockToken.scope,
|
|
349
|
-
refresh_token: mockToken.refresh_token,
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
/**
|
|
353
|
-
* @requirement REQ-002.3
|
|
354
|
-
* @scenario Token endpoint usage
|
|
355
|
-
* @given Device code for polling
|
|
356
|
-
* @when requesting token
|
|
357
|
-
* @then Uses https://chat.qwen.ai/api/v1/oauth2/token
|
|
358
|
-
*/
|
|
359
|
-
it('should use correct Qwen token endpoint', async () => {
|
|
360
|
-
const realConfig = {
|
|
361
|
-
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
|
|
362
|
-
authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
|
|
363
|
-
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
|
|
364
|
-
scopes: ['read'],
|
|
365
|
-
};
|
|
366
|
-
const realDeviceFlow = new QwenDeviceFlow(realConfig);
|
|
367
|
-
// This will fail with a real network request, which is expected
|
|
368
|
-
await expect(realDeviceFlow.pollForToken('test_device')).rejects.toThrow('HTTP 400: Bad Request');
|
|
369
|
-
// Verify the configuration contains the correct endpoint
|
|
370
|
-
expect(realConfig.tokenEndpoint).toBe('https://chat.qwen.ai/api/v1/oauth2/token');
|
|
371
|
-
});
|
|
372
|
-
/**
|
|
373
|
-
* @requirement REQ-002.1
|
|
374
|
-
* @scenario Respect polling interval
|
|
375
|
-
* @given Server specifies 5 second interval
|
|
376
|
-
* @when polling for token
|
|
377
|
-
* @then Waits at least 5 seconds between requests
|
|
378
|
-
*/
|
|
379
|
-
it('should respect server-specified polling interval', { timeout: 30000 }, async () => {
|
|
380
|
-
const timestamps = [];
|
|
381
|
-
let requestCount = 0;
|
|
382
|
-
testServer.removeAllListeners('request');
|
|
383
|
-
testServer.on('request', (req, res) => {
|
|
384
|
-
timestamps.push(Date.now());
|
|
385
|
-
requestCount++;
|
|
386
|
-
// Return success after 3 requests to avoid timeout
|
|
387
|
-
if (requestCount >= 3) {
|
|
388
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
389
|
-
res.end(JSON.stringify({
|
|
390
|
-
access_token: 'test_token',
|
|
391
|
-
token_type: 'Bearer',
|
|
392
|
-
expires_in: 3600,
|
|
393
|
-
}));
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
397
|
-
res.end(JSON.stringify({ error: 'authorization_pending' }));
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
// This test verifies the requirement for interval handling
|
|
401
|
-
// Should succeed after 3 requests and we can verify intervals
|
|
402
|
-
const result = await deviceFlow.pollForToken('test_device');
|
|
403
|
-
expect(result.access_token).toBe('test_token');
|
|
404
|
-
// Verify we made multiple requests with proper intervals
|
|
405
|
-
expect(timestamps.length).toBeGreaterThanOrEqual(3);
|
|
406
|
-
// Verify the intervals are at least close to 5 seconds (allowing some variance)
|
|
407
|
-
if (timestamps.length > 1) {
|
|
408
|
-
const intervals = timestamps
|
|
409
|
-
.slice(1)
|
|
410
|
-
.map((t, i) => t - timestamps[i]);
|
|
411
|
-
intervals.forEach((interval) => expect(interval).toBeGreaterThanOrEqual(4000)); // Allow some variance
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
/**
|
|
415
|
-
* @requirement REQ-002.1
|
|
416
|
-
* @scenario Token response validation
|
|
417
|
-
* @given Token response from endpoint
|
|
418
|
-
* @when parsing token
|
|
419
|
-
* @then Validates access_token and expiry
|
|
420
|
-
*/
|
|
421
|
-
it('should validate token response contains required fields', async () => {
|
|
422
|
-
const invalidTokenResponse = {
|
|
423
|
-
// Missing access_token
|
|
424
|
-
token_type: 'Bearer',
|
|
425
|
-
expires_in: 3600,
|
|
426
|
-
};
|
|
427
|
-
testServer.removeAllListeners('request');
|
|
428
|
-
testServer.on('request', (req, res) => {
|
|
429
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
430
|
-
res.end(JSON.stringify(invalidTokenResponse));
|
|
431
|
-
});
|
|
432
|
-
// Should throw validation error due to missing access_token
|
|
433
|
-
await expect(deviceFlow.pollForToken('test_device')).rejects.toThrow();
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
describe('Token Refresh', () => {
|
|
437
|
-
/**
|
|
438
|
-
* @requirement REQ-002.5
|
|
439
|
-
* @scenario Refresh token before expiry
|
|
440
|
-
* @given Token expires in 30 seconds
|
|
441
|
-
* @when refresh requested
|
|
442
|
-
* @then Obtains new access token
|
|
443
|
-
* @and Uses refresh token grant type
|
|
444
|
-
*/
|
|
445
|
-
it('should refresh token using refresh grant type', async () => {
|
|
446
|
-
const newToken = {
|
|
447
|
-
access_token: 'new_access_token_12345',
|
|
448
|
-
token_type: 'Bearer',
|
|
449
|
-
expiry: Math.floor(Date.now() / 1000) + 3600,
|
|
450
|
-
refresh_token: 'new_refresh_token_67890',
|
|
451
|
-
scope: 'read write',
|
|
452
|
-
};
|
|
453
|
-
testServer.removeAllListeners('request');
|
|
454
|
-
testServer.on('request', (req, res) => {
|
|
455
|
-
let body = '';
|
|
456
|
-
req.on('data', (chunk) => {
|
|
457
|
-
body += chunk;
|
|
458
|
-
});
|
|
459
|
-
req.on('end', () => {
|
|
460
|
-
const params = new URLSearchParams(body);
|
|
461
|
-
expect(params.get('grant_type')).toBe('refresh_token');
|
|
462
|
-
expect(params.get('refresh_token')).toBe('old_refresh_token');
|
|
463
|
-
expect(params.get('client_id')).toBe('f0304373b74a44d2b584a3fb70ca9e56');
|
|
464
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
465
|
-
res.end(JSON.stringify({
|
|
466
|
-
access_token: newToken.access_token,
|
|
467
|
-
token_type: newToken.token_type,
|
|
468
|
-
expires_in: 3600,
|
|
469
|
-
refresh_token: newToken.refresh_token,
|
|
470
|
-
scope: newToken.scope,
|
|
471
|
-
}));
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
const result = await deviceFlow.refreshToken('old_refresh_token');
|
|
475
|
-
expect(result).toMatchObject({
|
|
476
|
-
access_token: newToken.access_token,
|
|
477
|
-
token_type: 'Bearer',
|
|
478
|
-
scope: newToken.scope,
|
|
479
|
-
refresh_token: newToken.refresh_token,
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
/**
|
|
483
|
-
* @requirement REQ-002.5
|
|
484
|
-
* @scenario Automatic refresh buffer
|
|
485
|
-
* @given Token with expiry time
|
|
486
|
-
* @when checking if refresh needed
|
|
487
|
-
* @then Triggers 30 seconds before expiry
|
|
488
|
-
*/
|
|
489
|
-
it('should identify tokens needing refresh with 30-second buffer', () => {
|
|
490
|
-
const now = Date.now() / 1000;
|
|
491
|
-
// Token expiring in 25 seconds (less than 30-second buffer)
|
|
492
|
-
const soonExpiringToken = {
|
|
493
|
-
access_token: 'soon_expiring',
|
|
494
|
-
token_type: 'Bearer',
|
|
495
|
-
expiry: Math.floor(now + 25),
|
|
496
|
-
};
|
|
497
|
-
// Token expiring in 35 seconds (more than 30-second buffer)
|
|
498
|
-
const validToken = {
|
|
499
|
-
access_token: 'still_valid',
|
|
500
|
-
token_type: 'Bearer',
|
|
501
|
-
expiry: Math.floor(now + 35),
|
|
502
|
-
};
|
|
503
|
-
// Already expired token
|
|
504
|
-
const expiredToken = {
|
|
505
|
-
access_token: 'expired',
|
|
506
|
-
token_type: 'Bearer',
|
|
507
|
-
expiry: Math.floor(now - 10),
|
|
508
|
-
};
|
|
509
|
-
// When implemented, these should help verify refresh logic:
|
|
510
|
-
// expect(deviceFlow.needsRefresh(soonExpiringToken)).toBe(true);
|
|
511
|
-
// expect(deviceFlow.needsRefresh(validToken)).toBe(false);
|
|
512
|
-
// expect(deviceFlow.needsRefresh(expiredToken)).toBe(true);
|
|
513
|
-
// For now, just verify the test data is set up correctly
|
|
514
|
-
expect(soonExpiringToken.expiry).toBeLessThan(now + 30);
|
|
515
|
-
expect(validToken.expiry).toBeGreaterThan(now + 30);
|
|
516
|
-
expect(expiredToken.expiry).toBeLessThan(now);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
describe('Error Handling', () => {
|
|
520
|
-
/**
|
|
521
|
-
* @requirement REQ-002.1
|
|
522
|
-
* @scenario Handle authorization denial
|
|
523
|
-
* @given User denies authorization
|
|
524
|
-
* @when polling for token
|
|
525
|
-
* @then Returns specific denial error
|
|
526
|
-
*/
|
|
527
|
-
it('should handle user authorization denial', async () => {
|
|
528
|
-
testServer.removeAllListeners('request');
|
|
529
|
-
testServer.on('request', (req, res) => {
|
|
530
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
531
|
-
res.end(JSON.stringify({
|
|
532
|
-
error: 'access_denied',
|
|
533
|
-
error_description: 'User denied the authorization request',
|
|
534
|
-
}));
|
|
535
|
-
});
|
|
536
|
-
// Should throw with the specific access_denied error
|
|
537
|
-
await expect(deviceFlow.pollForToken('test_device')).rejects.toThrow('access_denied');
|
|
538
|
-
});
|
|
539
|
-
/**
|
|
540
|
-
* @requirement REQ-002.1
|
|
541
|
-
* @scenario Handle expired device code
|
|
542
|
-
* @given Device code expired (15 min)
|
|
543
|
-
* @when polling continues
|
|
544
|
-
* @then Returns expiration error
|
|
545
|
-
*/
|
|
546
|
-
it('should handle expired device code', async () => {
|
|
547
|
-
testServer.removeAllListeners('request');
|
|
548
|
-
testServer.on('request', (req, res) => {
|
|
549
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
550
|
-
res.end(JSON.stringify({
|
|
551
|
-
error: 'expired_token',
|
|
552
|
-
error_description: 'Device code has expired',
|
|
553
|
-
}));
|
|
554
|
-
});
|
|
555
|
-
// Should throw with the specific expired_token error
|
|
556
|
-
await expect(deviceFlow.pollForToken('expired_device_code')).rejects.toThrow('expired_token');
|
|
557
|
-
});
|
|
558
|
-
/**
|
|
559
|
-
* @requirement REQ-002.1
|
|
560
|
-
* @scenario Network failure handling
|
|
561
|
-
* @given Network request fails
|
|
562
|
-
* @when polling or refreshing
|
|
563
|
-
* @then Retries with exponential backoff
|
|
564
|
-
*/
|
|
565
|
-
it('should handle network failures with retry logic', { timeout: 20000 }, async () => {
|
|
566
|
-
let requestCount = 0;
|
|
567
|
-
testServer.removeAllListeners('request');
|
|
568
|
-
testServer.on('request', (req, res) => {
|
|
569
|
-
requestCount++;
|
|
570
|
-
if (requestCount <= 2) {
|
|
571
|
-
// First two requests fail
|
|
572
|
-
res.destroy();
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
// Third request succeeds
|
|
576
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
577
|
-
res.end(JSON.stringify({
|
|
578
|
-
access_token: 'recovered_token',
|
|
579
|
-
token_type: 'Bearer',
|
|
580
|
-
expires_in: 3600,
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
// The implementation will retry and eventually get the token on the third attempt
|
|
585
|
-
const result = await deviceFlow.pollForToken('network_test_device');
|
|
586
|
-
expect(result.access_token).toBe('recovered_token');
|
|
587
|
-
});
|
|
588
|
-
/**
|
|
589
|
-
* @requirement REQ-002.1
|
|
590
|
-
* @scenario Handle malformed JSON response
|
|
591
|
-
* @given Server returns invalid JSON
|
|
592
|
-
* @when parsing response
|
|
593
|
-
* @then Throws appropriate error
|
|
594
|
-
*/
|
|
595
|
-
it('should handle malformed JSON responses', async () => {
|
|
596
|
-
testServer.removeAllListeners('request');
|
|
597
|
-
testServer.on('request', (req, res) => {
|
|
598
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
599
|
-
res.end('{ invalid json }');
|
|
600
|
-
});
|
|
601
|
-
// Should throw a JSON parsing error
|
|
602
|
-
await expect(deviceFlow.initiateDeviceFlow()).rejects.toThrow();
|
|
603
|
-
});
|
|
604
|
-
/**
|
|
605
|
-
* @requirement REQ-002.1
|
|
606
|
-
* @scenario Handle HTTP error status codes
|
|
607
|
-
* @given Server returns 500 error
|
|
608
|
-
* @when making request
|
|
609
|
-
* @then Throws appropriate error
|
|
610
|
-
*/
|
|
611
|
-
it('should handle HTTP error status codes', async () => {
|
|
612
|
-
testServer.removeAllListeners('request');
|
|
613
|
-
testServer.on('request', (req, res) => {
|
|
614
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
615
|
-
res.end(JSON.stringify({
|
|
616
|
-
error: 'internal_server_error',
|
|
617
|
-
error_description: 'Server error occurred',
|
|
618
|
-
}));
|
|
619
|
-
});
|
|
620
|
-
// Should throw HTTP 500 error
|
|
621
|
-
await expect(deviceFlow.initiateDeviceFlow()).rejects.toThrow('HTTP 500: Internal Server Error');
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
describe('Security Validation', () => {
|
|
625
|
-
/**
|
|
626
|
-
* @requirement REQ-002.2
|
|
627
|
-
* @scenario PKCE verifier entropy validation
|
|
628
|
-
* @given Multiple PKCE verifiers generated
|
|
629
|
-
* @when analyzing randomness
|
|
630
|
-
* @then Verifiers have sufficient entropy
|
|
631
|
-
*/
|
|
632
|
-
it('should generate PKCE verifiers with sufficient entropy', () => {
|
|
633
|
-
const verifiers = new Set();
|
|
634
|
-
// Generate 100 verifiers to test uniqueness
|
|
635
|
-
for (let i = 0; i < 100; i++) {
|
|
636
|
-
const verifier = randomBytes(32).toString('base64url');
|
|
637
|
-
verifiers.add(verifier);
|
|
638
|
-
}
|
|
639
|
-
// All verifiers should be unique (high entropy)
|
|
640
|
-
expect(verifiers.size).toBe(100);
|
|
641
|
-
// Each verifier should be 43 characters (32 bytes base64url)
|
|
642
|
-
verifiers.forEach((verifier) => {
|
|
643
|
-
expect(verifier).toHaveLength(43);
|
|
644
|
-
expect(verifier).toMatch(/^[A-Za-z0-9_-]+$/); // base64url alphabet
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
/**
|
|
648
|
-
* @requirement REQ-002.2
|
|
649
|
-
* @scenario PKCE challenge verification
|
|
650
|
-
* @given Verifier and challenge pair
|
|
651
|
-
* @when verifying PKCE
|
|
652
|
-
* @then Challenge correctly matches verifier
|
|
653
|
-
*/
|
|
654
|
-
it('should generate verifiable PKCE challenge-verifier pairs', () => {
|
|
655
|
-
const verifier = randomBytes(32).toString('base64url');
|
|
656
|
-
const challenge = createHash('sha256')
|
|
657
|
-
.update(verifier)
|
|
658
|
-
.digest('base64url');
|
|
659
|
-
// Verification: regenerating challenge from verifier should match
|
|
660
|
-
const verificationChallenge = createHash('sha256')
|
|
661
|
-
.update(verifier)
|
|
662
|
-
.digest('base64url');
|
|
663
|
-
expect(challenge).toBe(verificationChallenge);
|
|
664
|
-
// Different verifiers should produce different challenges
|
|
665
|
-
const anotherVerifier = randomBytes(32).toString('base64url');
|
|
666
|
-
const anotherChallenge = createHash('sha256')
|
|
667
|
-
.update(anotherVerifier)
|
|
668
|
-
.digest('base64url');
|
|
669
|
-
expect(challenge).not.toBe(anotherChallenge);
|
|
670
|
-
});
|
|
671
|
-
/**
|
|
672
|
-
* @requirement REQ-002.1
|
|
673
|
-
* @scenario Request parameter validation
|
|
674
|
-
* @given Device flow request
|
|
675
|
-
* @when sending to authorization server
|
|
676
|
-
* @then All required parameters are present
|
|
677
|
-
*/
|
|
678
|
-
it('should include all required OAuth parameters in requests', async () => {
|
|
679
|
-
const requiredDeviceParams = [
|
|
680
|
-
'client_id',
|
|
681
|
-
'scope',
|
|
682
|
-
'code_challenge',
|
|
683
|
-
'code_challenge_method',
|
|
684
|
-
];
|
|
685
|
-
const requiredTokenParams = [
|
|
686
|
-
'grant_type',
|
|
687
|
-
'device_code',
|
|
688
|
-
'client_id',
|
|
689
|
-
'code_verifier',
|
|
690
|
-
];
|
|
691
|
-
testServer.removeAllListeners('request');
|
|
692
|
-
testServer.on('request', (req, res) => {
|
|
693
|
-
let body = '';
|
|
694
|
-
req.on('data', (chunk) => {
|
|
695
|
-
body += chunk;
|
|
696
|
-
});
|
|
697
|
-
req.on('end', () => {
|
|
698
|
-
const params = new URLSearchParams(body);
|
|
699
|
-
if (req.url?.includes('device/code')) {
|
|
700
|
-
requiredDeviceParams.forEach((param) => {
|
|
701
|
-
expect(params.has(param)).toBe(true);
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
else if (req.url?.includes('token')) {
|
|
705
|
-
requiredTokenParams.forEach((param) => {
|
|
706
|
-
expect(params.has(param)).toBe(true);
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
710
|
-
res.end(JSON.stringify({
|
|
711
|
-
device_code: 'test',
|
|
712
|
-
user_code: 'TEST',
|
|
713
|
-
verification_uri: 'https://test',
|
|
714
|
-
expires_in: 900,
|
|
715
|
-
interval: 5,
|
|
716
|
-
}));
|
|
717
|
-
});
|
|
718
|
-
});
|
|
719
|
-
const result = await deviceFlow.initiateDeviceFlow();
|
|
720
|
-
expect(result).toMatchObject({
|
|
721
|
-
device_code: 'test',
|
|
722
|
-
user_code: 'TEST',
|
|
723
|
-
verification_uri: 'https://test',
|
|
724
|
-
expires_in: 900,
|
|
725
|
-
interval: 5,
|
|
726
|
-
});
|
|
727
|
-
});
|
|
728
|
-
});
|
|
729
|
-
describe('Configuration Validation', () => {
|
|
730
|
-
/**
|
|
731
|
-
* @requirement REQ-002.4
|
|
732
|
-
* @scenario Validate client ID format
|
|
733
|
-
* @given Device flow configuration
|
|
734
|
-
* @when initializing with client ID
|
|
735
|
-
* @then Client ID matches expected format
|
|
736
|
-
*/
|
|
737
|
-
it('should validate Qwen client ID format', () => {
|
|
738
|
-
const validClientId = 'f0304373b74a44d2b584a3fb70ca9e56';
|
|
739
|
-
expect(validClientId).toHaveLength(32); // 32 character hex string
|
|
740
|
-
expect(validClientId).toMatch(/^[a-f0-9]+$/); // Lowercase hex characters only
|
|
741
|
-
const configWithValidClient = {
|
|
742
|
-
clientId: validClientId,
|
|
743
|
-
authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
|
|
744
|
-
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
|
|
745
|
-
scopes: ['read'],
|
|
746
|
-
};
|
|
747
|
-
expect(() => new QwenDeviceFlow(configWithValidClient)).not.toThrow();
|
|
748
|
-
});
|
|
749
|
-
/**
|
|
750
|
-
* @requirement REQ-002.3
|
|
751
|
-
* @scenario Validate endpoint URLs
|
|
752
|
-
* @given Device flow configuration
|
|
753
|
-
* @when initializing with endpoints
|
|
754
|
-
* @then URLs are valid and use HTTPS
|
|
755
|
-
*/
|
|
756
|
-
it('should validate Qwen endpoint URLs', () => {
|
|
757
|
-
const validConfig = {
|
|
758
|
-
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
|
|
759
|
-
authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
|
|
760
|
-
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
|
|
761
|
-
scopes: ['read', 'write'],
|
|
762
|
-
};
|
|
763
|
-
expect(validConfig.authorizationEndpoint.startsWith('https://')).toBe(true);
|
|
764
|
-
expect(validConfig.tokenEndpoint.startsWith('https://')).toBe(true);
|
|
765
|
-
expect(validConfig.authorizationEndpoint).toContain('chat.qwen.ai');
|
|
766
|
-
expect(validConfig.tokenEndpoint).toContain('chat.qwen.ai');
|
|
767
|
-
expect(() => new QwenDeviceFlow(validConfig)).not.toThrow();
|
|
768
|
-
});
|
|
769
|
-
/**
|
|
770
|
-
* @requirement REQ-002.1
|
|
771
|
-
* @scenario Validate scope configuration
|
|
772
|
-
* @given Device flow configuration
|
|
773
|
-
* @when initializing with scopes
|
|
774
|
-
* @then Scopes are properly formatted for request
|
|
775
|
-
*/
|
|
776
|
-
it('should validate and format scope configuration', () => {
|
|
777
|
-
const configWithScopes = {
|
|
778
|
-
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
|
|
779
|
-
authorizationEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
|
|
780
|
-
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
|
|
781
|
-
scopes: ['read', 'write', 'admin'],
|
|
782
|
-
};
|
|
783
|
-
expect(configWithScopes.scopes).toBeInstanceOf(Array);
|
|
784
|
-
expect(configWithScopes.scopes).toContain('read');
|
|
785
|
-
expect(configWithScopes.scopes).toContain('write');
|
|
786
|
-
// When implemented, scopes should be joined with spaces for OAuth request
|
|
787
|
-
const expectedScopeString = 'read write admin';
|
|
788
|
-
expect(configWithScopes.scopes.join(' ')).toBe(expectedScopeString);
|
|
789
|
-
expect(() => new QwenDeviceFlow(configWithScopes)).not.toThrow();
|
|
790
|
-
});
|
|
791
|
-
});
|
|
792
|
-
});
|
|
793
|
-
//# sourceMappingURL=qwen-device-flow.spec.js.map
|