Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cuenca/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'LoginToken',
'Otp',
'OperatorLogin',
'OperatorOtpChallenge',
'PasswordReset',
'Platform',
'Questionnaires',
Expand Down Expand Up @@ -82,6 +83,7 @@
LimitedWallet,
LoginToken,
OperatorLogin,
OperatorOtpChallenge,
Otp,
PasswordReset,
PhoneVerificationAssociations,
Expand Down
3 changes: 3 additions & 0 deletions cuenca/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'LoginToken',
'Otp',
'OperatorLogin',
'OperatorOtpChallenge',
'PasswordReset',
'Platform',
'PhoneVerificationAssociation',
Expand Down Expand Up @@ -76,6 +77,7 @@
from .limited_wallets import LimitedWallet
from .login_tokens import LoginToken
from .operator_logins import OperatorLogin
from .operator_otp_challenges import OperatorOtpChallenge
from .otps import Otp
from .password_resets import PasswordReset
from .phone_verification_associations import PhoneVerificationAssociations
Expand Down Expand Up @@ -127,6 +129,7 @@
LimitedWallet,
LoginToken,
OperatorLogin,
OperatorOtpChallenge,
PasswordReset,
Questionnaires,
Saving,
Expand Down
34 changes: 34 additions & 0 deletions cuenca/resources/operator_otp_challenges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import datetime as dt
from typing import ClassVar

from pydantic import ConfigDict

from ..http import Session, session as global_session
from .base import Creatable


class OperatorOtpChallenge(Creatable):
"""One-shot email OTP challenge for an operator session."""

_resource: ClassVar = 'operator-otp-challenges'

expires_at: dt.datetime
email_hint: str

model_config = ConfigDict(
json_schema_extra={
'example': {
'id': 'OCWqY5cvkISJOxHyEKjAKf8w',
'expires_at': '2026-09-18T22:10:00+00:00',
'email_hint': 'l***@example.com',
}
}
)

@classmethod
def create(
cls,
*,
session: Session = global_session,
) -> 'OperatorOtpChallenge':
return cls._create(session=session)
48 changes: 48 additions & 0 deletions tests/resources/test_operator_otp_challenges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import datetime as dt
from unittest.mock import MagicMock, patch

import pytest

from cuenca import OperatorOtpChallenge
from cuenca.http.client import Session


@pytest.fixture
def session() -> Session:
configured_session = Session()
configured_session.configure('api_key', 'api_secret', sandbox=True)
return configured_session


@patch('cuenca.http.client.requests.Session.request')
def test_operator_otp_challenge_create(
mock_request: MagicMock,
session: Session,
):
mock_request.return_value.ok = True
mock_request.return_value.content = (
b'{"id":"OCWqY5cvkISJOxHyEKjAKf8w",'
b'"expires_at":"2026-09-18T22:10:00+00:00",'
b'"email_hint":"l***@example.com"}'
)

challenge = OperatorOtpChallenge.create(session=session)

assert challenge.id == 'OCWqY5cvkISJOxHyEKjAKf8w'
assert challenge.expires_at == dt.datetime(
2026,
9,
18,
22,
10,
tzinfo=dt.timezone.utc,
)
assert challenge.email_hint == 'l***@example.com'

mock_request.assert_called_once()
_, kwargs = mock_request.call_args
assert kwargs['method'] == 'post'
assert kwargs['url'] == (
'https://sandbox.cuenca.com/operator-otp-challenges'
)
assert kwargs['json'] == {}
Loading