-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New test framework in place, a few tests for the test framework as well.
- Loading branch information
Thomi Richards
committed
May 19, 2015
1 parent
815b568
commit c1730e3
Showing
6 changed files
with
255 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.egg-info/ | ||
build/ | ||
__pycache__ |
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,106 @@ | ||
|
||
"""Message Test Classes. | ||
This module contains all the tests we ship. These can be used in the rules | ||
file to select messages. | ||
""" | ||
|
||
|
||
__all__ = [ | ||
'Test', | ||
'And', | ||
'Or', | ||
] | ||
|
||
class Test(object): | ||
|
||
"""This class represents a single test on a message. | ||
The only contractual obligation is the 'match' method, which should | ||
return a truthy value when the test matches. | ||
""" | ||
|
||
def match(self, message): | ||
"""Check if this test matches a given message. | ||
If the message matches this test, this method must return True. | ||
If the message does not match this test, the method may return False, | ||
or an instance of the Mismatch object. The latter is used to signal | ||
that we know that a message needs to be re-checked at a certain point | ||
in the future. | ||
TODO: Implement Mismatch. | ||
""" | ||
|
||
|
||
class And(Test): | ||
|
||
"""An aggregate test that performs a boolean and operation over multiple | ||
other tests. | ||
Can be constructed with an arbitrary number of sub, tests, like so: | ||
>>> And(test1, test2) | ||
>>> And(test1, test2, test3) | ||
...and can even be constructed without any sub-tests. In this configuration | ||
it will *not* match. | ||
""" | ||
def __init__(self, *tests): | ||
self._tests = tests | ||
|
||
def match(self, message): | ||
if not self._tests: | ||
return False | ||
return all([t.match(message) for t in self._tests]) | ||
|
||
|
||
class Or(Test): | ||
|
||
"""An aggregate test that performs a boolean 'or' operation over multiple | ||
other tests. | ||
Similar to And, this test can be constructed with an arbitrary number of | ||
sub-tests. If it is constructed with no sub-tests, it will never match. | ||
""" | ||
|
||
def __init__(self, *tests): | ||
self._tests = tests | ||
|
||
def match(self, message): | ||
return any([t.match(message) for t in self._tests]) | ||
|
||
|
||
class MatchesHeader(Test): | ||
|
||
"""Check whether an email has a given header. | ||
Can be used to check the existance of the header, by passing in the header | ||
name:: | ||
>>> MatchesHeader('X-Launchpad-Message-Rationale') | ||
...or can be used to match the header value, by passing in both the name | ||
and value:: | ||
>>> MatchesHeader('X-Launchpad-Message-Rationale', 'subscriber') | ||
""" | ||
|
||
def __init__(self, expected_key, expected_value=None): | ||
self.expected_key = expected_key | ||
self.expected_value = expected_value | ||
|
||
def match(self, message): | ||
headers = message.get_headers() | ||
if self.expected_key in headers: | ||
if self.expected_value: | ||
return headers[self.expected_key] == self.expected_value | ||
else: | ||
return True | ||
return False |
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,28 @@ | ||
"""Test factory for gmailfilter tests.""" | ||
|
||
from gmailfilter._message import Message | ||
|
||
class TestFactoryMixin(object): | ||
|
||
"""A mixin class that generates test fake values.""" | ||
|
||
def get_email_message(self, headers=None): | ||
"""Get an email message. | ||
:param headers: If set, must be a dict or 2-tuple iteratble of key/value | ||
pairs that will be set as the email message header values. | ||
""" | ||
message = FakeMessage() | ||
if headers: | ||
message.headers = dict(headers) | ||
return message | ||
|
||
|
||
class FakeMessage(Message): | ||
|
||
def __init__(self): | ||
self.headers = {} | ||
|
||
def get_headers(self): | ||
return self.headers | ||
|
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,88 @@ | ||
from unittest import TestCase | ||
|
||
|
||
from gmailfilter.tests.factory import TestFactoryMixin | ||
from gmailfilter.test import ( | ||
Test, | ||
And, | ||
Or, | ||
MatchesHeader, | ||
) | ||
from gmailfilter._message import Message | ||
|
||
# Let's define some tests that will pass and fail regardless of their input: | ||
class AlwaysPassingTest(Test): | ||
|
||
def match(self, message): | ||
return True | ||
|
||
|
||
class AlwaysFailingTest(Test): | ||
|
||
def match(self, message): | ||
return False | ||
|
||
|
||
class TestBooleanTests(TestCase, TestFactoryMixin): | ||
|
||
def test_and_can_be_Created_without_tests(self): | ||
And() | ||
|
||
def test_and_yields_false_with_no_tests(self): | ||
self.assertFalse(And().match(self.get_email_message())) | ||
|
||
def test_and_boolean_table(self): | ||
# boolean truth table - expected result is always first, operands | ||
# are after. | ||
table = [ | ||
(True, AlwaysPassingTest()), | ||
(True, AlwaysPassingTest(), AlwaysPassingTest()), | ||
(False, AlwaysFailingTest(), AlwaysPassingTest()), | ||
(False, AlwaysFailingTest()), | ||
] | ||
for expected_value, *operands in table: | ||
self.assertEqual( | ||
expected_value, | ||
And(*operands).match(self.get_email_message()) | ||
) | ||
|
||
def test_or_can_be_Created_without_tests(self): | ||
Or() | ||
|
||
def test_or_yields_false_with_no_tests(self): | ||
self.assertFalse(Or().match(self.get_email_message())) | ||
|
||
def test_or_boolean_table(self): | ||
# boolean truth table - expected result is always first, operands | ||
# are after. | ||
table = [ | ||
(True, AlwaysPassingTest()), | ||
(True, AlwaysPassingTest(), AlwaysPassingTest()), | ||
(True, AlwaysFailingTest(), AlwaysPassingTest()), | ||
(False, AlwaysFailingTest()), | ||
] | ||
for expected_value, *operands in table: | ||
self.assertEqual( | ||
expected_value, | ||
Or(*operands).match(self.get_email_message()) | ||
) | ||
|
||
|
||
class TestMatchesHeaderTests(TestCase, TestFactoryMixin): | ||
|
||
def test_fails_when_header_is_missing(self): | ||
self.assertFalse( | ||
MatchesHeader('SomeHeader').match(self.get_email_message()) | ||
) | ||
|
||
def test_passes_when_header_is_present(self): | ||
message = self.get_email_message(headers=dict(SomeHeader='123')) | ||
self.assertTrue(MatchesHeader('SomeHeader').match(message)) | ||
|
||
def test_fails_when_header_value_is_wrong(self): | ||
message = self.get_email_message(headers=dict(SomeHeader='123')) | ||
self.assertFalse(MatchesHeader('SomeHeader', 'foo').match(message)) | ||
|
||
def test_passes_with_correct_value(self): | ||
message = self.get_email_message(headers=dict(SomeHeader='123')) | ||
self.assertTrue(MatchesHeader('SomeHeader', '123').match(message)) |