diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_check.py index 4ef7524..58b09f8 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_check.py @@ -6,12 +6,15 @@ class SandboxCheck(YotiSerializable): - def __init__(self, result): + def __init__(self, result, handled_check_limit=None): """ :param result: the result :type result: SandboxCheckResult + :param handled_check_limit: optional limit on how many times this check response is used + :type handled_check_limit: int or None """ self.__result = result + self.__handled_check_limit = handled_check_limit @property def result(self): @@ -20,8 +23,22 @@ def result(self): """ return self.__result + @property + def handled_check_limit(self): + """ + The number of times this configured check response is used before the sandbox + advances to the next configured response. When None, the backend treats it as + no limit. + + :rtype: int or None + """ + return self.__handled_check_limit + def to_json(self): - return {"result": self.result} + json = {"result": self.result} + if self.__handled_check_limit is not None: + json["handled_check_limit"] = self.__handled_check_limit + return json class SandboxCheckBuilder(object): @@ -30,6 +47,7 @@ class SandboxCheckBuilder(object): def __init__(self): self.__recommendation = None self.__breakdown = [] + self.__handled_check_limit = None def with_recommendation(self, recommendation): self.__recommendation = recommendation @@ -43,6 +61,19 @@ def with_breakdowns(self, breakdowns): self.__breakdown = breakdowns return self + def with_handled_check_limit(self, handled_check_limit): + """ + Sets the number of times this configured check response is used before the sandbox + advances to the next configured response. + + :param handled_check_limit: the limit + :type handled_check_limit: int + :return: the builder + :rtype: SandboxCheckBuilder + """ + self.__handled_check_limit = handled_check_limit + return self + @property def recommendation(self): return self.__recommendation @@ -51,6 +82,10 @@ def recommendation(self): def breakdown(self): return self.__breakdown + @property + def handled_check_limit(self): + return self.__handled_check_limit + @abstractmethod def build(self): raise NotImplementedError diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_document_authenticity_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_document_authenticity_check.py index fcbf35d..7c23abd 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_document_authenticity_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_document_authenticity_check.py @@ -18,4 +18,6 @@ class SandboxDocumentAuthenticityCheckBuilder(SandboxDocumentCheckBuilder): def build(self): report = SandboxCheckReport(self.recommendation, self.breakdown) result = SandboxCheckResult(report) - return SandboxDocumentAuthenticityCheck(result, self.document_filter) + return SandboxDocumentAuthenticityCheck( + result, self.document_filter, self.handled_check_limit + ) diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_document_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_document_check.py index bd0f8bb..afc10d2 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_document_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_document_check.py @@ -5,8 +5,8 @@ class SandboxDocumentCheck(SandboxCheck): - def __init__(self, result, document_filter): - SandboxCheck.__init__(self, result) + def __init__(self, result, document_filter, handled_check_limit=None): + SandboxCheck.__init__(self, result, handled_check_limit) self.__document_filter = document_filter @property diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_document_face_match_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_document_face_match_check.py index dbee703..49c28a1 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_document_face_match_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_document_face_match_check.py @@ -18,4 +18,6 @@ class SandboxDocumentFaceMatchCheckBuilder(SandboxDocumentCheckBuilder): def build(self): report = SandboxCheckReport(self.recommendation, self.breakdown) result = SandboxCheckResult(report) - return SandboxDocumentFaceMatchCheck(result, self.document_filter) + return SandboxDocumentFaceMatchCheck( + result, self.document_filter, self.handled_check_limit + ) diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_document_text_data_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_document_text_data_check.py index 3c01347..c0490fc 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_document_text_data_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_document_text_data_check.py @@ -58,4 +58,6 @@ def with_document_fields(self, document_fields): def build(self): report = SandboxCheckReport(self.recommendation, self.breakdown) result = SandboxDocumentTextDataCheckResult(report, self.__document_fields) - return SandboxDocumentTextDataCheck(result, self.document_filter) + return SandboxDocumentTextDataCheck( + result, self.document_filter, self.handled_check_limit + ) diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_id_document_comparison_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_id_document_comparison_check.py index 1278af3..528dee0 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_id_document_comparison_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_id_document_comparison_check.py @@ -5,8 +5,8 @@ class SandboxIdDocumentComparisonCheck(SandboxCheck): - def __init__(self, result, secondary_document_filter): - SandboxCheck.__init__(self, result) + def __init__(self, result, secondary_document_filter, handled_check_limit=None): + SandboxCheck.__init__(self, result, handled_check_limit) self.__secondary_document_filter = secondary_document_filter @staticmethod @@ -37,5 +37,5 @@ def build(self): report = SandboxCheckReport(self.recommendation, self.breakdown) result = SandboxCheckResult(report) return SandboxIdDocumentComparisonCheck( - result, self.__secondary_document_filter + result, self.__secondary_document_filter, self.handled_check_limit ) diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_liveness_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_liveness_check.py index 57d5aa3..fa07f18 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_liveness_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_liveness_check.py @@ -2,8 +2,8 @@ class SandboxLivenessCheck(SandboxCheck): - def __init__(self, result, liveness_type): - SandboxCheck.__init__(self, result) + def __init__(self, result, liveness_type, handled_check_limit=None): + SandboxCheck.__init__(self, result, handled_check_limit) self.__liveness_type = liveness_type @property diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_supplementary_document_text_data_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_supplementary_document_text_data_check.py index 971a003..f220f38 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_supplementary_document_text_data_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_supplementary_document_text_data_check.py @@ -60,4 +60,6 @@ def build(self): result = SandboxSupplementaryDocumentTextDataCheckResult( report, self.__document_fields ) - return SandboxSupplementaryDocumentTextDataCheck(result, self.document_filter) + return SandboxSupplementaryDocumentTextDataCheck( + result, self.document_filter, self.handled_check_limit + ) diff --git a/yoti_python_sandbox/doc_scan/check/sandbox_zoom_liveness_check.py b/yoti_python_sandbox/doc_scan/check/sandbox_zoom_liveness_check.py index 783b777..c3ef5ba 100644 --- a/yoti_python_sandbox/doc_scan/check/sandbox_zoom_liveness_check.py +++ b/yoti_python_sandbox/doc_scan/check/sandbox_zoom_liveness_check.py @@ -9,12 +9,12 @@ class SandboxZoomLivenessCheck(SandboxLivenessCheck): - def __init__(self, result): - SandboxLivenessCheck.__init__(self, result, constants.ZOOM) + def __init__(self, result, handled_check_limit=None): + SandboxLivenessCheck.__init__(self, result, constants.ZOOM, handled_check_limit) class SandboxZoomLivenessCheckBuilder(SandboxCheckBuilder): def build(self): report = SandboxCheckReport(self.recommendation, self.breakdown) result = SandboxCheckResult(report) - return SandboxZoomLivenessCheck(result) + return SandboxZoomLivenessCheck(result, self.handled_check_limit) diff --git a/yoti_python_sandbox/tests/doc_scan/check/test_handled_check_limit.py b/yoti_python_sandbox/tests/doc_scan/check/test_handled_check_limit.py new file mode 100644 index 0000000..c8a3b78 --- /dev/null +++ b/yoti_python_sandbox/tests/doc_scan/check/test_handled_check_limit.py @@ -0,0 +1,144 @@ +""" +Tests for handled_check_limit on all IDV sandbox check-report types. +""" +import pytest + +from yoti_python_sandbox.doc_scan.check import ( + SandboxDocumentAuthenticityCheckBuilder, + SandboxDocumentFaceMatchCheckBuilder, + SandboxDocumentTextDataCheckBuilder, + SandboxIdDocumentComparisonCheckBuilder, + SandboxSupplementaryDocumentTextDataCheckBuilder, + SandboxZoomLivenessCheckBuilder, +) + + +HANDLED_CHECK_LIMIT = 3 + +ALL_BUILDER_CLASSES = [ + SandboxDocumentAuthenticityCheckBuilder, + SandboxDocumentFaceMatchCheckBuilder, + SandboxDocumentTextDataCheckBuilder, + SandboxIdDocumentComparisonCheckBuilder, + SandboxSupplementaryDocumentTextDataCheckBuilder, + SandboxZoomLivenessCheckBuilder, +] + + +@pytest.mark.parametrize("builder_class", ALL_BUILDER_CLASSES) +def test_handled_check_limit_is_included_in_json_when_set(builder_class): + check = builder_class().with_handled_check_limit(HANDLED_CHECK_LIMIT).build() + + json = check.to_json() + + assert json.get("handled_check_limit") == HANDLED_CHECK_LIMIT + + +@pytest.mark.parametrize("builder_class", ALL_BUILDER_CLASSES) +def test_handled_check_limit_is_omitted_from_json_when_not_set(builder_class): + check = builder_class().build() + + json = check.to_json() + + assert "handled_check_limit" not in json + + +@pytest.mark.parametrize("builder_class", ALL_BUILDER_CLASSES) +def test_handled_check_limit_property_returns_set_value(builder_class): + check = builder_class().with_handled_check_limit(HANDLED_CHECK_LIMIT).build() + + assert check.handled_check_limit == HANDLED_CHECK_LIMIT + + +@pytest.mark.parametrize("builder_class", ALL_BUILDER_CLASSES) +def test_handled_check_limit_property_returns_none_when_not_set(builder_class): + check = builder_class().build() + + assert check.handled_check_limit is None + + +def test_document_authenticity_check_handled_check_limit_end_to_end(): + check = ( + SandboxDocumentAuthenticityCheckBuilder() + .with_handled_check_limit(5) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 5 + assert check.handled_check_limit == 5 + + +def test_zoom_liveness_check_handled_check_limit_end_to_end(): + check = ( + SandboxZoomLivenessCheckBuilder() + .with_handled_check_limit(2) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 2 + assert check.handled_check_limit == 2 + assert check.liveness_type == "ZOOM" + + +def test_id_document_comparison_check_handled_check_limit_end_to_end(): + check = ( + SandboxIdDocumentComparisonCheckBuilder() + .with_handled_check_limit(1) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 1 + + +def test_document_face_match_check_handled_check_limit_end_to_end(): + check = ( + SandboxDocumentFaceMatchCheckBuilder() + .with_handled_check_limit(10) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 10 + + +def test_document_text_data_check_handled_check_limit_end_to_end(): + check = ( + SandboxDocumentTextDataCheckBuilder() + .with_handled_check_limit(7) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 7 + + +def test_supplementary_document_text_data_check_handled_check_limit_end_to_end(): + check = ( + SandboxSupplementaryDocumentTextDataCheckBuilder() + .with_handled_check_limit(4) + .build() + ) + + json = check.to_json() + + assert json.get("handled_check_limit") == 4 + + +def test_existing_builder_calls_remain_backward_compatible(): + """Ensure existing callers that do not set handled_check_limit still work.""" + check = ( + SandboxDocumentAuthenticityCheckBuilder() + .build() + ) + + assert check.handled_check_limit is None + json = check.to_json() + assert "handled_check_limit" not in json