Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Liveness check manager tests #255

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2a54e95
New Smart Selfie Capture UI (#202)
tobitech Jul 30, 2024
12e032e
Merge branch 'main' into new-smart-selfie-capture
tobitech Jul 31, 2024
0a24fa1
Merge branch 'main' into new-smart-selfie-capture
tobitech Aug 7, 2024
1c3293a
Merge branch 'main' into new-smart-selfie-capture
tobitech Aug 19, 2024
a758481
Merge branch 'main' into new-smart-selfie-capture
tobitech Aug 19, 2024
877bbb2
Merge branch 'main' into new-smart-selfie-capture
tobitech Aug 19, 2024
f6b9b83
Selfie Quality Check With CoreML Model (#206)
tobitech Aug 26, 2024
acd4b78
Merge branch 'main' into new-smart-selfie-capture
tobitech Aug 29, 2024
bc00398
run pod install
tobitech Sep 2, 2024
2ae0884
Introduce new versions for FaceDetector, SelfieViewModel classes and …
tobitech Sep 10, 2024
7ace3ea
Merge branch 'main' into new-smart-selfie-capture
tobitech Sep 10, 2024
ae9f30c
run pod install
tobitech Sep 10, 2024
16db3d6
Merge branch 'main' into new-smart-selfie-capture
tobitech Sep 18, 2024
801dbb6
run pod install.
tobitech Sep 18, 2024
7843341
Liveness Capture Instructions Screen (#229)
tobitech Sep 18, 2024
2bc0884
Merge branch 'main' into new-smart-selfie-capture
tobitech Sep 20, 2024
42c734c
run pod install.
tobitech Sep 20, 2024
5f1ab99
Merge branch 'main' into new-smart-selfie-capture
tobitech Sep 26, 2024
e26f608
run pod install
tobitech Sep 26, 2024
a4b7ffe
New Camera UI (#235)
tobitech Sep 26, 2024
58b3d3b
Merge branch 'main' into new-smart-selfie-capture
tobitech Oct 3, 2024
f0ac875
run pod install.
tobitech Oct 3, 2024
135f057
Connect ViewModel to New UI and Refactoring (#237)
tobitech Oct 9, 2024
4b62510
Merge branch 'main' into new-smart-selfie-capture
tobitech Oct 24, 2024
bdbf4a7
Merge branch 'main' into new-smart-selfie-capture
tobitech Oct 28, 2024
1c642f5
run pod install
tobitech Oct 28, 2024
a93950f
Box Oval Interface for Selfie Capture Screen (#250)
tobitech Nov 6, 2024
87f434f
Face Validator Tests (#252)
tobitech Nov 8, 2024
9951083
Merge branch 'main' into new-smart-selfie-capture
tobitech Nov 11, 2024
569676f
run pod install
tobitech Nov 11, 2024
ac1cd7f
decouple selfieviewmodel from livenesscheckmanager
tobitech Nov 12, 2024
04f8f0f
improve object references so to prevent retain cycles.
tobitech Nov 12, 2024
64441df
write custom encoding function for failure reason, replace forced fai…
tobitech Nov 13, 2024
ab190d3
check that submission task is nil before assigning it.
tobitech Nov 13, 2024
1c9fe5a
remove unnecessary comment
tobitech Nov 13, 2024
918a867
test that initialisation shuffles liveness tasks
tobitech Nov 14, 2024
4f5e608
introduce some abstraction to Timer and DispatchQueue so that they ca…
tobitech Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ SPEC CHECKSUMS:
lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418
netfox: 9d5cc727fe7576c4c7688a2504618a156b7d44b7
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
SmileID: 3c6d3101c7da84fe9acc36c10d2a189192f00d13
SmileID: 93184d185549dec6858a3cc567bd9423de79abbb
SwiftLint: 3fe909719babe5537c552ee8181c0031392be933
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c

Expand Down
152 changes: 80 additions & 72 deletions Example/SmileID.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

65 changes: 50 additions & 15 deletions Example/SmileID/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ struct HomeView: View {
@StateObject var viewModel: HomeViewModel

init(config: Config) {
_viewModel = StateObject(wrappedValue: HomeViewModel(config: config))
_viewModel = StateObject(wrappedValue: HomeViewModel(config: config))
}

let columns = [GridItem(.flexible()), GridItem(.flexible())]

var body: some View {
NavigationView {
VStack(spacing: 24) {
Text("Test Our Products")
.font(SmileID.theme.header2)
.foregroundColor(.black)

MyVerticalGrid(
maxColumns: 2,
items: [
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns) {
ProductCell(
image: "smart_selfie_enroll",
name: "SmartSelfie™ Enrollment",
Expand All @@ -38,7 +38,7 @@ struct HomeView: View {
)
)
}
),
)
ProductCell(
image: "smart_selfie_authentication",
name: "SmartSelfie™ Authentication",
Expand All @@ -51,7 +51,41 @@ struct HomeView: View {
delegate: viewModel
)
}
),
)
ProductCell(
image: "smart_selfie_enroll",
name: "SmartSelfie™ Enrollment (Strict Mode)",
onClick: {
viewModel.onProductClicked()
},
content: {
SmileID.smartSelfieEnrollmentScreen(
userId: viewModel.smartSelfieEnrollmentUserId,
jobId: viewModel.newJobId,
allowAgentMode: true,
useStrictMode: true,
delegate: SmartSelfieEnrollmentDelegate(
userId: viewModel.smartSelfieEnrollmentUserId,
onEnrollmentSuccess: viewModel.onSmartSelfieEnrollment,
onError: viewModel.didError
)
)
}
)
ProductCell(
image: "smart_selfie_authentication",
name: "SmartSelfie™ Authentication (Strict Mode)",
onClick: {
viewModel.onProductClicked()
},
content: {
SmartSelfieAuthWithUserIdEntry(
initialUserId: viewModel.smartSelfieEnrollmentUserId,
useStrictMode: true,
delegate: viewModel
)
}
)
ProductCell(
image: "enhanced_kyc",
name: "Enhanced KYC",
Expand All @@ -67,7 +101,7 @@ struct HomeView: View {
)
)
}
),
)
ProductCell(
image: "biometric",
name: "Biometric KYC",
Expand All @@ -83,7 +117,7 @@ struct HomeView: View {
)
)
}
),
)
ProductCell(
image: "document",
name: "\nDocument Verification",
Expand All @@ -97,7 +131,7 @@ struct HomeView: View {
delegate: viewModel
)
}
),
)
ProductCell(
image: "enhanced_doc_v",
name: "Enhanced Document Verification",
Expand All @@ -112,10 +146,8 @@ struct HomeView: View {
)
}
)
].map {
AnyView($0)
}
)
}

