Skip to content

Commit

Permalink
Merge pull request #73 from smkent/fastmail-maskedemail
Browse files Browse the repository at this point in the history
Add support for Fastmail's MaskedEmail extension
  • Loading branch information
smkent authored Nov 4, 2022
2 parents eb2a9ee + bbb58e3 commit 582fb10
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Currently implemented:
* `Mailbox/*` (`get`, `changes`, `query`, `queryChanges`, `set`)
* `Thread/*` (`get`, `changes`)
* Arbitrary methods via the `CustomMethod` class
* Fastmail-specific methods:
* [`MaskedEmail/*` (`get`, `set`)][fastmail-maskedemail]
* Combined requests with support for result references
* Basic JMAP method response error handling
* EventSource event handling
Expand Down Expand Up @@ -70,6 +72,7 @@ Created from [smkent/cookie-python][cookie-python] using
[codecov]: https://codecov.io/gh/smkent/jmapc
[cookie-python]: https://github.com/smkent/cookie-python
[cookiecutter]: https://github.com/cookiecutter/cookiecutter
[fastmail-maskedemail]: https://www.fastmail.com/developer/maskedemail/
[gh-actions]: https://github.com/smkent/jmapc/actions?query=branch%3Amain
[logo]: https://raw.github.com/smkent/jmapc/main/img/jmapc.png
[jmapc-pypi]: https://pypi.org/project/jmapc/
Expand Down
3 changes: 2 additions & 1 deletion jmapc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from . import auth, errors, methods, models
from . import auth, errors, fastmail, methods, models
from .__version__ import __version__ as version
from .client import Client, EventSourceConfig
from .errors import Error
Expand Down Expand Up @@ -82,6 +82,7 @@
"TypeState",
"UndoStatus",
"auth",
"fastmail",
"errors",
"log",
"methods",
Expand Down
15 changes: 15 additions & 0 deletions jmapc/fastmail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .maskedemail_methods import (
MaskedEmailGet,
MaskedEmailGetResponse,
MaskedEmailSet,
MaskedEmailSetResponse,
)
from .maskedemail_models import MaskedEmail

__all__ = [
"MaskedEmail",
"MaskedEmailGet",
"MaskedEmailGetResponse",
"MaskedEmailSet",
"MaskedEmailSetResponse",
]
37 changes: 37 additions & 0 deletions jmapc/fastmail/maskedemail_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, List, Optional

from dataclasses_json import config

from ..methods.base import Get, GetResponse, Set, SetResponse
from .maskedemail_models import MaskedEmail

URN = "https://www.fastmail.com/dev/maskedemail"


class MaskedEmailBase:
method_namespace: Optional[str] = "MaskedEmail"
using = {URN}


@dataclass
class MaskedEmailGet(MaskedEmailBase, Get):
pass


@dataclass
class MaskedEmailGetResponse(MaskedEmailBase, GetResponse):
data: List[MaskedEmail] = field(metadata=config(field_name="list"))


@dataclass
class MaskedEmailSet(MaskedEmailBase, Set):
pass


@dataclass
class MaskedEmailSetResponse(MaskedEmailBase, SetResponse):
created: Optional[Dict[str, Optional[MaskedEmail]]]
updated: Optional[Dict[str, Optional[MaskedEmail]]]
36 changes: 36 additions & 0 deletions jmapc/fastmail/maskedemail_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional

from dataclasses_json import config

from ..serializer import Model, datetime_decode, datetime_encode


class MaskedEmailState(Enum):
PENDING = "pending"
ENABLED = "enabled"
DISABLED = "disabled"
DELETED = "deleted"


@dataclass
class MaskedEmail(Model):
id: Optional[str] = None
email: Optional[str] = None
for_domain: Optional[str] = None
description: Optional[str] = None
last_message_at: Optional[datetime] = field(
default=None,
metadata=config(encoder=datetime_encode, decoder=datetime_decode),
)
created_at: Optional[datetime] = field(
default=None,
metadata=config(encoder=datetime_encode, decoder=datetime_decode),
)
created_by: Optional[str] = None
url: Optional[str] = None
email_prefix: Optional[str] = None
189 changes: 189 additions & 0 deletions tests/methods/test_fastmail_maskedemail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from datetime import datetime, timezone

