Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #814: add timestamps to newsletters and waitlists in contact responses #816

Merged
merged 4 commits into from
Oct 3, 2023
Merged
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
12 changes: 9 additions & 3 deletions ctms/schemas/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
)
from .fxa import FirefoxAccountsInSchema, FirefoxAccountsSchema
from .mofo import MozillaFoundationInSchema, MozillaFoundationSchema
from .newsletter import NewsletterInSchema, NewsletterSchema, NewsletterTableSchema
from .newsletter import (
NewsletterInSchema,
NewsletterSchema,
NewsletterTableSchema,
NewsletterTimestampedSchema,
)
from .product import ProductBaseSchema, ProductSegmentEnum
from .waitlist import (
RelayWaitlistInSchema,
Expand All @@ -26,6 +31,7 @@
WaitlistInSchema,
WaitlistSchema,
WaitlistTableSchema,
WaitlistTimestampedSchema,
validate_waitlist_newsletters,
)

Expand Down Expand Up @@ -360,8 +366,8 @@ class CTMSResponse(BaseModel):
email: EmailSchema
fxa: FirefoxAccountsSchema
mofo: MozillaFoundationSchema
newsletters: List[NewsletterSchema]
waitlists: List[WaitlistSchema]
newsletters: List[NewsletterTimestampedSchema]
waitlists: List[WaitlistTimestampedSchema]
# Retro-compat fields
vpn_waitlist: VpnWaitlistSchema
relay_waitlist: RelayWaitlistSchema
Expand Down
13 changes: 8 additions & 5 deletions ctms/schemas/newsletter.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ class Config:
NewsletterSchema = NewsletterBase


class NewsletterTableSchema(NewsletterBase):
email_id: UUID4 = Field(
description=EMAIL_ID_DESCRIPTION,
example=EMAIL_ID_EXAMPLE,
)
class NewsletterTimestampedSchema(NewsletterBase):
create_timestamp: datetime = Field(
description="Newsletter data creation timestamp",
example="2020-12-05T19:21:50.908000+00:00",
Expand All @@ -64,5 +60,12 @@ class NewsletterTableSchema(NewsletterBase):
example="2021-02-04T15:36:57.511000+00:00",
)


class NewsletterTableSchema(NewsletterTimestampedSchema):
email_id: UUID4 = Field(
description=EMAIL_ID_DESCRIPTION,
example=EMAIL_ID_EXAMPLE,
)

class Config:
extra = "forbid"
13 changes: 8 additions & 5 deletions ctms/schemas/waitlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ class Config:
WaitlistInSchema = WaitlistBase


class WaitlistTableSchema(WaitlistBase):
email_id: UUID4 = Field(
description=EMAIL_ID_DESCRIPTION,
example=EMAIL_ID_EXAMPLE,
)
class WaitlistTimestampedSchema(WaitlistBase):
create_timestamp: datetime = Field(
description="Waitlist data creation timestamp",
example="2020-12-05T19:21:50.908000+00:00",
Expand All @@ -74,6 +70,13 @@ class WaitlistTableSchema(WaitlistBase):
example="2021-02-04T15:36:57.511000+00:00",
)


class WaitlistTableSchema(WaitlistTimestampedSchema):
email_id: UUID4 = Field(
description=EMAIL_ID_DESCRIPTION,
example=EMAIL_ID_EXAMPLE,
)

class Config:
extra = "forbid"

Expand Down
52 changes: 52 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Common test configuration both unit and integration tests.
"""

from datetime import datetime


class FuzzyAssert:
"""
This class is a testing helper that provides flexible equality
of values.

.. code-block::

>>> FuzzyAssert(lambda x: x.startswith("a")) == "abc"
True
>>> FuzzyAssert(lambda x: x % 2 == 0) == 11
False

It is mainly used to make sure fields contain valid dates
without having to hardcode values:

.. code-block::