Text("Partner \(viewModel.partnerId) - Version \(version) - Build \(build)")
.font(SmileID.theme.body)
Expand Down Expand Up @@ -164,14 +196,17 @@ struct SmartSelfieEnrollmentDelegate: SmartSelfieResultDelegate {

private struct SmartSelfieAuthWithUserIdEntry: View {
let initialUserId: String
var useStrictMode: Bool = false
let delegate: SmartSelfieResultDelegate

@State private var userId: String?

var body: some View {
if let userId {
SmileID.smartSelfieAuthenticationScreen(
userId: userId,
allowAgentMode: true,
useStrictMode: useStrictMode,
delegate: delegate
)
} else {
Expand Down Expand Up @@ -262,9 +297,9 @@ private struct MyVerticalGrid: View {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
let numRows = (items.count + maxColumns - 1) / maxColumns
ForEach(0 ..< numRows) { rowIndex in
ForEach(0 ..< numRows, id: \.self) { rowIndex in
HStack(spacing: 16) {
ForEach(0 ..< maxColumns) { columnIndex in
ForEach(0 ..< maxColumns, id: \.self) { columnIndex in
let itemIndex = rowIndex * maxColumns + columnIndex
let width = geo.size.width / CGFloat(maxColumns)
if itemIndex < items.count {
Expand Down
17 changes: 12 additions & 5 deletions Example/SmileID/Home/ProductCell.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import SmileID
import SwiftUI

struct ProductCell: View {
struct ProductCell<Content: View>: View {
let image: String
let name: String
let onClick: (() -> Void)?
@ViewBuilder let content: () -> any View
@ViewBuilder let content: () -> Content
@State private var isPresented: Bool = false

init(
image: String,
name: String,
onClick: (() -> Void)? = nil,
@ViewBuilder content: @escaping () -> any View
@ViewBuilder content: @escaping () -> Content
) {
self.image = image
self.name = name
Expand Down Expand Up @@ -41,8 +41,15 @@ struct ProductCell: View {
.frame(maxWidth: .infinity)
.background(SmileID.theme.accent)
.cornerRadius(8)
.sheet(isPresented: $isPresented, content: { AnyView(content())
})
.fullScreenCover(
isPresented: $isPresented,
content: {
NavigationView {
content()
}
.environment(\.modalMode, $isPresented)
}
)
}
)
}
Expand Down
147 changes: 147 additions & 0 deletions Example/Tests/FaceValidatorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import XCTest

@testable import SmileID

class FaceValidatorTests: XCTestCase {
private var faceValidator: FaceValidator!
private var mockDelegate: MockFaceValidatorDelegate!

override func setUp() {
super.setUp()
faceValidator = FaceValidator()
mockDelegate = MockFaceValidatorDelegate()
faceValidator.delegate = mockDelegate
let guideFrame: CGRect = .init(x: 30, y: 100, width: 250, height: 350)
faceValidator.setLayoutGuideFrame(with: guideFrame)
}

override func tearDown() {
faceValidator = nil
mockDelegate = nil
super.tearDown()
}

func testValidateWithValidFace() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190),
selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9),
brighness: 100
)

XCTAssertTrue(result.faceInBounds)
XCTAssertTrue(result.hasDetectedValidFace)
XCTAssertNil(result.userInstruction)
}

func testValidateWithFaceTooSmall() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 100, height: 100),
selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9),
brighness: 100
)

XCTAssertFalse(result.faceInBounds)
XCTAssertFalse(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .moveCloser)
}

func testValidateWithFaceTooLarge() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 250, height: 250),
selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9),
brighness: 100
)

XCTAssertFalse(result.faceInBounds)
XCTAssertFalse(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .moveBack)
}

func testValidWithFaceOffCentre() {
let result = performValidation(
faceBoundingBox: CGRect(x: 125, y: 164, width: 190, height: 190),
selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9),
brighness: 100
)

XCTAssertFalse(result.faceInBounds)
XCTAssertFalse(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .headInFrame)
}

