-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tests for personal and work email addresses (#2110)
This commit adds backend support for testing if an account has work and personal email addresses associated with it. UI changes are not included.
- Loading branch information
Showing
8 changed files
with
265 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""Cache wrapper.""" | ||
|
||
from collections.abc import Iterator, MutableMapping | ||
from typing import Any | ||
|
||
from cachelib import BaseCache | ||
from flask_caching import Cache as CacheExtension | ||
|
||
__all__ = ['DictCache'] | ||
|
||
_marker = object() | ||
|
||
|
||
class DictCache(MutableMapping): | ||
""" | ||
Provide a dict-like interface to a Cachelib cache. | ||
This object has three significant differences from regular dicts: | ||
1. Since a cache can't be enumerated, this object will behave like an empty dict. | ||
2. However, this object is always truthy despite appearing to be empty. | ||
3. `None` is a special value indicating a cache miss and can't be used as a value. | ||
:param cache: Flask-Caching cache backend to wrap | ||
:param prefix: Prefix string to apply to all keys | ||
:param timeout: Timeout when setting a value | ||
""" | ||
|
||
def __init__( | ||
self, | ||
cache: CacheExtension | BaseCache, | ||
prefix: str = '', | ||
timeout: int | None = None, | ||
) -> None: | ||
self.cache = cache | ||
self.prefix = prefix | ||
self.timeout = timeout | ||
|
||
def __getitem__(self, key: str) -> Any: | ||
result = self.cache.get(self.prefix + key) | ||
if result is not None: | ||
return result | ||
raise KeyError(key) | ||
|
||
def __setitem__(self, key: str, value: Any) -> None: | ||
success = self.cache.set(self.prefix + key, value, timeout=self.timeout) | ||
if not success: | ||
raise KeyError(key) | ||
|
||
def __delitem__(self, key: str) -> None: | ||
success = self.cache.delete(self.prefix + key) | ||
if not success: | ||
raise KeyError(key) | ||
|
||
def __contains__(self, key: Any) -> bool: | ||
return self.cache.has(key) | ||
|
||
# Dummy implementations for compatibility with MutableMapping: | ||
|
||
def __iter__(self) -> Iterator: | ||
"""Return an empty iterable since the cache's contents can't be enumerated.""" | ||
return iter(()) | ||
|
||
def __len__(self) -> int: | ||
"""Return 0 since the cache's size is not queryable.""" | ||
return 0 | ||
|
||
def __bool__(self) -> bool: | ||
"""Return True since the cache can't be iterated and is assumed non-empty.""" | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
"""Tests for cache wrapper.""" | ||
# pylint: disable=redefined-outer-name,pointless-statement | ||
|
||
import pytest | ||
from cachelib import SimpleCache | ||
|
||
from funnel.utils import DictCache | ||
|
||
|
||
@pytest.fixture | ||
def cache() -> SimpleCache: | ||
return SimpleCache() | ||
|
||
|
||
@pytest.fixture | ||
def dict_cache(cache) -> DictCache: | ||
return DictCache(cache) | ||
|
||
|
||
def test_cache_interface(cache: SimpleCache, dict_cache: DictCache) -> None: | ||
"""Test dict interface to cachelib cache.""" | ||
assert not cache.has('test-key1') | ||
assert 'test-key1' not in dict_cache | ||
cache.set('test-key1', 'value1') | ||
assert cache.has('test-key1') | ||
assert 'test-key1' in dict_cache | ||
assert dict_cache['test-key1'] == 'value1' | ||
|
||
assert not cache.has('test-key2') | ||
assert 'test-key2' not in dict_cache | ||
dict_cache['test-key2'] = 'value2' | ||
assert 'test-key2' in dict_cache | ||
assert cache.has('test-key2') | ||
assert dict_cache['test-key2'] == 'value2' | ||
|
||
del dict_cache['test-key2'] | ||
assert 'test_key2' not in dict_cache | ||
assert not cache.has('test-key2') | ||
|
||
with pytest.raises(KeyError): | ||
del dict_cache['test-key2'] | ||
|
||
with pytest.raises(KeyError): | ||
dict_cache['test-key2'] | ||
|
||
# Cache is not enumerable | ||
assert len(dict_cache) == 0 | ||
assert not list(dict_cache) | ||
assert bool(dict_cache) is True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,9 @@ def test_username_available( | |
} | ||
|
||
|
||
# MARK: User agent details | ||
|
||
|
||
@pytest.mark.parametrize( | ||
('user_agent', 'client_hints', 'output'), | ||
[ | ||
|
@@ -327,3 +330,68 @@ def test_user_agent_details( | |
) | ||
== output | ||
) | ||
|
||
|
||
# MARK: Work and personal email | ||
|
||
|
||
def test_has_work_email_empty(user_twoflower: models.User) -> None: | ||
"""Test for work email addresses on an account (when user has no emails).""" | ||
assert user_twoflower.features.has_work_email is False | ||
|
||
|
||
def test_has_personal_email_empty(user_twoflower: models.User) -> None: | ||
"""Test for personal email addresses on an account (when user has no emails).""" | ||
assert user_twoflower.features.has_personal_email is False | ||
|
||
|
||
def test_may_need_email_empty(user_twoflower: models.User) -> None: | ||
"""Test if user needs to add any email addresses (when having none).""" | ||
assert user_twoflower.features.may_need_to_add_email is True | ||
|
||
|
||
@pytest.fixture | ||
def work_email(user_twoflower: models.User) -> models.AccountEmail: | ||
"""Provide a (test) work email for the user.""" | ||
return user_twoflower.add_email('[email protected]') | ||
|
||
|
||
@pytest.fixture | ||
def personal_email(user_twoflower: models.User) -> models.AccountEmail: | ||
"""Provide a (test) personal email for the user.""" | ||
return user_twoflower.add_email('[email protected]') | ||
|
||
|
||
@pytest.mark.usefixtures('personal_email') | ||
@pytest.mark.enable_socket | ||
def test_has_work_email_no(user_twoflower) -> None: | ||
"""Confirm user is missing a work email.""" | ||
assert user_twoflower.features.has_work_email is False | ||
|
||
|
||
@pytest.mark.usefixtures('work_email') | ||
@pytest.mark.enable_socket | ||
def test_has_work_email_yes(user_twoflower: models.User) -> None: | ||
"""Confirm user has a work email.""" | ||
assert user_twoflower.features.has_work_email is True | ||
|
||
|
||
@pytest.mark.usefixtures('personal_email') | ||
@pytest.mark.enable_socket | ||
def test_has_personal_not_work_email(user_twoflower) -> None: | ||
"""Confirm user has both work and personal email addresses.""" | ||
assert user_twoflower.features.may_need_to_add_email is True | ||
|
||
|
||
@pytest.mark.usefixtures('work_email') | ||
@pytest.mark.enable_socket | ||
def test_has_work_not_personal_email(user_twoflower) -> None: | ||
"""Confirm user has both work and personal email addresses.""" | ||
assert user_twoflower.features.may_need_to_add_email is True | ||
|
||
|
||
@pytest.mark.usefixtures('work_email', 'personal_email') | ||
@pytest.mark.enable_socket | ||
def test_has_both_work_and_personal_email(user_twoflower) -> None: | ||
"""Confirm user has both work and personal email addresses.""" | ||
assert user_twoflower.features.may_need_to_add_email is False |