catalyst-core 0.3.0-beta.5 → 0.3.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +185 -71
- package/dist/errors/generateDocs.js +100 -0
- package/dist/errors/index.js +243 -0
- package/dist/errors/registry.js +483 -0
- package/dist/errors-index.json +506 -0
- package/dist/globals.d.ts +62 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.jsx +5 -8
- package/dist/logger.js +1 -1
- package/dist/native/androidProject/app/build.gradle.kts +142 -7
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
- package/dist/native/androidProject/build.gradle.kts +1 -0
- package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
- package/dist/native/androidProject/settings.gradle.kts +11 -8
- package/dist/native/androidSetup.js +2 -2
- package/dist/native/bridge/WebBridge.d.ts +98 -0
- package/dist/native/bridge/WebBridge.js +40 -6
- package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
- package/dist/native/bridge/errors.d.ts +67 -0
- package/dist/native/bridge/errors.js +66 -151
- package/dist/native/bridge/hooks/index.d.ts +13 -0
- package/dist/native/bridge/hooks/index.js +0 -6
- package/dist/native/bridge/hooks/useAI.d.ts +1 -0
- package/dist/native/bridge/hooks/useAI.js +3 -1
- package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
- package/dist/native/bridge/hooks/useCamera.js +35 -6
- package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
- package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
- package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
- package/dist/native/bridge/hooks/useFilePicker.js +13 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
- package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
- package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
- package/dist/native/bridge/hooks/useIntent.js +2 -0
- package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
- package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
- package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
- package/dist/native/bridge/hooks/useNotification.js +81 -34
- package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
- package/dist/native/bridge/hooks/useSafeArea.js +8 -0
- package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
- package/dist/native/bridge/hooks/useVideoStream.js +38 -52
- package/dist/native/bridge/hooks/utils.d.ts +4 -0
- package/dist/native/bridge/hooks.d.ts +1 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
- package/dist/native/bridge/perf/core/constants.d.ts +76 -0
- package/dist/native/bridge/perf/core/measure.d.ts +39 -0
- package/dist/native/bridge/perf/core/measure.js +4 -0
- package/dist/native/bridge/perf/core/session.d.ts +49 -0
- package/dist/native/bridge/perf/core/session.js +4 -0
- package/dist/native/bridge/perf/core/store.d.ts +37 -0
- package/dist/native/bridge/perf/core/store.js +4 -0
- package/dist/native/bridge/perf/core/summary.d.ts +60 -0
- package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
- package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
- package/dist/native/bridge/perf/index.d.ts +63 -0
- package/dist/native/bridge/perf/index.js +4 -0
- package/dist/native/bridge/safeArea.d.ts +11 -0
- package/dist/native/bridge/safeArea.js +2 -0
- package/dist/native/bridge/useBaseHook.d.ts +137 -0
- package/dist/native/bridge/useBaseHook.js +34 -4
- package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
- package/dist/native/bridge/utils/CameraUtils.js +4 -0
- package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
- package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
- package/dist/native/bridge/utils/NativeBridge.js +10 -0
- package/dist/native/buildAndroid/index.js +1 -1
- package/dist/native/buildAppAndroid.js +9 -0
- package/dist/native/buildAppIos.js +16 -1
- package/dist/native/buildErrorFormat.js +57 -0
- package/dist/native/buildIos/config.js +15 -1
- package/dist/native/buildIos/index.js +1 -1
- package/dist/native/buildIos/plugins.js +10 -1
- package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
- package/dist/native/iosnativeWebView/Package.swift +10 -9
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
- package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
- package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
- package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
- package/dist/native/setupEmulatorIos.js +2 -2
- package/dist/native/terminalProgress.js +17 -17
- package/dist/router/ClientRouter.d.ts +8 -0
- package/dist/router/ClientRouter.js +13 -11
- package/dist/router/ServerRouter.d.ts +13 -0
- package/dist/router/ServerRouter.js +19 -7
- package/dist/scripts/build.js +14 -2
- package/dist/scripts/loadEnvironmentVariables.js +3 -2
- package/dist/scripts/plugins.js +1 -1
- package/dist/scripts/preServerInit.js +49 -3
- package/dist/scripts/preflight.js +111 -0
- package/dist/scripts/scriptUtils.js +46 -1
- package/dist/scripts/serve.js +7 -3
- package/dist/scripts/start.js +8 -3
- package/dist/server/expressServer.d.ts +1 -0
- package/dist/server/expressServer.js +247 -271
- package/dist/server/manifestCache.d.ts +2 -0
- package/dist/server/manifestCache.js +25 -33
- package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
- package/dist/server/renderer/ChunkExtractor.js +113 -125
- package/dist/server/renderer/document/Body.d.ts +21 -0
- package/dist/server/renderer/document/Body.jsx +45 -45
- package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
- package/dist/server/renderer/document/FastRefresh.jsx +7 -13
- package/dist/server/renderer/document/Head.d.ts +20 -0
- package/dist/server/renderer/document/Head.jsx +32 -32
- package/dist/server/renderer/document/index.d.ts +2 -0
- package/dist/server/renderer/document/index.jsx +2 -2
- package/dist/server/renderer/extract.d.ts +67 -0
- package/dist/server/renderer/extract.js +92 -89
- package/dist/server/renderer/handler.d.ts +2 -0
- package/dist/server/renderer/handler.jsx +401 -404
- package/dist/server/renderer/index.d.ts +2 -0
- package/dist/server/renderer/index.js +5 -8
- package/dist/server/renderer/render.d.ts +35 -0
- package/dist/server/renderer/render.js +25 -32
- package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
- package/dist/server/utils/botDetectionMiddleware.js +33 -23
- package/dist/server/utils/cjsRequire.d.ts +8 -0
- package/dist/server/utils/cjsRequire.js +2 -2
- package/dist/server/utils/userAgentUtil.d.ts +7 -0
- package/dist/server/utils/userAgentUtil.js +68 -71
- package/dist/server/utils/validator.d.ts +31 -0
- package/dist/server/utils/validator.js +133 -158
- package/dist/vite/inject-cache-key-plugin.js +3 -27
- package/dist/vite/moduleAliases.js +36 -0
- package/dist/vite/node-loader.mjs +2 -29
- package/dist/vite/resolveDevServerConfig.js +4 -1
- package/dist/vite/vite.config.js +2 -29
- package/dist/web-router/components/MetaTag.d.ts +4 -0
- package/dist/web-router/components/MetaTag.jsx +26 -28
- package/dist/web-router/components/MetaTag.test.jsx +100 -0
- package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
- package/dist/web-router/components/RouterDataProvider.jsx +260 -259
- package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
- package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
- package/dist/web-router/components/Split.d.ts +74 -0
- package/dist/web-router/components/Split.jsx +147 -156
- package/dist/web-router/components/Split.server.test.jsx +52 -0
- package/dist/web-router/components/Split.test.jsx +167 -0
- package/dist/web-router/components/SplitInview.d.ts +22 -0
- package/dist/web-router/components/SplitInview.jsx +78 -89
- package/dist/web-router/components/SplitInview.test.jsx +112 -0
- package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
- package/dist/web-router/components/SsrRequestContext.jsx +17 -7
- package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
- package/dist/web-router/context.d.ts +15 -0
- package/dist/web-router/context.jsx +5 -5
- package/dist/web-router/context.test.jsx +10 -0
- package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +86 -86
- package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
- package/dist/web-router/vitest.setup.js +12 -0
- package/mcp_v2/knowledge-base.json +115 -17
- package/mcp_v2/mcp.js +141 -3
- package/mcp_v2/package-lock.json +2031 -0
- package/mcp_v2/package.json +13 -2
- package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
- package/mcp_v2/test/errors.test.ts +123 -0
- package/mcp_v2/test/github.test.ts +177 -0
- package/mcp_v2/tools/errors.d.ts +20 -0
- package/mcp_v2/tools/errors.js +63 -0
- package/mcp_v2/tools/github.d.ts +41 -0
- package/mcp_v2/tools/github.js +231 -0
- package/mcp_v2/vitest.config.mjs +22 -0
- package/package.json +60 -11
- package/test/errors/docsDrift.test.ts +113 -0
- package/test/errors/index.test.ts +200 -0
- package/test/errors/registry.test.ts +108 -0
- package/test/scripts/preflight.test.ts +118 -0
- package/test/server/ChunkExtractor.test.ts +97 -0
- package/test/server/extract.test.ts +128 -0
- package/test/server/fixtures/template/server/document.jsx +19 -0
- package/test/server/fixtures/template/server/index.js +10 -0
- package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
- package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
- package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
- package/test/server/fixtures/template/src/js/store/index.js +19 -0
- package/test/server/handler.assets.test.ts +330 -0
- package/test/server/handler.test.ts +208 -0
- package/test/server/manifestCache.test.ts +71 -0
- package/test/server/render.test.ts +71 -0
- package/test/server/scriptUtils.test.ts +103 -0
- package/test/server/userAgentUtil.test.ts +48 -0
- package/test/server/validator.test.ts +225 -0
- package/test/vite/resolveDevServerConfig.test.js +4 -1
- package/tsconfig.test.json +23 -0
- package/vitest.config.ts +93 -0
- package/dist/caching.js +0 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
- package/dist/native/bridge/standardizedHooks.js +0 -611
- package/dist/router.js +0 -1
- package/dist/scripts/validator.js +0 -149
- package/dist/web-router/hooks.jsx +0 -72
- package/dist/web-router/types/components/MetaTag.d.ts +0 -2
- package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
- package/dist/web-router/types/context.d.ts +0 -14
- package/dist/web-router/types/hooks.d.ts +0 -1
- package/dist/web-router/types/index.d.ts +0 -3
- package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import CatalystCoreLogic
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unit tests for CatalystConstants
|
|
6
|
+
*
|
|
7
|
+
* Most of this file is static constant declarations, exercised here mainly
|
|
8
|
+
* as sanity/regression checks. The two pieces of real logic —
|
|
9
|
+
* `Bridge.isTraceExportEnabled` and `Bridge.validCommands` — get more
|
|
10
|
+
* thorough coverage.
|
|
11
|
+
*
|
|
12
|
+
* Known gap (documented, not silently accepted): `validCommands` also reads
|
|
13
|
+
* module-level `ConfigConstants.Profiler.enabled` /
|
|
14
|
+
* `ConfigConstants.Notifications.enabled`, which are `public static let`
|
|
15
|
+
* compile-time constants from the checked-in stub (both `false`). There is
|
|
16
|
+
* no test seam (unlike URLWhitelistManager's `testInitialize`) to force
|
|
17
|
+
* those to `true`, so the two `if` branches that add
|
|
18
|
+
* "exportCatalystTrace"/notification commands to `validCommands` cannot be
|
|
19
|
+
* exercised from this file alone. `isTraceExportEnabled` itself IS fully
|
|
20
|
+
* covered below since its parameters can be passed explicitly, bypassing
|
|
21
|
+
* the defaults.
|
|
22
|
+
*/
|
|
23
|
+
final class CatalystConstantsTests: XCTestCase {
|
|
24
|
+
|
|
25
|
+
// MARK: - FileTransport
|
|
26
|
+
|
|
27
|
+
func testFileTransport_Base64SizeLimit() {
|
|
28
|
+
XCTAssertEqual(CatalystConstants.FileTransport.base64SizeLimit, 2 * 1024 * 1024)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func testFileTransport_FrameworkServerSizeLimit() {
|
|
32
|
+
XCTAssertEqual(CatalystConstants.FileTransport.frameworkServerSizeLimit, 100 * 1024 * 1024)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// MARK: - ImageProcessing
|
|
36
|
+
|
|
37
|
+
func testImageProcessing_QualityLevels() {
|
|
38
|
+
XCTAssertEqual(CatalystConstants.ImageProcessing.Quality.high, 0.9)
|
|
39
|
+
XCTAssertEqual(CatalystConstants.ImageProcessing.Quality.medium, 0.7)
|
|
40
|
+
XCTAssertEqual(CatalystConstants.ImageProcessing.Quality.low, 0.5)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testImageProcessing_DefaultQualityMatchesMedium() {
|
|
44
|
+
XCTAssertEqual(
|
|
45
|
+
CatalystConstants.ImageProcessing.defaultQuality,
|
|
46
|
+
CatalystConstants.ImageProcessing.Quality.medium
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - NetworkServer
|
|
51
|
+
|
|
52
|
+
func testNetworkServer_PortRangeIsValidAndNonEmpty() {
|
|
53
|
+
let start = CatalystConstants.NetworkServer.portRangeStart
|
|
54
|
+
let end = CatalystConstants.NetworkServer.portRangeEnd
|
|
55
|
+
XCTAssertLessThan(start, end)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func testNetworkServer_TimeoutsArePositive() {
|
|
59
|
+
XCTAssertGreaterThan(CatalystConstants.NetworkServer.sessionTimeout, 0)
|
|
60
|
+
XCTAssertGreaterThan(CatalystConstants.NetworkServer.cleanupInterval, 0)
|
|
61
|
+
XCTAssertGreaterThan(CatalystConstants.NetworkServer.connectionTimeout, 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func testNetworkServer_MaxConnectionsIsPositive() {
|
|
65
|
+
XCTAssertGreaterThan(CatalystConstants.NetworkServer.maxConnections, 0)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - ErrorCodes
|
|
69
|
+
|
|
70
|
+
func testErrorCodes_MatchStandardHTTPStatusCodes() {
|
|
71
|
+
XCTAssertEqual(CatalystConstants.ErrorCodes.badRequest, 400)
|
|
72
|
+
XCTAssertEqual(CatalystConstants.ErrorCodes.fileNotFound, 404)
|
|
73
|
+
XCTAssertEqual(CatalystConstants.ErrorCodes.internalServerError, 500)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// MARK: - Cache
|
|
77
|
+
|
|
78
|
+
func testCache_FreshWindowIsShorterThanStaleWindow() {
|
|
79
|
+
XCTAssertLessThan(
|
|
80
|
+
CatalystConstants.Cache.freshWindow,
|
|
81
|
+
CatalystConstants.Cache.staleWindow
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func testCache_CapacitiesAreDiskLargerThanMemory() {
|
|
86
|
+
XCTAssertLessThan(
|
|
87
|
+
CatalystConstants.Cache.memoryCapacity,
|
|
88
|
+
CatalystConstants.Cache.diskCapacity
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - Logging.Categories
|
|
93
|
+
|
|
94
|
+
func testLogging_CategoriesAreAllNonEmptyAndDistinct() {
|
|
95
|
+
let categories = [
|
|
96
|
+
CatalystConstants.Logging.Categories.nativeBridge,
|
|
97
|
+
CatalystConstants.Logging.Categories.javascriptInterface,
|
|
98
|
+
CatalystConstants.Logging.Categories.messageValidator,
|
|
99
|
+
CatalystConstants.Logging.Categories.commandHandler,
|
|
100
|
+
CatalystConstants.Logging.Categories.fileHandler,
|
|
101
|
+
CatalystConstants.Logging.Categories.delegateHandler,
|
|
102
|
+
]
|
|
103
|
+
XCTAssertTrue(categories.allSatisfy { !$0.isEmpty })
|
|
104
|
+
XCTAssertEqual(Set(categories).count, categories.count, "Logging categories should be distinct")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - Bridge.isDebugBuild
|
|
108
|
+
|
|
109
|
+
func testBridge_IsDebugBuildMatchesBuildConfiguration() {
|
|
110
|
+
#if DEBUG
|
|
111
|
+
XCTAssertTrue(CatalystConstants.Bridge.isDebugBuild)
|
|
112
|
+
#else
|
|
113
|
+
XCTAssertFalse(CatalystConstants.Bridge.isDebugBuild)
|
|
114
|
+
#endif
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Bridge.isTraceExportEnabled — all 4 explicit combinations
|
|
118
|
+
|
|
119
|
+
func testIsTraceExportEnabled_DebugTrueProfilerTrue_ReturnsTrue() {
|
|
120
|
+
XCTAssertTrue(
|
|
121
|
+
CatalystConstants.Bridge.isTraceExportEnabled(debugBuild: true, profilerEnabled: true)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func testIsTraceExportEnabled_DebugTrueProfilerFalse_ReturnsFalse() {
|
|
126
|
+
XCTAssertFalse(
|
|
127
|
+
CatalystConstants.Bridge.isTraceExportEnabled(debugBuild: true, profilerEnabled: false)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func testIsTraceExportEnabled_DebugFalseProfilerTrue_ReturnsFalse() {
|
|
132
|
+
XCTAssertFalse(
|
|
133
|
+
CatalystConstants.Bridge.isTraceExportEnabled(debugBuild: false, profilerEnabled: true)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func testIsTraceExportEnabled_DebugFalseProfilerFalse_ReturnsFalse() {
|
|
138
|
+
XCTAssertFalse(
|
|
139
|
+
CatalystConstants.Bridge.isTraceExportEnabled(debugBuild: false, profilerEnabled: false)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// MARK: - Bridge.validCommands
|
|
144
|
+
|
|
145
|
+
func testValidCommands_ContainsAlwaysPresentBaseCommands() {
|
|
146
|
+
let commands = CatalystConstants.Bridge.validCommands
|
|
147
|
+
|
|
148
|
+
let expectedBaseCommands = [
|
|
149
|
+
"openCamera", "requestCameraPermission", "getDeviceInfo", "getNetworkStatus",
|
|
150
|
+
"logger", "pickFile", "openFileWithIntent", "requestHapticFeedback",
|
|
151
|
+
"googleSignIn", "getSafeArea", "setScreenSecure", "getScreenSecure",
|
|
152
|
+
"clearWebData", "startVideoStream", "stopVideoStream", "flipVideoStream",
|
|
153
|
+
"sendVideoStreamCommand", "setVideoStreamZoom", "setVideoStreamTorch",
|
|
154
|
+
"setVideoStreamFps", "startTransition", "commitTransition", "cancelTransition",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for command in expectedBaseCommands {
|
|
158
|
+
XCTAssertTrue(commands.contains(command), "Expected base command missing: \(command)")
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Known gap: with the checked-in ConfigConstants stub (Profiler.enabled
|
|
163
|
+
/// and Notifications.enabled both false), validCommands cannot be forced
|
|
164
|
+
/// down its `true`-branches from this test file — there's no equivalent
|
|
165
|
+
/// of URLWhitelistManager's testInitialize() seam for ConfigConstants.
|
|
166
|
+
/// This test documents and asserts the actual (disabled-config) behavior
|
|
167
|
+
/// rather than silently skipping the file.
|
|
168
|
+
func testValidCommands_ExportTraceAndNotificationCommandsAbsentUnderDisabledConfig() {
|
|
169
|
+
let commands = CatalystConstants.Bridge.validCommands
|
|
170
|
+
|
|
171
|
+
if !CatalystConstants.Bridge.isTraceExportEnabled() {
|
|
172
|
+
XCTAssertFalse(commands.contains("exportCatalystTrace"))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if !ConfigConstants.Notifications.enabled {
|
|
176
|
+
let notificationCommands = [
|
|
177
|
+
"requestNotificationPermission", "scheduleLocalNotification",
|
|
178
|
+
"cancelLocalNotification", "registerForPushNotifications",
|
|
179
|
+
"subscribeToTopic", "unsubscribeFromTopic", "getSubscribedTopics",
|
|
180
|
+
]
|
|
181
|
+
for command in notificationCommands {
|
|
182
|
+
XCTAssertFalse(commands.contains(command), "Unexpected notification command present: \(command)")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func testValidCommands_IsStableAcrossRepeatedCalls() {
|
|
188
|
+
let first = CatalystConstants.Bridge.validCommands
|
|
189
|
+
let second = CatalystConstants.Bridge.validCommands
|
|
190
|
+
XCTAssertEqual(first, second)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import Foundation
|
|
3
|
+
@testable import CatalystCoreLogic
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Targeted unit tests for FrameworkServerUtils' internal error/edge branches
|
|
7
|
+
* that don't need a running server or a real HTTP round-trip — the pure
|
|
8
|
+
* validation logic (validateFilePath, updateAllowedOrigin,
|
|
9
|
+
* addFileToServe's not-running guard) and FrameworkServerError's own
|
|
10
|
+
* computed property.
|
|
11
|
+
*
|
|
12
|
+
* Complements FrameworkServerUtilsTests.swift (lifecycle/config) and
|
|
13
|
+
* FrameworkServerUtilsLoopbackTests.swift (real request/response
|
|
14
|
+
* round-trips) — this file is specifically the "call the internal method
|
|
15
|
+
* directly with an input designed to hit one branch" layer.
|
|
16
|
+
*/
|
|
17
|
+
final class FrameworkServerUtilsErrorPathTests: XCTestCase {
|
|
18
|
+
|
|
19
|
+
var frameworkServer: FrameworkServerUtils!
|
|
20
|
+
|
|
21
|
+
override func setUp() {
|
|
22
|
+
super.setUp()
|
|
23
|
+
frameworkServer = FrameworkServerUtils.shared
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override func tearDown() {
|
|
27
|
+
if frameworkServer.isRunning() {
|
|
28
|
+
frameworkServer.stopServer()
|
|
29
|
+
}
|
|
30
|
+
frameworkServer = nil
|
|
31
|
+
super.tearDown()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// MARK: - FrameworkServerError.localizedDescription
|
|
35
|
+
|
|
36
|
+
func testFrameworkServerError_CacheInitializationFailed_Description() {
|
|
37
|
+
let error = FrameworkServerError.cacheInitializationFailed("disk full")
|
|
38
|
+
XCTAssertEqual(error.localizedDescription, "Cache initialization failed: disk full")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func testFrameworkServerError_DataConversionFailed_Description() {
|
|
42
|
+
let error = FrameworkServerError.dataConversionFailed("bad encoding")
|
|
43
|
+
XCTAssertEqual(error.localizedDescription, "Data conversion failed: bad encoding")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func testFrameworkServerError_ConnectionHandlingFailed_Description() {
|
|
47
|
+
let error = FrameworkServerError.connectionHandlingFailed("socket closed")
|
|
48
|
+
XCTAssertEqual(error.localizedDescription, "Connection handling failed: socket closed")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// MARK: - addFileToServe: not-running guard
|
|
52
|
+
|
|
53
|
+
func testAddFileToServe_ServerNotRunning_ReturnsNil() {
|
|
54
|
+
// Server is deliberately not started in this test.
|
|
55
|
+
XCTAssertFalse(frameworkServer.isRunning())
|
|
56
|
+
|
|
57
|
+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("not-served.txt")
|
|
58
|
+
try? Data("x".utf8).write(to: tempURL)
|
|
59
|
+
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
60
|
+
|
|
61
|
+
let result = frameworkServer.addFileToServe(file: tempURL, fileName: "not-served.txt", mimeType: "text/plain")
|
|
62
|
+
|
|
63
|
+
XCTAssertNil(result)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - copyAndServeFile: not-running / no cache directory guard
|
|
67
|
+
|
|
68
|
+
func testCopyAndServeFile_ServerNotRunning_ReturnsNil() {
|
|
69
|
+
XCTAssertFalse(frameworkServer.isRunning())
|
|
70
|
+
|
|
71
|
+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("not-copied.txt")
|
|
72
|
+
try? Data("x".utf8).write(to: tempURL)
|
|
73
|
+
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
74
|
+
|
|
75
|
+
let result = frameworkServer.copyAndServeFile(originalFile: tempURL, fileName: "not-copied.txt", mimeType: "text/plain")
|
|
76
|
+
|
|
77
|
+
XCTAssertNil(result)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// MARK: - updateAllowedOrigin
|
|
81
|
+
|
|
82
|
+
func testUpdateAllowedOrigin_EmptyURL_FallsBackToWildcard() {
|
|
83
|
+
// No direct getter for allowedOrigin, but this exercises the
|
|
84
|
+
// internal branch without crashing and documents the contract.
|
|
85
|
+
frameworkServer.updateAllowedOrigin(from: "")
|
|
86
|
+
// No assertion on internal state (private) — the value is exercised
|
|
87
|
+
// for real via the CORS header in the loopback tests' status
|
|
88
|
+
// endpoint response headers in a follow-up if a getter is ever added.
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func testUpdateAllowedOrigin_ValidURL_ExtractsSchemeHostPort() {
|
|
92
|
+
frameworkServer.updateAllowedOrigin(from: "http://192.168.0.104:3005/some/path")
|
|
93
|
+
// Same note as above — exercises the parse-success branch.
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func testUpdateAllowedOrigin_UnparsableURL_FallsBackToWildcard() {
|
|
97
|
+
// A string with characters URLComponents can't parse as a URL at all.
|
|
98
|
+
frameworkServer.updateAllowedOrigin(from: "http://[invalid")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func testUpdateAllowedOrigin_URLWithNoHostOrScheme_FallsBackToWildcard() {
|
|
102
|
+
// Parses as URLComponents but yields no scheme/host/port to build
|
|
103
|
+
// an origin from.
|
|
104
|
+
frameworkServer.updateAllowedOrigin(from: "/relative/path/only")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import Foundation
|
|
3
|
+
@testable import CatalystCoreLogic
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* In-process loopback HTTP tests for FrameworkServerUtils.
|
|
7
|
+
*
|
|
8
|
+
* The existing FrameworkServerUtilsTests.swift covers server lifecycle,
|
|
9
|
+
* config, and port/session-id logic, but skips actual request handling
|
|
10
|
+
* (receiveHTTPRequest / processHTTPRequest / serveFile / sendHTTPHeaders /
|
|
11
|
+
* streamFileContent) — that's most of the file's uncovered ~65%. This file
|
|
12
|
+
* makes real HTTP requests over URLSession against http://localhost:<port>
|
|
13
|
+
* to exercise that path directly.
|
|
14
|
+
*
|
|
15
|
+
* Not Tier 2 (#416, scaffolded-app simulator tests) — this stays entirely
|
|
16
|
+
* in-process, no simulator, same package/target as every other CoreLogic
|
|
17
|
+
* test. It's the loopback-client middle ground flagged when the coverage
|
|
18
|
+
* math showed 95% package-wide wasn't reachable while FrameworkServerUtils
|
|
19
|
+
* stayed at ~35%.
|
|
20
|
+
*
|
|
21
|
+
* Same "server may not start in this sandbox" guard as the existing test
|
|
22
|
+
* file — network restrictions in some CI/sandbox environments can prevent
|
|
23
|
+
* NWListener from binding. Tests skip (not fail) in that case, consistent
|
|
24
|
+
* with the established pattern.
|
|
25
|
+
*/
|
|
26
|
+
final class FrameworkServerUtilsLoopbackTests: XCTestCase {
|
|
27
|
+
|
|
28
|
+
var frameworkServer: FrameworkServerUtils!
|
|
29
|
+
private var tempFileURL: URL?
|
|
30
|
+
|
|
31
|
+
override func setUp() {
|
|
32
|
+
super.setUp()
|
|
33
|
+
frameworkServer = FrameworkServerUtils.shared
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override func tearDown() {
|
|
37
|
+
if let tempFileURL {
|
|
38
|
+
try? FileManager.default.removeItem(at: tempFileURL)
|
|
39
|
+
}
|
|
40
|
+
tempFileURL = nil
|
|
41
|
+
|
|
42
|
+
if frameworkServer.isRunning() {
|
|
43
|
+
frameworkServer.stopServer()
|
|
44
|
+
}
|
|
45
|
+
frameworkServer = nil
|
|
46
|
+
super.tearDown()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Starts the server and waits for NWListener's async stateUpdateHandler
|
|
50
|
+
/// to settle, mirroring the pattern already established in
|
|
51
|
+
/// FrameworkServerUtilsTests.swift. Returns false (and the caller should
|
|
52
|
+
/// skip) if the server isn't actually running afterward.
|
|
53
|
+
private func startServerAndWaitReady() -> Bool {
|
|
54
|
+
let started = frameworkServer.startServer()
|
|
55
|
+
guard started else { return false }
|
|
56
|
+
Thread.sleep(forTimeInterval: 0.3)
|
|
57
|
+
return frameworkServer.isRunning()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private func serveTemporaryFile(content: String, fileName: String, mimeType: String) throws -> String? {
|
|
61
|
+
let data = Data(content.utf8)
|
|
62
|
+
let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
|
|
63
|
+
try data.write(to: url)
|
|
64
|
+
tempFileURL = url
|
|
65
|
+
|
|
66
|
+
return frameworkServer.copyAndServeFile(originalFile: url, fileName: fileName, mimeType: mimeType)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private func makeRequest(
|
|
70
|
+
url: URL,
|
|
71
|
+
method: String = "GET"
|
|
72
|
+
) async throws -> (HTTPURLResponse, Data) {
|
|
73
|
+
var request = URLRequest(url: url)
|
|
74
|
+
request.httpMethod = method
|
|
75
|
+
request.timeoutInterval = 5
|
|
76
|
+
|
|
77
|
+
let (data, response) = try await URLSession.shared.data(for: request)
|
|
78
|
+
let httpResponse = try XCTUnwrap(response as? HTTPURLResponse)
|
|
79
|
+
return (httpResponse, data)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// MARK: - Status endpoint
|
|
83
|
+
|
|
84
|
+
func testStatusEndpoint_ReturnsRunningJSON() async throws {
|
|
85
|
+
guard startServerAndWaitReady() else {
|
|
86
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let port = frameworkServer.getServerPort()
|
|
90
|
+
let sessionId = frameworkServer.getSessionId()
|
|
91
|
+
let statusURL = try XCTUnwrap(URL(string: "http://localhost:\(port)/framework-\(sessionId)/status"))
|
|
92
|
+
|
|
93
|
+
let (response, data) = try await makeRequest(url: statusURL)
|
|
94
|
+
|
|
95
|
+
XCTAssertEqual(response.statusCode, 200)
|
|
96
|
+
XCTAssertEqual(response.value(forHTTPHeaderField: "Content-Type"), "application/json")
|
|
97
|
+
|
|
98
|
+
let json = try XCTUnwrap(try JSONSerialization.jsonObject(with: data) as? [String: Any])
|
|
99
|
+
XCTAssertEqual(json["status"] as? String, "running")
|
|
100
|
+
XCTAssertEqual(json["sessionId"] as? String, sessionId)
|
|
101
|
+
XCTAssertEqual(json["port"] as? Int, Int(port))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - File serving: success path
|
|
105
|
+
|
|
106
|
+
func testFileRequest_ServesRealFileContent() async throws {
|
|
107
|
+
guard startServerAndWaitReady() else {
|
|
108
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let content = "loopback test file content \(UUID().uuidString)"
|
|
112
|
+
let servedURLString = try XCTUnwrap(
|
|
113
|
+
try serveTemporaryFile(content: content, fileName: "loopback-test.txt", mimeType: "text/plain")
|
|
114
|
+
)
|
|
115
|
+
let servedURL = try XCTUnwrap(URL(string: servedURLString))
|
|
116
|
+
|
|
117
|
+
let (response, data) = try await makeRequest(url: servedURL)
|
|
118
|
+
|
|
119
|
+
XCTAssertEqual(response.statusCode, 200)
|
|
120
|
+
XCTAssertEqual(response.value(forHTTPHeaderField: "Content-Type"), "text/plain")
|
|
121
|
+
XCTAssertEqual(String(data: data, encoding: .utf8), content)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func testFileRequest_ContentLengthHeaderMatchesActualBytes() async throws {
|
|
125
|
+
guard startServerAndWaitReady() else {
|
|
126
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let content = String(repeating: "x", count: 5000)
|
|
130
|
+
let servedURLString = try XCTUnwrap(
|
|
131
|
+
try serveTemporaryFile(content: content, fileName: "loopback-large.txt", mimeType: "text/plain")
|
|
132
|
+
)
|
|
133
|
+
let servedURL = try XCTUnwrap(URL(string: servedURLString))
|
|
134
|
+
|
|
135
|
+
let (response, data) = try await makeRequest(url: servedURL)
|
|
136
|
+
|
|
137
|
+
XCTAssertEqual(response.statusCode, 200)
|
|
138
|
+
XCTAssertEqual(data.count, 5000)
|
|
139
|
+
let contentLengthHeader = response.value(forHTTPHeaderField: "Content-Length")
|
|
140
|
+
XCTAssertEqual(contentLengthHeader, "5000")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// MARK: - File serving: error paths
|
|
144
|
+
|
|
145
|
+
func testFileRequest_UnknownFileId_Returns404() async throws {
|
|
146
|
+
guard startServerAndWaitReady() else {
|
|
147
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let port = frameworkServer.getServerPort()
|
|
151
|
+
let sessionId = frameworkServer.getSessionId()
|
|
152
|
+
let url = try XCTUnwrap(URL(string: "http://localhost:\(port)/framework-\(sessionId)/file-does-not-exist"))
|
|
153
|
+
|
|
154
|
+
let (response, _) = try await makeRequest(url: url)
|
|
155
|
+
|
|
156
|
+
XCTAssertEqual(response.statusCode, 404)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func testInvalidRoute_Returns404() async throws {
|
|
160
|
+
guard startServerAndWaitReady() else {
|
|
161
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let port = frameworkServer.getServerPort()
|
|
165
|
+
let url = try XCTUnwrap(URL(string: "http://localhost:\(port)/not-a-real-route"))
|
|
166
|
+
|
|
167
|
+
let (response, _) = try await makeRequest(url: url)
|
|
168
|
+
|
|
169
|
+
XCTAssertEqual(response.statusCode, 404)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func testNonGETMethod_Returns405() async throws {
|
|
173
|
+
guard startServerAndWaitReady() else {
|
|
174
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let port = frameworkServer.getServerPort()
|
|
178
|
+
let sessionId = frameworkServer.getSessionId()
|
|
179
|
+
let url = try XCTUnwrap(URL(string: "http://localhost:\(port)/framework-\(sessionId)/status"))
|
|
180
|
+
|
|
181
|
+
let (response, _) = try await makeRequest(url: url, method: "POST")
|
|
182
|
+
|
|
183
|
+
XCTAssertEqual(response.statusCode, 405)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// MARK: - Physical file missing after being registered (not just unknown id)
|
|
187
|
+
|
|
188
|
+
func testFileRequest_PhysicalFileDeletedAfterRegistration_Returns404() async throws {
|
|
189
|
+
guard startServerAndWaitReady() else {
|
|
190
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let servedURLString = try XCTUnwrap(
|
|
194
|
+
try serveTemporaryFile(content: "will be deleted from cache", fileName: "loopback-vanish.txt", mimeType: "text/plain")
|
|
195
|
+
)
|
|
196
|
+
let servedURL = try XCTUnwrap(URL(string: servedURLString))
|
|
197
|
+
|
|
198
|
+
// copyAndServeFile copies into a documents-directory cache path
|
|
199
|
+
// deterministically named "<timestamp>_<fileName>" — reconstruct
|
|
200
|
+
// and delete it directly (cacheDirectory itself is private) to
|
|
201
|
+
// simulate the file having vanished between being registered and
|
|
202
|
+
// being requested (servedFile entry still present, physical file
|
|
203
|
+
// gone), exercising serveFile's "Physical file not found" branch
|
|
204
|
+
// rather than addFileToServe's earlier existence checks.
|
|
205
|
+
let documentsDirectory = try XCTUnwrap(
|
|
206
|
+
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
|
207
|
+
)
|
|
208
|
+
let cacheDirectory = documentsDirectory.appendingPathComponent("framework_server_files", isDirectory: true)
|
|
209
|
+
let cachedFiles = try FileManager.default.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: nil)
|
|
210
|
+
let vanishedCacheFile = try XCTUnwrap(cachedFiles.first { $0.lastPathComponent.hasSuffix("loopback-vanish.txt") })
|
|
211
|
+
try FileManager.default.removeItem(at: vanishedCacheFile)
|
|
212
|
+
|
|
213
|
+
let (response, _) = try await makeRequest(url: servedURL)
|
|
214
|
+
|
|
215
|
+
XCTAssertEqual(response.statusCode, 404)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// MARK: - removeServedFile
|
|
219
|
+
|
|
220
|
+
func testRemoveServedFile_SubsequentRequestReturns404() async throws {
|
|
221
|
+
guard startServerAndWaitReady() else {
|
|
222
|
+
throw XCTSkip("Server failed to start in this environment — skipping loopback test")
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let servedURLString = try XCTUnwrap(
|
|
226
|
+
try serveTemporaryFile(content: "to be removed", fileName: "loopback-remove.txt", mimeType: "text/plain")
|
|
227
|
+
)
|
|
228
|
+
let servedURL = try XCTUnwrap(URL(string: servedURLString))
|
|
229
|
+
|
|
230
|
+
// Sanity: file is servable before removal.
|
|
231
|
+
let (beforeResponse, _) = try await makeRequest(url: servedURL)
|
|
232
|
+
XCTAssertEqual(beforeResponse.statusCode, 200)
|
|
233
|
+
|
|
234
|
+
let fileId = String(servedURL.lastPathComponent.dropFirst("file-".count))
|
|
235
|
+
frameworkServer.removeServedFile(fileId: fileId)
|
|
236
|
+
|
|
237
|
+
// Give the barrier-queued removal a moment to land before re-requesting.
|
|
238
|
+
try await Task.sleep(nanoseconds: 100_000_000)
|
|
239
|
+
|
|
240
|
+
let (afterResponse, _) = try await makeRequest(url: servedURL)
|
|
241
|
+
XCTAssertEqual(afterResponse.statusCode, 404)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import Network
|
|
3
|
+
@testable import CatalystCoreLogic
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unit tests for NetworkMonitor
|
|
7
|
+
*
|
|
8
|
+
* NetworkMonitor is a `private init()` singleton wrapping a real
|
|
9
|
+
* NWPathMonitor, with no dependency-injection seam (unlike
|
|
10
|
+
* URLWhitelistManager's testInitialize()). Adding real DI is a production
|
|
11
|
+
* code change out of scope for a mechanical coverage pass — flagged as a
|
|
12
|
+
* follow-up (see issue #432 plan notes), not attempted here.
|
|
13
|
+
*
|
|
14
|
+
* What IS covered: `mapPathToStatus`, the one pure/static piece of logic in
|
|
15
|
+
* this file, made `internal` (from `private`) specifically so @testable
|
|
16
|
+
* import can reach it directly — no singleton, no async callback needed.
|
|
17
|
+
*
|
|
18
|
+
* `NWPath` has no public initializer for arbitrary synthetic states, so the
|
|
19
|
+
* "happy path" tests below obtain a REAL NWPath from a short-lived
|
|
20
|
+
* NWPathMonitor rather than hand-built fixtures. This means only whatever
|
|
21
|
+
* interface type is actually active on the test-running machine gets
|
|
22
|
+
* exercised (typically wifi or ethernet in this environment) — the
|
|
23
|
+
* cellular/loopback/"other"/none branches of the interface-type mapping
|
|
24
|
+
* are NOT exercised by this file. That is the honest, stated coverage gap
|
|
25
|
+
* for this file, not a hidden one.
|
|
26
|
+
*
|
|
27
|
+
* NetworkStatus's own struct/field access and the singleton's
|
|
28
|
+
* currentStatus/addListener/removeListener lifecycle (async, dependent on
|
|
29
|
+
* a live path callback firing) are left untested for the same DI reason.
|
|
30
|
+
*/
|
|
31
|
+
final class NetworkMonitorTests: XCTestCase {
|
|
32
|
+
|
|
33
|
+
/// Waits for a real NWPath from a fresh, short-lived monitor. Not the
|
|
34
|
+
/// shared NetworkMonitor singleton — an independent NWPathMonitor so
|
|
35
|
+
/// this test doesn't depend on NetworkMonitor's internal state/timing.
|
|
36
|
+
private func waitForRealPath(timeout: TimeInterval = 5) -> NWPath? {
|
|
37
|
+
let monitor = NWPathMonitor()
|
|
38
|
+
let expectation = self.expectation(description: "NWPathMonitor delivered a path")
|
|
39
|
+
var capturedPath: NWPath?
|
|
40
|
+
|
|
41
|
+
monitor.pathUpdateHandler = { path in
|
|
42
|
+
capturedPath = path
|
|
43
|
+
expectation.fulfill()
|
|
44
|
+
}
|
|
45
|
+
monitor.start(queue: DispatchQueue.global())
|
|
46
|
+
|
|
47
|
+
wait(for: [expectation], timeout: timeout)
|
|
48
|
+
monitor.cancel()
|
|
49
|
+
return capturedPath
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func testMapPathToStatus_RealPathProducesConsistentIsOnline() throws {
|
|
53
|
+
guard let path = waitForRealPath() else {
|
|
54
|
+
throw XCTSkip("No NWPath delivered within timeout — cannot exercise mapPathToStatus without a live path")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let status = NetworkMonitor.mapPathToStatus(path)
|
|
58
|
+
|
|
59
|
+
// isOnline should exactly mirror path.status == .satisfied
|
|
60
|
+
XCTAssertEqual(status.isOnline, path.status == .satisfied)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func testMapPathToStatus_RealPathTypeMatchesActualInterface() throws {
|
|
64
|
+
guard let path = waitForRealPath() else {
|
|
65
|
+
throw XCTSkip("No NWPath delivered within timeout — cannot exercise mapPathToStatus without a live path")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let status = NetworkMonitor.mapPathToStatus(path)
|
|
69
|
+
|
|
70
|
+
// Whichever interface is actually active should be reflected;
|
|
71
|
+
// this is inherently machine-dependent, so assert consistency
|
|
72
|
+
// with the source path rather than a specific expected value.
|
|
73
|
+
if path.usesInterfaceType(.wifi) {
|
|
74
|
+
XCTAssertEqual(status.type, "wifi")
|
|
75
|
+
} else if path.usesInterfaceType(.cellular) {
|
|
76
|
+
XCTAssertEqual(status.type, "cellular")
|
|
77
|
+
} else if path.usesInterfaceType(.wiredEthernet) {
|
|
78
|
+
XCTAssertEqual(status.type, "ethernet")
|
|
79
|
+
} else if path.usesInterfaceType(.other) {
|
|
80
|
+
XCTAssertEqual(status.type, "other")
|
|
81
|
+
} else if path.usesInterfaceType(.loopback) {
|
|
82
|
+
XCTAssertEqual(status.type, "loopback")
|
|
83
|
+
} else {
|
|
84
|
+
XCTAssertNil(status.type)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func testMapPathToStatus_IsPureAndDeterministicForSamePath() throws {
|
|
89
|
+
guard let path = waitForRealPath() else {
|
|
90
|
+
throw XCTSkip("No NWPath delivered within timeout — cannot exercise mapPathToStatus without a live path")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let first = NetworkMonitor.mapPathToStatus(path)
|
|
94
|
+
let second = NetworkMonitor.mapPathToStatus(path)
|
|
95
|
+
|
|
96
|
+
XCTAssertEqual(first.isOnline, second.isOnline)
|
|
97
|
+
XCTAssertEqual(first.type, second.type)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: - NetworkStatus struct itself
|
|
101
|
+
|
|
102
|
+
func testNetworkStatus_StoresProvidedValues() {
|
|
103
|
+
let online = NetworkStatus(isOnline: true, type: "wifi")
|
|
104
|
+
XCTAssertTrue(online.isOnline)
|
|
105
|
+
XCTAssertEqual(online.type, "wifi")
|
|
106
|
+
|
|
107
|
+
let offline = NetworkStatus(isOnline: false, type: nil)
|
|
108
|
+
XCTAssertFalse(offline.isOnline)
|
|
109
|
+
XCTAssertNil(offline.type)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// MARK: - Singleton smoke test (no assertions on async behavior)
|
|
113
|
+
|
|
114
|
+
func testShared_ReturnsSameInstanceAcrossAccesses() {
|
|
115
|
+
// Confirms the singleton wiring itself, without depending on any
|
|
116
|
+
// async NWPathMonitor callback having fired yet.
|
|
117
|
+
XCTAssertTrue(NetworkMonitor.shared === NetworkMonitor.shared)
|
|
118
|
+
}
|
|
119
|
+
}
|