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.
Files changed (316) hide show
  1. package/changelog.md +185 -71
  2. package/dist/errors/generateDocs.js +100 -0
  3. package/dist/errors/index.js +243 -0
  4. package/dist/errors/registry.js +483 -0
  5. package/dist/errors-index.json +506 -0
  6. package/dist/globals.d.ts +62 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.jsx +5 -8
  9. package/dist/logger.js +1 -1
  10. package/dist/native/androidProject/app/build.gradle.kts +142 -7
  11. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
  16. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
  17. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
  19. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
  20. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
  21. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
  22. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
  23. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
  24. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
  25. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
  26. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
  27. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
  28. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
  29. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
  30. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
  31. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
  32. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
  33. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
  34. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
  35. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
  36. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
  37. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
  38. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
  39. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
  40. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
  41. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
  42. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
  43. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
  44. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
  46. package/dist/native/androidProject/build.gradle.kts +1 -0
  47. package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
  48. package/dist/native/androidProject/settings.gradle.kts +11 -8
  49. package/dist/native/androidSetup.js +2 -2
  50. package/dist/native/bridge/WebBridge.d.ts +98 -0
  51. package/dist/native/bridge/WebBridge.js +40 -6
  52. package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
  53. package/dist/native/bridge/errors.d.ts +67 -0
  54. package/dist/native/bridge/errors.js +66 -151
  55. package/dist/native/bridge/hooks/index.d.ts +13 -0
  56. package/dist/native/bridge/hooks/index.js +0 -6
  57. package/dist/native/bridge/hooks/useAI.d.ts +1 -0
  58. package/dist/native/bridge/hooks/useAI.js +3 -1
  59. package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
  60. package/dist/native/bridge/hooks/useCamera.js +35 -6
  61. package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
  62. package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
  63. package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
  64. package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
  65. package/dist/native/bridge/hooks/useFilePicker.js +13 -0
  66. package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
  67. package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
  68. package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
  69. package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
  70. package/dist/native/bridge/hooks/useIntent.js +2 -0
  71. package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
  72. package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
  73. package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
  74. package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
  75. package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
  76. package/dist/native/bridge/hooks/useNotification.js +81 -34
  77. package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
  78. package/dist/native/bridge/hooks/useSafeArea.js +8 -0
  79. package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
  80. package/dist/native/bridge/hooks/useVideoStream.js +38 -52
  81. package/dist/native/bridge/hooks/utils.d.ts +4 -0
  82. package/dist/native/bridge/hooks.d.ts +1 -0
  83. package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
  84. package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
  85. package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
  86. package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
  87. package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
  88. package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
  89. package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
  90. package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
  91. package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
  92. package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
  93. package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
  94. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
  95. package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
  96. package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
  97. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
  98. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
  99. package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
  100. package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
  101. package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
  102. package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
  103. package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
  104. package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
  105. package/dist/native/bridge/perf/core/constants.d.ts +76 -0
  106. package/dist/native/bridge/perf/core/measure.d.ts +39 -0
  107. package/dist/native/bridge/perf/core/measure.js +4 -0
  108. package/dist/native/bridge/perf/core/session.d.ts +49 -0
  109. package/dist/native/bridge/perf/core/session.js +4 -0
  110. package/dist/native/bridge/perf/core/store.d.ts +37 -0
  111. package/dist/native/bridge/perf/core/store.js +4 -0
  112. package/dist/native/bridge/perf/core/summary.d.ts +60 -0
  113. package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
  114. package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
  115. package/dist/native/bridge/perf/index.d.ts +63 -0
  116. package/dist/native/bridge/perf/index.js +4 -0
  117. package/dist/native/bridge/safeArea.d.ts +11 -0
  118. package/dist/native/bridge/safeArea.js +2 -0
  119. package/dist/native/bridge/useBaseHook.d.ts +137 -0
  120. package/dist/native/bridge/useBaseHook.js +34 -4
  121. package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
  122. package/dist/native/bridge/utils/CameraUtils.js +4 -0
  123. package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
  124. package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
  125. package/dist/native/bridge/utils/NativeBridge.js +10 -0
  126. package/dist/native/buildAndroid/index.js +1 -1
  127. package/dist/native/buildAppAndroid.js +9 -0
  128. package/dist/native/buildAppIos.js +16 -1
  129. package/dist/native/buildErrorFormat.js +57 -0
  130. package/dist/native/buildIos/config.js +15 -1
  131. package/dist/native/buildIos/index.js +1 -1
  132. package/dist/native/buildIos/plugins.js +10 -1
  133. package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
  134. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
  135. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
  136. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
  137. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
  138. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
  139. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
  140. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
  141. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
  142. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
  143. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
  144. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
  145. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
  146. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
  147. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
  148. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
  149. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
  150. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
  151. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
  152. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
  153. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
  154. package/dist/native/iosnativeWebView/Package.swift +10 -9
  155. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
  156. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
  157. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
  158. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
  159. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
  160. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
  161. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
  162. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
  163. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
  164. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
  165. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
  166. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
  167. package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
  168. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
  169. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
  170. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
  171. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
  172. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
  173. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
  174. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
  175. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
  176. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
  177. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
  178. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
  179. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
  180. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
  181. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
  182. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
  183. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
  184. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
  185. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
  186. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
  187. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
  188. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
  189. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
  190. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
  191. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
  192. package/dist/native/setupEmulatorIos.js +2 -2
  193. package/dist/native/terminalProgress.js +17 -17
  194. package/dist/router/ClientRouter.d.ts +8 -0
  195. package/dist/router/ClientRouter.js +13 -11
  196. package/dist/router/ServerRouter.d.ts +13 -0
  197. package/dist/router/ServerRouter.js +19 -7
  198. package/dist/scripts/build.js +14 -2
  199. package/dist/scripts/loadEnvironmentVariables.js +3 -2
  200. package/dist/scripts/plugins.js +1 -1
  201. package/dist/scripts/preServerInit.js +49 -3
  202. package/dist/scripts/preflight.js +111 -0
  203. package/dist/scripts/scriptUtils.js +46 -1
  204. package/dist/scripts/serve.js +7 -3
  205. package/dist/scripts/start.js +8 -3
  206. package/dist/server/expressServer.d.ts +1 -0
  207. package/dist/server/expressServer.js +247 -271
  208. package/dist/server/manifestCache.d.ts +2 -0
  209. package/dist/server/manifestCache.js +25 -33
  210. package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
  211. package/dist/server/renderer/ChunkExtractor.js +113 -125
  212. package/dist/server/renderer/document/Body.d.ts +21 -0
  213. package/dist/server/renderer/document/Body.jsx +45 -45
  214. package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
  215. package/dist/server/renderer/document/FastRefresh.jsx +7 -13
  216. package/dist/server/renderer/document/Head.d.ts +20 -0
  217. package/dist/server/renderer/document/Head.jsx +32 -32
  218. package/dist/server/renderer/document/index.d.ts +2 -0
  219. package/dist/server/renderer/document/index.jsx +2 -2
  220. package/dist/server/renderer/extract.d.ts +67 -0
  221. package/dist/server/renderer/extract.js +92 -89
  222. package/dist/server/renderer/handler.d.ts +2 -0
  223. package/dist/server/renderer/handler.jsx +401 -404
  224. package/dist/server/renderer/index.d.ts +2 -0
  225. package/dist/server/renderer/index.js +5 -8
  226. package/dist/server/renderer/render.d.ts +35 -0
  227. package/dist/server/renderer/render.js +25 -32
  228. package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
  229. package/dist/server/utils/botDetectionMiddleware.js +33 -23
  230. package/dist/server/utils/cjsRequire.d.ts +8 -0
  231. package/dist/server/utils/cjsRequire.js +2 -2
  232. package/dist/server/utils/userAgentUtil.d.ts +7 -0
  233. package/dist/server/utils/userAgentUtil.js +68 -71
  234. package/dist/server/utils/validator.d.ts +31 -0
  235. package/dist/server/utils/validator.js +133 -158
  236. package/dist/vite/inject-cache-key-plugin.js +3 -27
  237. package/dist/vite/moduleAliases.js +36 -0
  238. package/dist/vite/node-loader.mjs +2 -29
  239. package/dist/vite/resolveDevServerConfig.js +4 -1
  240. package/dist/vite/vite.config.js +2 -29
  241. package/dist/web-router/components/MetaTag.d.ts +4 -0
  242. package/dist/web-router/components/MetaTag.jsx +26 -28
  243. package/dist/web-router/components/MetaTag.test.jsx +100 -0
  244. package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
  245. package/dist/web-router/components/RouterDataProvider.jsx +260 -259
  246. package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
  247. package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
  248. package/dist/web-router/components/Split.d.ts +74 -0
  249. package/dist/web-router/components/Split.jsx +147 -156
  250. package/dist/web-router/components/Split.server.test.jsx +52 -0
  251. package/dist/web-router/components/Split.test.jsx +167 -0
  252. package/dist/web-router/components/SplitInview.d.ts +22 -0
  253. package/dist/web-router/components/SplitInview.jsx +78 -89
  254. package/dist/web-router/components/SplitInview.test.jsx +112 -0
  255. package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
  256. package/dist/web-router/components/SsrRequestContext.jsx +17 -7
  257. package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
  258. package/dist/web-router/context.d.ts +15 -0
  259. package/dist/web-router/context.jsx +5 -5
  260. package/dist/web-router/context.test.jsx +10 -0
  261. package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
  262. package/dist/web-router/utils/metaDataUtils.jsx +86 -86
  263. package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
  264. package/dist/web-router/vitest.setup.js +12 -0
  265. package/mcp_v2/knowledge-base.json +115 -17
  266. package/mcp_v2/mcp.js +141 -3
  267. package/mcp_v2/package-lock.json +2031 -0
  268. package/mcp_v2/package.json +13 -2
  269. package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
  270. package/mcp_v2/test/errors.test.ts +123 -0
  271. package/mcp_v2/test/github.test.ts +177 -0
  272. package/mcp_v2/tools/errors.d.ts +20 -0
  273. package/mcp_v2/tools/errors.js +63 -0
  274. package/mcp_v2/tools/github.d.ts +41 -0
  275. package/mcp_v2/tools/github.js +231 -0
  276. package/mcp_v2/vitest.config.mjs +22 -0
  277. package/package.json +60 -11
  278. package/test/errors/docsDrift.test.ts +113 -0
  279. package/test/errors/index.test.ts +200 -0
  280. package/test/errors/registry.test.ts +108 -0
  281. package/test/scripts/preflight.test.ts +118 -0
  282. package/test/server/ChunkExtractor.test.ts +97 -0
  283. package/test/server/extract.test.ts +128 -0
  284. package/test/server/fixtures/template/server/document.jsx +19 -0
  285. package/test/server/fixtures/template/server/index.js +10 -0
  286. package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
  287. package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
  288. package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
  289. package/test/server/fixtures/template/src/js/store/index.js +19 -0
  290. package/test/server/handler.assets.test.ts +330 -0
  291. package/test/server/handler.test.ts +208 -0
  292. package/test/server/manifestCache.test.ts +71 -0
  293. package/test/server/render.test.ts +71 -0
  294. package/test/server/scriptUtils.test.ts +103 -0
  295. package/test/server/userAgentUtil.test.ts +48 -0
  296. package/test/server/validator.test.ts +225 -0
  297. package/test/vite/resolveDevServerConfig.test.js +4 -1
  298. package/tsconfig.test.json +23 -0
  299. package/vitest.config.ts +93 -0
  300. package/dist/caching.js +0 -1
  301. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
  302. package/dist/native/bridge/standardizedHooks.js +0 -611
  303. package/dist/router.js +0 -1
  304. package/dist/scripts/validator.js +0 -149
  305. package/dist/web-router/hooks.jsx +0 -72
  306. package/dist/web-router/types/components/MetaTag.d.ts +0 -2
  307. package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
  308. package/dist/web-router/types/context.d.ts +0 -14
  309. package/dist/web-router/types/hooks.d.ts +0 -1
  310. package/dist/web-router/types/index.d.ts +0 -3
  311. package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
  312. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
  313. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
  314. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
  315. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
  316. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
