From 753509514c279cdf3c6c56c0030a50d35d15c8f7 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Tue, 8 Jul 2025 17:39:16 -0600 Subject: [PATCH 01/20] fix: validate computed field to ensure reject expired documents --- mati/resources/verifications.py | 52 +++--- mati/version.py | 2 +- tests/conftest.py | 15 +- ...est_retrive_verification_invalid_govt.yaml | 165 ++++++++++++++++++ ...test_retrive_verification_invalid_poa.yaml | 165 ++++++++++++++++++ tests/resources/test_verifications.py | 13 ++ 6 files changed, 387 insertions(+), 25 deletions(-) create mode 100644 tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml create mode 100644 tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index e5332c3..2d62cb2 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -111,20 +111,7 @@ def govt_id_document(self) -> Optional[VerificationDocument]: @property def proof_of_residency_validation(self) -> Optional[DocumentScore]: - por = self.proof_of_residency_document - if not por: - return None - return DocumentScore( - all([step.status == 200 and not step.error for step in por.steps]) - and not ( - self.computed - and self.computed['is_document_expired']['data'][ - 'proof_of_residency' - ] - ), - sum([step.status for step in por.steps if not step.error]), - [step.error['code'] for step in por.steps if step.error], - ) + return self.get_document_validation(self.proof_of_residency_document) @property def proof_of_life_validation(self) -> Optional[DocumentScore]: @@ -139,13 +126,32 @@ def proof_of_life_validation(self) -> Optional[DocumentScore]: @property def govt_id_validation(self) -> Optional[DocumentScore]: - govt = self.govt_id_document - if not govt: - return None - return DocumentScore( - all( - [step.status == 200 and not step.error for step in govt.steps] - ), - sum([step.status for step in govt.steps if not step.error]), - [step.error['code'] for step in govt.steps if step.error], + return self.get_document_validation(self.govt_id_document) + + + def get_document_validation( + self, + document: Optional[VerificationDocument] + ) -> Optional[DocumentScore]: + if not document: + return None + type = document.type.replace("-", "_") + is_expired = ( + self.computed['is_document_expired']['data'][type] + if self.computed + else False ) + steps = document.steps + if is_expired: + steps.append(VerificationDocumentStep( + id=f'{type}_verification', + status=500, + error={ + 'verification': f'Document {type} expired', 'code': 500 + }) + ) + return DocumentScore( + all([step.status == 200 and not step.error for step in steps]), + sum([step.status for step in steps if not step.error]), + [step.error['code'] for step in steps if step.error], + ) \ No newline at end of file diff --git a/mati/version.py b/mati/version.py index 13ce17d..e22517e 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.6' +__version__ = '2.0.7.dev1' diff --git a/tests/conftest.py b/tests/conftest.py index 3f6193f..7360f8c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,7 +209,7 @@ def vcr_config() -> dict: @pytest.fixture def client() -> Generator: - yield Client('api_key', 'secret_key') + yield Client() @pytest.fixture @@ -227,6 +227,19 @@ def verification_without_pol(client: Client): verification.steps = [] yield verification +@pytest.fixture +def verification_with_govt_expired(client: Client): + verification = client.verifications.retrieve('686c77811ee936aece7016ac') + verification.computed["is_document_expired"]["data"]["national_id"] = True + yield verification + + +@pytest.fixture +def verification_with_poa_expired(client: Client): + verification = client.verifications.retrieve('686c77811ee936aece7016ac') + verification.computed["is_document_expired"]["data"]["proof_of_residency"] = True + yield verification + @pytest.fixture def verification_document_national_id() -> VerificationDocument: diff --git a/tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml b/tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml new file mode 100644 index 0000000..11f9e2d --- /dev/null +++ b/tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml @@ -0,0 +1,165 @@ +interactions: +- request: + body: grant_type=client_credentials + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '29' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - mati-python/2.0.7.dev1 + method: POST + uri: https://api.getmati.com/oauth + response: + body: + string: '{"access_token": "ACCESS_TOKEN", "expiresIn": 3600, "payload": {"user": + {"_id": "ID", "firstName": "FIRST_NAME", "lastName": "LAST_NAME"}}}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 08 Jul 2025 23:18:57 GMT + Transfer-Encoding: + - chunked + cache-control: + - no-store, no-cache, must-revalidate, proxy-revalidate + content-security-policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + expect-ct: + - max-age=0 + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - no-referrer + strict-transport-security: + - max-age=15552000; includeSubDomains; preload + surrogate-control: + - no-store + x-content-type-options: + - nosniff + x-dns-prefetch-control: + - 'off' + x-download-options: + - noopen + x-envoy-upstream-service-time: + - '141' + x-frame-options: + - DENY + x-permitted-cross-domain-policies: + - none + x-request-id: + - add8dd4e-74a3-414d-83aa-f8e1dd998672 + x-xss-protection: + - '1' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - mati-python/2.0.7.dev1 + method: GET + uri: https://api.getmati.com/v2/verifications/686c77811ee936aece7016ac + response: + body: + string: '{"expired": false, "identity": {"status": "verified"}, "flow": {"id": + "some_flow", "name": "Default flow"}, "documents": [{"country": "MX", "region": + "", "type": "national-id", "photos": ["https://media.getmati.com/media/xxx", + "https://media.getmati.com/media/yyy"], "steps": [{"error": null, "status": + 200, "id": "template-matching"}, {"error": null, "status": 200, "id": "mexican-curp-validation", + "data": {"curp": "CURP", "fullName": "LAST FIRST", "birthDate": "01/01/1980", + "gender": "HOMBRE", "nationality": "MEXICO", "surname": "LAST", "secondSurname": + "", "name": "FIRST"}}, {"error": null, "status": 200, "id": "document-reading", + "data": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive": + true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth": + {"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate": + {"value": "2030-12-31", "label": "Date of Expiration", "format": "date"}, + "curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia + 36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01", + "label": "Emission Date", "format": "date"}}}, {"error": null, "status": 200, + "id": "alteration-detection"}, {"error": null, "status": 200, "id": "watchlists"}], + "fields": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive": + true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth": + {"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate": + {"value": "2030-12-31", "label": "Date of Expiration", "format": "date"}, + "curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia + 36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01", + "label": "Emission Date", "format": "date"}}}, {"country": "MX", "region": + null, "type": "proof-of-residency", "steps": [{"status": 200, "id": "document-reading", + "data": {"fullName": {"required": true, "label": "Name", "value": "FIRST NAME"}, + "address": {"label": "Address", "value": "Varsovia 36, 06600 CDMX"}, "emissionDate": + {"format": "date", "label": "Emission Date", "value": "1880-01-01"}}, "error": + null}, {"status": 200, "id": "watchlists", "error": null}], "fields": {"address": + {"value": "Varsovia 36, 06600 CDMX"}, "emissionDate": {"value": "1880-01-01"}, + "fullName": {"value": "FIRST LASTNAME"}}, "photos": ["https://media.getmati.com/file?location=xyc"]}], + "steps": [{"status": 200, "id": "liveness", "data": {"videoUrl": "https://media.getmati.com/file?location=abc", + "spriteUrl": "https://media.getmati.com/file?location=def", "selfieUrl": "https://media.getmati.com/file?location=hij"}, + "error": null}], "hasProblem": false, "computed": {"age": {"data": 100}, "isDocumentExpired": + {"data": {"national-id": false, "proof-of-residency": false}}}, "id": "5d9fb1f5bfbfac001a349bfb", + "metadata": {"name": "First Last", "dob": "1980-01-01"}}' + headers: + Connection: + - keep-alive + Content-Length: + - '7918' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 08 Jul 2025 23:18:57 GMT + cache-control: + - no-store, no-cache, must-revalidate, proxy-revalidate + content-security-policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + expect-ct: + - max-age=0 + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - no-referrer + strict-transport-security: + - max-age=15552000; includeSubDomains; preload + surrogate-control: + - no-store + x-content-type-options: + - nosniff + x-dns-prefetch-control: + - 'off' + x-download-options: + - noopen + x-envoy-upstream-service-time: + - '258' + x-frame-options: + - DENY + x-permitted-cross-domain-policies: + - none + x-request-id: + - 98bc4ee3-a97d-442f-bfbc-5fdd49f302a7 + x-xss-protection: + - '1' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml b/tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml new file mode 100644 index 0000000..847dd7e --- /dev/null +++ b/tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml @@ -0,0 +1,165 @@ +interactions: +- request: + body: grant_type=client_credentials + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '29' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - mati-python/2.0.7.dev1 + method: POST + uri: https://api.getmati.com/oauth + response: + body: + string: '{"access_token": "ACCESS_TOKEN", "expiresIn": 3600, "payload": {"user": + {"_id": "ID", "firstName": "FIRST_NAME", "lastName": "LAST_NAME"}}}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 08 Jul 2025 23:33:07 GMT + Transfer-Encoding: + - chunked + cache-control: + - no-store, no-cache, must-revalidate, proxy-revalidate + content-security-policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + expect-ct: + - max-age=0 + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - no-referrer + strict-transport-security: + - max-age=15552000; includeSubDomains; preload + surrogate-control: + - no-store + x-content-type-options: + - nosniff + x-dns-prefetch-control: + - 'off' + x-download-options: + - noopen + x-envoy-upstream-service-time: + - '287' + x-frame-options: + - DENY + x-permitted-cross-domain-policies: + - none + x-request-id: + - f989bec9-f375-4bbb-a4f3-f84257ba582c + x-xss-protection: + - '1' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - mati-python/2.0.7.dev1 + method: GET + uri: https://api.getmati.com/v2/verifications/686c77811ee936aece7016ac + response: + body: + string: '{"expired": false, "identity": {"status": "verified"}, "flow": {"id": + "some_flow", "name": "Default flow"}, "documents": [{"country": "MX", "region": + "", "type": "national-id", "photos": ["https://media.getmati.com/media/xxx", + "https://media.getmati.com/media/yyy"], "steps": [{"error": null, "status": + 200, "id": "template-matching"}, {"error": null, "status": 200, "id": "mexican-curp-validation", + "data": {"curp": "CURP", "fullName": "LAST FIRST", "birthDate": "01/01/1980", + "gender": "HOMBRE", "nationality": "MEXICO", "surname": "LAST", "secondSurname": + "", "name": "FIRST"}}, {"error": null, "status": 200, "id": "document-reading", + "data": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive": + true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth": + {"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate": + {"value": "2030-12-31", "label": "Date of Expiration", "format": "date"}, + "curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia + 36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01", + "label": "Emission Date", "format": "date"}}}, {"error": null, "status": 200, + "id": "alteration-detection"}, {"error": null, "status": 200, "id": "watchlists"}], + "fields": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive": + true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth": + {"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate": + {"value": "2030-12-31", "label": "Date of Expiration", "format": "date"}, + "curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia + 36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01", + "label": "Emission Date", "format": "date"}}}, {"country": "MX", "region": + null, "type": "proof-of-residency", "steps": [{"status": 200, "id": "document-reading", + "data": {"fullName": {"required": true, "label": "Name", "value": "FIRST NAME"}, + "address": {"label": "Address", "value": "Varsovia 36, 06600 CDMX"}, "emissionDate": + {"format": "date", "label": "Emission Date", "value": "1880-01-01"}}, "error": + null}, {"status": 200, "id": "watchlists", "error": null}], "fields": {"address": + {"value": "Varsovia 36, 06600 CDMX"}, "emissionDate": {"value": "1880-01-01"}, + "fullName": {"value": "FIRST LASTNAME"}}, "photos": ["https://media.getmati.com/file?location=xyc"]}], + "steps": [{"status": 200, "id": "liveness", "data": {"videoUrl": "https://media.getmati.com/file?location=abc", + "spriteUrl": "https://media.getmati.com/file?location=def", "selfieUrl": "https://media.getmati.com/file?location=hij"}, + "error": null}], "hasProblem": false, "computed": {"age": {"data": 100}, "isDocumentExpired": + {"data": {"national-id": false, "proof-of-residency": false}}}, "id": "5d9fb1f5bfbfac001a349bfb", + "metadata": {"name": "First Last", "dob": "1980-01-01"}}' + headers: + Connection: + - keep-alive + Content-Length: + - '7918' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 08 Jul 2025 23:33:07 GMT + cache-control: + - no-store, no-cache, must-revalidate, proxy-revalidate + content-security-policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + expect-ct: + - max-age=0 + expires: + - '0' + pragma: + - no-cache + referrer-policy: + - no-referrer + strict-transport-security: + - max-age=15552000; includeSubDomains; preload + surrogate-control: + - no-store + x-content-type-options: + - nosniff + x-dns-prefetch-control: + - 'off' + x-download-options: + - noopen + x-envoy-upstream-service-time: + - '280' + x-frame-options: + - DENY + x-permitted-cross-domain-policies: + - none + x-request-id: + - b1ef368c-c806-486c-9a55-8945bba552ef + x-xss-protection: + - '1' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index 911f621..a25bd53 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -79,3 +79,16 @@ def test_retrieve_dni_verification(verification_without_pol): assert not verification.proof_of_life_document assert verification.documents[0].document_number == '111' assert not verification.documents[0].ocr_number + +@pytest.mark.vcr +def test_retrive_verification_invalid_govt(verification_with_govt_expired): + verification = verification_with_govt_expired + assert not verification.govt_id_validation.is_valid + assert verification.govt_id_validation.error_codes + + +@pytest.mark.vcr +def test_retrive_verification_invalid_poa(verification_with_poa_expired): + verification = verification_with_poa_expired + assert not verification.proof_of_residency_validation.is_valid + assert verification.proof_of_residency_validation.error_codes \ No newline at end of file From 3b615b511ee3181f5c688279bcc39e76cba4cf69 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Tue, 8 Jul 2025 18:00:15 -0600 Subject: [PATCH 02/20] fix: restore client for conftest --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7360f8c..82b80f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,7 +209,7 @@ def vcr_config() -> dict: @pytest.fixture def client() -> Generator: - yield Client() + yield Client('api_key', 'secret_key') @pytest.fixture From c77062c67976f8fc2c524f40c723b2339b633ff2 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Tue, 8 Jul 2025 20:56:39 -0600 Subject: [PATCH 03/20] bump: bump to version 2.0.7 --- mati/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/version.py b/mati/version.py index e22517e..4b259db 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7.dev1' +__version__ = '2.0.7' From f0c0e4a643207214e04ac8ec6c7d7677da7e3efe Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Tue, 8 Jul 2025 21:01:14 -0600 Subject: [PATCH 04/20] fix: run make format --- mati/resources/verifications.py | 27 ++++++++++++++------------- tests/conftest.py | 5 ++++- tests/resources/test_verifications.py | 3 ++- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index 2d62cb2..c097687 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -127,14 +127,12 @@ def proof_of_life_validation(self) -> Optional[DocumentScore]: @property def govt_id_validation(self) -> Optional[DocumentScore]: return self.get_document_validation(self.govt_id_document) - def get_document_validation( - self, - document: Optional[VerificationDocument] - ) -> Optional[DocumentScore]: + self, document: Optional[VerificationDocument] + ) -> Optional[DocumentScore]: if not document: - return None + return None type = document.type.replace("-", "_") is_expired = ( self.computed['is_document_expired']['data'][type] @@ -142,16 +140,19 @@ def get_document_validation( else False ) steps = document.steps - if is_expired: - steps.append(VerificationDocumentStep( - id=f'{type}_verification', - status=500, - error={ - 'verification': f'Document {type} expired', 'code': 500 - }) + if is_expired: + steps.append( + VerificationDocumentStep( + id=f'{type}_verification', + status=500, + error={ + 'verification': f'Document {type} expired', + 'code': 500, + }, + ) ) return DocumentScore( all([step.status == 200 and not step.error for step in steps]), sum([step.status for step in steps if not step.error]), [step.error['code'] for step in steps if step.error], - ) \ No newline at end of file + ) diff --git a/tests/conftest.py b/tests/conftest.py index 82b80f4..ccfe4b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -227,6 +227,7 @@ def verification_without_pol(client: Client): verification.steps = [] yield verification + @pytest.fixture def verification_with_govt_expired(client: Client): verification = client.verifications.retrieve('686c77811ee936aece7016ac') @@ -237,7 +238,9 @@ def verification_with_govt_expired(client: Client): @pytest.fixture def verification_with_poa_expired(client: Client): verification = client.verifications.retrieve('686c77811ee936aece7016ac') - verification.computed["is_document_expired"]["data"]["proof_of_residency"] = True + verification.computed["is_document_expired"]["data"][ + "proof_of_residency" + ] = True yield verification diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index a25bd53..7dfdce4 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -80,6 +80,7 @@ def test_retrieve_dni_verification(verification_without_pol): assert verification.documents[0].document_number == '111' assert not verification.documents[0].ocr_number + @pytest.mark.vcr def test_retrive_verification_invalid_govt(verification_with_govt_expired): verification = verification_with_govt_expired @@ -91,4 +92,4 @@ def test_retrive_verification_invalid_govt(verification_with_govt_expired): def test_retrive_verification_invalid_poa(verification_with_poa_expired): verification = verification_with_poa_expired assert not verification.proof_of_residency_validation.is_valid - assert verification.proof_of_residency_validation.error_codes \ No newline at end of file + assert verification.proof_of_residency_validation.error_codes From de5dc5144ec216b47871bc85290c090617e2dafe Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Tue, 8 Jul 2025 21:12:47 -0600 Subject: [PATCH 05/20] fix: fixes from coderabbit revision --- mati/resources/verifications.py | 8 ++++---- ....yaml => test_retrieve_verification_invalid_govt.yaml} | 0 ...a.yaml => test_retrieve_verification_invalid_poa.yaml} | 0 tests/resources/test_verifications.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename tests/resources/cassettes/{test_retrive_verification_invalid_govt.yaml => test_retrieve_verification_invalid_govt.yaml} (100%) rename tests/resources/cassettes/{test_retrive_verification_invalid_poa.yaml => test_retrieve_verification_invalid_poa.yaml} (100%) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index c097687..ca20c0a 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -133,9 +133,9 @@ def get_document_validation( ) -> Optional[DocumentScore]: if not document: return None - type = document.type.replace("-", "_") + document_type = document.type.replace("-", "_") is_expired = ( - self.computed['is_document_expired']['data'][type] + self.computed['is_document_expired']['data'][document_type] if self.computed else False ) @@ -143,10 +143,10 @@ def get_document_validation( if is_expired: steps.append( VerificationDocumentStep( - id=f'{type}_verification', + id=f'{document_type}_verification', status=500, error={ - 'verification': f'Document {type} expired', + 'verification': f'Document {document_type} expired', 'code': 500, }, ) diff --git a/tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml b/tests/resources/cassettes/test_retrieve_verification_invalid_govt.yaml similarity index 100% rename from tests/resources/cassettes/test_retrive_verification_invalid_govt.yaml rename to tests/resources/cassettes/test_retrieve_verification_invalid_govt.yaml diff --git a/tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml b/tests/resources/cassettes/test_retrieve_verification_invalid_poa.yaml similarity index 100% rename from tests/resources/cassettes/test_retrive_verification_invalid_poa.yaml rename to tests/resources/cassettes/test_retrieve_verification_invalid_poa.yaml diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index 7dfdce4..a09befb 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -82,14 +82,14 @@ def test_retrieve_dni_verification(verification_without_pol): @pytest.mark.vcr -def test_retrive_verification_invalid_govt(verification_with_govt_expired): +def test_retrieve_verification_invalid_govt(verification_with_govt_expired): verification = verification_with_govt_expired assert not verification.govt_id_validation.is_valid assert verification.govt_id_validation.error_codes @pytest.mark.vcr -def test_retrive_verification_invalid_poa(verification_with_poa_expired): +def test_retrieve_verification_invalid_poa(verification_with_poa_expired): verification = verification_with_poa_expired assert not verification.proof_of_residency_validation.is_valid assert verification.proof_of_residency_validation.error_codes From 07df0fc01561687eb3e37db15d76bb73272df50a Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 15:35:18 -0600 Subject: [PATCH 06/20] fix: add type annotation for return value --- .github/workflows/release.yml | 4 ++-- tests/conftest.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a3cc0d..db552e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,9 +8,9 @@ jobs: publish-pypi: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4.7.0 + uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install dependencies diff --git a/tests/conftest.py b/tests/conftest.py index ccfe4b3..c3ea28c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import pytest from mati import Client +from mati.resources.verifications import Verification from mati.types import VerificationDocument, VerificationDocumentStep VERIFICATION_RESP = { @@ -222,21 +223,27 @@ def verification(client: Client) -> Generator: @pytest.fixture -def verification_without_pol(client: Client): +def verification_without_pol( + client: Client, +) -> Generator[Verification, None, None]: verification = client.verifications.retrieve('634870763768f1001cac7591') verification.steps = [] yield verification @pytest.fixture -def verification_with_govt_expired(client: Client): +def verification_with_govt_expired( + client: Client, +) -> Generator[Verification, None, None]: verification = client.verifications.retrieve('686c77811ee936aece7016ac') verification.computed["is_document_expired"]["data"]["national_id"] = True yield verification @pytest.fixture -def verification_with_poa_expired(client: Client): +def verification_with_poa_expired( + client: Client, +) -> Generator[Verification, None, None]: verification = client.verifications.retrieve('686c77811ee936aece7016ac') verification.computed["is_document_expired"]["data"][ "proof_of_residency" From 0ae765bfd95f55811a1e26fb03c914b9a8054832 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 16:59:45 -0600 Subject: [PATCH 07/20] fix: validate error in test --- mati/resources/verifications.py | 6 +++--- tests/resources/test_verifications.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index ca20c0a..9216bc4 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -139,15 +139,15 @@ def get_document_validation( if self.computed else False ) - steps = document.steps + steps = document.steps.copy() if is_expired: steps.append( VerificationDocumentStep( id=f'{document_type}_verification', - status=500, + status=410, error={ 'verification': f'Document {document_type} expired', - 'code': 500, + 'code': 'document_expired', }, ) ) diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index a09befb..342211c 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -85,7 +85,8 @@ def test_retrieve_dni_verification(verification_without_pol): def test_retrieve_verification_invalid_govt(verification_with_govt_expired): verification = verification_with_govt_expired assert not verification.govt_id_validation.is_valid - assert verification.govt_id_validation.error_codes + assert len(verification.govt_id_validation.error_codes) == 1 + assert 'document_expired' in verification.govt_id_validation.error_codes @pytest.mark.vcr From f86facf1555f55a4070e37ed243576f4e3c04d36 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 17:02:51 -0600 Subject: [PATCH 08/20] fix: set correct status for document expired step --- mati/resources/verifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index 9216bc4..0c6f1ca 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -144,7 +144,7 @@ def get_document_validation( steps.append( VerificationDocumentStep( id=f'{document_type}_verification', - status=410, + status=200, error={ 'verification': f'Document {document_type} expired', 'code': 'document_expired', From f49ab3a2d7d4730558fd8769b0ca7217d892b1dd Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 17:34:46 -0600 Subject: [PATCH 09/20] feat: add expired step to retrieve errors correctly --- mati/resources/verifications.py | 18 ++++-------------- mati/types/enums.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index 0c6f1ca..02eea48 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -139,20 +139,10 @@ def get_document_validation( if self.computed else False ) - steps = document.steps.copy() if is_expired: - steps.append( - VerificationDocumentStep( - id=f'{document_type}_verification', - status=200, - error={ - 'verification': f'Document {document_type} expired', - 'code': 'document_expired', - }, - ) - ) + document.add_expired_step() return DocumentScore( - all([step.status == 200 and not step.error for step in steps]), - sum([step.status for step in steps if not step.error]), - [step.error['code'] for step in steps if step.error], + all([step.status == 200 and not step.error for step in document.steps]), + sum([step.status for step in document.steps if not step.error]), + [step.error['code'] for step in document.steps if step.error], ) diff --git a/mati/types/enums.py b/mati/types/enums.py index 6e4ef44..8782315 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -184,6 +184,21 @@ def ocr_number(self) -> str: return self.fields['ocr_number']['value'] return '' + def add_expired_step(self) -> None: + step_id = f"{self.type}_verification" + if not any(step.id == step_id for step in self.steps): + self.steps.append( + VerificationDocumentStep( + id=step_id, + status=200, + error={ + 'verification': f'Document {type} expired', + 'code': 'document_expired', + }, + ) + ) + + @dataclass class LivenessMedia: From baef2034b51e42c95408f13385c59426f6cfee20 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 17:38:08 -0600 Subject: [PATCH 10/20] fix: fix lint --- mati/resources/verifications.py | 7 ++++++- mati/types/enums.py | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mati/resources/verifications.py b/mati/resources/verifications.py index 02eea48..355f03c 100644 --- a/mati/resources/verifications.py +++ b/mati/resources/verifications.py @@ -142,7 +142,12 @@ def get_document_validation( if is_expired: document.add_expired_step() return DocumentScore( - all([step.status == 200 and not step.error for step in document.steps]), + all( + [ + step.status == 200 and not step.error + for step in document.steps + ] + ), sum([step.status for step in document.steps if not step.error]), [step.error['code'] for step in document.steps if step.error], ) diff --git a/mati/types/enums.py b/mati/types/enums.py index 8782315..db7a844 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -199,7 +199,6 @@ def add_expired_step(self) -> None: ) - @dataclass class LivenessMedia: video_url: str From 8a80833d4dcb29ba2d3c27c52ef93aa21dd78f60 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 17:39:19 -0600 Subject: [PATCH 11/20] feat: change version to generate pre-release --- mati/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/version.py b/mati/version.py index 4b259db..6015237 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7' +__version__ = '2.0.7.dev2' From 7b8ba3bc4f5e139e3614373f8aeb2cdfad1934eb Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Wed, 9 Jul 2025 17:41:21 -0600 Subject: [PATCH 12/20] feat: restore release version --- mati/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/version.py b/mati/version.py index 6015237..4b259db 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7.dev2' +__version__ = '2.0.7' From 22f0f0177ae8a2f35132c245f95353d6a18100c1 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Thu, 10 Jul 2025 15:17:04 -0600 Subject: [PATCH 13/20] fix: fix issue with expired error --- mati/types/enums.py | 5 +++-- mati/version.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mati/types/enums.py b/mati/types/enums.py index db7a844..6096727 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -185,15 +185,16 @@ def ocr_number(self) -> str: return '' def add_expired_step(self) -> None: - step_id = f"{self.type}_verification" + step_id = f"{self.type}_document_expired" if not any(step.id == step_id for step in self.steps): self.steps.append( VerificationDocumentStep( id=step_id, status=200, error={ - 'verification': f'Document {type} expired', + 'type': 'StepError', 'code': 'document_expired', + 'message': f'Document {self.type} expired', }, ) ) diff --git a/mati/version.py b/mati/version.py index 4b259db..a43f88e 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7' +__version__ = '2.0.7.dev3' From 3f174fbfdd451ccff1f372c6bb2e2f0e9856a585 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Thu, 10 Jul 2025 15:18:48 -0600 Subject: [PATCH 14/20] feat: restore version --- mati/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/version.py b/mati/version.py index a43f88e..4b259db 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7.dev3' +__version__ = '2.0.7' From 669368770273133fae1b2b821d6e795de260d7c5 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Thu, 10 Jul 2025 15:52:59 -0600 Subject: [PATCH 15/20] feat: set error id and code according to errors from mati --- mati/types/enums.py | 6 +++--- mati/version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mati/types/enums.py b/mati/types/enums.py index 6096727..bc0b596 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -185,7 +185,7 @@ def ocr_number(self) -> str: return '' def add_expired_step(self) -> None: - step_id = f"{self.type}_document_expired" + step_id = f"{self.type}-document-expired" if not any(step.id == step_id for step in self.steps): self.steps.append( VerificationDocumentStep( @@ -193,8 +193,8 @@ def add_expired_step(self) -> None: status=200, error={ 'type': 'StepError', - 'code': 'document_expired', - 'message': f'Document {self.type} expired', + 'code': 'document.expired', + 'message': f'Document {self.document_type} expired', }, ) ) diff --git a/mati/version.py b/mati/version.py index 4b259db..551cac5 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7' +__version__ = '2.0.7.dev4' From dd5299314c3f0d9cb3312ef2d2211ac73f5368f1 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Thu, 10 Jul 2025 15:53:57 -0600 Subject: [PATCH 16/20] feat: restore to release version --- mati/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mati/version.py b/mati/version.py index 551cac5..4b259db 100644 --- a/mati/version.py +++ b/mati/version.py @@ -1 +1 @@ -__version__ = '2.0.7.dev4' +__version__ = '2.0.7' From bbac8921eb55cbd8ae35fdc7f72d5ebc0d086bfe Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Thu, 10 Jul 2025 16:17:28 -0600 Subject: [PATCH 17/20] fix: fix broken test --- tests/resources/test_verifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index 342211c..f2bf8f4 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -86,7 +86,7 @@ def test_retrieve_verification_invalid_govt(verification_with_govt_expired): verification = verification_with_govt_expired assert not verification.govt_id_validation.is_valid assert len(verification.govt_id_validation.error_codes) == 1 - assert 'document_expired' in verification.govt_id_validation.error_codes + assert 'document.expired' in verification.govt_id_validation.error_codes @pytest.mark.vcr From 05097998d8b0fc41de68e53000f334a09bcf5aa1 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Mon, 14 Jul 2025 14:24:57 -0600 Subject: [PATCH 18/20] feat: add comment doc for clarity --- mati/types/enums.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mati/types/enums.py b/mati/types/enums.py index bc0b596..0765796 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -185,6 +185,13 @@ def ocr_number(self) -> str: return '' def add_expired_step(self) -> None: + ''' + Appends an expired error step to the document if missing. + The steps list is used by the errors property. + This ensures document expiration is reflected + in the reported errors. + Required because Metamap does not add this error. + ''' step_id = f"{self.type}-document-expired" if not any(step.id == step_id for step in self.steps): self.steps.append( From 7f33c9326b004c4461ad5881d4ede73d8d8bce83 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Mon, 14 Jul 2025 14:26:28 -0600 Subject: [PATCH 19/20] fix: fix lint --- mati/types/enums.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mati/types/enums.py b/mati/types/enums.py index 0765796..8410988 100644 --- a/mati/types/enums.py +++ b/mati/types/enums.py @@ -186,10 +186,10 @@ def ocr_number(self) -> str: def add_expired_step(self) -> None: ''' - Appends an expired error step to the document if missing. - The steps list is used by the errors property. + Appends an expired error step to the document if missing. + The steps list is used by the errors property. This ensures document expiration is reflected - in the reported errors. + in the reported errors. Required because Metamap does not add this error. ''' step_id = f"{self.type}-document-expired" From 803141ee960fc772425a9c02366a10490c7035d6 Mon Sep 17 00:00:00 2001 From: Eduardo Garcia Date: Mon, 14 Jul 2025 14:29:15 -0600 Subject: [PATCH 20/20] fix: add missing validation to a test --- tests/resources/test_verifications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/resources/test_verifications.py b/tests/resources/test_verifications.py index f2bf8f4..64a3249 100644 --- a/tests/resources/test_verifications.py +++ b/tests/resources/test_verifications.py @@ -93,4 +93,5 @@ def test_retrieve_verification_invalid_govt(verification_with_govt_expired): def test_retrieve_verification_invalid_poa(verification_with_poa_expired): verification = verification_with_poa_expired assert not verification.proof_of_residency_validation.is_valid - assert verification.proof_of_residency_validation.error_codes + errors = verification.proof_of_residency_validation.error_codes + assert 'document.expired' in errors