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..d725b77f 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 process of the sub check + + :return: the process + :rtype: str or None + """ + return self.__process + @property def details(self): """ 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..78747261 --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/share_code_media_response.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +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): + """ + :param data: the data to parse + :type data: dict or 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..23445faa --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/share_code_resource_response.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +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): + """ + :param data: the data to parse + :type data: dict or None + """ + if data is None: + data = dict() + + ResourceResponse.__init__(self, data) + + source = data.get("source", None) + if isinstance(source, dict): + self.__source = source.get("type", str(source)) + else: + self.__source = source + 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 string + :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 string + :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 associated with the share code + + :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/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..10638bae --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/retrieve/verify_share_code_task_response.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from yoti_python_sdk.doc_scan.session.retrieve.task_response import TaskResponse + + +class VerifyShareCodeTaskResponse(TaskResponse): + """ + Represents a Verify Share Code task response + """ 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..9d6769ed 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,6 +18,7 @@ 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, } @@ -24,6 +26,7 @@ def test_should_build_correctly(self): assert result.sub_check is self.SOME_SUB_CHECK assert result.result is self.SOME_RESULT + assert result.process is 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_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..e58c60f9 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_media_response.py @@ -0,0 +1,47 @@ +import unittest + +from yoti_python_sdk.doc_scan.session.retrieve.media_response import MediaResponse +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"}} + + result = ShareCodeMediaResponse(data) + + assert isinstance(result.media, MediaResponse) + assert result.media.id == "some-media-id" + assert result.media.type == "JSON" + + def test_should_return_none_media_when_key_missing(self): + result = ShareCodeMediaResponse({}) + + assert result.media is None + + def test_should_parse_when_none(self): + result = ShareCodeMediaResponse(None) + + assert result.media is None + + def test_should_parse_empty_media(self): + data = {"media": {}} + + result = ShareCodeMediaResponse(data) + + assert isinstance(result.media, MediaResponse) + assert result.media.id is None + assert result.media.type is None + + def test_should_return_none_media_when_value_is_null(self): + data = {"media": None} + + result = ShareCodeMediaResponse(data) + + 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..022d49b0 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_share_code_resource_response.py @@ -0,0 +1,241 @@ +import unittest + +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.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): + SOME_ID = "share-code-123" + SOME_SOURCE = "test-source" + SOME_CREATED_AT = "2026-01-14T10:00:00Z" + SOME_LAST_UPDATED = "2026-01-14T11:00:00Z" + + def test_should_be_instance_of_resource_response(self): + result = ShareCodeResourceResponse({}) + + assert isinstance(result, ResourceResponse) + + def test_should_parse_correctly(self): + data = { + "id": self.SOME_ID, + "source": self.SOME_SOURCE, + "created_at": self.SOME_CREATED_AT, + "last_updated": self.SOME_LAST_UPDATED, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.id == self.SOME_ID + assert result.source == self.SOME_SOURCE + assert result.created_at == self.SOME_CREATED_AT + assert result.last_updated == self.SOME_LAST_UPDATED + + def test_should_parse_source_as_object(self): + data = { + "id": self.SOME_ID, + "source": {"type": "END_USER"}, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.source == "END_USER" + + def test_should_parse_source_object_without_type_key(self): + data = { + "id": self.SOME_ID, + "source": {"unknown_key": "some_value"}, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert result.source is not 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 + assert len(result.tasks) == 0 + + def test_should_parse_media_fields(self): + data = { + "id": self.SOME_ID, + "lookup_profile": {"media": {"id": "media-1", "type": "JSON"}}, + "returned_profile": {"media": {"id": "media-2", "type": "JSON"}}, + "id_photo": {"media": {"id": "media-3", "type": "IMAGE"}}, + "file": {"media": {"id": "media-4", "type": "PDF"}}, + "tasks": [], + } + + result = ShareCodeResourceResponse(data) + + assert isinstance(result.lookup_profile, ShareCodeMediaResponse) + assert result.lookup_profile.media.id == "media-1" + assert result.lookup_profile.media.type == "JSON" + assert isinstance(result.returned_profile, ShareCodeMediaResponse) + assert result.returned_profile.media.id == "media-2" + assert isinstance(result.id_photo, ShareCodeMediaResponse) + assert result.id_photo.media.id == "media-3" + assert result.id_photo.media.type == "IMAGE" + assert isinstance(result.file, ShareCodeMediaResponse) + assert result.file.media.id == "media-4" + assert result.file.media.type == "PDF" + + def test_should_return_none_for_null_media_fields(self): + data = { + "id": self.SOME_ID, + "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_return_none_for_missing_media_fields(self): + result = ShareCodeResourceResponse({"tasks": []}) + + 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_verify_share_code_tasks(self): + data = { + "id": self.SOME_ID, + "tasks": [ + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-1", "state": "DONE"}, + ], + } + + result = ShareCodeResourceResponse(data) + + assert len(result.tasks) == 1 + assert len(result.verify_share_code_tasks) == 1 + assert isinstance(result.verify_share_code_tasks[0], VerifyShareCodeTaskResponse) + + def test_should_filter_verify_share_code_tasks(self): + data = { + "id": self.SOME_ID, + "tasks": [ + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-verify", "state": "DONE"}, + {"type": "OTHER_TASK_TYPE", "id": "task-other", "state": "PENDING"}, + ], + } + + 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-verify" + + def test_should_parse_multiple_verify_share_code_tasks(self): + data = { + "id": self.SOME_ID, + "tasks": [ + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-1", "state": "PENDING"}, + {"type": "VERIFY_SHARE_CODE_TASK", "id": "task-2", "state": "DONE"}, + ], + } + + result = ShareCodeResourceResponse(data) + + assert len(result.verify_share_code_tasks) == 2 + assert result.verify_share_code_tasks[0].id == "task-1" + assert result.verify_share_code_tasks[1].id == "task-2" + + def test_should_parse_full_realistic_payload(self): + data = { + "id": "abc12345-6789-abcd-ef01-234567890abc", + "source": "END_USER", + "created_at": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + "lookup_profile": { + "media": { + "id": "df419a66-0449-41cf-a795-6dfaa993d1f6", + "type": "JSON", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + }, + "returned_profile": { + "media": { + "id": "f2152059-2868-47c9-8f5f-64966c1b66b0", + "type": "JSON", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + }, + "id_photo": { + "media": { + "id": "45e4ee9d-a77b-4007-afe9-ab7067687aff", + "type": "IMAGE", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + }, + "file": { + "media": { + "id": "c83a9f12-1234-5678-9abc-def012345678", + "type": "PDF", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + } + }, + "tasks": [ + { + "type": "VERIFY_SHARE_CODE_TASK", + "id": "73141aa9-a01f-4de9-9281-1b11cda7ab75", + "state": "DONE", + "created": "2026-02-05T11:33:46Z", + "last_updated": "2026-02-05T11:33:50Z", + "generated_media": [ + {"id": "df419a66-0449-41cf-a795-6dfaa993d1f6", "type": "PDF"}, + {"id": "45e4ee9d-a77b-4007-afe9-ab7067687aff", "type": "IMAGE"}, + {"id": "f2152059-2868-47c9-8f5f-64966c1b66b0", "type": "JSON"}, + ], + } + ], + } + + result = ShareCodeResourceResponse(data) + + assert result.id == "abc12345-6789-abcd-ef01-234567890abc" + assert result.source == "END_USER" + assert result.lookup_profile.media.id == "df419a66-0449-41cf-a795-6dfaa993d1f6" + assert result.lookup_profile.media.type == "JSON" + assert result.returned_profile.media.id == "f2152059-2868-47c9-8f5f-64966c1b66b0" + assert result.id_photo.media.id == "45e4ee9d-a77b-4007-afe9-ab7067687aff" + assert result.id_photo.media.type == "IMAGE" + assert result.file.media.id == "c83a9f12-1234-5678-9abc-def012345678" + assert result.file.media.type == "PDF" + assert len(result.verify_share_code_tasks) == 1 + assert result.verify_share_code_tasks[0].state == "DONE" + assert len(result.verify_share_code_tasks[0].generated_media) == 3 + + +if __name__ == "__main__": + unittest.main() 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..781b2129 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/retrieve/test_verify_share_code_task_response.py @@ -0,0 +1,56 @@ +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-01-14T10:00:00Z", + "last_updated": "2026-01-14T11:00:00Z", + } + + 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 + + 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()