func testValidateWithPoorBrightness() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190),
selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9),
brighness: 70
)

XCTAssertTrue(result.faceInBounds)
XCTAssertFalse(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .goodLight)
}

func testValidateWithPoorSelfieQuality() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190),
selfieQualityData: SelfieQualityData(failed: 0.6, passed: 0.4),
brighness: 70
)

XCTAssertTrue(result.faceInBounds)
XCTAssertFalse(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .goodLight)
}

func testValidateWithLivenessTask() {
let result = performValidation(
faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190),
selfieQualityData: SelfieQualityData(failed: 0.3, passed: 0.7),
brighness: 100,
livenessTask: .lookLeft
)

XCTAssertTrue(result.faceInBounds)
XCTAssertTrue(result.hasDetectedValidFace)
XCTAssertEqual(result.userInstruction, .lookLeft)
}
}

// MARK: - Helpers
extension FaceValidatorTests {
func performValidation(
faceBoundingBox: CGRect,
selfieQualityData: SelfieQualityData,
brighness: Int,
livenessTask: LivenessTask? = nil
) -> FaceValidationResult {
let faceGeometry = FaceGeometryData(
boundingBox: faceBoundingBox,
roll: 0,
yaw: 0,
pitch: 0,
direction: .none
)
faceValidator.validate(
faceGeometry: faceGeometry,
selfieQuality: selfieQualityData,
brightness: brighness,
currentLivenessTask: livenessTask
)

guard let mockValidationResult = mockDelegate.validationResult else {
XCTFail("Validation result should not be nil")
return FaceValidationResult(userInstruction: nil, hasDetectedValidFace: false, faceInBounds: false)
}
return mockValidationResult
}
}

// MARK: - Mocks
class MockFaceValidatorDelegate: FaceValidatorDelegate {
var validationResult: FaceValidationResult?

func updateValidationResult(_ result: FaceValidationResult) {
self.validationResult = result
}
}
Loading
Loading