import responses

from jmapc import Client
from jmapc.fastmail import (
MaskedEmail,
MaskedEmailGet,
MaskedEmailGetResponse,
MaskedEmailSet,
MaskedEmailSetResponse,
)

from ..utils import expect_jmap_call


def test_maskedemail_get(
client: Client, http_responses: responses.RequestsMock
) -> None:
expected_request = {
"methodCalls": [
[
"MaskedEmail/get",
{"accountId": "u1138", "ids": ["masked-1138"]},
"single.MaskedEmail/get",
]
],
"using": [
"https://www.fastmail.com/dev/maskedemail",
"urn:ietf:params:jmap:core",
],
}
response = {
"methodResponses": [
[
"MaskedEmail/get",
{
"accountId": "u1138",
"list": [
{
"id": "masked-1138",
"email": "pk.fire@ness.example.com",
"forDomain": "ness.example.com",
"description": (
"Masked Email (pk.fire@ness.example.com)"
),
"lastMessageAt": "1994-08-24T12:01:02Z",
"createdAt": "1994-08-24T12:01:02Z",
"createdBy": "ness",
"url": None,
},
],
"not_found": [],
"state": "2187",
},
"single.MaskedEmail/get",
]
]
}
expect_jmap_call(http_responses, expected_request, response)
jmap_response = client.request(MaskedEmailGet(ids=["masked-1138"]))
assert jmap_response == MaskedEmailGetResponse(
account_id="u1138",
state="2187",
not_found=[],
data=[
MaskedEmail(
id="masked-1138",
email="pk.fire@ness.example.com",
for_domain="ness.example.com",
description="Masked Email (pk.fire@ness.example.com)",
last_message_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_by="ness",
),
],
)


def test_maskedemail_set(
client: Client, http_responses: responses.RequestsMock
) -> None:
expected_request = {
"methodCalls": [
[
"MaskedEmail/set",
{
"accountId": "u1138",
"create": {
"create": {
"email": "pk.fire@ness.example.com",
"forDomain": "ness.example.com",
"description": (
"Masked Email (pk.fire@ness.example.com)"
),
"lastMessageAt": "1994-08-24T12:01:02Z",
"createdAt": "1994-08-24T12:01:02Z",
"createdBy": "ness",
},
},
},
"single.MaskedEmail/set",
]
],
"using": [
"https://www.fastmail.com/dev/maskedemail",
"urn:ietf:params:jmap:core",
],
}
response = {
"methodResponses": [
[
"MaskedEmail/set",
{
"accountId": "u1138",
"created": {
"create": {
"id": "masked-42",
"email": "pk.fire@ness.example.com",
"forDomain": "ness.example.com",
"description": (
"Masked Email (pk.fire@ness.example.com)"
),
"lastMessageAt": "1994-08-24T12:01:02Z",
"createdAt": "1994-08-24T12:01:02Z",
"createdBy": "ness",
}
},
"destroyed": None,
"newState": "2",
"notCreated": None,
"notDestroyed": None,
"notUpdated": None,
"oldState": "1",
"updated": None,
},
"single.MaskedEmail/set",
]
]
}
expect_jmap_call(http_responses, expected_request, response)

assert client.request(
MaskedEmailSet(
create=dict(
create=MaskedEmail(
id=None,
email="pk.fire@ness.example.com",
for_domain="ness.example.com",
description="Masked Email (pk.fire@ness.example.com)",
last_message_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_by="ness",
)
)
)
) == MaskedEmailSetResponse(
account_id="u1138",
old_state="1",
new_state="2",
created=dict(
create=MaskedEmail(
id="masked-42",
email="pk.fire@ness.example.com",
for_domain="ness.example.com",
description="Masked Email (pk.fire@ness.example.com)",
last_message_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_at=datetime(
1994, 8, 24, 12, 1, 2, tzinfo=timezone.utc
),
created_by="ness",
),
),
updated=None,
destroyed=None,
not_created=None,
not_updated=None,
not_destroyed=None,
)

0 comments on commit 582fb10

Please sign in to comment.