>>> FuzzyAssert.iso8601() == "2020-01-01"
True
>>> FuzzyAssert.iso8601() == None
False
"""

def __init__(self, test=lambda x: True, name="unnamed"):
self.test = test
self.name = name

def __eq__(self, other):
return self.test(other)

def __repr__(self):
return f"<{self.__class__.__name__}.{self.name}>"

@classmethod
def iso8601(cls):
def is_iso8601_date(sdate):
if not isinstance(sdate, str):
return False
try:
datetime.fromisoformat(sdate)
return True
except ValueError:
return False

return cls(is_iso8601_date, name="datetime")
16 changes: 16 additions & 0 deletions tests/integration/test_basket_waitlist_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import requests
from pydantic import BaseSettings

from tests.conftest import FuzzyAssert

TEST_FOLDER = os.path.dirname(os.path.realpath(__file__))


Expand Down Expand Up @@ -137,6 +139,8 @@ def fetch_created():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
}
]
# Legacy (read-only) fields.
Expand Down Expand Up @@ -173,6 +177,8 @@ def fetch_created():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
}
]
# Legacy (read-only) fields.
Expand Down Expand Up @@ -231,6 +237,8 @@ def fetch_created():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
}
]
# Legacy (read-only) fields.
Expand Down Expand Up @@ -265,6 +273,8 @@ def check_subscribed():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
},
{
"name": "relay-vpn-bundle",
Expand All @@ -274,6 +284,8 @@ def check_subscribed():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
},
]
# Legacy (read-only) fields.
Expand Down Expand Up @@ -305,6 +317,8 @@ def check_unsubscribed():
"source": "https://relay.firefox.com/",
"subscribed": False,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
},
{
"name": "relay-vpn-bundle",
Expand All @@ -314,6 +328,8 @@ def check_unsubscribed():
},
"subscribed": True,
"unsub_reason": None,
"create_timestamp": FuzzyAssert.iso8601(),
"update_timestamp": FuzzyAssert.iso8601(),
},
]
# Legacy (read-only) fields.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/routers/contacts/test_api_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def test_get_ctms_for_minimal_contact(client, dbsession, email_factory):
"source": newsletter.source,
"subscribed": newsletter.subscribed,
"unsub_reason": newsletter.unsub_reason,
"create_timestamp": newsletter.create_timestamp.isoformat(),
"update_timestamp": newsletter.update_timestamp.isoformat(),
}
],
"status": "ok",
Expand Down Expand Up @@ -133,6 +135,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": "https://www.mozilla.org/en-US/contribute/studentambassadors/",
"subscribed": False,
"unsub_reason": "Graduated, don't have time for FSA",
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "T",
Expand All @@ -141,6 +145,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": "https://commonvoice.mozilla.org/fr",
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "H",
Expand All @@ -149,6 +155,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": "https://www.mozilla.org/fr/firefox/accounts/",
"subscribed": False,
"unsub_reason": "done with this mailing list",
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "H",
Expand All @@ -157,6 +165,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "H",
Expand All @@ -165,6 +175,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "H",
Expand All @@ -173,6 +185,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"format": "H",
Expand All @@ -181,6 +195,8 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
],
"status": "ok",
Expand All @@ -193,27 +209,35 @@ def test_get_ctms_for_maximal_contact(client, maximal_contact):
"source": "https://a-software.mozilla.org/",
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"fields": {"geo": "cn"},
"name": "relay",
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"fields": {"geo": "fr", "platform": "win64"},
"name": "super-product",
"source": "https://super-product.mozilla.org/",
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
{
"fields": {"geo": "ca", "platform": "windows,android"},
"name": "vpn",
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2010-01-01T08:04:00+00:00",
"update_timestamp": "2020-01-28T14:50:00+00:00",
},
],
}
Expand Down Expand Up @@ -277,6 +301,8 @@ def test_get_ctms_for_api_example(client, example_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2020-12-05T19:21:50.908000+00:00",
"update_timestamp": "2021-02-04T15:36:57.511000+00:00",
},
{
"format": "H",
Expand All @@ -285,6 +311,8 @@ def test_get_ctms_for_api_example(client, example_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2020-12-05T19:21:50.908000+00:00",
"update_timestamp": "2021-02-04T15:36:57.511000+00:00",
},
],
"status": "ok",
Expand All @@ -297,20 +325,26 @@ def test_get_ctms_for_api_example(client, example_contact):
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2020-12-05T19:21:50.908000+00:00",
"update_timestamp": "2021-02-04T15:36:57.511000+00:00",
},
{
"fields": {"geo": "fr"},
"name": "relay",
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2020-12-05T19:21:50.908000+00:00",
"update_timestamp": "2021-02-04T15:36:57.511000+00:00",
},
{
"fields": {"geo": "fr", "platform": "ios,mac"},
"name": "vpn",
"source": None,
"subscribed": True,
"unsub_reason": None,
"create_timestamp": "2020-12-05T19:21:50.908000+00:00",
"update_timestamp": "2021-02-04T15:36:57.511000+00:00",
},
],
}
Expand Down
Loading