@@ -0,0 +1,193 @@
1
+ package io.yourname.androidproject.security
2
+
3
+ import org.junit.Assert.assertFalse
4
+ import org.junit.Assert.assertTrue
5
+ import org.junit.Test
6
+ import java.io.File
7
+ import java.net.InetSocketAddress
8
+ import java.net.ServerSocket
9
+
10
+ /**
11
+ * Unit tests for FridaDetector (Android coverage batch 3), previously
12
+ * entirely untested (0/~96 lines).
13
+ *
14
+ * FridaDetector is a pure-JVM `object` for its core isFridaDetected() path
15
+ * -- java.io.File existence checks, a real SocketChannel connect against
16
+ * Frida's default ports, /proc/self/maps line scanning, and
17
+ * Class.forName() lookups. None of that needs Context or a View, so this
18
+ * suite exercises it directly with no Mockito mocking of Android types at
19
+ * all:
20
+ *
21
+ * - checkFridaPorts: verified empirically (see task history) that binding
22
+ * a real java.net.ServerSocket on 127.0.0.1:27042 makes
23
+ * isFridaDetected() return true via the real non-blocking
24
+ * SocketChannel.connect()/finishConnect() loop; no listener there means
25
+ * the connect fails/times out and the check contributes false. This is
26
+ * more reliable than trying to mock java.nio.channels.SocketChannel
27
+ * (a concrete JDK class, not a mockable Android stub).
28
+ * - checkFridaFiles / checkHookingFrameworks' XposedBridge.jar file check:
29
+ * these check hardcoded absolute paths (e.g. /data/local/tmp/
30
+ * frida-server, /system/framework/XposedBridge.jar) that do not exist on
31
+ * a dev/CI JVM test runner, so they deterministically contribute false
32
+ * here. This suite does not attempt to fake those paths into existing
33
+ * (would require filesystem root manipulation out of scope for a unit
34
+ * test) -- the false branch is exercised implicitly by every test that
35
+ * doesn't plant a listening socket.
36
+ * - checkLoadedLibraries reads the real /proc/self/maps, which does not
37
+ * exist on macOS (only Linux) -- mapsFile.exists() is false there, so
38
+ * this check deterministically contributes false regardless of
39
+ * platform. On a Linux CI runner it would read the actual test JVM's
40
+ * real maps, which won't contain Frida/Xposed library names in a normal
41
+ * run either. Not independently forced true in this suite (would
42
+ * require a fake /proc/self/maps, not feasible without a mockable JDK
43
+ * File).
44
+ * - checkHookingFrameworks' Class.forName() checks (XposedBridge,
45
+ * substrate.MS) genuinely throw ClassNotFoundException on the plain JVM
46
+ * test classpath, exercising their catch-and-return-false branches for
47
+ * real -- no mocking needed.
48
+ *
49
+ * Deliberately OUT OF SCOPE: getFridaCheckDetails() as a whole. It builds
50
+ * its return value as a single `mapOf(...)` literal, so the "isDebuggable"
51
+ * entry's Build.FINGERPRINT/Build.TAGS access (static final fields under
52
+ * the mockable android.jar, NOT interceptable by Mockito) is evaluated
53
+ * eagerly and unconditionally -- confirmed empirically to throw an
54
+ * uncaught NullPointerException before the function can even return.
55
+ * There is no reachable subset of that function's behavior to test
56
+ * without touching those two fields, so it is skipped entirely (see the
57
+ * dedicated comment above its section for detail) rather than chased for
58
+ * partial coverage.
59
+ */
60
+ class FridaDetectorTest {
61
+
62
+ // ============================================================
63
+ // isFridaDetected -- no hooking indicators present (baseline)
64
+ // ============================================================
65
+
66
+ @Test
67
+ fun `isFridaDetected returns false in a clean test environment with no listener and no suspicious files`() {
68
+ // Unlike the positive tests below (which need a specific port
69
+ // bound and should fail loudly if that's not possible), this
70
+ // negative assertion's only real dependency is that NOTHING is
71
+ // listening on Frida's ports right now -- if something is (even,
72
+ // ironically, a real frida-server on the host), that's an
73
+ // environment fact this test can't control, so skip rather than
74
+ // fail. checkPortFree(...) reports that state without binding
75
+ // anything itself, avoiding a bind-vs-connect race with the
76
+ // detector's own check.
77
+ org.junit.Assume.assumeTrue(
78
+ "Something is already listening on port 27042 or 27043 in this environment — skipping",
79
+ checkPortFree(27042) && checkPortFree(27043)
80
+ )
81
+ assertFalse(FridaDetector.isFridaDetected())
82
+ }
83
+
84
+ // ============================================================
85
+ // isFridaDetected -- Frida port check forced positive
86
+ // ============================================================
87
+
88
+ @Test
89
+ fun `isFridaDetected returns true when a listener is bound on Frida's default port 27042`() {
90
+ val server = bindOrFailLoudly(27042)
91
+ try {
92
+ assertTrue(FridaDetector.isFridaDetected())
93
+ } finally {
94
+ server.close()
95
+ }
96
+ }
97
+
98
+ @Test
99
+ fun `isFridaDetected returns true when a listener is bound on Frida's alternate port 27043`() {
100
+ val server = bindOrFailLoudly(27043)
101
+ try {
102
+ assertTrue(FridaDetector.isFridaDetected())
103
+ } finally {
104
+ server.close()
105
+ }
106
+ }
107
+
108
+ // ============================================================
109
+ // checkFridaFiles indirectly, via isFridaDetected -- files absent
110
+ // ============================================================
111
+
112
+ @Test
113
+ fun `isFridaDetected does not false-positive on unrelated existing files`() {
114
+ // Sanity check that checkFridaFiles is checking specific hardcoded
115
+ // paths, not "any file exists somewhere" -- a real file existing
116
+ // elsewhere on disk must not trip detection.
117
+ val unrelated = File.createTempFile("not-frida", ".tmp")
118
+ try {
119
+ assertFalse(FridaDetector.isFridaDetected())
120
+ } finally {
121
+ unrelated.delete()
122
+ }
123
+ }
124
+
125
+ // ============================================================
126
+ // getFridaCheckDetails -- NOT tested, entirely out of scope.
127
+ //
128
+ // Empirically confirmed (java.lang.NullPointerException at the
129
+ // Build.FINGERPRINT.lowercase() call): the function builds its
130
+ // return value as a single Kotlin `mapOf(...)` literal, which
131
+ // eagerly evaluates every entry -- including the "isDebuggable"
132
+ // entry's inline `Build.FINGERPRINT.lowercase().contains(...) ||
133
+ // Build.TAGS.lowercase().contains(...)` expression -- before the
134
+ // map is ever constructed. Build.FINGERPRINT/Build.TAGS are null
135
+ // under the mockable android.jar (isReturnDefaultValues=true), so
136
+ // calling getFridaCheckDetails() at all throws uncaught, with no
137
+ // try/catch anywhere in the function to absorb it. Unlike
138
+ // EmulatorDetector.getEmulatorCheckDetails or FridaDetector's own
139
+ // isFridaDetected() (which wraps checks in try/catch and degrades to
140
+ // false), there is no reachable code path here that avoids touching
141
+ // those two fields. Per the task's Build.* limitation, this function
142
+ // is skipped entirely rather than partially asserted on.
143
+ // ============================================================
144
+ // helpers
145
+ // ============================================================
146
+
147
+ /**
148
+ * Binds a real listening socket on the given loopback port. Frida's
149
+ * default ports (27042/27043) are vanishingly unlikely to already be
150
+ * occupied on a dev/CI machine, but fail loudly with a clear message
151
+ * rather than producing a mystery assertion failure if they are.
152
+ *
153
+ * Named to reflect what it actually does (previously `bindOrSkip`,
154
+ * which never skipped anything -- CodeRabbit flagged the mismatch).
155
+ * These positive-detection tests intentionally do NOT tolerate a
156
+ * bind failure the way the negative test above does: if the port is
157
+ * already occupied, that's worth knowing loudly rather than quietly
158
+ * skipping a test meant to prove isFridaDetected() actually detects
159
+ * something.
160
+ */
161
+ private fun bindOrFailLoudly(port: Int): ServerSocket {
162
+ return try {
163
+ ServerSocket().apply {
164
+ reuseAddress = true
165
+ bind(InetSocketAddress("127.0.0.1", port))
166
+ }
167
+ } catch (e: Exception) {
168
+ throw IllegalStateException(
169
+ "Could not bind port $port for FridaDetector test -- is something " +
170
+ "already listening on it (ironically, maybe even a real frida-server)?",
171
+ e
172
+ )
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Reports whether a loopback port currently has nothing listening on
178
+ * it, without holding a bind itself (so it can't race the detector's
179
+ * own connect attempt in isFridaDetected()). Used only to decide
180
+ * whether the clean-environment negative test should run at all.
181
+ */
182
+ private fun checkPortFree(port: Int): Boolean {
183
+ return try {
184
+ ServerSocket().use {
185
+ it.reuseAddress = true
186
+ it.bind(InetSocketAddress("127.0.0.1", port))
187
+ }
188
+ true
189
+ } catch (e: Exception) {
190
+ false
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,260 @@
1
+ package io.yourname.androidproject.security
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import kotlinx.coroutines.test.runTest
6
+ import org.json.JSONObject
7
+ import org.junit.After
8
+ import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertFalse
10
+ import org.junit.Assert.assertNull
11
+ import org.junit.Assert.assertTrue
12
+ import org.junit.Before
13
+ import org.junit.Test
14
+ import org.mockito.kotlin.any
15
+ import org.mockito.kotlin.doAnswer
16
+ import org.mockito.kotlin.doReturn
17
+ import org.mockito.kotlin.mock
18
+ import java.io.ByteArrayInputStream
19
+ import java.net.InetSocketAddress
20
+ import java.net.ServerSocket
21
+
22
+ /**
23
+ * Unit tests for SecurityCheckManager (Android coverage batch 3),
24
+ * previously entirely untested (0/~67 lines).
25
+ *
26
+ * SecurityCheckManager is an `object` that only needs a Context (for
27
+ * `RootBeer(context)` and `context.getSharedPreferences(...)`) -- no
28
+ * Views. It orchestrates EmulatorDetector / FridaDetector / RootBeer via
29
+ * coroutines (Dispatchers.Default, real -- not Dispatchers.Main, so
30
+ * kotlinx-coroutines-test's virtual-time scheduler does not intercept it;
31
+ * runTest{} is used only to satisfy the `suspend` signature, matching this
32
+ * project's established pattern for suspend functions under test, e.g.
33
+ * WebCacheManagerTest).
34
+ *
35
+ * Key empirically-verified facts this suite relies on (see task history):
36
+ * - `RootBeer(context).isRooted` throws an uncaught NullPointerException
37
+ * against a plain Mockito Context mock (PackageManager is null under
38
+ * the mockable jar) -- but checkRooted() wraps the call in try/catch,
39
+ * so in every test here isRooted resolves to false. This suite does NOT
40
+ * attempt to force isRooted=true: RootBeer is constructed directly
41
+ * inside checkRooted() (not injected), and RootBeer is a real
42
+ * non-Android library class with no Context-driven seam to intercept
43
+ * its result -- forcing a rooted result would require Mockito's
44
+ * inline mockConstruction, which is unverified to fire reliably across
45
+ * the coroutine worker thread checkRooted() actually runs on
46
+ * (Dispatchers.Default), so it is deliberately not attempted.
47
+ * - EmulatorDetector.isEmulator(mockContext) returns false cleanly (its
48
+ * own outer try/catch absorbs the Build.FINGERPRINT-driven NPE path;
49
+ * see FridaDetectorTest and EmulatorDetector's own known Build.* limits)
50
+ * -- so isEmulator is always false in this suite. Forcing it true would
51
+ * require mocking Build.* static final fields, which Mockito cannot do
52
+ * (see project-wide rule).
53
+ * - FridaDetector.isFridaDetected() CAN be deterministically forced true
54
+ * by binding a real listening socket on Frida's default port 127.0.0.1:
55
+ * 27042 (see FridaDetectorTest) -- this is therefore the only one of
56
+ * the three local checks this suite drives to the BLOCK-triggering
57
+ * branch, and it is enough to exercise isCompromised=true /
58
+ * recommendation="BLOCK" end-to-end through performSecurityChecks
59
+ * without touching Build.* or RootBeer at all.
60
+ *
61
+ * Covered: performSecurityChecks' ALLOW path (nothing detected) and BLOCK
62
+ * path (via the Frida-socket trick), its JSONObject field shape,
63
+ * saveLatestResults/getLatestSecurityResults round-trip through a mocked
64
+ * SharedPreferences, calculateRecommendation's branches (exercised
65
+ * indirectly through performSecurityChecks, since it's private), and
66
+ * getSecurityMode's present/default/malformed-config branches via a mocked
67
+ * AssetManager.
68
+ *
69
+ * Deliberately OUT OF SCOPE: createErrorResponse() is only reachable when
70
+ * the try block in performSecurityChecks throws -- every checked call
71
+ * inside it (checkRooted/checkEmulator/checkFridaDetected) has its own
72
+ * internal try/catch that swallows exceptions and returns false, and
73
+ * saveLatestResults also catches internally, so there is no reachable
74
+ * failure path to trigger it from the public API surface without
75
+ * reflection or bytecode manipulation. Not attempted. All of the
76
+ * commented-out Play Integrity code (getGoogleToken,
77
+ * performPlayIntegrityCheck, cachePlayIntegrityResult, etc.) is inactive
78
+ * source (commented out) and not compiled, so there is nothing to test.
79
+ */
80
+ class SecurityCheckManagerTest {
81
+
82
+ private lateinit var prefsStorage: MutableMap<String, String?>
83
+ private lateinit var sharedPreferences: SharedPreferences
84
+ private lateinit var editor: SharedPreferences.Editor
85
+ private lateinit var context: Context
86
+
87
+ @Before
88
+ fun setUp() {
89
+ prefsStorage = mutableMapOf()
90
+
91
+ editor = mock {
92
+ on { putString(any(), any()) } doAnswer { invocation ->
93
+ val key = invocation.getArgument<String>(0)
94
+ val value = invocation.getArgument<String?>(1)
95
+ prefsStorage[key] = value
96
+ editor
97
+ }
98
+ }
99
+ // apply() is Unit-returning; default mock behavior (no-op) is fine,
100
+ // but we still want the stored value visible without invoking a
101
+ // real Android disk write.
102
+ org.mockito.kotlin.doNothing().`when`(editor).apply()
103
+
104
+ sharedPreferences = mock {
105
+ on { getString(any(), org.mockito.kotlin.anyOrNull()) } doAnswer { invocation ->
106
+ val key = invocation.getArgument<String>(0)
107
+ val default = invocation.getArgument<String?>(1)
108
+ prefsStorage[key] ?: default
109
+ }
110
+ on { edit() } doReturn editor
111
+ }
112
+
113
+ context = mock {
114
+ on { getSharedPreferences(any(), any()) } doReturn sharedPreferences
115
+ }
116
+ }
117
+
118
+ @After
119
+ fun tearDown() {
120
+ prefsStorage.clear()
121
+ }
122
+
123
+ // ============================================================
124
+ // performSecurityChecks -- ALLOW path (clean environment)
125
+ // ============================================================
126
+
127
+ @Test
128
+ fun `performSecurityChecks returns ALLOW and not compromised when no local threat is detected`() = runTest {
129
+ val results = SecurityCheckManager.performSecurityChecks(context)
130
+
131
+ assertFalse(results.getBoolean("isRooted"))
132
+ assertFalse(results.getBoolean("isEmulator"))
133
+ assertFalse(results.getBoolean("isFridaDetected"))
134
+ assertFalse(results.getBoolean("isCompromised"))
135
+ assertEquals("ALLOW", results.getString("recommendation"))
136
+ assertFalse(results.getBoolean("pending"))
137
+ assertTrue(results.has("timestamp"))
138
+ }
139
+
140
+ @Test
141
+ fun `performSecurityChecks does not include a playIntegrity entry while Play Integrity is disabled`() {
142
+ runTest {
143
+ val results = SecurityCheckManager.performSecurityChecks(context)
144
+ assertFalse(results.has("playIntegrity"))
145
+ }
146
+ }
147
+
148
+ @Test
149
+ fun `performSecurityChecks persists the latest results to SharedPreferences`() = runTest {
150
+ SecurityCheckManager.performSecurityChecks(context)
151
+
152
+ val saved = prefsStorage["latest_security_results"]
153
+ assertTrue(saved != null)
154
+ val savedJson = JSONObject(saved!!)
155
+ assertEquals("ALLOW", savedJson.getString("recommendation"))
156
+ }
157
+
158
+ // ============================================================
159
+ // performSecurityChecks -- BLOCK path (Frida socket trick)
160
+ // ============================================================
161
+
162
+ @Test
163
+ fun `performSecurityChecks returns BLOCK and compromised when Frida is detected`() = runTest {
164
+ val server = bindOrSkip(27042)
165
+ try {
166
+ val results = SecurityCheckManager.performSecurityChecks(context)
167
+
168
+ assertTrue(results.getBoolean("isFridaDetected"))
169
+ assertTrue(results.getBoolean("isCompromised"))
170
+ assertEquals("BLOCK", results.getString("recommendation"))
171
+ } finally {
172
+ server.close()
173
+ }
174
+ }
175
+
176
+ // ============================================================
177
+ // getLatestSecurityResults
178
+ // ============================================================
179
+
180
+ @Test
181
+ fun `getLatestSecurityResults returns null when nothing has been saved yet`() {
182
+ assertNull(SecurityCheckManager.getLatestSecurityResults(context))
183
+ }
184
+
185
+ @Test
186
+ fun `getLatestSecurityResults returns the previously saved results`() = runTest {
187
+ SecurityCheckManager.performSecurityChecks(context)
188
+
189
+ val latest = SecurityCheckManager.getLatestSecurityResults(context)
190
+ assertTrue(latest != null)
191
+ assertEquals("ALLOW", latest!!.getString("recommendation"))
192
+ }
193
+
194
+ @Test
195
+ fun `getLatestSecurityResults returns null when SharedPreferences access throws`() {
196
+ val throwingContext: Context = mock {
197
+ on { getSharedPreferences(any(), any()) } doAnswer { throw RuntimeException("boom") }
198
+ }
199
+
200
+ assertNull(SecurityCheckManager.getLatestSecurityResults(throwingContext))
201
+ }
202
+
203
+ // ============================================================
204
+ // getSecurityMode
205
+ // ============================================================
206
+
207
+ @Test
208
+ fun `getSecurityMode returns the configured value when present`() {
209
+ val propsContext = contextWithProperties("android.security.mode=strict\n")
210
+
211
+ assertEquals("strict", SecurityCheckManager.getSecurityMode(propsContext))
212
+ }
213
+
214
+ @Test
215
+ fun `getSecurityMode defaults to 'default' when the key is absent`() {
216
+ val propsContext = contextWithProperties("some.other.key=value\n")
217
+
218
+ assertEquals("default", SecurityCheckManager.getSecurityMode(propsContext))
219
+ }
220
+
221
+ @Test
222
+ fun `getSecurityMode defaults to 'default' when reading the properties file throws`() {
223
+ val throwingAssets: android.content.res.AssetManager = mock {
224
+ on { open(any()) } doAnswer { throw java.io.IOException("no such asset") }
225
+ }
226
+ val propsContext: Context = mock {
227
+ on { getAssets() } doReturn throwingAssets
228
+ }
229
+
230
+ assertEquals("default", SecurityCheckManager.getSecurityMode(propsContext))
231
+ }
232
+
233
+ // ============================================================
234
+ // helpers
235
+ // ============================================================
236
+
237
+ private fun contextWithProperties(propertiesContent: String): Context {
238
+ val assets: android.content.res.AssetManager = mock {
239
+ on { open("webview_config.properties") } doReturn ByteArrayInputStream(propertiesContent.toByteArray())
240
+ }
241
+ return mock {
242
+ on { getAssets() } doReturn assets
243
+ }
244
+ }
245
+
246
+ private fun bindOrSkip(port: Int): ServerSocket {
247
+ return try {
248
+ ServerSocket().apply {
249
+ reuseAddress = true
250
+ bind(InetSocketAddress("127.0.0.1", port))
251
+ }
252
+ } catch (e: Exception) {
253
+ throw IllegalStateException(
254
+ "Could not bind port $port for SecurityCheckManager test -- is something " +
255
+ "already listening on it?",
256
+ e
257
+ )
258
+ }
259
+ }
260
+ }
@@ -0,0 +1,166 @@
1
+ package io.yourname.androidproject.security
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import kotlinx.coroutines.Dispatchers
6
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
7
+ import kotlinx.coroutines.test.StandardTestDispatcher
8
+ import kotlinx.coroutines.test.resetMain
9
+ import kotlinx.coroutines.test.runTest
10
+ import kotlinx.coroutines.test.setMain
11
+ import org.junit.After
12
+ import org.junit.Assert.assertEquals
13
+ import org.junit.Assert.assertTrue
14
+ import org.junit.Before
15
+ import org.junit.Test
16
+ import org.mockito.kotlin.any
17
+ import org.mockito.kotlin.doAnswer
18
+ import org.mockito.kotlin.doReturn
19
+ import org.mockito.kotlin.mock
20
+ import java.util.concurrent.CountDownLatch
21
+ import java.util.concurrent.TimeUnit
22
+
23
+ /**
24
+ * Unit tests for SecurityCheckScheduler (Android coverage batch 4),
25
+ * previously entirely untested (0/19 lines).
26
+ *
27
+ * Scope: this is a thin Context + CoroutineScope wrapper that delegates to
28
+ * SecurityCheckManager.performSecurityChecks(context) (already covered in
29
+ * depth by SecurityCheckManagerTest -- its ALLOW/BLOCK branches, JSON
30
+ * shape, and persistence are NOT re-verified here). This suite only
31
+ * verifies that SecurityCheckScheduler.initialize():
32
+ * - launches the check in the given scope and the callback receives the
33
+ * results SecurityCheckManager produced,
34
+ * - works with no callback at all (callback is nullable, `callback?.let`),
35
+ * - does not throw when SecurityCheckManager's own call throws (the
36
+ * launched coroutine's inner try/catch swallows it).
37
+ *
38
+ * Empirically-required setup: initialize() delivers the callback via
39
+ * `withContext(Dispatchers.Main)`. A JVM unit test has no real Android
40
+ * main-thread Looper, so Dispatchers.Main is uninitialized by default and
41
+ * would throw ("Module with the Main dispatcher is missing"). Per
42
+ * kotlinx-coroutines-test (already a project dependency -- see
43
+ * SecurityCheckManagerTest's runTest usage), Dispatchers.setMain(...) with
44
+ * a StandardTestDispatcher is installed in setUp()/torn down with
45
+ * Dispatchers.resetMain() in tearDown(), so the Main hop has somewhere
46
+ * real to resolve on instead of throwing.
47
+ *
48
+ * Note this test does NOT rely on runTest's virtual-time
49
+ * advanceUntilIdle() to observe completion: SecurityCheckManager.
50
+ * performSecurityChecks() internally does `withContext(Dispatchers.Default)`
51
+ * -- the REAL Default dispatcher, backed by real background threads, which
52
+ * runTest's virtual scheduler has no visibility into or control over (only
53
+ * the StandardTestDispatcher installed as Main is virtual-time-controlled
54
+ * here). So instead of advancing virtual time, these tests block the test
55
+ * thread on a CountDownLatch that the callback (or a manual completion
56
+ * signal) counts down, with a real wall-clock timeout -- the same
57
+ * "observe real async completion from a JVM unit test" shape as
58
+ * FridaDetectorTest's socket-based tests elsewhere in this suite.
59
+ *
60
+ * Context/SharedPreferences mocking mirrors SecurityCheckManagerTest's
61
+ * setUp() exactly, since initialize() -> performSecurityChecks() ->
62
+ * saveLatestResults() needs a working getSharedPreferences()/edit() chain
63
+ * to complete without throwing.
64
+ */
65
+ @OptIn(ExperimentalCoroutinesApi::class)
66
+ class SecurityCheckSchedulerTest {
67
+
68
+ private lateinit var prefsStorage: MutableMap<String, String?>
69
+ private lateinit var sharedPreferences: SharedPreferences
70
+ private lateinit var editor: SharedPreferences.Editor
71
+ private lateinit var context: Context
72
+
73
+ @Before
74
+ fun setUp() {
75
+ Dispatchers.setMain(StandardTestDispatcher())
76
+
77
+ prefsStorage = mutableMapOf()
78
+
79
+ editor = mock {
80
+ on { putString(any(), any()) } doAnswer { invocation ->
81
+ val key = invocation.getArgument<String>(0)
82
+ val value = invocation.getArgument<String?>(1)
83
+ prefsStorage[key] = value
84
+ editor
85
+ }
86
+ }
87
+ org.mockito.kotlin.doNothing().`when`(editor).apply()
88
+
89
+ sharedPreferences = mock {
90
+ on { getString(any(), org.mockito.kotlin.anyOrNull()) } doAnswer { invocation ->
91
+ val key = invocation.getArgument<String>(0)
92
+ val default = invocation.getArgument<String?>(1)
93
+ prefsStorage[key] ?: default
94
+ }
95
+ on { edit() } doReturn editor
96
+ }
97
+
98
+ context = mock {
99
+ on { getSharedPreferences(any(), any()) } doReturn sharedPreferences
100
+ }
101
+ }
102
+
103
+ @After
104
+ fun tearDown() {
105
+ Dispatchers.resetMain()
106
+ prefsStorage.clear()
107
+ }
108
+
109
+ @Test
110
+ fun `initialize invokes the callback with the security check results on the main dispatcher`() {
111
+ val latch = CountDownLatch(1)
112
+ var received: org.json.JSONObject? = null
113
+ val callback = object : SecurityCheckScheduler.SecurityCheckCallback {
114
+ override fun onSecurityCheckComplete(results: org.json.JSONObject) {
115
+ received = results
116
+ latch.countDown()
117
+ }
118
+ }
119
+
120
+ runTest {
121
+ SecurityCheckScheduler.initialize(context, this, callback)
122
+ }
123
+
124
+ assertTrue("callback should have been invoked within 5s", latch.await(5, TimeUnit.SECONDS))
125
+ assertEquals("ALLOW", received!!.getString("recommendation"))
126
+ }
127
+
128
+ @Test
129
+ fun `initialize with no callback completes without throwing`() = runTest {
130
+ SecurityCheckScheduler.initialize(context, this, callback = null)
131
+ // Reaching here without an exception is the assertion -- initialize()
132
+ // must not require a callback to be supplied. There is no callback
133
+ // signal to await, so this only proves the synchronous launch path
134
+ // (the try/catch around scope.launch{}) doesn't throw.
135
+ }
136
+
137
+ @Test
138
+ fun `initialize does not propagate an exception when the underlying check throws`() {
139
+ val throwingContext: Context = mock {
140
+ on { getSharedPreferences(any(), any()) } doAnswer { throw RuntimeException("boom") }
141
+ }
142
+ val latch = CountDownLatch(1)
143
+ val callback = object : SecurityCheckScheduler.SecurityCheckCallback {
144
+ override fun onSecurityCheckComplete(results: org.json.JSONObject) {
145
+ latch.countDown()
146
+ }
147
+ }
148
+
149
+ // SecurityCheckManager.performSecurityChecks itself catches broadly and
150
+ // returns an error JSONObject rather than throwing (see its own
151
+ // createErrorResponse fallback) -- but even if it didn't, initialize()'s
152
+ // own outer try/catch around scope.launch{} plus the inner try/catch
153
+ // around performSecurityChecks() must keep this call from propagating.
154
+ runTest {
155
+ SecurityCheckScheduler.initialize(throwingContext, this, callback)
156
+ }
157
+
158
+ // The call above completing without throwing is the primary
159
+ // assertion. Whether the callback fires at all depends entirely on
160
+ // SecurityCheckManager's already-covered internals (it does still
161
+ // reach ALLOW here since checkRooted/checkEmulator/checkFridaDetected
162
+ // don't touch getSharedPreferences), so this just confirms
163
+ // initialize() itself stays exception-safe with a throwing Context.
164
+ assertTrue(latch.await(5, TimeUnit.SECONDS))
165
+ }
166
+ }