diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 083d6efa..4fd8690c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -17,11 +17,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - - run: pip install -U setuptools + - run: pip install -U setuptools # NOSONAR - - run: pip install -r requirements.txt + - run: pip install -r requirements.txt # NOSONAR - - run: pip install -e .[dev] + - run: pip install -e .[dev] # NOSONAR - run: pytest -v @@ -36,14 +36,17 @@ jobs: with: python-version: 3.9 - - run: pip install --upgrade setuptools + - run: pip install --upgrade setuptools # NOSONAR - - run: pip install -e . + - run: pip install -e . # NOSONAR - - run: pushd examples/aml && pip install -r requirements.txt && popd + - run: pushd examples/aml && pip install -r requirements.txt && popd # NOSONAR - - run: pushd examples/yoti_example_django && pip install --upgrade pip && pip install -r requirements.txt && popd + - run: pushd examples/yoti_example_django && pip install --upgrade pip && pip install -r requirements.txt && popd # NOSONAR - - run: pushd examples/yoti_example_flask && pip install -r requirements.txt && popd + # Reinstall local SDK after AML/Django, which pin yoti==2.14.0 from PyPI and override it + - run: pip install -e . # NOSONAR - - run: pushd examples/doc_scan && pip install -r requirements.txt && popd + - run: pushd examples/yoti_example_flask && pip install -r requirements.txt && popd # NOSONAR + + - run: pushd examples/doc_scan && pip install -r requirements.txt && popd # NOSONAR diff --git a/.gitignore b/.gitignore index 022b5cb0..d53377e2 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,4 @@ examples/yoti_example_flask/*.pem .scannerwork .venv/ +.claude/ diff --git a/examples/aml/requirements.in b/examples/aml/requirements.in index 7e25054c..39d4ba4f 100644 --- a/examples/aml/requirements.in +++ b/examples/aml/requirements.in @@ -1,2 +1,3 @@ yoti>=2.14.0 +protobuf<6 python-dotenv>=0.7.1 diff --git a/examples/aml/requirements.txt b/examples/aml/requirements.txt index 33889910..d5b65599 100644 --- a/examples/aml/requirements.txt +++ b/examples/aml/requirements.txt @@ -1,46 +1,48 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: # -# pip-compile --output-file=requirements.txt requirements.in +# pip-compile --output-file=examples/aml/requirements.txt examples/aml/requirements.in # asn1==2.2.0 # via yoti -certifi==2021.10.8 +certifi==2026.4.22 # via requests -cffi==1.15.0 +cffi==2.0.0 # via cryptography -charset-normalizer==2.0.10 +charset-normalizer==3.4.7 # via requests -cryptography==36.0.1 +cryptography==48.0.0 # via # pyopenssl # yoti -deprecated==1.2.10 +deprecated==1.3.1 # via yoti -future==0.18.2 - # via yoti -idna==3.3 +idna==3.15 # via requests -iso8601==0.1.13 - # via yoti -protobuf==3.19.3 +iso8601==2.1.0 # via yoti -pycparser==2.21 +protobuf==5.29.6 + # via + # -r examples/aml/requirements.in + # yoti +pycparser==2.23 # via cffi -pyopenssl==21.0.0 +pyopenssl==26.2.0 # via yoti -python-dotenv==0.19.2 - # via -r requirements.in -pytz==2020.4 +python-dotenv==1.2.1 + # via -r examples/aml/requirements.in +pytz==2026.2 # via yoti -requests==2.27.1 +requests==2.32.5 # via yoti -six==1.16.0 - # via pyopenssl -urllib3==1.26.8 +typing-extensions==4.15.0 + # via + # cryptography + # pyopenssl +urllib3==2.6.3 # via requests -wrapt==1.13.3 +wrapt==2.1.2 # via deprecated -yoti==2.14.0 - # via -r requirements.in +yoti==2.14.5 + # via -r examples/aml/requirements.in diff --git a/examples/doc_scan/requirements.in b/examples/doc_scan/requirements.in index 0a5d434f..26f386a3 100644 --- a/examples/doc_scan/requirements.in +++ b/examples/doc_scan/requirements.in @@ -7,20 +7,7 @@ pyopenssl>=24.0.0 click>=8.0 future>=1.0.0 -# Required for yoti compatibility -deprecated>=1.2.14 -iso8601>=1.1.0 -pytz>=2025.2ements -flask>=2.2.0 -python-dotenv>=0.21.0 -yoti>=2.14.0 -filetype>=1.0.7 -pyopenssl>=24.0.0 -click>=8.0 -future>=1.0.0 - # Required for yoti compatibility deprecated>=1.2.14 iso8601>=1.1.0 pytz>=2025.2 - diff --git a/examples/doc_scan/requirements.txt b/examples/doc_scan/requirements.txt index c5a22510..54e5a453 100644 --- a/examples/doc_scan/requirements.txt +++ b/examples/doc_scan/requirements.txt @@ -1,11 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: +# This file was generated by pip-compile and has been updated to reflect +# the SDK's install_requires constraints (>=). To fully regenerate, run: # # pip-compile --output-file=requirements.txt requirements.in # asn1==2.2.0 - # via yoti + # via yoti (installed separately) blinker==1.9.0 # via flask @@ -19,8 +19,7 @@ click==8.1.8 # via # -r requirements.in # flask -cryptography==41.0.7 - +cryptography>=42.0.0 # via # pyopenssl # yoti @@ -52,7 +51,7 @@ markupsafe==3.0.2 # werkzeug protobuf==4.21.12 - # via yoti + # via yoti (installed separately) pycparser==2.22 # via cffi pyopenssl>=24.0.0 @@ -66,7 +65,7 @@ pytz>=2025.2 # -r requirements.in # yoti requests>=2.31.0 - # via yoti + # via yoti (installed separately) six>=1.16.0 # via protobuf urllib3==1.25.9 diff --git a/examples/yoti_example_django/requirements.in b/examples/yoti_example_django/requirements.in index 884d3bd9..c7629691 100644 --- a/examples/yoti_example_django/requirements.in +++ b/examples/yoti_example_django/requirements.in @@ -1,8 +1,7 @@ -django>=4.0.1 +django>=4.2,<5.3 django-sslserver>=0.22.0 python-dotenv>=0.7.1 -requests>=2.20.0 -urllib3>=1.24.2 +requests>=2.32.4 +urllib3>=2.6.3 yoti>=2.14.0 -six>=1.16.0 -cffi>=1.15.0 \ No newline at end of file +cffi>=1.17.1 diff --git a/examples/yoti_example_django/requirements.txt b/examples/yoti_example_django/requirements.txt index d966b31c..dd988ae1 100644 --- a/examples/yoti_example_django/requirements.txt +++ b/examples/yoti_example_django/requirements.txt @@ -66,6 +66,3 @@ wrapt==1.12.1 # via deprecated yoti==2.14.0 # via -r requirements.in - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/examples/yoti_example_django/yoti_example/settings.py b/examples/yoti_example_django/yoti_example/settings.py index 73bd1b2a..60d28b85 100644 --- a/examples/yoti_example_django/yoti_example/settings.py +++ b/examples/yoti_example_django/yoti_example/settings.py @@ -80,6 +80,8 @@ } } +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators diff --git a/examples/yoti_example_flask/requirements.txt b/examples/yoti_example_flask/requirements.txt index c4d5a9fa..1491dd4d 100644 --- a/examples/yoti_example_flask/requirements.txt +++ b/examples/yoti_example_flask/requirements.txt @@ -1,11 +1,11 @@ # -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: +# This file was generated by pip-compile and has been updated to reflect +# the SDK's install_requires constraints (>=). To fully regenerate, run: # # pip-compile --output-file=requirements.txt requirements.in # asn1==2.2.0 - # via yoti + # via yoti (installed separately) certifi==2018.4.16 # via requests cffi==1.15.0 @@ -23,7 +23,7 @@ cryptography>=42.0.0 # pyopenssl # yoti deprecated==1.2.18 - # via yoti + # via yoti (installed separately) flask==1.1.1 # via -r requirements.in future==1.0.0 @@ -33,7 +33,7 @@ future==1.0.0 idna==2.7 # via requests iso8601>=1.1.0 - # via yoti + # via yoti (installed separately) itsdangerous==1.1.0 # via flask jinja2==3.0.3 @@ -43,7 +43,7 @@ jinja2==3.0.3 markupsafe==2.0.1 # via jinja2 protobuf==4.21.12 - # via yoti + # via yoti (installed separately) pycparser==2.18 # via cffi pyopenssl>=24.0.0 @@ -53,7 +53,7 @@ pyopenssl>=24.0.0 python-dotenv>=0.7.1 # via -r requirements.in pytz>=2025.2 - # via yoti + # via yoti (installed separately) requests>=2.31.0 # via # -r requirements.in diff --git a/requirements.in b/requirements.in index 45803552..ef7329d1 100644 --- a/requirements.in +++ b/requirements.in @@ -1,6 +1,6 @@ asn1==2.2.0 -cryptography>=41.0.7 +cryptography>=42.0.0 cffi>=1.16.0 future>=0.18.3 itsdangerous>=2.1.2 diff --git a/setup.py b/setup.py index fd3be6db..e40e3721 100644 --- a/setup.py +++ b/setup.py @@ -22,10 +22,11 @@ "asn1==2.2.0", # still pinned due to enum34 issue - "cryptography>=42.0.0", - "protobuf>=4.21.12", - "requests>=2.31.0", - "pyopenssl>=24.0.0", + "cryptography>=44.0.1", + "protobuf>=4.25.8,<6", + "requests>=2.32.4", + "urllib3>=2.6.3", + "pyopenssl>=26.0.0", "pytz>=2025.2", "iso8601>=1.1.0", "deprecated>=1.2.14", @@ -33,11 +34,11 @@ ], extras_require={ "examples": [ - "Django>=3.0.7", - "Flask>=1.0.4", + "Django>=4.2,<5.3", + "Flask>=3.0.6", "python-dotenv>=0.7.1", "django-sslserver>=0.22.0", - "Werkzeug==2.1.2", + "Werkzeug>=3.0.6", ], "dev": [ "pre-commit==2.16.0", diff --git a/yoti_python_sdk/doc_scan/constants.py b/yoti_python_sdk/doc_scan/constants.py index ec2ea3a3..8227c0cc 100644 --- a/yoti_python_sdk/doc_scan/constants.py +++ b/yoti_python_sdk/doc_scan/constants.py @@ -13,6 +13,7 @@ SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION = ( "SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION" ) +VERIFY_SHARE_CODE_TASK = "VERIFY_SHARE_CODE_TASK" CAMERA = "CAMERA" CAMERA_AND_UPLOAD = "CAMERA_AND_UPLOAD" @@ -39,3 +40,11 @@ PROOF_OF_ADDRESS = "PROOF_OF_ADDRESS" WATCHLIST_SCREENING_CHECK_TYPE = "WATCHLIST_SCREENING" + +ID_DOCUMENT_EDUCATION = "ID_DOCUMENT_EDUCATION" +ID_DOCUMENT_REQUIREMENTS = "ID_DOCUMENT_REQUIREMENTS" +SUPPLEMENTARY_DOCUMENT_EDUCATION = "SUPPLEMENTARY_DOCUMENT_EDUCATION" +ZOOM_LIVENESS_EDUCATION = "ZOOM_LIVENESS_EDUCATION" +STATIC_LIVENESS_EDUCATION = "STATIC_LIVENESS_EDUCATION" +FACE_CAPTURE_EDUCATION = "FACE_CAPTURE_EDUCATION" +FLOW_COMPLETION = "FLOW_COMPLETION" diff --git a/yoti_python_sdk/doc_scan/session/create/sdk_config.py b/yoti_python_sdk/doc_scan/session/create/sdk_config.py index 37cde6b5..366538de 100644 --- a/yoti_python_sdk/doc_scan/session/create/sdk_config.py +++ b/yoti_python_sdk/doc_scan/session/create/sdk_config.py @@ -23,6 +23,8 @@ def __init__( error_url, allow_handoff=None, privacy_policy_url=None, + brand_id=None, + suppressed_screens=None, ): """ :param allowed_capture_methods: the allowed capture methods @@ -41,10 +43,14 @@ def __init__( :type success_url: str :param error_url: the error url :type error_url: str - :param privacy_policy_url: the privacy policy url - :type privacy_policy_url: str :param allow_handoff: boolean flag for allow_handoff :type allow_handoff: bool + :param privacy_policy_url: the privacy policy url + :type privacy_policy_url: str + :param brand_id: the brand id used to theme the IDV iframe + :type brand_id: str + :param suppressed_screens: list of screen names to be suppressed + :type suppressed_screens: list[str] """ self.__allowed_capture_methods = allowed_capture_methods self.__primary_colour = primary_colour @@ -56,6 +62,8 @@ def __init__( self.__error_url = error_url self.__privacy_policy_url = privacy_policy_url self.__allow_handoff = allow_handoff + self.__brand_id = brand_id + self.__suppressed_screens = suppressed_screens @property def allowed_capture_methods(self): @@ -148,6 +156,25 @@ def allow_handoff(self): """ return self.__allow_handoff + @property + def brand_id(self): + """ + The brand id used to theme the IDV iframe. + + :return: the brand id + """ + return self.__brand_id + + @property + def suppressed_screens(self): + """ + The list of screen names that should be omitted from the IDV flow + + :return: the list of suppressed screens + :rtype: list[str] or None + """ + return self.__suppressed_screens + def to_json(self): return remove_null_values( { @@ -161,6 +188,8 @@ def to_json(self): "error_url": self.error_url, "privacy_policy_url": self.privacy_policy_url, "allow_handoff": self.allow_handoff, + "brand_id": self.brand_id, + "suppressed_screens": self.suppressed_screens, } ) @@ -181,6 +210,8 @@ def __init__(self): self.__error_url = None self.__privacy_policy_url = None self.__allow_handoff = None + self.__brand_id = None + self.__suppressed_screens = None def with_allowed_capture_methods(self, allowed_capture_methods): """ @@ -320,6 +351,51 @@ def with_allow_handoff(self, flag): self.__allow_handoff = flag return self + def with_brand_id(self, brand_id): + """ + Sets the brand id used to theme the IDV iframe + + :param brand_id: the brand id + :type brand_id: str + :return: the builder + :rtype: SdkConfigBuilder + """ + self.__brand_id = brand_id + return self + + def with_suppressed_screens(self, suppressed_screens): + """ + Sets the list of screens to be suppressed from the IDV flow. + + Valid screen names are defined in + :mod:`yoti_python_sdk.doc_scan.constants`: + ``ID_DOCUMENT_EDUCATION``, ``ID_DOCUMENT_REQUIREMENTS``, + ``SUPPLEMENTARY_DOCUMENT_EDUCATION``, ``ZOOM_LIVENESS_EDUCATION``, + ``STATIC_LIVENESS_EDUCATION``, ``FACE_CAPTURE_EDUCATION``, + ``FLOW_COMPLETION``. + + :param suppressed_screens: the list of screens to suppress + :type suppressed_screens: list[str] + :return: the builder + :rtype: SdkConfigBuilder + """ + self.__suppressed_screens = list(suppressed_screens) if suppressed_screens is not None else None + return self + + def with_suppressed_screen(self, screen): + """ + Adds a single screen name to the list of suppressed screens. + + :param screen: the screen name to suppress + :type screen: str + :return: the builder + :rtype: SdkConfigBuilder + """ + if self.__suppressed_screens is None: + self.__suppressed_screens = [] + self.__suppressed_screens.append(screen) + return self + def build(self): return SdkConfig( self.__allowed_capture_methods, @@ -332,4 +408,6 @@ def build(self): self.__error_url, self.__allow_handoff, self.__privacy_policy_url, + self.__brand_id, + list(self.__suppressed_screens) if self.__suppressed_screens is not None else None, ) diff --git a/yoti_python_sdk/doc_scan/session/create/session_spec.py b/yoti_python_sdk/doc_scan/session/create/session_spec.py index 75a2b8be..c5c8afee 100644 --- a/yoti_python_sdk/doc_scan/session/create/session_spec.py +++ b/yoti_python_sdk/doc_scan/session/create/session_spec.py @@ -43,7 +43,7 @@ def __init__( :param block_biometric_consent: block the collection of biometric consent :type block_biometric_consent: bool :param session_deadline: session deadline using a Zoned timestamp - "type session_deadline: str + :type session_deadline: str """ if requested_tasks is None: requested_tasks = [] diff --git a/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py b/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py index 376171b7..fe2df1e4 100644 --- a/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py +++ b/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from yoti_python_sdk.doc_scan.session.create.filter.document_filter import DocumentFilter -from yoti_python_sdk.utils import YotiSerializable -from .sub_check import SubRequestedCheck +from yoti_python_sdk.utils import YotiSerializable, remove_null_values -class IssuingAuthoritySubCheck(SubRequestedCheck): +class IssuingAuthoritySubCheck(YotiSerializable): """ Requests creation of an Issuing Authority Sub Check. """ @@ -21,6 +20,9 @@ def requested(self): def filter(self): return self._filter + def to_json(self): + return remove_null_values({"requested": self.requested, "filter": self.filter}) + class IssuingAuthoritySubCheckBuilder: """ diff --git a/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py b/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py index d4fa882d..c707552b 100644 --- a/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py +++ b/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py @@ -1,7 +1,7 @@ from abc import ABCMeta from abc import abstractmethod -from yoti_python_sdk.utils import YotiSerializable +from yoti_python_sdk.utils import YotiSerializable, remove_null_values class SubRequestedCheck(YotiSerializable): diff --git a/yoti_python_sdk/doc_scan/session/retrieve/breakdown_response.py b/yoti_python_sdk/doc_scan/session/retrieve/breakdown_response.py index 20622087..3d22d2e7 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/breakdown_response.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/breakdown_response.py @@ -14,6 +14,7 @@ def __init__(self, data): """ self.__sub_check = data.get("sub_check", None) self.__result = data.get("result", None) + self.__process = data.get("process", None) self.__details = [DetailsResponse(detail) for detail in data.get("details", [])] @property @@ -36,6 +37,16 @@ def result(self): """ return self.__result + @property + def process(self): + """ + The breakdown process type for the sub-check (e.g. AUTOMATED or EXPERT_REVIEW) + + :return: the breakdown process type + :rtype: str or None + """ + return self.__process + @property def details(self): """ diff --git a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py index 7f238281..1d995d04 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py @@ -81,7 +81,7 @@ def __parse_check(check): constants.ID_DOCUMENT_FACE_MATCH: FaceMatchCheckResponse, constants.ID_DOCUMENT_TEXT_DATA_CHECK: TextDataCheckResponse, constants.LIVENESS: LivenessCheckResponse, - constants.WATCHLIST_SCREENING_CHECK_TYPE: WatchlistScreeningCheckResponse, + constants.WATCHLIST_SCREENING_CHECK_TYPE: WatchlistScreeningCheckResponse, constants.ID_DOCUMENT_COMPARISON: IDDocumentComparisonCheckResponse, constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK: SupplementaryDocumentTextDataCheckResponse, } diff --git a/yoti_python_sdk/doc_scan/session/retrieve/page_response.py b/yoti_python_sdk/doc_scan/session/retrieve/page_response.py index 25661660..9d296244 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/page_response.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/page_response.py @@ -23,6 +23,7 @@ def __init__(self, data=None): ) self.__media = MediaResponse(data["media"]) if "media" in data.keys() else None self.__frames = [FrameResponse(frame) for frame in data.get("frames", [])] + self.__extraction_image_ids = list(data.get("extraction_image_ids") or []) @property def capture_method(self): @@ -53,3 +54,13 @@ def frames(self): :rtype: list[FrameResponse] """ return self.__frames + + @property + def extraction_image_ids(self): + """ + Returns the list of media IDs used for automated extraction + + :return: the extraction image IDs + :rtype: list[str] + """ + return self.__extraction_image_ids diff --git a/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py b/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py index 3aaf0ecd..03351c48 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/resource_container.py @@ -14,6 +14,9 @@ from yoti_python_sdk.doc_scan.session.retrieve.static_liveness_resource_response import ( StaticLivenessResourceResponse, ) +from yoti_python_sdk.doc_scan.session.retrieve.share_code_resource_response import ( + ShareCodeResourceResponse, +) class ResourceContainer(object): @@ -45,6 +48,11 @@ def __init__(self, data=None): for liveness in data.get("liveness_capture", []) ] + self.__share_codes = [ + ShareCodeResourceResponse(share_code) + for share_code in data.get("share_codes", []) + ] + @staticmethod def __parse_liveness_capture(liveness_capture): """ @@ -122,3 +130,13 @@ def static_liveness_resources(self): for liveness in self.__liveness_capture if isinstance(liveness, StaticLivenessResourceResponse) ] + + @property + def share_codes(self): + """ + Return a list of share code resources + + :return: list of share codes + :rtype: list[ShareCodeResourceResponse] + """ + return self.__share_codes diff --git a/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py index bd5c8087..bd9e83a1 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/resource_response.py @@ -4,6 +4,7 @@ TextExtractionTaskResponse, SupplementaryDocumentTextExtractionTaskResponse, ) +from .verify_share_code_task_response import VerifyShareCodeTaskResponse class ResourceResponse(object): @@ -35,6 +36,7 @@ def __parse_task(task): types = { constants.ID_DOCUMENT_TEXT_DATA_EXTRACTION: TextExtractionTaskResponse, constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_EXTRACTION: SupplementaryDocumentTextExtractionTaskResponse, + constants.VERIFY_SHARE_CODE_TASK: VerifyShareCodeTaskResponse, } clazz = types.get( task.get("type", None), TaskResponse # Default fallback for task type diff --git a/yoti_python_sdk/doc_scan/session/retrieve/share_code_media_response.py b/yoti_python_sdk/doc_scan/session/retrieve/share_code_media_response.py new file mode 100644 index 00000000..70156c11 --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/share_code_media_response.py @@ -0,0 +1,26 @@ +from yoti_python_sdk.doc_scan.session.retrieve.media_response import MediaResponse + + +class ShareCodeMediaResponse(object): + """ + Wraps a MediaResponse inside a share code resource field + """ + + def __init__(self, data=None): + if data is None: + data = dict() + + if "media" in data and data["media"] is not None: + self.__media = MediaResponse(data["media"]) + else: + self.__media = None + + @property + def media(self): + """ + The media object + + :return: the media + :rtype: MediaResponse or None + """ + return self.__media diff --git a/yoti_python_sdk/doc_scan/session/retrieve/share_code_resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/share_code_resource_response.py new file mode 100644 index 00000000..305df909 --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/share_code_resource_response.py @@ -0,0 +1,137 @@ +from yoti_python_sdk.doc_scan.session.retrieve.resource_response import ( + ResourceResponse, +) +from yoti_python_sdk.doc_scan.session.retrieve.share_code_media_response import ( + ShareCodeMediaResponse, +) +from yoti_python_sdk.doc_scan.session.retrieve.verify_share_code_task_response import ( + VerifyShareCodeTaskResponse, +) + + +class ShareCodeResourceResponse(ResourceResponse): + """ + Represents a share code resource for a given session + """ + + def __init__(self, data=None): + if data is None: + data = dict() + + ResourceResponse.__init__(self, data) + + source = data.get("source", None) + if isinstance(source, str): + self.__source = source + elif isinstance(source, dict): + self.__source = source.get("type", None) + else: + self.__source = None + + self.__created_at = data.get("created_at", None) + self.__last_updated = data.get("last_updated", None) + + self.__lookup_profile = ( + ShareCodeMediaResponse(data["lookup_profile"]) + if "lookup_profile" in data and data["lookup_profile"] is not None + else None + ) + self.__returned_profile = ( + ShareCodeMediaResponse(data["returned_profile"]) + if "returned_profile" in data and data["returned_profile"] is not None + else None + ) + self.__id_photo = ( + ShareCodeMediaResponse(data["id_photo"]) + if "id_photo" in data and data["id_photo"] is not None + else None + ) + self.__file = ( + ShareCodeMediaResponse(data["file"]) + if "file" in data and data["file"] is not None + else None + ) + + @property + def source(self): + """ + The source of the share code + + :return: the source + :rtype: str or None + """ + return self.__source + + @property + def created_at(self): + """ + The date the share code was created + + :return: the created at date + :rtype: str or None + """ + return self.__created_at + + @property + def last_updated(self): + """ + The date the share code was last updated + + :return: the last updated date + :rtype: str or None + """ + return self.__last_updated + + @property + def lookup_profile(self): + """ + The lookup profile media + + :return: the lookup profile + :rtype: ShareCodeMediaResponse or None + """ + return self.__lookup_profile + + @property + def returned_profile(self): + """ + The returned profile media + + :return: the returned profile + :rtype: ShareCodeMediaResponse or None + """ + return self.__returned_profile + + @property + def id_photo(self): + """ + The ID photo media + + :return: the ID photo + :rtype: ShareCodeMediaResponse or None + """ + return self.__id_photo + + @property + def file(self): + """ + The file media + + :return: the file + :rtype: ShareCodeMediaResponse or None + """ + return self.__file + + @property + def verify_share_code_tasks(self): + """ + Returns a list of verify share code tasks + + :return: list of verify share code tasks + :rtype: list[VerifyShareCodeTaskResponse] + """ + return [ + task + for task in self.tasks + if isinstance(task, VerifyShareCodeTaskResponse) + ] diff --git a/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py b/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py index 970258e1..4a4e9b00 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/static_liveness_resource_response.py @@ -23,6 +23,7 @@ def __init__(self, data=None): self.__image = ( ImageResponse(data["image"]) if "image" in data.keys() else None ) + self.__capture_type = data.get("capture_type", None) @property def image(self): @@ -33,3 +34,13 @@ def image(self): :rtype: ImageResponse or None """ return self.__image + + @property + def capture_type(self): + """ + Returns the capture type for the static liveness resource + + :return: the capture type + :rtype: str or None + """ + return self.__capture_type diff --git a/yoti_python_sdk/doc_scan/session/retrieve/verify_share_code_task_response.py b/yoti_python_sdk/doc_scan/session/retrieve/verify_share_code_task_response.py new file mode 100644 index 00000000..f942a26a --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/verify_share_code_task_response.py @@ -0,0 +1,9 @@ +from yoti_python_sdk.doc_scan.session.retrieve.task_response import TaskResponse + + +class VerifyShareCodeTaskResponse(TaskResponse): + """ + Represents a Verify Share Code task response + """ + + pass diff --git a/yoti_python_sdk/protobuf/attrpubapi_v1/AttributeDisplayRequest_pb2.py b/yoti_python_sdk/protobuf/attrpubapi_v1/AttributeDisplayRequest_pb2.py index 7f7b37c8..9048ae9b 100644 --- a/yoti_python_sdk/protobuf/attrpubapi_v1/AttributeDisplayRequest_pb2.py +++ b/yoti_python_sdk/protobuf/attrpubapi_v1/AttributeDisplayRequest_pb2.py @@ -11,7 +11,7 @@ _sym_db = _symbol_database.Default() -from devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 +from yoti_python_sdk.protobuf.devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import Metadata_pb2 as compubapi__v1_dot_Metadata__pb2 diff --git a/yoti_python_sdk/protobuf/attrpubapi_v1/Refresh_pb2.py b/yoti_python_sdk/protobuf/attrpubapi_v1/Refresh_pb2.py index 7d9239c2..4117acdc 100644 --- a/yoti_python_sdk/protobuf/attrpubapi_v1/Refresh_pb2.py +++ b/yoti_python_sdk/protobuf/attrpubapi_v1/Refresh_pb2.py @@ -11,7 +11,7 @@ _sym_db = _symbol_database.Default() -from devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 +from yoti_python_sdk.protobuf.devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import Metadata_pb2 as compubapi__v1_dot_Metadata__pb2 diff --git a/yoti_python_sdk/protobuf/attrpubapi_v1/Unlock_pb2.py b/yoti_python_sdk/protobuf/attrpubapi_v1/Unlock_pb2.py index 097c2e70..699460be 100644 --- a/yoti_python_sdk/protobuf/attrpubapi_v1/Unlock_pb2.py +++ b/yoti_python_sdk/protobuf/attrpubapi_v1/Unlock_pb2.py @@ -11,7 +11,7 @@ _sym_db = _symbol_database.Default() -from devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 +from yoti_python_sdk.protobuf.devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import Metadata_pb2 as compubapi__v1_dot_Metadata__pb2 diff --git a/yoti_python_sdk/protobuf/devicepubapi_v1/StaticCredentialAuth_pb2.py b/yoti_python_sdk/protobuf/devicepubapi_v1/StaticCredentialAuth_pb2.py new file mode 100644 index 00000000..4607d565 --- /dev/null +++ b/yoti_python_sdk/protobuf/devicepubapi_v1/StaticCredentialAuth_pb2.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: devicepubapi_v1/StaticCredentialAuth.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n*devicepubapi_v1/StaticCredentialAuth.proto\x12\x19yoti.core.devicepubapi_v1\"\x16\n\x14StaticCredentialAuthb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'StaticCredentialAuth_pb2.py', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = None + _STATICCREDENTIALAUTH._serialized_start=73 + _STATICCREDENTIALAUTH._serialized_end=95 +# @@protoc_insertion_point(module_scope) diff --git a/yoti_python_sdk/protobuf/devicepubapi_v1/__init__.py b/yoti_python_sdk/protobuf/devicepubapi_v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/yoti_python_sdk/protobuf/sharepubapi_v1/IdentityProfileShare_pb2.py b/yoti_python_sdk/protobuf/sharepubapi_v1/IdentityProfileShare_pb2.py index d3f71163..7a7a0f03 100644 --- a/yoti_python_sdk/protobuf/sharepubapi_v1/IdentityProfileShare_pb2.py +++ b/yoti_python_sdk/protobuf/sharepubapi_v1/IdentityProfileShare_pb2.py @@ -15,7 +15,7 @@ from . import Sharing_pb2 as sharepubapi__v1_dot_Sharing__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import Metadata_pb2 as compubapi__v1_dot_Metadata__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import DocumentTypes_pb2 as compubapi__v1_dot_DocumentTypes__pb2 -from devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 +from yoti_python_sdk.protobuf.devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)sharepubapi_v1/IdentityProfileShare.proto\x12\x18yoti.core.sharepubapi_v1\x1a\x1bsharepubapi_v1/Policy.proto\x1a\x1csharepubapi_v1/Sharing.proto\x1a\x1b\x63ompubapi_v1/Metadata.proto\x1a compubapi_v1/DocumentTypes.proto\x1a*devicepubapi_v1/StaticCredentialAuth.proto\"\xf9\x04\n\x0fNextStepRequest\x12\x12\n\ncredential\x18\x01 \x01(\x0c\x12\x13\n\x0bwrapped_key\x18\x02 \x01(\x0c\x12\x16\n\x0e\x61uth_data_list\x18\x03 \x01(\x0c\x12\x30\n\x06policy\x18\x04 \x01(\x0b\x32 .yoti.core.sharepubapi_v1.Policy\x12\x15\n\rsharing_token\x18\x05 \x01(\x0c\x12\x14\n\x0csession_data\x18\x06 \x01(\t\x12\x30\n\x06issuer\x18\x07 \x01(\x0e\x32 .yoti.core.sharepubapi_v1.Issuer\x12\x36\n\ttransport\x18\x08 \x01(\x0e\x32#.yoti.core.sharepubapi_v1.Transport\x12!\n\x19user_profile_attribute_id\x18\t \x01(\x0c\x12M\n\x11unable_to_provide\x18\n \x01(\x0b\x32\x32.yoti.core.compubapi_v1.DocumentRestrictionsFilter\x12\x15\n\rattribute_ids\x18\x0b \x03(\x0c\x12\x14\n\x0creference_id\x18\x0c \x01(\t\x12:\n\x0f\x64\x65vice_metadata\x18\xc0\x0c \x01(\x0b\x32 .yoti.core.compubapi_v1.Metadata\x12\x16\n\rfeature_flags\x18\xc1\x0c \x01(\x06\x12\x17\n\x0e\x63orrelation_id\x18\xc2\x0c \x01(\t\x12P\n\x16static_credential_auth\x18\xb8\x17 \x01(\x0b\x32/.yoti.core.devicepubapi_v1.StaticCredentialAuth\"\xfc\x04\n\x10NextStepResponse\x12?\n\x05state\x18\x01 \x01(\x0e\x32\x30.yoti.core.sharepubapi_v1.NextStepResponse.State\x12?\n\x0e\x66\x61ilure_reason\x18\x02 \x01(\x0b\x32\'.yoti.core.sharepubapi_v1.FailureReason\x12:\n\x0brequirement\x18\x03 \x01(\x0b\x32%.yoti.core.sharepubapi_v1.Requirement\x12\x43\n\x10progress_summary\x18\x04 \x01(\x0b\x32).yoti.core.sharepubapi_v1.ProgressSummary\x12\x0f\n\x07payload\x18\x05 \x01(\x0c\x12\x46\n\x12unusable_documents\x18\x06 \x03(\x0b\x32*.yoti.core.sharepubapi_v1.UnusableDocument\x12m\n\x1esubsequent_next_step_may_abort\x18\x07 \x01(\x0e\x32\x45.yoti.core.sharepubapi_v1.NextStepResponse.SubsequentNextStepMayAbort\"V\n\x05State\x12\r\n\tUNDEFINED\x10\x00\x12\x08\n\x04\x44ONE\x10\x01\x12\x16\n\x12NEXT_STEP_REQUIRED\x10\x02\x12\x0c\n\x08\x44\x45\x41\x44_END\x10\x03\x12\x0e\n\nCALL_AGAIN\x10\x04\"E\n\x1aSubsequentNextStepMayAbort\x12\x12\n\x0eNOT_APPLICABLE\x10\x00\x12\t\n\x05\x46\x41LSE\x10\x01\x12\x08\n\x04TRUE\x10\x02\"\xc8\x01\n\x10UnusableDocument\x12\x34\n\x08\x64ocument\x18\x01 \x01(\x0b\x32\".yoti.core.compubapi_v1.IdDocument\x12\x41\n\x06reason\x18\x02 \x01(\x0e\x32\x31.yoti.core.sharepubapi_v1.UnusableDocument.Reason\";\n\x06Reason\x12\r\n\tUNDEFINED\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\x15\n\x11OVERSEAS_PASSPORT\x10\x02\"\xde\x01\n\x0fProgressSummary\x12=\n\x0bproof_of_id\x18\x01 \x01(\x0b\x32(.yoti.core.sharepubapi_v1.CategoryStatus\x12\x42\n\x10proof_of_address\x18\x02 \x01(\x0b\x32(.yoti.core.sharepubapi_v1.CategoryStatus\x12H\n\x16\x62iometric_verification\x18\x03 \x01(\x0b\x32(.yoti.core.sharepubapi_v1.CategoryStatus\"$\n\rFailureReason\x12\x13\n\x0breason_code\x18\x01 \x01(\t\"\xf7\x01\n\x0e\x43\x61tegoryStatus\x12=\n\x05state\x18\x01 \x01(\x0e\x32..yoti.core.sharepubapi_v1.CategoryStatus.State\x12N\n\x16\x66ulfilled_requirements\x18\x02 \x03(\x0b\x32..yoti.core.sharepubapi_v1.FulfilledRequirement\"V\n\x05State\x12\r\n\tUNDEFINED\x10\x00\x12\x08\n\x04\x44ONE\x10\x01\x12\x0e\n\nTO_BE_DONE\x10\x02\x12\x0e\n\nNOT_NEEDED\x10\x03\x12\x14\n\x10NO_LONGER_NEEDED\x10\x04\"\x99\x01\n\x14\x46ulfilledRequirement\x12\x38\n\x04type\x18\x01 \x01(\x0e\x32*.yoti.core.sharepubapi_v1.Requirement.Type\x12\x34\n\x08\x64ocument\x18\x02 \x01(\x0b\x32\".yoti.core.compubapi_v1.IdDocument\x12\x11\n\ttimestamp\x18\x03 \x01(\t\"\xaf\x03\n\x0bRequirement\x12\x38\n\x04type\x18\x01 \x01(\x0e\x32*.yoti.core.sharepubapi_v1.Requirement.Type\x12W\n\x14requirement_category\x18\x02 \x01(\x0e\x32\x39.yoti.core.sharepubapi_v1.Requirement.RequirementCategory\x12M\n\x15\x64ocument_restrictions\x18\x03 \x01(\x0b\x32..yoti.core.sharepubapi_v1.DocumentRestrictions\"R\n\x04Type\x12\x10\n\x0cUNDEFINED_RT\x10\x00\x12\x0f\n\x0bID_DOCUMENT\x10\x01\x12\x12\n\x0eMANUAL_ADDRESS\x10\x02\x12\x13\n\x0f\x43ONFIRM_ADDRESS\x10\x03\"j\n\x13RequirementCategory\x12\x10\n\x0cUNDEFINED_RC\x10\x00\x12\x0f\n\x0bPROOF_OF_ID\x10\x01\x12\x14\n\x10PROOF_OF_ADDRESS\x10\x02\x12\x1a\n\x16\x42IOMETRIC_VERIFICATION\x10\x03\"\x81\x01\n\x14\x44ocumentRestrictions\x12%\n\x1d\x63hip_verification_recommended\x18\x01 \x01(\x08\x12\x42\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x32.yoti.core.compubapi_v1.DocumentRestrictionsFilterBu\n\x17\x63om.yoti.sharepubapi_v1B\x19IdentityProfileShareProtoZ.go.internal.yoti.com/proto/core/sharepubapi_v1\xba\x02\x0eSharepubapiV1_b\x06proto3') diff --git a/yoti_python_sdk/protobuf/sharepubapi_v1/Sharing_pb2.py b/yoti_python_sdk/protobuf/sharepubapi_v1/Sharing_pb2.py index 931f09e8..e7d97ee8 100644 --- a/yoti_python_sdk/protobuf/sharepubapi_v1/Sharing_pb2.py +++ b/yoti_python_sdk/protobuf/sharepubapi_v1/Sharing_pb2.py @@ -15,7 +15,7 @@ from . import DataEntry_pb2 as sharepubapi__v1_dot_DataEntry__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import Metadata_pb2 as compubapi__v1_dot_Metadata__pb2 from yoti_python_sdk.protobuf.compubapi_v1 import IdentityProfile_pb2 as compubapi__v1_dot_IdentityProfile__pb2 -from devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 +from yoti_python_sdk.protobuf.devicepubapi_v1 import StaticCredentialAuth_pb2 as devicepubapi__v1_dot_StaticCredentialAuth__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1csharepubapi_v1/Sharing.proto\x12\x18yoti.core.sharepubapi_v1\x1a\x1bsharepubapi_v1/Policy.proto\x1a\x1esharepubapi_v1/DataEntry.proto\x1a\x1b\x63ompubapi_v1/Metadata.proto\x1a\"compubapi_v1/IdentityProfile.proto\x1a*devicepubapi_v1/StaticCredentialAuth.proto\"\xba\x04\n\x13StartSharingRequest\x12\x16\n\x0e\x63orrelation_id\x18\x01 \x01(\t\x12\x30\n\x06policy\x18\x02 \x01(\x0b\x32 .yoti.core.sharepubapi_v1.Policy\x12\x15\n\rattribute_ids\x18\x03 \x03(\x0c\x12\x13\n\x0bwrapped_key\x18\x04 \x01(\x0c\x12\x12\n\ncredential\x18\x05 \x01(\x0c\x12\x11\n\tauth_list\x18\x06 \x01(\x0c\x12\x18\n\x0bttl_seconds\x18\x07 \x01(\x03H\x00\x88\x01\x01\x12\x1a\n\rone_time_only\x18\x08 \x01(\x08H\x01\x88\x01\x01\x12\x37\n\nextra_data\x18\t \x03(\x0b\x32#.yoti.core.sharepubapi_v1.DataEntry\x12\x1a\n\x12\x62illing_account_id\x18\n \x01(\t\x12\x19\n\x11\x62illing_source_id\x18\x0b \x01(\t\x12\x30\n\x07subject\x18\x0c \x01(\x0b\x32\x1f.yoti.core.compubapi_v1.Subject\x12:\n\x0f\x64\x65vice_metadata\x18\xc0\x0c \x01(\x0b\x32 .yoti.core.compubapi_v1.Metadata\x12P\n\x16static_credential_auth\x18\xb8\x17 \x01(\x0b\x32/.yoti.core.devicepubapi_v1.StaticCredentialAuthB\x0e\n\x0c_ttl_secondsB\x10\n\x0e_one_time_only\"\x80\x01\n\x14StartSharingResponse\x12\x15\n\rsharing_token\x18\x01 \x01(\x0c\x12\x30\n\x06policy\x18\x02 \x01(\x0b\x32 .yoti.core.sharepubapi_v1.Policy\x12\x1f\n\x17sharing_token_for_stats\x18\x03 \x01(\t\"\xf1\x03\n\x16\x43ompleteSharingRequest\x12\x15\n\rsharing_token\x18\x01 \x01(\x0c\x12\x16\n\x0e\x63orrelation_id\x18\x02 \x01(\t\x12\x30\n\x06policy\x18\x03 \x01(\x0b\x32 .yoti.core.sharepubapi_v1.Policy\x12\x15\n\rattribute_ids\x18\x04 \x03(\x0c\x12\x13\n\x0bwrapped_key\x18\x05 \x01(\x0c\x12\x12\n\ncredential\x18\x06 \x01(\x0c\x12\x11\n\tauth_list\x18\x07 \x01(\x0c\x12\x14\n\x0csession_data\x18\x08 \x01(\t\x12\x30\n\x06issuer\x18\t \x01(\x0e\x32 .yoti.core.sharepubapi_v1.Issuer\x12\x36\n\ttransport\x18\n \x01(\x0e\x32#.yoti.core.sharepubapi_v1.Transport\x12\x37\n\nextra_data\x18\x0b \x03(\x0b\x32#.yoti.core.sharepubapi_v1.DataEntry\x12\x14\n\x0creference_id\x18\x0c \x01(\t\x12\x18\n\x10mobile_device_id\x18\r \x01(\t\x12:\n\x0f\x64\x65vice_metadata\x18\xc0\x0c \x01(\x0b\x32 .yoti.core.compubapi_v1.Metadata*7\n\x06Issuer\x12\x08\n\x04\x43ORE\x10\x00\x12\x0b\n\x07\x43ONNECT\x10\x01\x12\n\n\x06PORTAL\x10\x02\x12\n\n\x06MOBILE\x10\x03*0\n\tTransport\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03URI\x10\x01\x12\x0b\n\x07QR_CODE\x10\x02\x42h\n\x17\x63om.yoti.sharepubapi_v1B\x0cSharingProtoZ.go.internal.yoti.com/proto/core/sharepubapi_v1\xba\x02\x0eSharepubapiV1_b\x06proto3') diff --git a/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py b/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py index 1fa09b29..aa2ca183 100644 --- a/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py +++ b/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py @@ -116,7 +116,7 @@ def test_build_with_basic_auth_type(self): .build() ) - assert result.auth_type is "BASIC" + assert result.auth_type == "BASIC" def test_build_with_bearer_auth_type(self): result = ( @@ -128,7 +128,7 @@ def test_build_with_bearer_auth_type(self): .build() ) - assert result.auth_type is "BEARER" + assert result.auth_type == "BEARER" def test_should_serialize_to_json_without_error(self): result = ( diff --git a/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py b/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py index d621a441..bd57276b 100644 --- a/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py +++ b/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py @@ -1,6 +1,7 @@ import json import unittest +from yoti_python_sdk.doc_scan import constants from yoti_python_sdk.doc_scan.session.create import SdkConfigBuilder from yoti_python_sdk.doc_scan.session.create.sdk_config import SdkConfig from yoti_python_sdk.utils import YotiEncoder @@ -16,6 +17,11 @@ class SdkConfigTest(unittest.TestCase): SOME_ERROR_URL = "https://mysite.com/yoti/error" SOME_PRIVACY_POLICY_URL = "https://mysite.com/privacy" SOME_ALLOW_HANDOFF = True + SOME_BRAND_ID = "your-brand-id" + SOME_SUPPRESSED_SCREENS = [ + constants.ID_DOCUMENT_EDUCATION, + constants.FLOW_COMPLETION, + ] def test_should_build_correctly(self): result = ( @@ -30,6 +36,8 @@ def test_should_build_correctly(self): .with_error_url(self.SOME_ERROR_URL) .with_privacy_policy_url(self.SOME_PRIVACY_POLICY_URL) .with_allow_handoff(self.SOME_ALLOW_HANDOFF) + .with_brand_id(self.SOME_BRAND_ID) + .with_suppressed_screens(self.SOME_SUPPRESSED_SCREENS) .build() ) @@ -40,10 +48,12 @@ def test_should_build_correctly(self): assert result.font_colour is self.SOME_FONT_COLOUR assert result.locale is self.SOME_LOCALE assert result.preset_issuing_country is self.SOME_PRESET_ISSUING_COUNTRY - assert result.success_url is self.SOME_SUCCESS_URL - assert result.error_url is self.SOME_ERROR_URL - assert result.privacy_policy_url is self.SOME_PRIVACY_POLICY_URL + assert result.success_url == self.SOME_SUCCESS_URL + assert result.error_url == self.SOME_ERROR_URL + assert result.privacy_policy_url == self.SOME_PRIVACY_POLICY_URL assert result.allow_handoff is True + assert result.brand_id == self.SOME_BRAND_ID + assert result.suppressed_screens == self.SOME_SUPPRESSED_SCREENS def test_should_allows_camera(self): result = SdkConfigBuilder().with_allows_camera().build() @@ -78,6 +88,106 @@ def test_should_serialize_to_json_without_error(self): s = json.dumps(result, cls=YotiEncoder) assert s is not None and s != "" + def test_not_passing_brand_id(self): + result = SdkConfigBuilder().with_allows_camera().build() + + assert result.brand_id is None + + def test_with_brand_id(self): + result = SdkConfigBuilder().with_brand_id(self.SOME_BRAND_ID).build() + + assert result.brand_id == self.SOME_BRAND_ID + + def test_brand_id_in_json_when_set(self): + result = SdkConfigBuilder().with_brand_id(self.SOME_BRAND_ID).build() + + serialised = json.loads(json.dumps(result, cls=YotiEncoder)) + assert serialised["brand_id"] == self.SOME_BRAND_ID + + def test_brand_id_absent_from_json_when_not_set(self): + result = SdkConfigBuilder().with_allows_camera().build() + + serialised = json.loads(json.dumps(result, cls=YotiEncoder)) + assert "brand_id" not in serialised + + def test_brand_id_absent_from_json_when_none(self): + result = SdkConfigBuilder().with_brand_id(None).build() + + serialised = json.loads(json.dumps(result, cls=YotiEncoder)) + assert "brand_id" not in serialised + + def test_suppressed_screens_default_to_none(self): + result = SdkConfigBuilder().with_allows_camera().build() + + assert result.suppressed_screens is None + + def test_should_add_individual_suppressed_screens(self): + result = ( + SdkConfigBuilder() + .with_suppressed_screen(constants.ID_DOCUMENT_EDUCATION) + .with_suppressed_screen(constants.FLOW_COMPLETION) + .build() + ) + + assert result.suppressed_screens == [ + constants.ID_DOCUMENT_EDUCATION, + constants.FLOW_COMPLETION, + ] + + def test_suppressed_screens_serialized_when_set(self): + result = ( + SdkConfigBuilder() + .with_suppressed_screens(self.SOME_SUPPRESSED_SCREENS) + .build() + ) + + s = json.dumps(result, cls=YotiEncoder) + parsed = json.loads(s) + + assert "suppressed_screens" in parsed + assert parsed["suppressed_screens"] == self.SOME_SUPPRESSED_SCREENS + + def test_suppressed_screens_empty_list_serialized(self): + result = SdkConfigBuilder().with_suppressed_screens([]).build() + + s = json.dumps(result, cls=YotiEncoder) + parsed = json.loads(s) + + assert "suppressed_screens" in parsed + assert parsed["suppressed_screens"] == [] + + def test_suppressed_screens_omitted_when_not_set(self): + result = SdkConfigBuilder().with_allows_camera().build() + + s = json.dumps(result, cls=YotiEncoder) + parsed = json.loads(s) + + assert "suppressed_screens" not in parsed + + def test_with_suppressed_screens_returns_builder(self): + builder = SdkConfigBuilder() + result = builder.with_suppressed_screens(self.SOME_SUPPRESSED_SCREENS) + + assert result is builder + + def test_with_suppressed_screen_returns_builder(self): + builder = SdkConfigBuilder() + result = builder.with_suppressed_screen(constants.FLOW_COMPLETION) + + assert result is builder + + def test_suppressed_screen_constants_defined(self): + assert constants.ID_DOCUMENT_EDUCATION == "ID_DOCUMENT_EDUCATION" + assert constants.ID_DOCUMENT_REQUIREMENTS == "ID_DOCUMENT_REQUIREMENTS" + assert ( + constants.SUPPLEMENTARY_DOCUMENT_EDUCATION + == "SUPPLEMENTARY_DOCUMENT_EDUCATION" + ) + assert constants.ZOOM_LIVENESS_EDUCATION == "ZOOM_LIVENESS_EDUCATION" + assert constants.STATIC_LIVENESS_EDUCATION == "STATIC_LIVENESS_EDUCATION" + assert constants.FACE_CAPTURE_EDUCATION == "FACE_CAPTURE_EDUCATION" + assert constants.FLOW_COMPLETION == "FLOW_COMPLETION" + if __name__ == "__main__": unittest.main() diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_breakdown_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_breakdown_response.py index 2b534512..b64c0ddc 100644 --- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_breakdown_response.py +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_breakdown_response.py @@ -8,6 +8,7 @@ class BreakdownResponseTest(unittest.TestCase): SOME_SUB_CHECK = "someSubCheck" SOME_RESULT = "someResult" + SOME_PROCESS = "AUTOMATED" SOME_DETAILS = [ {"name": "firstDetailName", "value": "firstDetailValue"}, {"name": "secondDetailName", "value": "secondDetailValue"}, @@ -17,13 +18,15 @@ def test_should_build_correctly(self): data = { "sub_check": self.SOME_SUB_CHECK, "result": self.SOME_RESULT, + "process": self.SOME_PROCESS, "details": self.SOME_DETAILS, } result = BreakdownResponse(data) - assert result.sub_check is self.SOME_SUB_CHECK - assert result.result is self.SOME_RESULT + assert result.sub_check == self.SOME_SUB_CHECK + assert result.result == self.SOME_RESULT + assert result.process == self.SOME_PROCESS assert len(result.details) == 2 assert result.details[0].name == "firstDetailName" assert result.details[0].value == "firstDetailValue" @@ -31,6 +34,7 @@ def test_should_build_correctly(self): def test_should_default_details_to_empty_list(self): result = BreakdownResponse({}) assert len(result.details) == 0 + assert result.process is None if __name__ == "__main__": diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_page_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_page_response.py index 22e66d18..6b9e95d8 100644 --- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_page_response.py +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_page_response.py @@ -8,12 +8,18 @@ class PageResponseTest(unittest.TestCase): SOME_CAPTURE_METHOD = "someCaptureMethod" SOME_FRAMES = [{"first": "frame"}, {"second": "frame"}] + SOME_EXTRACTION_IMAGE_ID = "066a9372-1ab9-49f0-b390-1b58e08f17f6" + SOME_OTHER_EXTRACTION_IMAGE_ID = "1a2b3c4d-5e6f-7890-abcd-ef1234567890" def test_should_parse_correctly(self): data = { "capture_method": self.SOME_CAPTURE_METHOD, "media": {}, "frames": self.SOME_FRAMES, + "extraction_image_ids": [ + self.SOME_EXTRACTION_IMAGE_ID, + self.SOME_OTHER_EXTRACTION_IMAGE_ID, + ], } result = PageResponse(data) @@ -23,6 +29,10 @@ def test_should_parse_correctly(self): assert len(result.frames) == 2 assert isinstance(result.frames[0], FrameResponse) assert isinstance(result.frames[1], FrameResponse) + assert result.extraction_image_ids == [ + self.SOME_EXTRACTION_IMAGE_ID, + self.SOME_OTHER_EXTRACTION_IMAGE_ID, + ] def test_should_parse_with_none(self): result = PageResponse(None) @@ -30,6 +40,35 @@ def test_should_parse_with_none(self): assert result.capture_method is None assert result.media is None assert len(result.frames) == 0 + assert result.extraction_image_ids == [] + + def test_should_parse_extraction_image_ids_with_single_uuid(self): + data = {"extraction_image_ids": [self.SOME_EXTRACTION_IMAGE_ID]} + + result = PageResponse(data) + + assert result.extraction_image_ids == [self.SOME_EXTRACTION_IMAGE_ID] + + def test_should_parse_extraction_image_ids_with_empty_array(self): + data = {"extraction_image_ids": []} + + result = PageResponse(data) + + assert result.extraction_image_ids == [] + + def test_should_parse_extraction_image_ids_with_null(self): + data = {"extraction_image_ids": None} + + result = PageResponse(data) + + assert result.extraction_image_ids == [] + + def test_should_parse_extraction_image_ids_when_field_absent(self): + data = {"capture_method": self.SOME_CAPTURE_METHOD} + + result = PageResponse(data) + + assert result.extraction_image_ids == [] if __name__ == "__main__": diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py index 6607d2d9..1890aa47 100644 --- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_resource_container.py @@ -9,6 +9,12 @@ from yoti_python_sdk.doc_scan.session.retrieve.resource_container import ( ResourceContainer, ) +from yoti_python_sdk.doc_scan.session.retrieve.static_liveness_resource_response import ( + StaticLivenessResourceResponse, +) +from yoti_python_sdk.doc_scan.session.retrieve.share_code_resource_response import ( + ShareCodeResourceResponse, +) class ResourceContainerTest(unittest.TestCase): @@ -62,6 +68,51 @@ def test_should_filter_static_liveness_resources(self): assert len(result.liveness_capture) == 2 assert len(result.static_liveness_resources) == 1 + def test_should_expose_capture_type_on_static_liveness_resource(self): + data = { + "liveness_capture": [ + {"liveness_type": "STATIC", "capture_type": "PHOTOGRAPH"}, + {"liveness_type": "ZOOM"}, + ] + } + + result = ResourceContainer(data) + + static_resources = result.static_liveness_resources + assert len(static_resources) == 1 + assert isinstance(static_resources[0], StaticLivenessResourceResponse) + assert static_resources[0].capture_type == "PHOTOGRAPH" + + zoom_resources = result.zoom_liveness_resources + assert len(zoom_resources) == 1 + assert not hasattr(zoom_resources[0], "capture_type") + + def test_should_parse_share_codes(self): + data = { + "share_codes": [ + {"id": "share-code-1", "source": "END_USER", "tasks": []}, + {"id": "share-code-2", "source": "END_USER", "tasks": []}, + ] + } + + result = ResourceContainer(data) + + assert len(result.share_codes) == 2 + assert isinstance(result.share_codes[0], ShareCodeResourceResponse) + assert isinstance(result.share_codes[1], ShareCodeResourceResponse) + + def test_should_parse_with_empty_share_codes(self): + data = {"share_codes": []} + + result = ResourceContainer(data) + + assert len(result.share_codes) == 0 + + def test_should_parse_with_missing_share_codes(self): + result = ResourceContainer({}) + + assert len(result.share_codes) == 0 + if __name__ == "__main__": unittest.main() diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_media_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_media_response.py new file mode 100644 index 00000000..82d989a6 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_media_response.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import unittest + +from yoti_python_sdk.doc_scan.session.retrieve.share_code_media_response import ( + ShareCodeMediaResponse, +) + + +class ShareCodeMediaResponseTest(unittest.TestCase): + def test_should_parse_media(self): + data = { + "media": { + "id": "some-media-id", + "type": "JSON", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + } + + result = ShareCodeMediaResponse(data) + + assert result.media is not None + assert result.media.id == "some-media-id" + assert result.media.type == "JSON" + assert result.media.created is not None + assert result.media.last_updated is not None + + def test_should_return_none_media_when_key_missing(self): + result = ShareCodeMediaResponse({}) + + assert result.media is None + + def test_should_return_none_media_when_value_is_null(self): + data = {"media": None} + + result = ShareCodeMediaResponse(data) + + assert result.media is None + + def test_should_parse_empty_media_object(self): + data = {"media": {}} + + result = ShareCodeMediaResponse(data) + + assert result.media is not None + assert result.media.id is None + assert result.media.type is None + + def test_should_parse_when_none(self): + result = ShareCodeMediaResponse(None) + + assert result.media is None + + +if __name__ == "__main__": + unittest.main() diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_resource_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_resource_response.py new file mode 100644 index 00000000..668a5263 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_resource_response.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import unittest + +from yoti_python_sdk.doc_scan.session.retrieve.resource_response import ( + ResourceResponse, +) +from yoti_python_sdk.doc_scan.session.retrieve.share_code_resource_response import ( + ShareCodeResourceResponse, +) +from yoti_python_sdk.doc_scan.session.retrieve.verify_share_code_task_response import ( + VerifyShareCodeTaskResponse, +) + + +class ShareCodeResourceResponseTest(unittest.TestCase): + def test_should_parse_correctly(self): + data = { + "id": "some-id", + "source": "END_USER", + "created_at": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.id == "some-id" + assert result.source == "END_USER" + assert result.created_at == "2026-02-05T11:33:46Z" + assert result.last_updated == "2026-02-05T11:33:50Z" + + def test_should_be_instance_of_resource_response(self): + result = ShareCodeResourceResponse({}) + + assert isinstance(result, ResourceResponse) + + def test_should_filter_verify_share_code_tasks(self): + data = { + "tasks": [ + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-1"}, + {"type": "SOME_OTHER_TASK", "id": "task-2"}, + ] + } + + result = ShareCodeResourceResponse(data) + + assert len(result.tasks) == 2 + assert len(result.verify_share_code_tasks) == 1 + assert result.verify_share_code_tasks[0].id == "task-1" + + def test_should_parse_multiple_verify_share_code_tasks(self): + data = { + "tasks": [ + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-1"}, + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-2"}, + ] + } + + result = ShareCodeResourceResponse(data) + + assert len(result.verify_share_code_tasks) == 2 + + def test_should_parse_media_fields(self): + data = { + "lookup_profile": {"media": {"id": "lp-media", "type": "JSON"}}, + "returned_profile": {"media": {"id": "rp-media", "type": "JSON"}}, + "id_photo": {"media": {"id": "ip-media", "type": "IMAGE"}}, + "file": {"media": {"id": "f-media", "type": "PDF"}}, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.lookup_profile is not None + assert result.lookup_profile.media.id == "lp-media" + assert result.lookup_profile.media.type == "JSON" + assert result.returned_profile is not None + assert result.returned_profile.media.id == "rp-media" + assert result.id_photo is not None + assert result.id_photo.media.id == "ip-media" + assert result.id_photo.media.type == "IMAGE" + assert result.file is not None + assert result.file.media.id == "f-media" + assert result.file.media.type == "PDF" + + def test_should_parse_source_as_object(self): + data = {"source": {"type": "END_USER"}, "tasks": []} + + result = ShareCodeResourceResponse(data) + + assert result.source == "END_USER" + + def test_should_parse_source_as_string(self): + data = {"source": "END_USER", "tasks": []} + + result = ShareCodeResourceResponse(data) + + assert result.source == "END_USER" + + def test_should_return_none_for_missing_media_fields(self): + data = {"tasks": []} + + result = ShareCodeResourceResponse(data) + + assert result.lookup_profile is None + assert result.returned_profile is None + assert result.id_photo is None + assert result.file is None + + def test_should_return_none_for_null_media_fields(self): + data = { + "lookup_profile": None, + "returned_profile": None, + "id_photo": None, + "file": None, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.lookup_profile is None + assert result.returned_profile is None + assert result.id_photo is None + assert result.file is None + + def test_should_parse_when_none(self): + result = ShareCodeResourceResponse(None) + + assert result.id is None + assert result.source is None + assert result.created_at is None + assert result.last_updated is None + assert result.lookup_profile is None + assert result.returned_profile is None + assert result.id_photo is None + assert result.file is None + + def test_should_parse_with_no_tasks(self): + data = {"id": "some-id", "tasks": []} + + result = ShareCodeResourceResponse(data) + + assert len(result.verify_share_code_tasks) == 0 + + def test_should_parse_full_realistic_payload(self): + data = { + "id": "abc12345-share-code-id", + "source": {"type": "END_USER"}, + "created_at": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + "lookup_profile": { + "media": { + "id": "lp-media-id", + "type": "JSON", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + }, + "returned_profile": { + "media": { + "id": "rp-media-id", + "type": "JSON", + } + }, + "id_photo": { + "media": { + "id": "ip-media-id", + "type": "IMAGE", + } + }, + "file": { + "media": { + "id": "f-media-id", + "type": "PDF", + } + }, + "tasks": [ + { + "type": "VERIFY_SHARE_CODE_TASK", + "id": "task-id-1", + "state": "DONE", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + "generated_media": [ + {"id": "gen-media-1", "type": "PDF"}, + {"id": "gen-media-2", "type": "IMAGE"}, + ], + } + ], + } + + result = ShareCodeResourceResponse(data) + + assert result.id == "abc12345-share-code-id" + assert result.source == "END_USER" + assert result.created_at == "2026-02-05T11:33:46Z" + assert result.last_updated == "2026-02-05T11:33:50Z" + assert result.lookup_profile.media.id == "lp-media-id" + assert result.returned_profile.media.id == "rp-media-id" + assert result.id_photo.media.id == "ip-media-id" + assert result.file.media.id == "f-media-id" + assert len(result.verify_share_code_tasks) == 1 + assert isinstance( + result.verify_share_code_tasks[0], VerifyShareCodeTaskResponse + ) + assert result.verify_share_code_tasks[0].id == "task-id-1" + assert result.verify_share_code_tasks[0].state == "DONE" + assert len(result.verify_share_code_tasks[0].generated_media) == 2 + + +if __name__ == "__main__": + unittest.main() diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py index 0037810c..8f57874b 100644 --- a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_static_liveness_resource.py @@ -12,6 +12,7 @@ def test_should_parse_static_liveness_resource(self): "id": "bbbbbbb-5717-4562-b3fc-2c963f66afa6", "source": {"type": "END_USER"}, "liveness_type": "STATIC", + "capture_type": "PHOTOGRAPH", "image": { "media": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", @@ -27,6 +28,7 @@ def test_should_parse_static_liveness_resource(self): assert result.id == "bbbbbbb-5717-4562-b3fc-2c963f66afa6" assert result.liveness_type == "STATIC" + assert result.capture_type == "PHOTOGRAPH" assert isinstance(result.image, ImageResponse) assert isinstance(result.image.media, MediaResponse) assert result.image.media.id == "3fa85f64-5717-4562-b3fc-2c963f66afa6" @@ -44,6 +46,33 @@ def test_should_handle_missing_image(self): assert result.id == "test-id" assert result.liveness_type == "STATIC" assert result.image is None + assert result.capture_type is None + + def test_should_handle_missing_capture_type(self): + data = { + "id": "test-id", + "liveness_type": "STATIC", + "image": { + "media": { + "id": "media-id-123", + "type": "IMAGE", + "created": "2021-06-11T11:39:24Z", + "last_updated": "2021-06-11T11:39:24Z", + } + }, + "tasks": [], + } + + result = StaticLivenessResourceResponse(data) + + assert result.capture_type is None + assert result.image is not None + + def test_should_handle_none_data(self): + result = StaticLivenessResourceResponse(None) + + assert result.capture_type is None + assert result.image is None def test_should_parse_media_id_for_retrieval(self): data = { diff --git a/yoti_python_sdk/tests/doc_scan/session/retrieve/test_verify_share_code_task_response.py b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_verify_share_code_task_response.py new file mode 100644 index 00000000..78d9e63a --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_verify_share_code_task_response.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import unittest + +from yoti_python_sdk.doc_scan.session.retrieve.task_response import TaskResponse +from yoti_python_sdk.doc_scan.session.retrieve.verify_share_code_task_response import ( + VerifyShareCodeTaskResponse, +) + + +class VerifyShareCodeTaskResponseTest(unittest.TestCase): + def test_should_be_instance_of_task_response(self): + result = VerifyShareCodeTaskResponse({}) + + assert isinstance(result, TaskResponse) + + def test_should_parse_task_fields(self): + data = { + "type": "VERIFY_SHARE_CODE_TASK", + "id": "some-task-id", + "state": "DONE", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + + result = VerifyShareCodeTaskResponse(data) + + assert result.type == "VERIFY_SHARE_CODE_TASK" + assert result.id == "some-task-id" + assert result.state == "DONE" + assert result.created is not None + assert result.last_updated is not None + + def test_should_parse_generated_media(self): + data = { + "type": "VERIFY_SHARE_CODE_TASK", + "id": "some-task-id", + "state": "DONE", + "generated_media": [ + {"id": "media-1", "type": "PDF"}, + {"id": "media-2", "type": "IMAGE"}, + ], + } + + result = VerifyShareCodeTaskResponse(data) + + assert len(result.generated_media) == 2 + assert result.generated_media[0].id == "media-1" + assert result.generated_media[0].type == "PDF" + assert result.generated_media[1].id == "media-2" + assert result.generated_media[1].type == "IMAGE" + + def test_should_parse_when_none(self): + result = VerifyShareCodeTaskResponse(None) + + assert result.id is None + assert result.type is None + assert result.state is None + + +if __name__ == "__main__": + unittest.main() diff --git a/yoti_python_sdk/tests/fixtures/response_get_docs_scan_session.txt b/yoti_python_sdk/tests/fixtures/response_get_docs_scan_session.txt index 2bb3194e..2087ed58 100644 --- a/yoti_python_sdk/tests/fixtures/response_get_docs_scan_session.txt +++ b/yoti_python_sdk/tests/fixtures/response_get_docs_scan_session.txt @@ -28,7 +28,8 @@ "type": "IMAGE", "created": "2020-01-30T15:00:00Z", "last_updated": "2020-01-30T15:00:00Z" - } + }, + "extraction_image_ids": [""] }], "document_fields": { "media": {