facecog-liveness-showcase 0.0.1

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 (229) hide show
  1. package/.browserslistrc +15 -0
  2. package/.dockerignore +48 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintrc.json +47 -0
  5. package/.vercelignore +7 -0
  6. package/.vscode/extensions.json +5 -0
  7. package/.vscode/settings.json +3 -0
  8. package/DOCKER.md +221 -0
  9. package/Dockerfile +33 -0
  10. package/README.md +268 -0
  11. package/angular.json +156 -0
  12. package/capacitor.config.ts +9 -0
  13. package/docker-compose.dev.yml +20 -0
  14. package/docker-compose.yml +18 -0
  15. package/ionic.config.json +7 -0
  16. package/jest.config.js +38 -0
  17. package/nginx.conf +50 -0
  18. package/package.json +131 -0
  19. package/patches/ng-packagr+20.3.2.patch +60 -0
  20. package/projects/facecog-liveness-verification/README.md +295 -0
  21. package/projects/facecog-liveness-verification/ng-package.json +7 -0
  22. package/projects/facecog-liveness-verification/package.json +48 -0
  23. package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
  24. package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
  25. package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
  26. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
  27. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
  28. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
  29. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
  30. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
  31. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
  32. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
  33. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
  34. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
  35. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
  36. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
  37. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
  38. package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
  39. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
  40. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
  41. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
  42. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
  43. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
  44. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
  45. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
  46. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
  47. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
  48. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
  49. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
  50. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
  51. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
  52. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
  53. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
  54. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
  55. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
  56. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
  57. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
  58. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
  59. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
  60. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
  61. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
  62. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
  63. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
  64. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
  65. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
  66. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
  67. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
  68. package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
  69. package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
  70. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
  71. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
  72. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
  73. package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
  74. package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
  75. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
  76. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
  77. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
  78. package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
  79. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
  80. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
  81. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
  82. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
  83. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
  84. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
  85. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
  86. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
  87. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
  88. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
  89. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
  90. package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
  91. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
  92. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
  93. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
  94. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
  95. package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
  96. package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
  97. package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
  98. package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
  99. package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
  100. package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
  101. package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
  102. package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
  103. package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
  104. package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
  105. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
  106. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
  107. package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
  108. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
  109. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
  110. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
  111. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
  112. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
  113. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
  114. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
  115. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
  116. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
  117. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
  118. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
  119. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
  120. package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
  121. package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
  122. package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
  123. package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
  124. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
  125. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
  126. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
  127. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
  128. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
  129. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
  130. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
  131. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
  132. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
  133. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
  134. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
  135. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
  136. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
  137. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
  138. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
  139. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
  140. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
  141. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
  142. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
  143. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
  144. package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
  145. package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
  146. package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
  147. package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
  148. package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
  149. package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
  150. package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
  151. package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
  152. package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
  153. package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
  154. package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
  155. package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
  156. package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
  157. package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
  158. package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
  159. package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
  160. package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
  161. package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
  162. package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
  163. package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
  164. package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
  165. package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
  166. package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
  167. package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
  168. package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
  169. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
  170. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
  171. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
  172. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
  173. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
  174. package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
  175. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
  176. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
  177. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
  178. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
  179. package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
  180. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
  181. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
  182. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
  183. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
  184. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
  185. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
  186. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
  187. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
  188. package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
  189. package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
  190. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
  191. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
  192. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
  193. package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
  194. package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
  195. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
  196. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
  197. package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
  198. package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
  199. package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
  200. package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
  201. package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
  202. package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
  203. package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
  204. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
  205. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
  206. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
  207. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
  208. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
  209. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
  210. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
  211. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
  212. package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
  213. package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
  214. package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
  215. package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
  216. package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
  217. package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
  218. package/projects/facecog-liveness-verification-test/src/index.html +31 -0
  219. package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
  220. package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
  221. package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
  222. package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
  223. package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
  224. package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
  225. package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
  226. package/setup-jest.ts +118 -0
  227. package/tsconfig.json +41 -0
  228. package/tsconfig.spec.json +15 -0
  229. package/vercel.json +24 -0
