Skip to content

Commit 4481946

Browse files
Verificar la propiedad de "computed" para asegurar rechazar documentos expirados. (#122)
* fix: validate computed field to ensure reject expired documents * fix: restore client for conftest * bump: bump to version 2.0.7 * fix: run make format * fix: fixes from coderabbit revision * fix: add type annotation for return value * fix: validate error in test * fix: set correct status for document expired step * feat: add expired step to retrieve errors correctly * fix: fix lint * feat: change version to generate pre-release * feat: restore release version * fix: fix issue with expired error * feat: restore version * feat: set error id and code according to errors from mati * feat: restore to release version * fix: fix broken test * feat: add comment doc for clarity * fix: fix lint * fix: add missing validation to a test
1 parent 6f3c1fc commit 4481946

8 files changed

Lines changed: 416 additions & 23 deletions

File tree

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ jobs:
88
publish-pypi:
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@master
11+
- uses: actions/checkout@v4
1212
- name: Set up Python 3.8
13-
uses: actions/setup-python@v4.7.0
13+
uses: actions/setup-python@v5
1414
with:
1515
python-version: 3.8
1616
- name: Install dependencies

mati/resources/verifications.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,7 @@ def govt_id_document(self) -> Optional[VerificationDocument]:
111111

112112
@property
113113
def proof_of_residency_validation(self) -> Optional[DocumentScore]:
114-
por = self.proof_of_residency_document
115-
if not por:
116-
return None
117-
return DocumentScore(
118-
all([step.status == 200 and not step.error for step in por.steps])
119-
and not (
120-
self.computed
121-
and self.computed['is_document_expired']['data'][
122-
'proof_of_residency'
123-
]
124-
),
125-
sum([step.status for step in por.steps if not step.error]),
126-
[step.error['code'] for step in por.steps if step.error],
127-
)
114+
return self.get_document_validation(self.proof_of_residency_document)
128115

129116
@property
130117
def proof_of_life_validation(self) -> Optional[DocumentScore]:
@@ -139,13 +126,28 @@ def proof_of_life_validation(self) -> Optional[DocumentScore]:
139126

140127
@property
141128
def govt_id_validation(self) -> Optional[DocumentScore]:
142-
govt = self.govt_id_document
143-
if not govt:
129+
return self.get_document_validation(self.govt_id_document)
130+
131+
def get_document_validation(
132+
self, document: Optional[VerificationDocument]
133+
) -> Optional[DocumentScore]:
134+
if not document:
144135
return None
136+
document_type = document.type.replace("-", "_")
137+
is_expired = (
138+
self.computed['is_document_expired']['data'][document_type]
139+
if self.computed
140+
else False
141+
)
142+
if is_expired:
143+
document.add_expired_step()
145144
return DocumentScore(
146145
all(
147-
[step.status == 200 and not step.error for step in govt.steps]
146+
[
147+
step.status == 200 and not step.error
148+
for step in document.steps
149+
]
148150
),
149-
sum([step.status for step in govt.steps if not step.error]),
150-
[step.error['code'] for step in govt.steps if step.error],
151+
sum([step.status for step in document.steps if not step.error]),
152+
[step.error['code'] for step in document.steps if step.error],
151153
)

mati/types/enums.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,28 @@ def ocr_number(self) -> str:
184184
return self.fields['ocr_number']['value']
185185
return ''
186186

187+
def add_expired_step(self) -> None:
188+
'''
189+
Appends an expired error step to the document if missing.
190+
The steps list is used by the errors property.
191+
This ensures document expiration is reflected
192+
in the reported errors.
193+
Required because Metamap does not add this error.
194+
'''
195+
step_id = f"{self.type}-document-expired"
196+
if not any(step.id == step_id for step in self.steps):
197+
self.steps.append(
198+
VerificationDocumentStep(
199+
id=step_id,
200+
status=200,
201+
error={
202+
'type': 'StepError',
203+
'code': 'document.expired',
204+
'message': f'Document {self.document_type} expired',
205+
},
206+
)
207+
)
208+
187209

188210
@dataclass
189211
class LivenessMedia:

mati/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.6'
1+
__version__ = '2.0.7'

tests/conftest.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
from mati import Client
8+
from mati.resources.verifications import Verification
89
from mati.types import VerificationDocument, VerificationDocumentStep
910

1011
VERIFICATION_RESP = {
@@ -222,12 +223,34 @@ def verification(client: Client) -> Generator:
222223

223224

224225
@pytest.fixture
225-
def verification_without_pol(client: Client):
226+
def verification_without_pol(
227+
client: Client,
228+
) -> Generator[Verification, None, None]:
226229
verification = client.verifications.retrieve('634870763768f1001cac7591')
227230
verification.steps = []
228231
yield verification
229232

230233

234+
@pytest.fixture
235+
def verification_with_govt_expired(
236+
client: Client,
237+
) -> Generator[Verification, None, None]:
238+
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
239+
verification.computed["is_document_expired"]["data"]["national_id"] = True
240+
yield verification
241+
242+
243+
@pytest.fixture
244+
def verification_with_poa_expired(
245+
client: Client,
246+
) -> Generator[Verification, None, None]:
247+
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
248+
verification.computed["is_document_expired"]["data"][
249+
"proof_of_residency"
250+
] = True
251+
yield verification
252+
253+
231254
@pytest.fixture
232255
def verification_document_national_id() -> VerificationDocument:
233256
return VerificationDocument(
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
interactions:
2+
- request:
3+
body: grant_type=client_credentials
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '29'
13+
Content-Type:
14+
- application/x-www-form-urlencoded
15+
User-Agent:
16+
- mati-python/2.0.7.dev1
17+
method: POST
18+
uri: https://api.getmati.com/oauth
19+
response:
20+
body:
21+
string: '{"access_token": "ACCESS_TOKEN", "expiresIn": 3600, "payload": {"user":
22+
{"_id": "ID", "firstName": "FIRST_NAME", "lastName": "LAST_NAME"}}}'
23+
headers:
24+
Connection:
25+
- keep-alive
26+
Content-Type:
27+
- application/json; charset=utf-8
28+
Date:
29+
- Tue, 08 Jul 2025 23:18:57 GMT
30+
Transfer-Encoding:
31+
- chunked
32+
cache-control:
33+
- no-store, no-cache, must-revalidate, proxy-revalidate
34+
content-security-policy:
35+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
36+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
37+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
38+
expect-ct:
39+
- max-age=0
40+
expires:
41+
- '0'
42+
pragma:
43+
- no-cache
44+
referrer-policy:
45+
- no-referrer
46+
strict-transport-security:
47+
- max-age=15552000; includeSubDomains; preload
48+
surrogate-control:
49+
- no-store
50+
x-content-type-options:
51+
- nosniff
52+
x-dns-prefetch-control:
53+
- 'off'
54+
x-download-options:
55+
- noopen
56+
x-envoy-upstream-service-time:
57+
- '141'
58+
x-frame-options:
59+
- DENY
60+
x-permitted-cross-domain-policies:
61+
- none
62+
x-request-id:
63+
- add8dd4e-74a3-414d-83aa-f8e1dd998672
64+
x-xss-protection:
65+
- '1'
66+
status:
67+
code: 200
68+
message: OK
69+
- request:
70+
body: null
71+
headers:
72+
Accept:
73+
- '*/*'
74+
Accept-Encoding:
75+
- gzip, deflate
76+
Connection:
77+
- keep-alive
78+
User-Agent:
79+
- mati-python/2.0.7.dev1
80+
method: GET
81+
uri: https://api.getmati.com/v2/verifications/686c77811ee936aece7016ac
82+
response:
83+
body:
84+
string: '{"expired": false, "identity": {"status": "verified"}, "flow": {"id":
85+
"some_flow", "name": "Default flow"}, "documents": [{"country": "MX", "region":
86+
"", "type": "national-id", "photos": ["https://media.getmati.com/media/xxx",
87+
"https://media.getmati.com/media/yyy"], "steps": [{"error": null, "status":
88+
200, "id": "template-matching"}, {"error": null, "status": 200, "id": "mexican-curp-validation",
89+
"data": {"curp": "CURP", "fullName": "LAST FIRST", "birthDate": "01/01/1980",
90+
"gender": "HOMBRE", "nationality": "MEXICO", "surname": "LAST", "secondSurname":
91+
"", "name": "FIRST"}}, {"error": null, "status": 200, "id": "document-reading",
92+
"data": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
93+
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
94+
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
95+
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
96+
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
97+
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
98+
"label": "Emission Date", "format": "date"}}}, {"error": null, "status": 200,
99+
"id": "alteration-detection"}, {"error": null, "status": 200, "id": "watchlists"}],
100+
"fields": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
101+
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
102+
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
103+
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
104+
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
105+
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
106+
"label": "Emission Date", "format": "date"}}}, {"country": "MX", "region":
107+
null, "type": "proof-of-residency", "steps": [{"status": 200, "id": "document-reading",
108+
"data": {"fullName": {"required": true, "label": "Name", "value": "FIRST NAME"},
109+
"address": {"label": "Address", "value": "Varsovia 36, 06600 CDMX"}, "emissionDate":
110+
{"format": "date", "label": "Emission Date", "value": "1880-01-01"}}, "error":
111+
null}, {"status": 200, "id": "watchlists", "error": null}], "fields": {"address":
112+
{"value": "Varsovia 36, 06600 CDMX"}, "emissionDate": {"value": "1880-01-01"},
113+
"fullName": {"value": "FIRST LASTNAME"}}, "photos": ["https://media.getmati.com/file?location=xyc"]}],
114+
"steps": [{"status": 200, "id": "liveness", "data": {"videoUrl": "https://media.getmati.com/file?location=abc",
115+
"spriteUrl": "https://media.getmati.com/file?location=def", "selfieUrl": "https://media.getmati.com/file?location=hij"},
116+
"error": null}], "hasProblem": false, "computed": {"age": {"data": 100}, "isDocumentExpired":
117+
{"data": {"national-id": false, "proof-of-residency": false}}}, "id": "5d9fb1f5bfbfac001a349bfb",
118+
"metadata": {"name": "First Last", "dob": "1980-01-01"}}'
119+
headers:
120+
Connection:
121+
- keep-alive
122+
Content-Length:
123+
- '7918'
124+
Content-Type:
125+
- application/json; charset=utf-8
126+
Date:
127+
- Tue, 08 Jul 2025 23:18:57 GMT
128+
cache-control:
129+
- no-store, no-cache, must-revalidate, proxy-revalidate
130+
content-security-policy:
131+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
132+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
133+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
134+
expect-ct:
135+
- max-age=0
136+
expires:
137+
- '0'
138+
pragma:
139+
- no-cache
140+
referrer-policy:
141+
- no-referrer
142+
strict-transport-security:
143+
- max-age=15552000; includeSubDomains; preload
144+
surrogate-control:
145+
- no-store
146+
x-content-type-options:
147+
- nosniff
148+
x-dns-prefetch-control:
149+
- 'off'
150+
x-download-options:
151+
- noopen
152+
x-envoy-upstream-service-time:
153+
- '258'
154+
x-frame-options:
155+
- DENY
156+
x-permitted-cross-domain-policies:
157+
- none
158+
x-request-id:
159+
- 98bc4ee3-a97d-442f-bfbc-5fdd49f302a7
160+
x-xss-protection:
161+
- '1'
162+
status:
163+
code: 200
164+
message: OK
165+
version: 1

0 commit comments

Comments
 (0)