@@ -0,0 +1,673 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { Type } from '@angular/core';
3
+ import { NO_ERRORS_SCHEMA } from '@angular/core';
4
+ import { CommonModule } from '@angular/common';
5
+ import { IonicModule } from '@ionic/angular';
6
+ import { of, throwError as rxThrowError } from 'rxjs';
7
+
8
+ import { LivenessFlowComponent } from './liveness-flow.component';
9
+ import { LivenessConfigService } from '../../services/liveness-config.service';
10
+ import { LivenessOrchestratorService } from '../../services/liveness-orchestrator.service';
11
+ import { PoseSelectionService } from '../../services/pose-selection.service';
12
+ import { VerificationApiService } from '../../services/verification-api.service';
13
+ import { AWS_FACE_LIVENESS_COMPONENT } from '../../models/constants/aws-face-liveness-component.token';
14
+ import type { LivenessBackend } from '../../types/liveness-backend.type';
15
+ import type { LivenessResult } from '../../interfaces/liveness-result.interface';
16
+ import type { SelectedPoseInfo } from '../../services/pose-selection.service';
17
+
18
+ describe('LivenessFlowComponent', () => {
19
+ let component: LivenessFlowComponent;
20
+ let fixture: ComponentFixture<LivenessFlowComponent>;
21
+ let configServiceSpy: jasmine.SpyObj<LivenessConfigService>;
22
+ let orchestratorSpy: jasmine.SpyObj<LivenessOrchestratorService>;
23
+ let poseSelectionSpy: jasmine.SpyObj<PoseSelectionService>;
24
+ let verificationApiSpy: jasmine.SpyObj<VerificationApiService>;
25
+
26
+ const defaultConfig = {
27
+ apiUrl: 'http://localhost:3002',
28
+ backends: ['amazon'] as LivenessBackend[],
29
+ maxRetries: 3,
30
+ showIntroScreen: true,
31
+ customPose: undefined,
32
+ poseId: undefined
33
+ };
34
+
35
+ const mockSelectedPose: SelectedPoseInfo = {
36
+ pose: { id: 1, name: 'Smile', description: 'Smile', category: 'face', imagePath: '', detectionStrategy: 'face-api', difficulty: 'easy' },
37
+ imageUrl: 'assets/smile.png',
38
+ timestamp: new Date()
39
+ };
40
+
41
+ beforeEach(async () => {
42
+ configServiceSpy = jasmine.createSpyObj('LivenessConfigService', ['getConfig', 'updateConfig']);
43
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig });
44
+
45
+ orchestratorSpy = jasmine.createSpyObj('LivenessOrchestratorService', ['verifyLiveness', 'uploadVideoToS3']);
46
+ poseSelectionSpy = jasmine.createSpyObj('PoseSelectionService', ['getSelectedPose']);
47
+ poseSelectionSpy.getSelectedPose.and.returnValue(null);
48
+
49
+ verificationApiSpy = jasmine.createSpyObj('VerificationApiService', ['startSession', 'verifyGestures', 'verifyFaceTec', 'finalize']);
50
+
51
+ await TestBed.configureTestingModule({
52
+ imports: [
53
+ CommonModule,
54
+ IonicModule.forRoot(),
55
+ LivenessFlowComponent
56
+ ],
57
+ providers: [
58
+ { provide: LivenessConfigService, useValue: configServiceSpy },
59
+ { provide: LivenessOrchestratorService, useValue: orchestratorSpy },
60
+ { provide: PoseSelectionService, useValue: poseSelectionSpy },
61
+ { provide: VerificationApiService, useValue: verificationApiSpy },
62
+ { provide: AWS_FACE_LIVENESS_COMPONENT, useValue: null }
63
+ ],
64
+ schemas: [NO_ERRORS_SCHEMA]
65
+ }).compileComponents();
66
+
67
+ fixture = TestBed.createComponent(LivenessFlowComponent);
68
+ component = fixture.componentInstance;
69
+ });
70
+
71
+ it('should create', () => {
72
+ expect(component).toBeTruthy();
73
+ });
74
+
75
+ it('ngOnInit sets config from LivenessConfigService', () => {
76
+ const libConfig = { ...defaultConfig, backends: undefined };
77
+ configServiceSpy.getConfig.and.returnValue(libConfig);
78
+
79
+ component.ngOnInit();
80
+
81
+ expect(component.config).toBeDefined();
82
+ expect(component.config.backends).toEqual(['amazon']);
83
+ expect(configServiceSpy.getConfig).toHaveBeenCalled();
84
+ });
85
+
86
+ it('should skip intro when showIntroScreen is false and customPose is set', () => {
87
+ configServiceSpy.getConfig.and.returnValue({
88
+ ...defaultConfig,
89
+ showIntroScreen: false,
90
+ customPose: { imageData: 'base64...', description: 'Custom pose' }
91
+ });
92
+ poseSelectionSpy.getSelectedPose.and.returnValue(mockSelectedPose);
93
+
94
+ component.ngOnInit();
95
+
96
+ expect(component.currentStep()).toBe('permission');
97
+ expect(component.selectedPose).toEqual(mockSelectedPose);
98
+ });
99
+
100
+ it('should skip intro when showIntroScreen is false and poseId is set', () => {
101
+ configServiceSpy.getConfig.and.returnValue({
102
+ ...defaultConfig,
103
+ showIntroScreen: false,
104
+ poseId: 5
105
+ });
106
+ poseSelectionSpy.getSelectedPose.and.returnValue(mockSelectedPose);
107
+
108
+ component.ngOnInit();
109
+
110
+ expect(component.currentStep()).toBe('permission');
111
+ });
112
+
113
+ it('should not skip intro when showIntroScreen is true', () => {
114
+ configServiceSpy.getConfig.and.returnValue({
115
+ ...defaultConfig,
116
+ showIntroScreen: true
117
+ });
118
+
119
+ component.ngOnInit();
120
+
121
+ expect(component.currentStep()).toBe('intro');
122
+ });
123
+
124
+ it('onStartClicked sets currentStep to permission', () => {
125
+ component.currentStep.set('intro');
126
+ component.onStartClicked();
127
+
128
+ expect(component.currentStep()).toBe('permission');
129
+ });
130
+
131
+ it('onPermissionGranted sets currentStep to preview', () => {
132
+ component.currentStep.set('permission');
133
+ component.onPermissionGranted();
134
+
135
+ expect(component.currentStep()).toBe('preview');
136
+ });
137
+
138
+ it('onPermissionDenied emits result via resultSubject', (done) => {
139
+ component.result$.subscribe((result) => {
140
+ expect(result.success).toBe(false);
141
+ expect(result.error).toBe('Camera permission denied');
142
+ done();
143
+ });
144
+
145
+ component.onPermissionDenied();
146
+ });
147
+
148
+ it('onRetry resets state and sets currentStep to preview', () => {
149
+ component.currentStep.set('result');
150
+ component.attemptCount = 1;
151
+ component.capturedFrames = ['frame1'];
152
+ component.result.set({ success: false, confidence: 0, verifiedImage: '', metadata: {} as any });
153
+ component.config = { ...defaultConfig, maxRetries: 3 } as any;
154
+
155
+ component.onRetry();
156
+
157
+ expect(component.currentStep()).toBe('preview');
158
+ expect(component.processingProgress()).toBe(0);
159
+ expect(component.capturedFrames).toEqual([]);
160
+ expect(component.result()).toBeNull();
161
+ });
162
+
163
+ it('onRetry when max retries reached emits error result', (done) => {
164
+ component.attemptCount = 3;
165
+ component.config = { ...defaultConfig, maxRetries: 3 } as any;
166
+ component.result.set({
167
+ success: false,
168
+ confidence: 0,
169
+ verifiedImage: '',
170
+ error: 'Previous error',
171
+ metadata: { timestamp: '', backend: 'amazon', confidence: 0, attemptNumber: 3 }
172
+ });
173
+
174
+ component.result$.subscribe((result) => {
175
+ expect(result.error).toBe('Maximum retry attempts reached');
176
+ done();
177
+ });
178
+
179
+ component.onRetry();
180
+ });
181
+
182
+ it('onCancel emits cancelled result and resets currentStep to intro', () => {
183
+ component.currentStep.set('result');
184
+ let emittedResult: any;
185
+ component.result$.subscribe((r) => (emittedResult = r));
186
+
187
+ component.onCancel();
188
+
189
+ expect(emittedResult.success).toBe(false);
190
+ expect(emittedResult.error).toBe('User cancelled');
191
+ expect(component.currentStep()).toBe('intro');
192
+ });
193
+
194
+ it('emitResult resets currentStep to intro', () => {
195
+ component.currentStep.set('result');
196
+ const testResult: LivenessResult = {
197
+ success: true,
198
+ confidence: 0.95,
199
+ verifiedImage: 'img',
200
+ metadata: { timestamp: '', backend: 'amazon' as LivenessBackend, confidence: 0.95, attemptNumber: 1 }
201
+ };
202
+ let emittedResult: any;
203
+ component.result$.subscribe((r) => (emittedResult = r));
204
+
205
+ (component as any).emitResult(testResult);
206
+
207
+ expect(emittedResult).toEqual(testResult);
208
+ expect(component.currentStep()).toBe('intro');
209
+ });
210
+
211
+ it('onDone emits the current result when result exists', (done) => {
212
+ const testResult: LivenessResult = {
213
+ success: true,
214
+ confidence: 0.9,
215
+ verifiedImage: 'img',
216
+ metadata: { timestamp: '', backend: 'amazon' as LivenessBackend, confidence: 0.9, attemptNumber: 1 }
217
+ };
218
+ component.result.set(testResult);
219
+
220
+ component.result$.subscribe((result) => {
221
+ expect(result).toEqual(testResult);
222
+ done();
223
+ });
224
+
225
+ component.onDone();
226
+ });
227
+
228
+ it('onDone does not emit when result is null', () => {
229
+ component.result.set(null);
230
+ let emitted = false;
231
+ component.result$.subscribe(() => (emitted = true));
232
+ component.onDone();
233
+ expect(emitted).toBe(false);
234
+ });
235
+
236
+ it('startLivenessFlow returns result$ observable', () => {
237
+ const obs = component.startLivenessFlow();
238
+ expect(obs).toBe(component.result$);
239
+ });
240
+
241
+ it('startLivenessFlow with config updates config', () => {
242
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig, showIntroScreen: true });
243
+ component.startLivenessFlow({ maxRetries: 5 });
244
+ expect(configServiceSpy.updateConfig).toHaveBeenCalledWith({ maxRetries: 5 });
245
+ });
246
+
247
+ it('onCaptureComplete sets capturedFrames and videoBlob', async () => {
248
+ const mockBlob = new Blob(['video'], { type: 'video/webm' });
249
+ verificationApiSpy.startSession.and.returnValue(rxThrowError(() => new Error('API unavailable')));
250
+
251
+ await component.onCaptureComplete({ frames: ['frame1', 'frame2'], videoBlob: mockBlob });
252
+
253
+ expect(component.capturedFrames).toEqual(['frame1', 'frame2']);
254
+ expect(component.videoBlob).toBe(mockBlob);
255
+ expect(component.currentStep()).toBe('result');
256
+ expect(component.result()?.success).toBe(false);
257
+ });
258
+
259
+ it('onCaptureComplete handles session creation failure', async () => {
260
+ verificationApiSpy.startSession.and.returnValue(of(null as any));
261
+
262
+ await component.onCaptureComplete({ frames: ['frame1'] });
263
+
264
+ expect(component.currentStep()).toBe('result');
265
+ expect(component.result()?.success).toBe(false);
266
+ expect(component.result()?.error).toContain('Failed to create verification session');
267
+ });
268
+
269
+ it('onFaceTecScanComplete with failed result sets step to result', async () => {
270
+ await component.onFaceTecScanComplete({ success: false, error: 'Scan failed' } as any);
271
+
272
+ expect(component.currentStep()).toBe('result');
273
+ expect(component.result()?.success).toBe(false);
274
+ expect(component.result()?.error).toBe('Scan failed');
275
+ });
276
+
277
+ it('onFaceTecScanCancelled calls onCancel', () => {
278
+ let emittedResult: any;
279
+ component.result$.subscribe((r) => (emittedResult = r));
280
+
281
+ component.onFaceTecScanCancelled();
282
+
283
+ expect(emittedResult.error).toBe('User cancelled');
284
+ expect(component.currentStep()).toBe('intro');
285
+ });
286
+
287
+ it('onAwsFaceLivenessComplete with failed result sets step to result', async () => {
288
+ await component.onAwsFaceLivenessComplete({ success: false, error: 'AWS check failed' } as any);
289
+
290
+ expect(component.currentStep()).toBe('result');
291
+ expect(component.result()?.success).toBe(false);
292
+ expect(component.result()?.error).toBe('AWS check failed');
293
+ });
294
+
295
+ it('onAwsFaceLivenessCancelled calls onCancel', () => {
296
+ let emittedResult: any;
297
+ component.result$.subscribe((r) => (emittedResult = r));
298
+
299
+ component.onAwsFaceLivenessCancelled();
300
+
301
+ expect(emittedResult.error).toBe('User cancelled');
302
+ });
303
+
304
+ it('onReferencePoseUploaded sets referencePose', () => {
305
+ const pose = { poseDescription: 'Test', keypoints: [] } as any;
306
+ component.onReferencePoseUploaded(pose);
307
+ expect(component.referencePose).toBe(pose);
308
+ });
309
+
310
+ it('onPoseSelected sets selectedPose', () => {
311
+ component.onPoseSelected(mockSelectedPose);
312
+ expect(component.selectedPose).toBe(mockSelectedPose);
313
+ });
314
+
315
+ it('onAwsFaceLivenessToggled sets useAwsFaceLiveness', () => {
316
+ component.onAwsFaceLivenessToggled(true);
317
+ expect(component.useAwsFaceLiveness).toBe(true);
318
+ component.onAwsFaceLivenessToggled(false);
319
+ expect(component.useAwsFaceLiveness).toBe(false);
320
+ });
321
+
322
+ it('poseSpecificMode returns true when customPose is set', () => {
323
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig, customPose: { imageData: 'x', description: 'y' } });
324
+ expect(component.poseSpecificMode).toBe(true);
325
+ });
326
+
327
+ it('poseSpecificMode returns true when poseId > 0', () => {
328
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig, poseId: 5 });
329
+ expect(component.poseSpecificMode).toBe(true);
330
+ });
331
+
332
+ it('poseSpecificMode returns false when neither customPose nor poseId set', () => {
333
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig });
334
+ expect(component.poseSpecificMode).toBe(false);
335
+ });
336
+
337
+ it('ngOnDestroy calls destroyDynamicAwsComponent', () => {
338
+ const destroySpy = spyOn(component as any, 'destroyDynamicAwsComponent');
339
+ component.ngOnDestroy();
340
+ expect(destroySpy).toHaveBeenCalled();
341
+ });
342
+
343
+ it('awsFaceLivenessInputs returns correct config', () => {
344
+ component.awsRegion = 'eu-west-1';
345
+ const inputs = component.awsFaceLivenessInputs;
346
+ expect(inputs.region).toBe('eu-west-1');
347
+ expect(inputs.autoStart).toBe(true);
348
+ expect(inputs.showCancelButton).toBe(true);
349
+ });
350
+
351
+ it('onCaptureComplete with successful session goes to facetec step', async () => {
352
+ verificationApiSpy.startSession.and.returnValue(of({ sessionId: 'sess-1', plan: [] }));
353
+ verificationApiSpy.verifyGestures.and.returnValue(of({ success: true, frameHash: 'hash123' }));
354
+
355
+ await component.onCaptureComplete({ frames: ['frame1'] });
356
+
357
+ expect(component.verificationSessionId).toBe('sess-1');
358
+ expect(component.gestureFrameHash).toBe('hash123');
359
+ expect(component.currentStep()).toBe('facetec');
360
+ });
361
+
362
+ it('onCaptureComplete with useAwsFaceLiveness goes to aws-liveness step', async () => {
363
+ component.useAwsFaceLiveness = true;
364
+ verificationApiSpy.startSession.and.returnValue(of({ sessionId: 'sess-1', plan: [] }));
365
+ verificationApiSpy.verifyGestures.and.returnValue(of({ success: true, frameHash: 'hash123' }));
366
+
367
+ await component.onCaptureComplete({ frames: ['frame1'] });
368
+
369
+ expect(component.currentStep()).toBe('aws-liveness');
370
+ });
371
+
372
+ it('onCaptureComplete handles gesture verification failure', async () => {
373
+ verificationApiSpy.startSession.and.returnValue(of({ sessionId: 'sess-1', plan: [] }));
374
+ verificationApiSpy.verifyGestures.and.returnValue(of(null as any));
375
+
376
+ await component.onCaptureComplete({ frames: ['frame1'] });
377
+
378
+ expect(component.currentStep()).toBe('result');
379
+ expect(component.result()?.success).toBe(false);
380
+ expect(component.result()?.error).toContain('Gesture verification failed');
381
+ });
382
+
383
+ it('onFaceTecScanComplete with success goes to processing', async () => {
384
+ component.capturedFrames = ['frame1'];
385
+ orchestratorSpy.verifyLiveness.and.returnValue({
386
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.9 }); }
387
+ } as any);
388
+ orchestratorSpy.multiBackendResult = null;
389
+ verificationApiSpy.verifyFaceTec.and.returnValue(of({ success: true }));
390
+ verificationApiSpy.finalize.and.returnValue(of({ allPassed: true, confidence: 0.92 }));
391
+
392
+ await component.onFaceTecScanComplete({
393
+ success: true,
394
+ faceScan: 'scan-data',
395
+ auditTrailImage: 'img',
396
+ lowQualityAuditTrailImage: 'img-low'
397
+ } as any);
398
+
399
+ expect(component.result()?.success).toBe(true);
400
+ expect(component.result()?.confidence).toBe(0.92);
401
+ expect(component.currentStep()).toBe('result');
402
+ });
403
+
404
+ it('onAwsFaceLivenessComplete with success processes verification', async () => {
405
+ component.capturedFrames = ['frame1'];
406
+ orchestratorSpy.verifyLiveness.and.returnValue({
407
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.88 }); }
408
+ } as any);
409
+ orchestratorSpy.multiBackendResult = null;
410
+
411
+ await component.onAwsFaceLivenessComplete({
412
+ success: true,
413
+ isLive: true,
414
+ confidence: 0.95,
415
+ sessionId: 'aws-sess'
416
+ } as any);
417
+
418
+ expect(component.result()?.success).toBe(true);
419
+ expect(component.currentStep()).toBe('result');
420
+ });
421
+
422
+ it('processMultiBackendVerification handles orchestrator error', async () => {
423
+ component.capturedFrames = ['frame1'];
424
+ orchestratorSpy.verifyLiveness.and.returnValue({
425
+ subscribe: (obs: any) => { obs.error(new Error('Backend down')); }
426
+ } as any);
427
+
428
+ await (component as any).processMultiBackendVerification();
429
+
430
+ expect(component.result()?.success).toBe(false);
431
+ expect(component.result()?.error).toBe('Backend down');
432
+ expect(component.currentStep()).toBe('result');
433
+ });
434
+
435
+ it('processMultiBackendVerification succeeds with backend response', async () => {
436
+ component.capturedFrames = ['frame1'];
437
+ orchestratorSpy.verifyLiveness.and.returnValue({
438
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.91 }); }
439
+ } as any);
440
+ orchestratorSpy.multiBackendResult = null;
441
+
442
+ await (component as any).processMultiBackendVerification();
443
+
444
+ expect(component.result()?.success).toBe(true);
445
+ expect(component.result()?.confidence).toBe(0.91);
446
+ expect(component.currentStep()).toBe('result');
447
+ expect(component.processingProgress()).toBe(100);
448
+ });
449
+
450
+ it('processHybridVerification handles error', async () => {
451
+ component.capturedFrames = ['frame1'];
452
+ component.faceTecResult = { success: true, faceScan: 'scan' } as any;
453
+ orchestratorSpy.verifyLiveness.and.returnValue({
454
+ subscribe: (obs: any) => { obs.error(new Error('Hybrid failed')); }
455
+ } as any);
456
+
457
+ await (component as any).processHybridVerification();
458
+
459
+ expect(component.result()?.success).toBe(false);
460
+ expect(component.result()?.error).toBe('Hybrid failed');
461
+ expect(component.currentStep()).toBe('result');
462
+ });
463
+
464
+ it('processAwsFaceLivenessVerification handles error', async () => {
465
+ component.capturedFrames = ['frame1'];
466
+ component.awsFaceLivenessResult = { success: true, isLive: true, confidence: 0.95 } as any;
467
+ orchestratorSpy.verifyLiveness.and.returnValue({
468
+ subscribe: (obs: any) => { obs.error(new Error('AWS verify failed')); }
469
+ } as any);
470
+
471
+ await (component as any).processAwsFaceLivenessVerification();
472
+
473
+ expect(component.result()?.success).toBe(false);
474
+ expect(component.result()?.error).toBe('AWS verify failed');
475
+ expect(component.currentStep()).toBe('result');
476
+ });
477
+
478
+ it('onAwsFaceLivenessCancelled cleans up subscriptions', () => {
479
+ const mockSub = { unsubscribe: jest.fn() };
480
+ (component as any).awsOutputSubs = [mockSub];
481
+ let emittedResult: any;
482
+ component.result$.subscribe((r) => (emittedResult = r));
483
+
484
+ component.onAwsFaceLivenessCancelled();
485
+
486
+ expect(mockSub.unsubscribe).toHaveBeenCalled();
487
+ expect(emittedResult.error).toBe('User cancelled');
488
+ });
489
+
490
+ it('shouldSkipIntro returns true when showIntroScreen false and customPose set', () => {
491
+ configServiceSpy.getConfig.and.returnValue({
492
+ ...defaultConfig,
493
+ showIntroScreen: false,
494
+ customPose: { imageData: 'x', description: 'y' }
495
+ });
496
+ expect((component as any).shouldSkipIntro()).toBe(true);
497
+ });
498
+
499
+ it('shouldSkipIntro returns false when showIntroScreen true', () => {
500
+ configServiceSpy.getConfig.and.returnValue({ ...defaultConfig, showIntroScreen: true });
501
+ expect((component as any).shouldSkipIntro()).toBe(false);
502
+ });
503
+
504
+ it('ngAfterViewChecked triggers createDynamicAwsComponent when conditions met', () => {
505
+ component.currentStep.set('aws-liveness');
506
+ component['awsFaceLivenessComponent'] = {} as any;
507
+ component['awsComponentCreated'] = false;
508
+ // Mock createDynamicAwsComponent to avoid actually creating a component
509
+ const origCreate = (component as any).createDynamicAwsComponent;
510
+ let createCalled = false;
511
+ (component as any).createDynamicAwsComponent = () => { createCalled = true; };
512
+ component['awsLivenessContainer'] = { clear: jest.fn() } as any;
513
+
514
+ component.ngAfterViewChecked();
515
+
516
+ expect(createCalled).toBe(true);
517
+ // Restore
518
+ (component as any).createDynamicAwsComponent = origCreate;
519
+ });
520
+
521
+ it('ngAfterViewChecked destroys AWS component when leaving step', () => {
522
+ component.currentStep.set('result');
523
+ component['awsComponentCreated'] = true;
524
+ component['awsComponentRef'] = { destroy: jest.fn() } as any;
525
+
526
+ component.ngAfterViewChecked();
527
+
528
+ expect(component['awsComponentCreated']).toBe(false);
529
+ });
530
+
531
+ it('ngAfterViewChecked does nothing when not on aws-liveness step', () => {
532
+ component.currentStep.set('intro');
533
+ component['awsComponentCreated'] = false;
534
+
535
+ component.ngAfterViewChecked();
536
+
537
+ expect(component['awsComponentCreated']).toBe(false);
538
+ });
539
+
540
+ it('createDynamicAwsComponent subscribes to outputs', () => {
541
+ const mockScanComplete = { subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }) };
542
+ const mockScanCancelled = { subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }) };
543
+
544
+ component['awsFaceLivenessComponent'] = {} as any;
545
+ component['awsLivenessContainer'] = {
546
+ clear: jest.fn(),
547
+ createComponent: jest.fn().mockReturnValue({
548
+ instance: {
549
+ region: '',
550
+ autoStart: false,
551
+ showCancelButton: false,
552
+ scanComplete: mockScanComplete,
553
+ scanCancelled: mockScanCancelled
554
+ },
555
+ changeDetectorRef: { detectChanges: jest.fn() }
556
+ })
557
+ } as any;
558
+
559
+ (component as any).createDynamicAwsComponent();
560
+
561
+ expect(mockScanComplete.subscribe).toHaveBeenCalled();
562
+ expect(mockScanCancelled.subscribe).toHaveBeenCalled();
563
+ expect(component['awsOutputSubs'].length).toBe(2);
564
+
565
+ // Clean up to prevent Angular teardown errors
566
+ component['awsComponentRef'] = null as any;
567
+ component['awsComponentCreated'] = false;
568
+ });
569
+
570
+ it('createDynamicAwsComponent is safe without container', () => {
571
+ component['awsFaceLivenessComponent'] = {} as any;
572
+ component['awsLivenessContainer'] = null as any;
573
+ expect(() => (component as any).createDynamicAwsComponent()).not.toThrow();
574
+ });
575
+
576
+ it('processMultiBackendVerification uploads video when blob exists', async () => {
577
+ component.capturedFrames = ['frame1'];
578
+ component.videoBlob = new Blob(['video']);
579
+ const mockMultiResult = {
580
+ primary: { metadata: { sessionId: 'sess-1' } },
581
+ amazon: null, openpose: null, facetec: null, consensus: { isLive: true, confidence: 0.91 }
582
+ } as any;
583
+ orchestratorSpy.verifyLiveness.and.returnValue({
584
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.91 }); }
585
+ } as any);
586
+ orchestratorSpy.multiBackendResult = mockMultiResult;
587
+ orchestratorSpy.uploadVideoToS3.and.returnValue({
588
+ subscribe: (obs: any) => { obs.next({ videoUrl: 'https://s3.example.com/video.webm' }); }
589
+ } as any);
590
+
591
+ await (component as any).processMultiBackendVerification();
592
+
593
+ expect(orchestratorSpy.uploadVideoToS3).toHaveBeenCalled();
594
+ expect(component.result()?.videoUrl).toBe('https://s3.example.com/video.webm');
595
+ expect(component.result()?.success).toBe(true);
596
+ });
597
+
598
+ it('processMultiBackendVerification handles video upload failure gracefully', async () => {
599
+ component.capturedFrames = ['frame1'];
600
+ component.videoBlob = new Blob(['video']);
601
+ const mockMultiResult = {
602
+ primary: { metadata: { sessionId: 'sess-1' } },
603
+ amazon: null, openpose: null, facetec: null, consensus: { isLive: true, confidence: 0.91 }
604
+ } as any;
605
+ orchestratorSpy.verifyLiveness.and.returnValue({
606
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.91 }); }
607
+ } as any);
608
+ orchestratorSpy.multiBackendResult = mockMultiResult;
609
+ orchestratorSpy.uploadVideoToS3.and.returnValue({
610
+ subscribe: (obs: any) => { obs.error(new Error('Upload failed')); }
611
+ } as any);
612
+
613
+ await (component as any).processMultiBackendVerification();
614
+
615
+ expect(component.result()?.success).toBe(true);
616
+ expect(component.result()?.videoUrl).toBeUndefined();
617
+ });
618
+
619
+ it('processHybridVerification succeeds with full flow', async () => {
620
+ component.capturedFrames = ['frame1'];
621
+ component.faceTecResult = { success: true, faceScan: 'scan' } as any;
622
+ orchestratorSpy.verifyLiveness.and.returnValue({
623
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.9 }); }
624
+ } as any);
625
+ orchestratorSpy.multiBackendResult = null;
626
+ verificationApiSpy.verifyFaceTec.and.returnValue(of({ success: true }));
627
+ verificationApiSpy.finalize.and.returnValue(of({ allPassed: true, confidence: 0.95 }));
628
+
629
+ await (component as any).processHybridVerification();
630
+
631
+ expect(component.result()?.success).toBe(true);
632
+ expect(component.result()?.confidence).toBe(0.95);
633
+ expect(component.currentStep()).toBe('result');
634
+ });
635
+
636
+ it('processAwsFaceLivenessVerification succeeds', async () => {
637
+ component.capturedFrames = ['frame1'];
638
+ component.awsFaceLivenessResult = { success: true, isLive: true, confidence: 0.95 } as any;
639
+ orchestratorSpy.verifyLiveness.and.returnValue({
640
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.88 }); }
641
+ } as any);
642
+ orchestratorSpy.multiBackendResult = null;
643
+
644
+ await (component as any).processAwsFaceLivenessVerification();
645
+
646
+ expect(component.result()?.success).toBe(true);
647
+ const avgConf = (0.95 + 0.88) / 2;
648
+ expect(component.result()?.confidence).toBeCloseTo(avgConf, 2);
649
+ expect(component.currentStep()).toBe('result');
650
+ });
651
+
652
+ it('processAwsFaceLivenessVerification uploads video when available', async () => {
653
+ component.capturedFrames = ['frame1'];
654
+ component.videoBlob = new Blob(['video']);
655
+ component.awsFaceLivenessResult = { success: true, isLive: true, confidence: 0.95 } as any;
656
+ const mockMultiResult = {
657
+ primary: { metadata: { sessionId: 'sess-1' } },
658
+ amazon: null, openpose: null, facetec: null, consensus: { isLive: true, confidence: 0.88 }
659
+ } as any;
660
+ orchestratorSpy.verifyLiveness.and.returnValue({
661
+ subscribe: (obs: any) => { obs.next({ isLive: true, confidence: 0.88 }); }
662
+ } as any);
663
+ orchestratorSpy.multiBackendResult = mockMultiResult;
664
+ orchestratorSpy.uploadVideoToS3.and.returnValue({
665
+ subscribe: (obs: any) => { obs.next({ videoUrl: 'https://s3.example.com/video.webm' }); }
666
+ } as any);
667
+
668
+ await (component as any).processAwsFaceLivenessVerification();
669
+
670
+ expect(orchestratorSpy.uploadVideoToS3).toHaveBeenCalled();
671
+ expect(component.result()?.videoUrl).toBe('https://s3.example.com/video.webm');
672
+ });
673
+ });