Initial commit.
This commit is contained in:
18
mail/spamassassin/pyzor-0.7.0/tests/__init__.py
Normal file
18
mail/spamassassin/pyzor-0.7.0/tests/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Package reserved for tests and test utilities."""
|
||||
|
||||
import unittest
|
||||
|
||||
import unit
|
||||
import functional
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this package in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
|
||||
test_suite.addTest(unit.suite())
|
||||
test_suite.addTest(functional.suite())
|
||||
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
33
mail/spamassassin/pyzor-0.7.0/tests/functional/__init__.py
Normal file
33
mail/spamassassin/pyzor-0.7.0/tests/functional/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""A suite of functional tests that verifies the correct behaviour of the
|
||||
pyzor client and server as a whole.
|
||||
|
||||
Functional test should not touch real data and are usually safe, but it's not
|
||||
recommended to run theses on production servers.
|
||||
|
||||
Note these tests the installed version of pyzor, not the version from the
|
||||
source.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import test_gdbm
|
||||
import test_pyzor
|
||||
import test_mysql
|
||||
import test_redis
|
||||
import test_digest
|
||||
import test_account
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this package in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
|
||||
test_suite.addTest(test_gdbm.suite())
|
||||
test_suite.addTest(test_mysql.suite())
|
||||
test_suite.addTest(test_redis.suite())
|
||||
test_suite.addTest(test_pyzor.suite())
|
||||
test_suite.addTest(test_digest.suite())
|
||||
test_suite.addTest(test_account.suite())
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
138
mail/spamassassin/pyzor-0.7.0/tests/functional/test_account.py
Normal file
138
mail/spamassassin/pyzor-0.7.0/tests/functional/test_account.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import unittest
|
||||
|
||||
from tests.util import *
|
||||
|
||||
class AccountPyzorTest(PyzorTestBase):
|
||||
|
||||
# test bob which has access to everything
|
||||
def test_ping(self):
|
||||
self.check_pyzor("ping", "bob", code=200, exit_code=0)
|
||||
|
||||
def test_pong(self):
|
||||
self.check_pyzor("pong", "bob", input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_check(self):
|
||||
self.check_pyzor("check", "bob", input=msg, code=200)
|
||||
|
||||
def test_report(self):
|
||||
self.check_pyzor("report", "bob", input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_whitelist(self):
|
||||
self.check_pyzor("whitelist", "bob", input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_info(self):
|
||||
self.check_pyzor("info", "bob", input=msg, code=200, exit_code=0)
|
||||
|
||||
# test alice which does not has access to anything
|
||||
# Error should be 403 Forbidden
|
||||
def test_ping_forbidden(self):
|
||||
self.check_pyzor("ping", "alice", code=403, exit_code=1)
|
||||
|
||||
def test_pong_forbidden(self):
|
||||
self.check_pyzor("pong", "alice", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_check_forbidden(self):
|
||||
self.check_pyzor("check", "alice", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_report_forbidden(self):
|
||||
self.check_pyzor("report", "alice", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_whitelist_forbidden(self):
|
||||
self.check_pyzor("whitelist", "alice", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_info_forbidden(self):
|
||||
self.check_pyzor("info", "alice", input=msg, code=403, exit_code=1)
|
||||
|
||||
# test chuck which does tries to steal bob's account but has the wrong key
|
||||
# Error should be 401 Unauthorized
|
||||
def test_ping_unauthorized(self):
|
||||
self.check_pyzor("ping", "chuck", code=401, exit_code=1)
|
||||
|
||||
def test_pong_unauthorized(self):
|
||||
self.check_pyzor("pong", "chuck", input=msg, code=401, exit_code=1)
|
||||
|
||||
def test_check_unauthorized(self):
|
||||
self.check_pyzor("check", "chuck", input=msg, code=401, exit_code=1)
|
||||
|
||||
def test_report_unauthorized(self):
|
||||
self.check_pyzor("report", "chuck", input=msg, code=401, exit_code=1)
|
||||
|
||||
def test_whitelist_unauthorized(self):
|
||||
self.check_pyzor("whitelist", "chuck", input=msg, code=401, exit_code=1)
|
||||
|
||||
def test_info_unauthorized(self):
|
||||
self.check_pyzor("info", "chuck", input=msg, code=401, exit_code=1)
|
||||
|
||||
# test dan account, which has some access
|
||||
def test_ping_combo(self):
|
||||
self.check_pyzor("ping", "dan", code=200, exit_code=0)
|
||||
|
||||
def test_pong_combo(self):
|
||||
self.check_pyzor("pong", "dan", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_check_combo(self):
|
||||
self.check_pyzor("check", "dan", input=msg, code=200)
|
||||
|
||||
def test_report_combo(self):
|
||||
self.check_pyzor("report", "dan", input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_whitelist_combo(self):
|
||||
self.check_pyzor("whitelist", "dan", input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_info_combo(self):
|
||||
self.check_pyzor("info", "dan", input=msg, code=403, exit_code=1)
|
||||
|
||||
# test anonymous account, which should is not currently set up in the server
|
||||
def test_ping_anonymous(self):
|
||||
self.check_pyzor("ping", None, code=403, exit_code=1)
|
||||
|
||||
def test_pong_anonymous(self):
|
||||
self.check_pyzor("pong", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_check_anonymous(self):
|
||||
self.check_pyzor("check", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_report_anonymous(self):
|
||||
self.check_pyzor("report", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_whitelist_anonymous(self):
|
||||
self.check_pyzor("whitelist", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_info_anonymous(self):
|
||||
self.check_pyzor("info", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
class AnonymousPyzorTest(PyzorTestBase):
|
||||
"""Test accounts with no access or password file set-up. And test
|
||||
anonymous default access.
|
||||
"""
|
||||
access_file = None
|
||||
password_file = None
|
||||
def test_ping(self):
|
||||
self.check_pyzor("ping", None, code=200, exit_code=0)
|
||||
|
||||
def test_pong(self):
|
||||
self.check_pyzor("pong", None, input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_check(self):
|
||||
self.check_pyzor("check", None, input=msg, code=200)
|
||||
|
||||
def test_report(self):
|
||||
self.check_pyzor("report", None, input=msg, code=200, exit_code=0)
|
||||
|
||||
def test_whitelist(self):
|
||||
# anonymous account are not allowed to whitelist by default
|
||||
self.check_pyzor("whitelist", None, input=msg, code=403, exit_code=1)
|
||||
|
||||
def test_info(self):
|
||||
self.check_pyzor("info", None, input=msg, code=200, exit_code=0)
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(AccountPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(AnonymousPyzorTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
843
mail/spamassassin/pyzor-0.7.0/tests/functional/test_digest.py
Normal file
843
mail/spamassassin/pyzor-0.7.0/tests/functional/test_digest.py
Normal file
@@ -0,0 +1,843 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import hashlib
|
||||
import unittest
|
||||
|
||||
from tests.util import *
|
||||
|
||||
TEXT = """MIME-Version: 1.0
|
||||
Sender: chirila@spamexperts.com
|
||||
Received: by 10.216.90.129 with HTTP; Fri, 23 Aug 2013 01:59:03 -0700 (PDT)
|
||||
Date: Fri, 23 Aug 2013 11:59:03 +0300
|
||||
Delivered-To: chirila@spamexperts.com
|
||||
X-Google-Sender-Auth: p6ay4c-tEtdFpavndA9KBmP0CVs
|
||||
Message-ID: <CAK-mJS9aV6Kb7Z5XCRJ_z_UOKEaQjRY8gMzsuxUQcN5iqxNWUg@mail.gmail.com>
|
||||
Subject: Test
|
||||
From: Alexandru Chirila <chirila@spamexperts.com>
|
||||
To: Alexandru Chirila <chirila@spamexperts.com>
|
||||
Content-Type: multipart/alternative; boundary=001a11c2893246a9e604e4999ea3
|
||||
|
||||
--001a11c2893246a9e604e4999ea3
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
|
||||
%s
|
||||
|
||||
--001a11c2893246a9e604e4999ea3
|
||||
"""
|
||||
|
||||
HTML_TEXT = """MIME-Version: 1.0
|
||||
Sender: chirila@gapps.spamexperts.com
|
||||
Received: by 10.216.157.70 with HTTP; Thu, 16 Jan 2014 00:43:31 -0800 (PST)
|
||||
Date: Thu, 16 Jan 2014 10:43:31 +0200
|
||||
Delivered-To: chirila@gapps.spamexperts.com
|
||||
X-Google-Sender-Auth: ybCmONS9U9D6ZUfjx-9_tY-hF2Q
|
||||
Message-ID: <CAK-mJS8sE-V6qtspzzZ+bZ1eSUE_FNMt3K-5kBOG-z3NMgU_Rg@mail.gmail.com>
|
||||
Subject: Test
|
||||
From: Alexandru Chirila <chirila@spamexperts.com>
|
||||
To: Alexandru Chirila <chirila@gapps.spamexperts.com>
|
||||
Content-Type: multipart/alternative; boundary=001a11c25ff293069304f0126bfd
|
||||
|
||||
--001a11c25ff293069304f0126bfd
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
|
||||
Email spam.
|
||||
|
||||
Email spam, also known as junk email or unsolicited bulk email, is a subset
|
||||
of electronic spam involving nearly identical messages sent to numerous
|
||||
recipients by email. Clicking on links in spam email may send users to
|
||||
phishing web sites or sites that are hosting malware.
|
||||
|
||||
--001a11c25ff293069304f0126bfd
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<div dir=3D"ltr"><div>Email spam.</div><div><br></div><div>Email spam, also=
|
||||
known as junk email or unsolicited bulk email, is a subset of electronic s=
|
||||
pam involving nearly identical messages sent to numerous recipients by emai=
|
||||
l. Clicking on links in spam email may send users to phishing web sites or =
|
||||
sites that are hosting malware.</div>
|
||||
</div>
|
||||
|
||||
--001a11c25ff293069304f0126bfd--
|
||||
"""
|
||||
|
||||
TEXT_ATTACHMENT = """MIME-Version: 1.0
|
||||
Received: by 10.76.127.40 with HTTP; Fri, 17 Jan 2014 02:21:43 -0800 (PST)
|
||||
Date: Fri, 17 Jan 2014 12:21:43 +0200
|
||||
Delivered-To: chirila.s.alexandru@gmail.com
|
||||
Message-ID: <CALTHOsuHFaaatiXJKU=LdDCo4NmD_h49yvG2RDsWw17D0-NXJg@mail.gmail.com>
|
||||
Subject: Test
|
||||
From: Alexandru Chirila <chirila.s.alexandru@gmail.com>
|
||||
To: Alexandru Chirila <chirila.s.alexandru@gmail.com>
|
||||
Content-Type: multipart/mixed; boundary=f46d040a62c49bb1c804f027e8cc
|
||||
|
||||
--f46d040a62c49bb1c804f027e8cc
|
||||
Content-Type: multipart/alternative; boundary=f46d040a62c49bb1c404f027e8ca
|
||||
|
||||
--f46d040a62c49bb1c404f027e8ca
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
|
||||
This is a test mailing
|
||||
|
||||
--f46d040a62c49bb1c404f027e8ca--
|
||||
--f46d040a62c49bb1c804f027e8cc
|
||||
Content-Type: image/png; name="tar.png"
|
||||
Content-Disposition: attachment; filename="tar.png"
|
||||
Content-Transfer-Encoding: base64
|
||||
X-Attachment-Id: f_hqjas5ad0
|
||||
|
||||
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
|
||||
QmCC
|
||||
--f46d040a62c49bb1c804f027e8cc--"""
|
||||
|
||||
class PyzorPreDigestTest(PyzorTestBase):
|
||||
# we don't need the pyzord server to test this
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
pass
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
pass
|
||||
def setUp(self):
|
||||
# no argument necessary
|
||||
self.client_args = {}
|
||||
|
||||
def test_predigest_email(self):
|
||||
"""Test email removal in the predigest process"""
|
||||
emails = ["t@abc.ro",
|
||||
"t1@abc.ro",
|
||||
"t+@abc.ro",
|
||||
"t.@abc.ro",
|
||||
]
|
||||
message = "Test %s Test2"
|
||||
expected = b"TestTest2\n"
|
||||
for email in emails:
|
||||
msg = message % email
|
||||
res = self.check_pyzor("predigest", None, input=TEXT % msg)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_long(self):
|
||||
"""Test long "words" removal in the predigest process"""
|
||||
strings = ["0A2D3f%a#S",
|
||||
"3sddkf9jdkd9",
|
||||
"@@#@@@@@@@@@"]
|
||||
message = "Test %s Test2"
|
||||
expected = b"TestTest2\n"
|
||||
for s in strings:
|
||||
msg = message % s
|
||||
res = self.check_pyzor("predigest", None, input=TEXT % msg)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_line_length(self):
|
||||
"""Test small lines removal in the predigest process"""
|
||||
msg = "This line is included\n"\
|
||||
"not this\n"\
|
||||
"This also"
|
||||
expected = b"Thislineisincluded\nThisalso\n"
|
||||
res = self.check_pyzor("predigest", None, input=TEXT % msg)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_atomic(self):
|
||||
"""Test atomic messages (lines <= 4) in the predigest process"""
|
||||
msg = "All this message\nShould be included\nIn the predigest"
|
||||
expected = b"Allthismessage\nShouldbeincluded\nInthepredigest\n"
|
||||
res = self.check_pyzor("predigest", None, input=TEXT % msg)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_pieced(self):
|
||||
"""Test pieced messages (lines > 4) in the predigest process"""
|
||||
msg = ""
|
||||
for i in range(100):
|
||||
msg += "Line%d test test test\n" % i
|
||||
expected = b""
|
||||
for i in [20, 21, 22, 60, 61, 62]:
|
||||
expected += ("Line%dtesttesttest\n" % i).encode("utf8")
|
||||
res = self.check_pyzor("predigest", None, input=TEXT % msg)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_html(self):
|
||||
expected = """Emailspam,alsoknownasjunkemailorbulkemail,isasubset
|
||||
ofspaminvolvingnearlyidenticalmessagessenttonumerous
|
||||
byemail.Clickingonlinksinspamemailmaysendusersto
|
||||
byemail.Clickingonlinksinspamemailmaysendusersto
|
||||
phishingwebsitesorsitesthatarehostingmalware.
|
||||
Emailspam.Emailspam,alsoknownasjunkemailorbulkemail,isasubsetofspaminvolvingnearlyidenticalmessagessenttonumerousbyemail.Clickingonlinksinspamemailmaysenduserstophishingwebsitesorsitesthatarehostingmalware.
|
||||
""".encode("utf8")
|
||||
res = self.check_pyzor("predigest", None, input=HTML_TEXT)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_predigest_attachemnt(self):
|
||||
expected = b"Thisisatestmailing\n"
|
||||
res = self.check_pyzor("predigest", None, input=TEXT_ATTACHMENT)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
class PyzorDigestTest(PyzorTestBase):
|
||||
# we don't need the pyzord server to test this
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
pass
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
pass
|
||||
def setUp(self):
|
||||
# no argument necessary
|
||||
self.client_args = {}
|
||||
|
||||
def test_digest_email(self):
|
||||
"""Test email removal in the digest process"""
|
||||
emails = ["t@abc.ro",
|
||||
"t1@abc.ro",
|
||||
"t+@abc.ro",
|
||||
"t.@abc.ro",
|
||||
]
|
||||
message = "Test %s Test2"
|
||||
expected = b"TestTest2"
|
||||
for email in emails:
|
||||
msg = message % email
|
||||
res = self.check_pyzor("digest", None, input=TEXT % msg)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_long(self):
|
||||
"""Test long "words" removal in the digest process"""
|
||||
strings = ["0A2D3f%a#S",
|
||||
"3sddkf9jdkd9",
|
||||
"@@#@@@@@@@@@"]
|
||||
message = "Test %s Test2"
|
||||
expected = b"TestTest2"
|
||||
for s in strings:
|
||||
msg = message % s
|
||||
res = self.check_pyzor("digest", None, input=TEXT % msg)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_line_length(self):
|
||||
"""Test small lines removal in the digest process"""
|
||||
msg = "This line is included\n"\
|
||||
"not this\n"\
|
||||
"This also"
|
||||
expected = b"ThislineisincludedThisalso"
|
||||
res = self.check_pyzor("digest", None, input=TEXT % msg)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_atomic(self):
|
||||
"""Test atomic messages (lines <= 4) in the digest process"""
|
||||
msg = "All this message\nShould be included\nIn the digest"
|
||||
expected = b"AllthismessageShouldbeincludedInthedigest"
|
||||
res = self.check_pyzor("digest", None, input=TEXT % msg)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_pieced(self):
|
||||
"""Test pieced messages (lines > 4) in the digest process"""
|
||||
msg = ""
|
||||
for i in range(100):
|
||||
msg += "Line%d test test test\n" % i
|
||||
expected = b""
|
||||
for i in [20, 21, 22, 60, 61, 62]:
|
||||
expected += ("Line%dtesttesttest" % i).encode("utf8")
|
||||
res = self.check_pyzor("digest", None, input=TEXT % msg)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_html(self):
|
||||
expected = """Emailspam,alsoknownasjunkemailorbulkemail,isasubset
|
||||
ofspaminvolvingnearlyidenticalmessagessenttonumerous
|
||||
byemail.Clickingonlinksinspamemailmaysendusersto
|
||||
byemail.Clickingonlinksinspamemailmaysendusersto
|
||||
phishingwebsitesorsitesthatarehostingmalware.
|
||||
Emailspam.Emailspam,alsoknownasjunkemailorbulkemail,isasubsetofspaminvolvingnearlyidenticalmessagessenttonumerousbyemail.Clickingonlinksinspamemailmaysenduserstophishingwebsitesorsitesthatarehostingmalware.
|
||||
""".replace("\n", "").encode("utf8")
|
||||
res = self.check_pyzor("digest", None, input=HTML_TEXT)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
def test_digest_attachemnt(self):
|
||||
expected = b"Thisisatestmailing"
|
||||
res = self.check_pyzor("digest", None, input=TEXT_ATTACHMENT)
|
||||
self.assertEqual(res.decode("utf8"),
|
||||
hashlib.sha1(expected).hexdigest().lower() + "\n")
|
||||
|
||||
|
||||
ENCODING_TEST_EMAIL = """From nobody Tue Apr 1 13:18:54 2014
|
||||
Content-Type: multipart/related;
|
||||
boundary="===============0632694142025794937=="
|
||||
MIME-Version: 1.0
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--===============0632694142025794937==
|
||||
Content-Type: text/plain; charset="iso-8859-1"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Thist is a t=E9st
|
||||
--===============0632694142025794937==
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
VGhpcyBpcyBhIHRlc3Qg5r+A5YWJ6YCZ
|
||||
|
||||
--===============0632694142025794937==
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset="cp1258"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
VGhpcyBpcyBhIHTpc3Qg4qXG
|
||||
|
||||
--===============0632694142025794937==--
|
||||
|
||||
"""
|
||||
|
||||
BAD_ENCODING = """From nobody Tue Apr 1 13:18:54 2014
|
||||
Content-Type: multipart/related;
|
||||
boundary="===============0632694142025794937=="
|
||||
MIME-Version: 1.0
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--===============0632694142025794937==
|
||||
Content-Type: text/plain; charset=ISO-8859-1Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
This is a test
|
||||
|
||||
--===============0632694142025794937==
|
||||
Content-Type: text/plain; charset=us-asciia
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
This is a test
|
||||
|
||||
--===============0632694142025794937==
|
||||
|
||||
|
||||
"""
|
||||
|
||||
class PyzorEncodingTest(PyzorTestBase):
|
||||
# we don't need the pyzord server to test this
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
pass
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
pass
|
||||
def setUp(self):
|
||||
# no argument necessary
|
||||
self.client_args = {}
|
||||
|
||||
def test_encodings(self):
|
||||
expected = "47a83cd0e5cc9bd2c64c06c00e3853f79e63014f\n"
|
||||
res = self.check_pyzor("digest", None, input=ENCODING_TEST_EMAIL)
|
||||
self.assertEqual(res.decode("utf8"), expected)
|
||||
|
||||
def test_bad_encoding(self):
|
||||
expected = "2b4dbf2fb521edd21d997f3f04b1c7155ba91fff\n"
|
||||
res = self.check_pyzor("digest", None, input=BAD_ENCODING)
|
||||
self.assertEqual(res.decode("utf8"), expected)
|
||||
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(PyzorDigestTest))
|
||||
test_suite.addTest(unittest.makeSuite(PyzorPreDigestTest))
|
||||
test_suite.addTest(unittest.makeSuite(PyzorEncodingTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
30
mail/spamassassin/pyzor-0.7.0/tests/functional/test_gdbm.py
Normal file
30
mail/spamassassin/pyzor-0.7.0/tests/functional/test_gdbm.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import unittest
|
||||
|
||||
from tests.util import *
|
||||
|
||||
class GdbmPyzorTest(PyzorTest, PyzorTestBase):
|
||||
"""Test the gdbm engine"""
|
||||
dsn = "pyzord.db"
|
||||
engine = "gdbm"
|
||||
|
||||
class ThreadsGdbmPyzorTest(GdbmPyzorTest):
|
||||
"""Test the gdbm engine with threads activated."""
|
||||
threads = "True"
|
||||
max_threads = "0"
|
||||
|
||||
class MaxThreadsGdbmPyzorTest(GdbmPyzorTest):
|
||||
"""Test the gdbm engine with with maximum threads."""
|
||||
threads = "True"
|
||||
max_threads = "10"
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(GdbmPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(ThreadsGdbmPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(MaxThreadsGdbmPyzorTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
108
mail/spamassassin/pyzor-0.7.0/tests/functional/test_mysql.py
Normal file
108
mail/spamassassin/pyzor-0.7.0/tests/functional/test_mysql.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import unittest
|
||||
import ConfigParser
|
||||
|
||||
from tests.util import *
|
||||
|
||||
try:
|
||||
import MySQLdb
|
||||
except ImportError:
|
||||
MySQLdb = None
|
||||
|
||||
schema = """
|
||||
CREATE TABLE IF NOT EXISTS `%s` (
|
||||
`digest` char(40) default NULL,
|
||||
`r_count` int(11) default NULL,
|
||||
`wl_count` int(11) default NULL,
|
||||
`r_entered` datetime default NULL,
|
||||
`wl_entered` datetime default NULL,
|
||||
`r_updated` datetime default NULL,
|
||||
`wl_updated` datetime default NULL,
|
||||
PRIMARY KEY (`digest`)
|
||||
)
|
||||
"""
|
||||
|
||||
@unittest.skipIf(not os.path.exists("./test.conf"),
|
||||
"test.conf is not available")
|
||||
@unittest.skipIf(MySQLdb == None, "MySQLdb library not available")
|
||||
class MySQLdbPyzorTest(PyzorTest, PyzorTestBase):
|
||||
"""Test the mysql engine."""
|
||||
dsn = None
|
||||
engine = "mysql"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
conf = ConfigParser.ConfigParser()
|
||||
conf.read("./test.conf")
|
||||
table = conf.get("test", "table")
|
||||
db = MySQLdb.Connect(host=conf.get("test", "host"),
|
||||
user=conf.get("test", "user"),
|
||||
passwd=conf.get("test", "passwd"),
|
||||
db=conf.get("test", "db"))
|
||||
c = db.cursor()
|
||||
c.execute(schema % table)
|
||||
c.close()
|
||||
db.close()
|
||||
cls.dsn = "%s,%s,%s,%s,%s" % (conf.get("test", "host"),
|
||||
conf.get("test", "user"),
|
||||
conf.get("test", "passwd"),
|
||||
conf.get("test", "db"),
|
||||
conf.get("test", "table"))
|
||||
super(MySQLdbPyzorTest, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(MySQLdbPyzorTest, cls).tearDownClass()
|
||||
try:
|
||||
conf = ConfigParser.ConfigParser()
|
||||
conf.read("./test.conf")
|
||||
table = conf.get("test", "table")
|
||||
db = MySQLdb.Connect(host=conf.get("test", "host"),
|
||||
user=conf.get("test", "user"),
|
||||
passwd=conf.get("test", "passwd"),
|
||||
db=conf.get("test", "db"))
|
||||
c = db.cursor()
|
||||
c.execute("DROP TABLE %s" % table)
|
||||
c.close()
|
||||
db.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
class ThreadsMySQLdbPyzorTest(MySQLdbPyzorTest):
|
||||
"""Test the mysql engine with threads activated."""
|
||||
threads = "True"
|
||||
max_threads = "0"
|
||||
|
||||
class BoundedThreadsMySQLdbPyzorTest(MySQLdbPyzorTest):
|
||||
"""Test the mysql engine with threads and DBConnections set."""
|
||||
threads = "True"
|
||||
max_threads = "0"
|
||||
db_connections = "10"
|
||||
|
||||
class MaxThreadsMySQLdbPyzorTest(MySQLdbPyzorTest):
|
||||
"""Test the mysql engine with threads and MaxThreads set."""
|
||||
threads = "True"
|
||||
max_threads = "10"
|
||||
|
||||
class BoundedMaxThreadsMySQLdbPyzorTest(MySQLdbPyzorTest):
|
||||
"""Test the mysql engine with threads, MaxThreads and DBConnections set."""
|
||||
threads = "True"
|
||||
max_threads = "10"
|
||||
db_connections = "10"
|
||||
|
||||
class ProcessesMySQLdbPyzorTest(MySQLdbPyzorTest):
|
||||
processes = "True"
|
||||
max_processes = "10"
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(MySQLdbPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(ThreadsMySQLdbPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(BoundedThreadsMySQLdbPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(MaxThreadsMySQLdbPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(BoundedMaxThreadsMySQLdbPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(ProcessesMySQLdbPyzorTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
164
mail/spamassassin/pyzor-0.7.0/tests/functional/test_pyzor.py
Normal file
164
mail/spamassassin/pyzor-0.7.0/tests/functional/test_pyzor.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from tests.util import *
|
||||
|
||||
class PyzorScriptTest(PyzorTestBase):
|
||||
password_file = None
|
||||
access = """ALL : anonymous : allow
|
||||
"""
|
||||
def test_report_threshold(self):
|
||||
input = "Test1 report threshold 1 Test2"
|
||||
self.client_args["-r"] = "2"
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(1, 0))
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(2, 0))
|
||||
# Exit code will be success now, since the report count exceeds the
|
||||
# threshold
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(3, 0))
|
||||
|
||||
def test_whitelist_threshold(self):
|
||||
input = "Test1 white list threshold 1 Test2"
|
||||
self.client_args["-w"] = "2"
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(1, 0))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(1, 1))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(1, 2))
|
||||
# Exit code will be failure now, since the whitelist count exceeds the
|
||||
# threshold
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(1, 3))
|
||||
|
||||
def test_report_whitelist_threshold(self):
|
||||
input = "Test1 report white list threshold 1 Test2"
|
||||
self.client_args["-w"] = "2"
|
||||
self.client_args["-r"] = "1"
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(1, 0))
|
||||
# Exit code will be success now, since the report count exceeds the
|
||||
# threshold
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(2, 0))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(2, 1))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(2, 2))
|
||||
# Exit code will be failure now, since the whitelist count exceeds the
|
||||
# threshold
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(2, 3))
|
||||
|
||||
def test_digest_style(self):
|
||||
input = "da39a3ee5e6b4b0d3255bfef95601890afd80700"
|
||||
self.client_args["-s"] = "digests"
|
||||
self.check_pyzor("pong", None, input=input, code=200, exit_code=0,
|
||||
counts=(sys.maxint, 0))
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(0, 0))
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(1, 0))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(1, 1))
|
||||
r = self.get_record(input, None)
|
||||
self.assertEqual(r["Count"], "1")
|
||||
self.assertEqual(r["WL-Count"], "1")
|
||||
|
||||
def test_digest_style_multiple(self):
|
||||
input2 = "da39a3ee5e6b4b0d3255bfef95601890afd80705\n"\
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80706\n"
|
||||
input3 = "da39a3ee5e6b4b0d3255bfef95601890afd80705\n"\
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80706\n"\
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80707\n"
|
||||
self.client_args["-s"] = "digests"
|
||||
self.check_pyzor_multiple("pong", None, input=input3, exit_code=0,
|
||||
code=[200, 200, 200],
|
||||
counts=[(sys.maxint, 0),
|
||||
(sys.maxint, 0),
|
||||
(sys.maxint, 0)])
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=1,
|
||||
code=[200, 200, 200],
|
||||
counts=[(0, 0), (0, 0), (0, 0)])
|
||||
self.check_pyzor_multiple("report", None, input=input2, exit_code=0)
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=0,
|
||||
code=[200, 200, 200],
|
||||
counts=[(1, 0), (1, 0), (0, 0)])
|
||||
self.check_pyzor_multiple("whitelist", None, input=input3, exit_code=0)
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=1,
|
||||
code=[200, 200, 200],
|
||||
counts=[(1, 1), (1, 1), (0, 1)])
|
||||
|
||||
def test_mbox_style(self):
|
||||
input = "From MAILER-DAEMON Mon Jan 6 15:13:33 2014\n\nTest1 message 0 Test2\n\n"
|
||||
self.client_args["-s"] = "mbox"
|
||||
self.check_pyzor("pong", None, input=input, code=200, exit_code=0,
|
||||
counts=(sys.maxint, 0))
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(0, 0))
|
||||
self.check_pyzor("report", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=0,
|
||||
counts=(1, 0))
|
||||
self.check_pyzor("whitelist", None, input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", None, input=input, code=200, exit_code=1,
|
||||
counts=(1, 1))
|
||||
r = self.get_record(input, None)
|
||||
self.assertEqual(r["Count"], "1")
|
||||
self.assertEqual(r["WL-Count"], "1")
|
||||
|
||||
def test_mbox_style_multiple(self):
|
||||
input2 = "From MAILER-DAEMON Mon Jan 6 15:08:02 2014\n\nTest1 message 1 Test2\n\n"\
|
||||
"From MAILER-DAEMON Mon Jan 6 15:08:05 2014\n\nTest1 message 2 Test2\n\n"
|
||||
input3 = "From MAILER-DAEMON Mon Jan 6 15:08:02 2014\n\nTest1 message 1 Test2\n\n"\
|
||||
"From MAILER-DAEMON Mon Jan 6 15:08:05 2014\n\nTest1 message 2 Test2\n\n"\
|
||||
"From MAILER-DAEMON Mon Jan 6 15:08:08 2014\n\nTest1 message 3 Test2\n\n"
|
||||
self.client_args["-s"] = "mbox"
|
||||
self.check_pyzor_multiple("pong", None, input=input3, exit_code=0,
|
||||
code=[200, 200, 200],
|
||||
counts=[(sys.maxint, 0),
|
||||
(sys.maxint, 0),
|
||||
(sys.maxint, 0)])
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=1,
|
||||
code=[200, 200, 200],
|
||||
counts=[(0, 0), (0, 0), (0, 0)])
|
||||
self.check_pyzor_multiple("report", None, input=input2, exit_code=0)
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=0,
|
||||
code=[200, 200, 200],
|
||||
counts=[(1, 0), (1, 0), (0, 0)])
|
||||
self.check_pyzor_multiple("whitelist", None, input=input3, exit_code=0)
|
||||
self.check_pyzor_multiple("check", None, input=input3, exit_code=1,
|
||||
code=[200, 200, 200],
|
||||
counts=[(1, 1), (1, 1), (0, 1)])
|
||||
|
||||
def test_predigest(self):
|
||||
out = self.check_pyzor("predigest", None, input=msg).strip()
|
||||
self.assertEqual(out.decode("utf8"), "TestEmail")
|
||||
|
||||
def test_digest(self):
|
||||
out = self.check_pyzor("digest", None, input=msg).strip()
|
||||
self.assertEqual(out.decode("utf8"), digest)
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(PyzorScriptTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
40
mail/spamassassin/pyzor-0.7.0/tests/functional/test_redis.py
Normal file
40
mail/spamassassin/pyzor-0.7.0/tests/functional/test_redis.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
redis = None
|
||||
|
||||
from tests.util import *
|
||||
|
||||
@unittest.skipIf(redis == None, "redis library not available")
|
||||
class RedisPyzorTest(PyzorTest, PyzorTestBase):
|
||||
"""Test the redis engine"""
|
||||
dsn = "localhost,,,10"
|
||||
engine = "redis"
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(RedisPyzorTest, cls).tearDownClass()
|
||||
redis.StrictRedis(db=10).flushdb()
|
||||
|
||||
|
||||
class ThreadsRedisPyzorTest(RedisPyzorTest):
|
||||
"""Test the redis engine with threads activated."""
|
||||
threads = "True"
|
||||
|
||||
class MaxThreadsRedisPyzorTest(RedisPyzorTest):
|
||||
"""Test the gdbm engine with with maximum threads."""
|
||||
threads = "True"
|
||||
max_threads = "10"
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(RedisPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(ThreadsRedisPyzorTest))
|
||||
test_suite.addTest(unittest.makeSuite(MaxThreadsRedisPyzorTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
32
mail/spamassassin/pyzor-0.7.0/tests/unit/__init__.py
Normal file
32
mail/spamassassin/pyzor-0.7.0/tests/unit/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""A suite of unit tests that verifies the correct behaviour of various
|
||||
functions/methods in the pyzord code.
|
||||
|
||||
Note these tests the source of pyzor, not the version currently installed.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import test_gdbm
|
||||
import test_mysql
|
||||
import test_redis
|
||||
import test_client
|
||||
import test_digest
|
||||
import test_server
|
||||
import test_account
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this package in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
|
||||
test_suite.addTest(test_gdbm.suite())
|
||||
test_suite.addTest(test_mysql.suite())
|
||||
test_suite.addTest(test_redis.suite())
|
||||
test_suite.addTest(test_client.suite())
|
||||
test_suite.addTest(test_digest.suite())
|
||||
test_suite.addTest(test_server.suite())
|
||||
test_suite.addTest(test_account.suite())
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
|
||||
139
mail/spamassassin/pyzor-0.7.0/tests/unit/test_account.py
Normal file
139
mail/spamassassin/pyzor-0.7.0/tests/unit/test_account.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Test the pyzor.account module
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import email
|
||||
import hashlib
|
||||
import unittest
|
||||
import StringIO
|
||||
|
||||
import pyzor
|
||||
import pyzor.config
|
||||
import pyzor.account
|
||||
|
||||
class AccountTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.timestamp = 1381219396
|
||||
self.msg = email.message_from_string("")
|
||||
self.msg["Op"] = "ping"
|
||||
self.msg["Thread"] = "14941"
|
||||
self.msg["PV"] = "2.1"
|
||||
self.msg["User"] = "anonymous"
|
||||
self.msg["Time"] = str(self.timestamp)
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
|
||||
def test_sign_msg(self):
|
||||
"""Test the sign message function"""
|
||||
hashed_key = hashlib.sha1(b"test_key").hexdigest()
|
||||
expected = "2ab1bad2aae6fd80c656a896c82eef0ec1ec38a0"
|
||||
result = pyzor.account.sign_msg(hashed_key, self.timestamp, self.msg)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_hash_key(self):
|
||||
"""Test the hash key function"""
|
||||
user = "testuser"
|
||||
key = "testkey"
|
||||
expected = "0957bd79b58263657127a39762879098286d8477"
|
||||
result = pyzor.account.hash_key(key, user)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_verify_signature(self):
|
||||
"""Test the verify signature function"""
|
||||
def mock_sm(h, t, m):
|
||||
return "testsig"
|
||||
real_sm = pyzor.account.sign_msg
|
||||
pyzor.account.sign_msg = mock_sm
|
||||
try:
|
||||
self.msg["Sig"] = "testsig"
|
||||
del self.msg["Time"]
|
||||
self.msg["Time"] = str(int(time.time()))
|
||||
pyzor.account.verify_signature(self.msg, "testkey")
|
||||
finally:
|
||||
pyzor.account.sign_msg = real_sm
|
||||
|
||||
def test_verify_signature_old_timestamp(self):
|
||||
"""Test the verify signature with old timestamp"""
|
||||
def mock_sm(h, t, m):
|
||||
return "testsig"
|
||||
real_sm = pyzor.account.sign_msg
|
||||
pyzor.account.sign_msg = mock_sm
|
||||
try:
|
||||
self.msg["Sig"] = "testsig"
|
||||
self.assertRaises(pyzor.SignatureError, pyzor.account.verify_signature, self.msg, "testkey")
|
||||
finally:
|
||||
pyzor.account.sign_msg = real_sm
|
||||
|
||||
def test_verify_signature_bad_signature(self):
|
||||
"""Test the verify signature with invalid signature"""
|
||||
def mock_sm(h, t, m):
|
||||
return "testsig"
|
||||
real_sm = pyzor.account.sign_msg
|
||||
pyzor.account.sign_msg = mock_sm
|
||||
try:
|
||||
self.msg["Sig"] = "testsig-bad"
|
||||
del self.msg["Time"]
|
||||
self.msg["Time"] = str(int(time.time()))
|
||||
self.assertRaises(pyzor.SignatureError, pyzor.account.verify_signature, self.msg, "testkey")
|
||||
finally:
|
||||
pyzor.account.sign_msg = real_sm
|
||||
|
||||
class LoadAccountTest(unittest.TestCase):
|
||||
"""Tests for the load_accounts function"""
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
|
||||
self.real_exists = os.path.exists
|
||||
os.path.exists = lambda p: True
|
||||
self.mock_file = StringIO.StringIO()
|
||||
self.real_open = pyzor.account.__builtins__["open"]
|
||||
def mock_open(path, mode="r", buffering=-1):
|
||||
if path == "test_file":
|
||||
self.mock_file.seek(0)
|
||||
return self.mock_file
|
||||
else:
|
||||
return self.real_open(path, mode, buffering)
|
||||
pyzor.account.__builtins__["open"] = mock_open
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
os.path.exists = self.real_exists
|
||||
pyzor.account.__builtins__["open"] = self.real_open
|
||||
|
||||
def test_load_accounts(self):
|
||||
"""Test loading the account file"""
|
||||
self.mock_file.write("public.pyzor.org : 24441 : test : 123abc,cba321\n"
|
||||
"public2.pyzor.org : 24441 : test2 : 123abc,cba321")
|
||||
result = pyzor.config.load_accounts("test_file")
|
||||
self.assertIn(("public.pyzor.org", 24441), result)
|
||||
self.assertIn(("public2.pyzor.org", 24441), result)
|
||||
account = result[("public.pyzor.org", 24441)]
|
||||
self.assertEqual((account.username, account.salt, account.key),
|
||||
("test", "123abc", "cba321"))
|
||||
account = result[("public2.pyzor.org", 24441)]
|
||||
self.assertEqual((account.username, account.salt, account.key),
|
||||
("test2", "123abc", "cba321"))
|
||||
|
||||
def test_load_accounts_comment(self):
|
||||
"""Test skipping commented lines"""
|
||||
self.mock_file.write("#public1.pyzor.org : 24441 : test : 123abc,cba321")
|
||||
result = pyzor.config.load_accounts("test_file")
|
||||
self.assertNotIn(("public.pyzor.org", 24441), result)
|
||||
self.assertFalse(result)
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(AccountTest))
|
||||
test_suite.addTest(unittest.makeSuite(LoadAccountTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
|
||||
168
mail/spamassassin/pyzor-0.7.0/tests/unit/test_client.py
Normal file
168
mail/spamassassin/pyzor-0.7.0/tests/unit/test_client.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import unittest
|
||||
|
||||
import pyzor
|
||||
import pyzor.client
|
||||
import pyzor.account
|
||||
import pyzor.message
|
||||
|
||||
def make_MockSocket(response, request):
|
||||
"""Create a MockSocket class that will append requests to
|
||||
the specified `request` list and return the specified `response`
|
||||
"""
|
||||
class MockSocket():
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
def settimeout(self, timeout):
|
||||
pass
|
||||
def recvfrom(self, packetsize):
|
||||
return response, ("127.0.0.1", 24441)
|
||||
def sendto(self, data, flag, address):
|
||||
request.append(data)
|
||||
return MockSocket
|
||||
|
||||
def make_MockThreadId(thread):
|
||||
"""Creates a MockThreadId class that will generate
|
||||
the specified thread number.
|
||||
"""
|
||||
class MockThreadId(int):
|
||||
def __new__(cls, i):
|
||||
return int.__new__(cls, i)
|
||||
@classmethod
|
||||
def generate(cls):
|
||||
return thread
|
||||
|
||||
def in_ok_range(self):
|
||||
return True
|
||||
return MockThreadId
|
||||
|
||||
def mock_sign_msg(hash_key, timestamp, msg):
|
||||
return "TestSig"
|
||||
|
||||
def mock_hash_key(user_key, user):
|
||||
return None
|
||||
|
||||
class ClientTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.real_sg = pyzor.account.sign_msg
|
||||
pyzor.account.sign_msg = mock_sign_msg
|
||||
self.real_hk = pyzor.account.hash_key
|
||||
pyzor.account.hash_key = mock_hash_key
|
||||
self.thread = 33715
|
||||
|
||||
# the response the mock socket will send
|
||||
self.response = "Code: 200\nDiag: OK\nPV: 2.1\nThread: 33715\n\n"
|
||||
# the requests send by the client will be stored here
|
||||
self.request = []
|
||||
# the expected request that the client should send
|
||||
self.expected = {"Thread": str(self.thread),
|
||||
"PV": str(pyzor.proto_version),
|
||||
"User": "anonymous",
|
||||
"Time": str(int(time.time())),
|
||||
"Sig": "TestSig"}
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
pyzor.account.sign_msg = self.real_sg
|
||||
pyzor.account.hash_key = self.real_hk
|
||||
|
||||
def check_request(self, request):
|
||||
"""Check if the request sent by the client is equal
|
||||
to the expected one.
|
||||
"""
|
||||
req = {}
|
||||
request = request.decode("utf8").replace("\n\n", "\n")
|
||||
for line in request.splitlines():
|
||||
key = line.split(":")[0].strip()
|
||||
value = line.split(":")[1].strip()
|
||||
req[key] = value
|
||||
self.assertEqual(req, self.expected)
|
||||
|
||||
def check_client(self, accounts, method, *args, **kwargs):
|
||||
"""Tests if the request and response are sent
|
||||
and read correctly by the client.
|
||||
"""
|
||||
real_socket = socket.socket
|
||||
socket.socket = make_MockSocket(self.response.encode("utf8"),
|
||||
self.request)
|
||||
|
||||
real_ThreadId = pyzor.message.ThreadId
|
||||
pyzor.message.ThreadId = make_MockThreadId(self.thread)
|
||||
client = pyzor.client.Client(accounts)
|
||||
try:
|
||||
response = getattr(client, method)(*args, **kwargs)
|
||||
self.assertEqual(str(response), self.response)
|
||||
self.check_request(self.request[0])
|
||||
finally:
|
||||
socket.socket = real_socket
|
||||
pyzor.message.ThreadId = real_ThreadId
|
||||
return client
|
||||
|
||||
def test_ping(self):
|
||||
"""Test the client ping request"""
|
||||
self.expected["Op"] = "ping"
|
||||
self.check_client(None, "ping")
|
||||
|
||||
def test_pong(self):
|
||||
"""Test the client pong request"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op"] = "pong"
|
||||
self.expected["Op-Digest"] = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.check_client(None, "pong", digest)
|
||||
|
||||
def test_check(self):
|
||||
"""Test the client check request"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op"] = "check"
|
||||
self.expected["Op-Digest"] = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.check_client(None, "check", digest)
|
||||
|
||||
def test_info(self):
|
||||
"""Test the client info request"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op"] = "info"
|
||||
self.expected["Op-Digest"] = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.check_client(None, "info", digest)
|
||||
|
||||
def test_report(self):
|
||||
"""Test the client report request"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op"] = "report"
|
||||
self.expected["Op-Digest"] = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op-Spec"] = "20,3,60,3"
|
||||
self.check_client(None, "report", digest)
|
||||
|
||||
def test_whitelist(self):
|
||||
"""Test the client whitelist request"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op"] = "whitelist"
|
||||
self.expected["Op-Digest"] = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
self.expected["Op-Spec"] = "20,3,60,3"
|
||||
self.check_client(None, "whitelist", digest)
|
||||
|
||||
def test_handle_account(self):
|
||||
"""Test client handling accounts"""
|
||||
test_account = pyzor.account.Account("TestUser", "TestKey", "TestSalt")
|
||||
self.expected["Op"] = "ping"
|
||||
self.expected["User"] = "TestUser"
|
||||
self.check_client({("public.pyzor.org", 24441): test_account}, "ping")
|
||||
|
||||
def test_handle_invalid_thread(self):
|
||||
"""Test invalid thread id"""
|
||||
self.thread += 20
|
||||
self.expected["Op"] = "ping"
|
||||
self.assertRaises(pyzor.ProtocolError, self.check_client, None, "ping")
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(ClientTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
200
mail/spamassassin/pyzor-0.7.0/tests/unit/test_digest.py
Normal file
200
mail/spamassassin/pyzor-0.7.0/tests/unit/test_digest.py
Normal file
@@ -0,0 +1,200 @@
|
||||
"""The the pyzor.digest module
|
||||
"""
|
||||
|
||||
import sys
|
||||
import hashlib
|
||||
import unittest
|
||||
|
||||
from pyzor.digest import *
|
||||
|
||||
HTML_TEXT = """<html><head><title>Email spam</title></head><body>
|
||||
<p><b>Email spam</b>, also known as <b>junk email</b>
|
||||
or <b>unsolicited bulk email</b> (<i>UBE</i>), is a subset of
|
||||
<a href="/wiki/Spam_(electronic)" title="Spam (electronic)">electronic spam</a>
|
||||
involving nearly identical messages sent to numerous recipients by <a href="/wiki/Email" title="Email">
|
||||
email</a>. Clicking on <a href="/wiki/Html_email#Security_vulnerabilities" title="Html email" class="mw-redirect">
|
||||
links in spam email</a> may send users to <a href="/wiki/Phishing" title="Phishing">phishing</a>
|
||||
web sites or sites that are hosting <a href="/wiki/Malware" title="Malware">malware</a>.</body></html>"""
|
||||
|
||||
HTML_TEXT_STRIPED = 'Email spam Email spam , also known as junk email or unsolicited bulk email ( UBE ),'\
|
||||
' is a subset of electronic spam involving nearly identical messages sent to numerous recipients by email'\
|
||||
' . Clicking on links in spam email may send users to phishing web sites or sites that are hosting malware .'
|
||||
|
||||
class HTMLStripperTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.data = []
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
|
||||
def test_HTMLStripper(self):
|
||||
stripper = HTMLStripper(self.data)
|
||||
stripper.feed(HTML_TEXT)
|
||||
res = " ".join(self.data)
|
||||
self.assertEqual(res, HTML_TEXT_STRIPED)
|
||||
|
||||
|
||||
class PreDigestTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.lines = []
|
||||
|
||||
def mock_digest_paylods(c, message):
|
||||
yield message.decode("utf8")
|
||||
|
||||
def mock_handle_line(s, line):
|
||||
self.lines.append(line.decode("utf8"))
|
||||
|
||||
self.real_digest_payloads = DataDigester.digest_payloads
|
||||
self.real_handle_line = DataDigester.handle_line
|
||||
DataDigester.digest_payloads = mock_digest_paylods
|
||||
DataDigester.handle_line = mock_handle_line
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
DataDigester.digest_payloads = self.real_digest_payloads
|
||||
DataDigester.handle_line = self.real_handle_line
|
||||
|
||||
|
||||
def test_predigest_emails(self):
|
||||
"""Test email removal in the predigest process"""
|
||||
real_longstr = DataDigester.longstr_ptrn
|
||||
DataDigester.longstr_ptrn = re.compile(r'\S{100,}')
|
||||
emails = ["test@example.com",
|
||||
"test123@example.com",
|
||||
"test+abc@example.com",
|
||||
"test.test2@example.com",
|
||||
"test.test2+abc@example.com", ]
|
||||
message = "Test %s Test2"
|
||||
expected = "TestTest2"
|
||||
try:
|
||||
for email in emails:
|
||||
self.lines = []
|
||||
DataDigester((message % email).encode("utf8"))
|
||||
self.assertEqual(self.lines[0], expected)
|
||||
finally:
|
||||
DataDigester.longstr_ptrn = real_longstr
|
||||
|
||||
# XXX This fails
|
||||
# def test_predigest_emails_whitespace(self):
|
||||
# real_longstr = DataDigester.longstr_ptrn
|
||||
# DataDigester.longstr_ptrn = re.compile(r'\S{100,}')
|
||||
# emails = ["chirila@example. com",
|
||||
# "chirila@example . com",
|
||||
# "chirila @example. com",
|
||||
# "chirila@ example. com",
|
||||
# "chirila @example . com",
|
||||
# "chirila @ example. com",
|
||||
# "chirila @ example . com",]
|
||||
# message = "Test %s Test2"
|
||||
# expected = "TestTest2"
|
||||
# try:
|
||||
# for email in emails:
|
||||
# self.lines = []
|
||||
# DataDigester(message % email)
|
||||
# self.assertEqual(self.lines[0], expected)
|
||||
# finally:
|
||||
# DataDigester.longstr_ptrn = real_longstr
|
||||
|
||||
|
||||
def test_predigest_urls(self):
|
||||
"""Test url removal in the predigest process"""
|
||||
real_longstr = DataDigester.longstr_ptrn
|
||||
DataDigester.longstr_ptrn = re.compile(r'\S{100,}')
|
||||
urls = ["http://www.example.com",
|
||||
# "www.example.com", # XXX This also fail
|
||||
"http://example.com",
|
||||
# "example.com", # XXX This also fails
|
||||
"http://www.example.com/test/"
|
||||
"http://www.example.com/test/test2", ]
|
||||
message = "Test %s Test2"
|
||||
expected = "TestTest2"
|
||||
try:
|
||||
for url in urls:
|
||||
self.lines = []
|
||||
DataDigester((message % url).encode("utf8"))
|
||||
self.assertEqual(self.lines[0], expected)
|
||||
finally:
|
||||
DataDigester.longstr_ptrn = real_longstr
|
||||
|
||||
def test_predigest_long(self):
|
||||
"""Test long "words" removal in the predigest process"""
|
||||
strings = ["0A2D3f%a#S",
|
||||
"3sddkf9jdkd9",
|
||||
"@@#@@@@@@@@@"]
|
||||
message = "Test %s Test2"
|
||||
expected = "TestTest2"
|
||||
for string in strings:
|
||||
self.lines = []
|
||||
DataDigester((message % string).encode("utf8"))
|
||||
self.assertEqual(self.lines[0], expected)
|
||||
|
||||
def test_predigest_min_line_lenght(self):
|
||||
"""Test small lines removal in the predigest process"""
|
||||
message = "This line is included\n"\
|
||||
"not this\n"\
|
||||
"This also"
|
||||
expected = ["Thislineisincluded", "Thisalso"]
|
||||
DataDigester(message.encode("utf8"))
|
||||
self.assertEqual(self.lines, expected)
|
||||
|
||||
def test_predigest_atomic(self):
|
||||
"""Test atomic messages (lines <= 4) in the predigest process"""
|
||||
message = "All this message\nShould be included\nIn the predigest"
|
||||
expected = ["Allthismessage", "Shouldbeincluded", "Inthepredigest"]
|
||||
DataDigester(message.encode("utf8"))
|
||||
self.assertEqual(self.lines, expected)
|
||||
|
||||
def test_predigest_pieced(self):
|
||||
"""Test pieced messages (lines > 4) in the predigest process"""
|
||||
message = ""
|
||||
for i in range(100):
|
||||
message += "Line%d test test test\n" % i
|
||||
expected = []
|
||||
for i in [20, 21, 22, 60, 61, 62]:
|
||||
expected.append("Line%dtesttesttest" % i)
|
||||
DataDigester(message.encode("utf8"))
|
||||
self.assertEqual(self.lines, expected)
|
||||
|
||||
class DigestTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.lines = []
|
||||
|
||||
def mock_digest_paylods(c, message):
|
||||
yield message.decode("utf8")
|
||||
|
||||
self.real_digest_payloads = DataDigester.digest_payloads
|
||||
DataDigester.digest_payloads = mock_digest_paylods
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
DataDigester.digest_payloads = self.real_digest_payloads
|
||||
|
||||
def test_digest(self):
|
||||
message = b"That's some good ham right there"
|
||||
predigested = b"That'ssomegoodhamrightthere"
|
||||
|
||||
digest = hashlib.sha1()
|
||||
digest.update(predigested)
|
||||
|
||||
expected = digest.hexdigest()
|
||||
result = DataDigester(message).value
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(HTMLStripperTests))
|
||||
test_suite.addTest(unittest.makeSuite(PreDigestTests))
|
||||
test_suite.addTest(unittest.makeSuite(DigestTests))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
153
mail/spamassassin/pyzor-0.7.0/tests/unit/test_gdbm.py
Normal file
153
mail/spamassassin/pyzor-0.7.0/tests/unit/test_gdbm.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Test the pyzor.engines.gdbm_ module."""
|
||||
|
||||
import gdbm
|
||||
import unittest
|
||||
import threading
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pyzor.engines
|
||||
import pyzor.engines.gdbm_
|
||||
import pyzor.engines.common
|
||||
|
||||
class MockTimer():
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
def start(self):
|
||||
pass
|
||||
def setDaemon(self, daemon):
|
||||
pass
|
||||
|
||||
class MockGdbm(dict):
|
||||
"""Mock a gdbm database"""
|
||||
|
||||
def firstkey(self):
|
||||
if not self.keys():
|
||||
return None
|
||||
self.key_index = 1
|
||||
return self.keys()[0]
|
||||
|
||||
def nextkey(self, key):
|
||||
if len(self.keys()) <= self.key_index:
|
||||
return None
|
||||
else:
|
||||
self.key_index += 1
|
||||
return self.keys()[self.key_index]
|
||||
|
||||
def sync(self):
|
||||
pass
|
||||
def reorganize(self):
|
||||
pass
|
||||
|
||||
class GdbmTest(unittest.TestCase):
|
||||
"""Test the GdbmDBHandle class"""
|
||||
|
||||
max_age = 60 * 60 * 24 * 30 * 4
|
||||
r_count = 24
|
||||
wl_count = 42
|
||||
entered = datetime.now() - timedelta(days=10)
|
||||
updated = datetime.now() - timedelta(days=2)
|
||||
wl_entered = datetime.now() - timedelta(days=20)
|
||||
wl_updated = datetime.now() - timedelta(days=3)
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.real_timer = threading.Timer
|
||||
threading.Timer = MockTimer
|
||||
|
||||
self.db = MockGdbm()
|
||||
def mock_open(fn, mode):
|
||||
return self.db
|
||||
self.real_open = gdbm.open
|
||||
gdbm.open = mock_open
|
||||
|
||||
self.record = pyzor.engines.common.Record(self.r_count, self.wl_count,
|
||||
self.entered, self.updated,
|
||||
self.wl_entered, self.wl_updated)
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
threading.Timer = self.real_timer
|
||||
gdbm.open = self.real_open
|
||||
|
||||
def record_as_str(self, record=None):
|
||||
if not record:
|
||||
record = self.record
|
||||
return ("1,%s,%s,%s,%s,%s,%s" % (record.r_count, record.r_entered,
|
||||
record.r_updated, record.wl_count,
|
||||
record.wl_entered, record.wl_updated)).encode("utf8")
|
||||
|
||||
def test_set_item(self):
|
||||
"""Test GdbmDBHandle.__setitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=self.max_age)
|
||||
handle[digest] = self.record
|
||||
|
||||
self.assertEqual(self.db[digest], self.record_as_str().decode("utf8"))
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test GdbmDBHandle.__getitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=self.max_age)
|
||||
self.db[digest] = self.record_as_str()
|
||||
|
||||
result = handle[digest]
|
||||
|
||||
self.assertEqual(self.record_as_str(result), self.record_as_str())
|
||||
|
||||
def test_del_item(self):
|
||||
"""Test GdbmDBHandle.__delitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=self.max_age)
|
||||
self.db[digest] = self.record_as_str()
|
||||
|
||||
del handle[digest]
|
||||
|
||||
self.assertFalse(self.db.get(digest))
|
||||
|
||||
def test_reorganize_older(self):
|
||||
"""Test GdbmDBHandle.start_reorganizing with older records"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
self.db[digest] = self.record_as_str()
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=3600 * 24)
|
||||
|
||||
self.assertFalse(self.db.get(digest))
|
||||
|
||||
def test_reorganize_older_no_max_age(self):
|
||||
"""Test GdbmDBHandle.start_reorganizing with older records, but no
|
||||
max_age set.
|
||||
"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
self.db[digest] = self.record_as_str()
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=None)
|
||||
|
||||
self.assertEqual(self.db[digest], self.record_as_str())
|
||||
|
||||
def test_reorganize_fresh(self):
|
||||
"""Test GdbmDBHandle.start_reorganizing with newer records"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
self.db[digest] = self.record_as_str()
|
||||
handle = pyzor.engines.gdbm_.GdbmDBHandle(None, None,
|
||||
max_age=3600 * 24 * 3)
|
||||
|
||||
self.assertEqual(self.db[digest], self.record_as_str())
|
||||
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(GdbmTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
156
mail/spamassassin/pyzor-0.7.0/tests/unit/test_mysql.py
Normal file
156
mail/spamassassin/pyzor-0.7.0/tests/unit/test_mysql.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""Test the pyzor.engines.mysql module."""
|
||||
|
||||
import unittest
|
||||
import threading
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pyzor.engines
|
||||
import pyzor.engines.mysql
|
||||
import pyzor.engines.common
|
||||
|
||||
class MockTimer():
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
def start(self):
|
||||
pass
|
||||
def setDaemon(self, daemon):
|
||||
pass
|
||||
|
||||
def make_MockMySQL(result, queries):
|
||||
class MockCursor():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def fetchone(self):
|
||||
return result
|
||||
def fetchall(self):
|
||||
return [result]
|
||||
def execute(self, query, args=None):
|
||||
queries.append((query, args))
|
||||
def close(self):
|
||||
pass
|
||||
class MockDB():
|
||||
def cursor(self):
|
||||
return MockCursor()
|
||||
def close(self):
|
||||
pass
|
||||
def commit(self):
|
||||
pass
|
||||
def autocommit(self, value):
|
||||
pass
|
||||
class MockMysql():
|
||||
@staticmethod
|
||||
def connect(*args, **kwargs):
|
||||
return MockDB()
|
||||
class Error(Exception):
|
||||
pass
|
||||
return MockMysql
|
||||
|
||||
|
||||
class MySQLTest(unittest.TestCase):
|
||||
"""Test the GdbmDBHandle class"""
|
||||
|
||||
max_age = 60 * 60 * 24 * 30 * 4
|
||||
r_count = 24
|
||||
wl_count = 42
|
||||
entered = datetime.now() - timedelta(days=10)
|
||||
updated = datetime.now() - timedelta(days=2)
|
||||
wl_entered = datetime.now() - timedelta(days=20)
|
||||
wl_updated = datetime.now() - timedelta(days=3)
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.real_timer = threading.Timer
|
||||
threading.Timer = MockTimer
|
||||
|
||||
self.record = pyzor.engines.common.Record(self.r_count, self.wl_count,
|
||||
self.entered, self.updated,
|
||||
self.wl_entered, self.wl_updated)
|
||||
|
||||
self.response = self.record_unpack()
|
||||
self.queries = []
|
||||
|
||||
mock_MySQL = make_MockMySQL(self.response, self.queries)
|
||||
self.real_mysql = pyzor.engines.mysql.MySQLdb
|
||||
pyzor.engines.mysql.MySQLdb = mock_MySQL
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
threading.Timer = self.real_timer
|
||||
pyzor.engines.mysql.MySQLdb = self.real_mysql
|
||||
|
||||
def record_unpack(self, record=None):
|
||||
if not record:
|
||||
record = self.record
|
||||
return (record.r_count, record.wl_count,
|
||||
record.r_entered, record.r_updated,
|
||||
record.wl_entered, record.wl_updated)
|
||||
|
||||
def test_reconnect(self):
|
||||
"""Test MySQLDBHandle.__init__"""
|
||||
expected = "DELETE FROM testtable WHERE r_updated<%s"
|
||||
|
||||
pyzor.engines.mysql.MySQLDBHandle("testhost,testuser,testpass,testdb,testtable",
|
||||
None, max_age=self.max_age)
|
||||
|
||||
self.assertEqual(self.queries[0][0], expected)
|
||||
|
||||
def test_no_reorganize(self):
|
||||
pyzor.engines.mysql.MySQLDBHandle("testhost,testuser,testpass,testdb,testtable",
|
||||
None, max_age=None)
|
||||
self.assertFalse(self.queries)
|
||||
|
||||
def test_set_item(self):
|
||||
"""Test MySQLDBHandle.__setitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
expected = ("INSERT INTO testtable (digest, r_count, wl_count, "
|
||||
"r_entered, r_updated, wl_entered, wl_updated) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, %s) ON "
|
||||
"DUPLICATE KEY UPDATE r_count=%s, wl_count=%s, "
|
||||
"r_entered=%s, r_updated=%s, wl_entered=%s, "
|
||||
"wl_updated=%s",
|
||||
(digest, self.r_count, self.wl_count, self.entered,
|
||||
self.updated, self.wl_entered, self.wl_updated,
|
||||
self.r_count, self.wl_count, self.entered,
|
||||
self.updated, self.wl_entered, self.wl_updated))
|
||||
handle = pyzor.engines.mysql.MySQLDBHandle("testhost,testuser,testpass,testdb,testtable",
|
||||
None, max_age=self.max_age)
|
||||
|
||||
handle[digest] = self.record
|
||||
self.assertEqual(self.queries[1], expected)
|
||||
|
||||
def test_get_item(self):
|
||||
"""Test MySQLDBHandle.__getitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
expected = ("SELECT r_count, wl_count, r_entered, r_updated, "
|
||||
"wl_entered, wl_updated FROM testtable WHERE digest=%s",
|
||||
(digest,))
|
||||
handle = pyzor.engines.mysql.MySQLDBHandle("testhost,testuser,testpass,testdb,testtable",
|
||||
None, max_age=self.max_age)
|
||||
|
||||
result = handle[digest]
|
||||
self.assertEqual(self.queries[1], expected)
|
||||
self.assertEqual(self.record_unpack(result), self.record_unpack())
|
||||
|
||||
def test_del_item(self):
|
||||
"""Test MySQLDBHandle.__detitem__"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
expected = ("DELETE FROM testtable WHERE digest=%s", (digest,))
|
||||
|
||||
handle = pyzor.engines.mysql.MySQLDBHandle("testhost,testuser,testpass,testdb,testtable",
|
||||
None, max_age=self.max_age)
|
||||
del handle[digest]
|
||||
self.assertEqual(self.queries[1], expected)
|
||||
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(MySQLTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
188
mail/spamassassin/pyzor-0.7.0/tests/unit/test_redis.py
Normal file
188
mail/spamassassin/pyzor-0.7.0/tests/unit/test_redis.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""Test the pyzor.engines.gdbm_ module."""
|
||||
|
||||
import unittest
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pyzor.engines
|
||||
import pyzor.engines.redis_
|
||||
import pyzor.engines.common
|
||||
|
||||
|
||||
class EncodingRedisTest(unittest.TestCase):
|
||||
"""Test the RedisDBHandle class"""
|
||||
|
||||
r_count = 24
|
||||
wl_count = 42
|
||||
entered = datetime(2014, 4, 23, 15, 41, 30)
|
||||
updated = datetime(2014, 4, 25, 17, 22, 25)
|
||||
wl_entered = datetime(2014, 2, 12, 11, 10, 55)
|
||||
wl_updated = datetime(2014, 3, 25, 5, 1, 50)
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
|
||||
self.record = pyzor.engines.common.Record(self.r_count, self.wl_count,
|
||||
self.entered, self.updated,
|
||||
self.wl_entered, self.wl_updated)
|
||||
|
||||
def compare_records(self, r1, r2):
|
||||
attrs = ("r_count", "r_entered", "r_updated",
|
||||
"wl_count", "wl_entered", "wl_updated")
|
||||
self.assertTrue(all(getattr(r1, attr) == getattr(r2, attr)
|
||||
for attr in attrs))
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
|
||||
def test_encode_record(self):
|
||||
expected = ("24,2014-04-23 15:41:30,2014-04-25 17:22:25,"
|
||||
"42,2014-02-12 11:10:55,2014-03-25 05:01:50").encode()
|
||||
result = pyzor.engines.redis_.RedisDBHandle._encode_record(self.record)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_encode_record_no_date(self):
|
||||
expected = ("24,2014-04-23 15:41:30,,"
|
||||
"42,2014-02-12 11:10:55,2014-03-25 05:01:50").encode()
|
||||
self.record.r_updated = None
|
||||
result = pyzor.engines.redis_.RedisDBHandle._encode_record(self.record)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_encode_record_no_white(self):
|
||||
expected = ("24,2014-04-23 15:41:30,2014-04-25 17:22:25,"
|
||||
"0,,").encode()
|
||||
self.record.wl_count = 0
|
||||
self.record.wl_entered = None
|
||||
self.record.wl_updated = None
|
||||
result = pyzor.engines.redis_.RedisDBHandle._encode_record(self.record)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_decode_record(self):
|
||||
encoded = ("24,2014-04-23 15:41:30,2014-04-25 17:22:25,"
|
||||
"42,2014-02-12 11:10:55,2014-03-25 05:01:50").encode()
|
||||
result = pyzor.engines.redis_.RedisDBHandle._decode_record(encoded)
|
||||
self.compare_records(result, self.record)
|
||||
|
||||
def test_decode_record_no_date(self):
|
||||
encoded = ("24,2014-04-23 15:41:30,,"
|
||||
"42,2014-02-12 11:10:55,2014-03-25 05:01:50").encode()
|
||||
result = pyzor.engines.redis_.RedisDBHandle._decode_record(encoded)
|
||||
self.record.r_updated = None
|
||||
self.compare_records(result, self.record)
|
||||
|
||||
def test_decode_record_no_white(self):
|
||||
encoded = ("24,2014-04-23 15:41:30,2014-04-25 17:22:25,"
|
||||
"0,,").encode()
|
||||
result = pyzor.engines.redis_.RedisDBHandle._decode_record(encoded)
|
||||
self.record.wl_count = 0
|
||||
self.record.wl_entered = None
|
||||
self.record.wl_updated = None
|
||||
self.compare_records(result, self.record)
|
||||
|
||||
def make_MockRedis(commands):
|
||||
class MockRedis():
|
||||
def __init__(self, *args, **kwargs):
|
||||
commands.append(("init", args, kwargs))
|
||||
def set(self, *args, **kwargs):
|
||||
commands.append(("set", args, kwargs))
|
||||
def setex(self, *args, **kwargs):
|
||||
commands.append(("setex", args, kwargs))
|
||||
def get(self, *args, **kwargs):
|
||||
commands.append(("get", args, kwargs))
|
||||
def delete(self, *args, **kwargs):
|
||||
commands.append(("delete", args, kwargs))
|
||||
return MockRedis
|
||||
|
||||
mock_encode_record = lambda s, x: x
|
||||
mock_decode_record = lambda s, x: x
|
||||
|
||||
class RedisTest(unittest.TestCase):
|
||||
|
||||
max_age = 60 * 60
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
|
||||
self.commands = []
|
||||
|
||||
self.real_redis = pyzor.engines.redis_.redis.StrictRedis
|
||||
self.real_encode = pyzor.engines.redis_.RedisDBHandle._encode_record
|
||||
self.real_decode = pyzor.engines.redis_.RedisDBHandle._decode_record
|
||||
|
||||
pyzor.engines.redis_.redis.StrictRedis = make_MockRedis(self.commands)
|
||||
pyzor.engines.redis_.RedisDBHandle._encode_record = mock_encode_record
|
||||
pyzor.engines.redis_.RedisDBHandle._decode_record = mock_decode_record
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
pyzor.engines.redis_.redis.StrictRedis = self.real_redis
|
||||
pyzor.engines.redis_.RedisDBHandle._encode_record = self.real_encode
|
||||
pyzor.engines.redis_.RedisDBHandle._decode_record = self.real_decode
|
||||
|
||||
def test_init(self):
|
||||
expected = {"host": "example.com",
|
||||
"port": 6387,
|
||||
"password": "passwd",
|
||||
"db": 5,
|
||||
}
|
||||
db = pyzor.engines.redis_.RedisDBHandle("example.com,6387,passwd,5",
|
||||
None)
|
||||
self.assertEqual(self.commands[0], ("init", (), expected))
|
||||
|
||||
def test_init_defaults(self):
|
||||
expected = {"host": "localhost",
|
||||
"port": 6379,
|
||||
"password": None,
|
||||
"db": 0,
|
||||
}
|
||||
db = pyzor.engines.redis_.RedisDBHandle(",,,", None)
|
||||
self.assertEqual(self.commands[0], ("init", (), expected))
|
||||
|
||||
def test_set(self):
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
value = "record test"
|
||||
|
||||
db = pyzor.engines.redis_.RedisDBHandle(",,,", None)
|
||||
db[digest] = value
|
||||
|
||||
expected = ("pyzord.digest.%s" % digest, value)
|
||||
self.assertEqual(self.commands[1], ("set", expected, {}))
|
||||
|
||||
def test_set_max_age(self):
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
value = "record test"
|
||||
|
||||
db = pyzor.engines.redis_.RedisDBHandle(",,,", None,
|
||||
max_age=self.max_age)
|
||||
db[digest] = value
|
||||
|
||||
expected = ("pyzord.digest.%s" % digest, self.max_age, value)
|
||||
self.assertEqual(self.commands[1], ("setex", expected, {}))
|
||||
|
||||
def test_get(self):
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
db = pyzor.engines.redis_.RedisDBHandle(",,,", None)
|
||||
result = db[digest]
|
||||
|
||||
expected = ("pyzord.digest.%s" % digest,)
|
||||
self.assertEqual(self.commands[1], ("get", expected, {}))
|
||||
|
||||
def test_delete(self):
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
|
||||
db = pyzor.engines.redis_.RedisDBHandle(",,,", None)
|
||||
del db[digest]
|
||||
|
||||
expected = ("pyzord.digest.%s" % digest,)
|
||||
self.assertEqual(self.commands[1], ("delete", expected, {}))
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(EncodingRedisTest))
|
||||
test_suite.addTest(unittest.makeSuite(RedisTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
341
mail/spamassassin/pyzor-0.7.0/tests/unit/test_server.py
Normal file
341
mail/spamassassin/pyzor-0.7.0/tests/unit/test_server.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""Test the pyzor.server module
|
||||
"""
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import hashlib
|
||||
import unittest
|
||||
import SocketServer
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pyzor
|
||||
import pyzor.server
|
||||
import pyzor.engines.common
|
||||
|
||||
class MockServer():
|
||||
"""Mocks the pyzor.server.Server class"""
|
||||
|
||||
def __init__(self):
|
||||
self.log = logging.getLogger("pyzord")
|
||||
self.usage_log = logging.getLogger("pyzord-usage")
|
||||
self.log.addHandler(logging.NullHandler())
|
||||
self.usage_log.addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class MockDatagramRequestHandler():
|
||||
""" Mock the SocketServer.DatagramRequestHand."""
|
||||
|
||||
def __init__(self, headers, database=None, acl=None, accounts=None):
|
||||
"""Initiates an request handler and set's the data in `headers` as
|
||||
the request. Also set's the database, acl and accounts for the
|
||||
MockServer.
|
||||
|
||||
This will be set as base class for RequestHandler.
|
||||
"""
|
||||
self.rfile = io.BytesIO()
|
||||
self.wfile = io.BytesIO()
|
||||
for i, j in headers.iteritems():
|
||||
self.rfile.write(("%s: %s\n" % (i, j)).encode("utf8"))
|
||||
self.rfile.seek(0)
|
||||
self.packet = None
|
||||
self.client_address = ["127.0.0.1"]
|
||||
|
||||
# Setup MockServer data
|
||||
self.server = MockServer()
|
||||
self.server.database = database
|
||||
if acl:
|
||||
self.server.acl = acl
|
||||
else:
|
||||
self.server.acl = {pyzor.anonymous_user: ("check", "report", "ping", "info", "whitelist",)}
|
||||
self.server.accounts = accounts
|
||||
|
||||
self.handle()
|
||||
|
||||
def handle(self):
|
||||
pass
|
||||
|
||||
|
||||
class RequestHandlerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.real_drh = SocketServer.DatagramRequestHandler
|
||||
SocketServer.DatagramRequestHandler = MockDatagramRequestHandler
|
||||
pyzor.server.RequestHandler.__bases__ = (MockDatagramRequestHandler,)
|
||||
|
||||
# setup the basic values for request and response
|
||||
self.request = {"User": pyzor.anonymous_user,
|
||||
"Time": str(int(time.time())),
|
||||
"PV": str(pyzor.proto_version),
|
||||
"Thread": "3597"}
|
||||
self.expected_response = {"Code": "200",
|
||||
"Diag": "OK",
|
||||
"PV": str(pyzor.proto_version),
|
||||
"Thread": "3597"}
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
SocketServer.DatagramRequestHandler = self.real_drh
|
||||
pyzor.server.RequestHandler.__bases__ = (self.real_drh,)
|
||||
|
||||
def check_response(self, handler):
|
||||
"""Checks if the response from the handler is equal to
|
||||
the expected response.
|
||||
"""
|
||||
handler.wfile.seek(0)
|
||||
response = handler.wfile.read()
|
||||
response = response.decode("utf8").replace("\n\n", "\n")
|
||||
|
||||
result = {}
|
||||
for line in response.splitlines():
|
||||
key = line.split(":", 1)[0].strip()
|
||||
value = line.split(":")[1].strip()
|
||||
result[key] = value
|
||||
|
||||
self.assertEqual(result, self.expected_response)
|
||||
|
||||
def timestamp(self, time_obj):
|
||||
if not time_obj:
|
||||
return 0
|
||||
else:
|
||||
return str(int(time.mktime(time_obj.timetuple())))
|
||||
|
||||
def test_ping(self):
|
||||
"""Tests the ping command handler"""
|
||||
self.request["Op"] = "ping"
|
||||
handler = pyzor.server.RequestHandler(self.request)
|
||||
|
||||
self.check_response(handler)
|
||||
|
||||
def test_pong(self):
|
||||
"""Tests the pong command handler"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {digest: pyzor.engines.common.Record(24, 42)}
|
||||
|
||||
self.request["Op"] = "pong"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
self.expected_response["Count"] = str(sys.maxint)
|
||||
self.expected_response["WL-Count"] = "0"
|
||||
|
||||
def test_check(self):
|
||||
"""Tests the check command handler"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {digest: pyzor.engines.common.Record(24, 42)}
|
||||
|
||||
self.request["Op"] = "check"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
self.expected_response["Count"] = "24"
|
||||
self.expected_response["WL-Count"] = "42"
|
||||
|
||||
self.check_response(handler)
|
||||
|
||||
def test_check_new(self):
|
||||
"""Tests the check command handler with a new record"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {}
|
||||
|
||||
self.request["Op"] = "check"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
self.expected_response["Count"] = "0"
|
||||
self.expected_response["WL-Count"] = "0"
|
||||
|
||||
self.check_response(handler)
|
||||
|
||||
def test_info(self):
|
||||
"""Tests the info command handler"""
|
||||
entered = datetime.now() - timedelta(days=10)
|
||||
updated = datetime.now()
|
||||
wl_entered = datetime.now() - timedelta(days=20)
|
||||
wl_updated = datetime.now() - timedelta(days=2)
|
||||
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {digest: pyzor.engines.common.Record(24, 42, entered, updated,
|
||||
wl_entered, wl_updated)}
|
||||
self.request["Op"] = "info"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
self.expected_response["Count"] = "24"
|
||||
self.expected_response["WL-Count"] = "42"
|
||||
self.expected_response["Entered"] = self.timestamp(entered)
|
||||
self.expected_response["Updated"] = self.timestamp(updated)
|
||||
self.expected_response["WL-Entered"] = self.timestamp(wl_entered)
|
||||
self.expected_response["WL-Updated"] = self.timestamp(wl_updated)
|
||||
|
||||
self.check_response(handler)
|
||||
|
||||
def test_info_new(self):
|
||||
"""Tests the info command handler with a new record"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {}
|
||||
self.request["Op"] = "info"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
self.expected_response["Count"] = "0"
|
||||
self.expected_response["WL-Count"] = "0"
|
||||
self.expected_response["Entered"] = "0"
|
||||
self.expected_response["Updated"] = "0"
|
||||
self.expected_response["WL-Entered"] = "0"
|
||||
self.expected_response["WL-Updated"] = "0"
|
||||
|
||||
self.check_response(handler)
|
||||
|
||||
def test_report(self):
|
||||
"""Tests the report command handler"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {digest: pyzor.engines.common.Record(24, 42)}
|
||||
|
||||
self.request["Op"] = "report"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
|
||||
self.check_response(handler)
|
||||
self.assertEqual(database[digest].r_count, 25)
|
||||
|
||||
def test_report_new(self):
|
||||
"""Tests the report command handler with a new record"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {}
|
||||
|
||||
self.request["Op"] = "report"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
|
||||
self.check_response(handler)
|
||||
self.assertEqual(database[digest].r_count, 1)
|
||||
|
||||
def test_whitelist(self):
|
||||
"""Tests the whitelist command handler"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {digest: pyzor.engines.common.Record(24, 42)}
|
||||
|
||||
self.request["Op"] = "whitelist"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
|
||||
self.check_response(handler)
|
||||
self.assertEqual(database[digest].wl_count, 43)
|
||||
|
||||
def test_whitelist_new(self):
|
||||
"""Tests the whitelist command handler with a new record"""
|
||||
digest = "2aedaac999d71421c9ee49b9d81f627a7bc570aa"
|
||||
database = {}
|
||||
|
||||
self.request["Op"] = "whitelist"
|
||||
self.request["Op-Digest"] = digest
|
||||
handler = pyzor.server.RequestHandler(self.request, database)
|
||||
|
||||
self.check_response(handler)
|
||||
self.assertEqual(database[digest].wl_count, 1)
|
||||
|
||||
def test_handle_no_version(self):
|
||||
"""Tests handling an request with no version specified"""
|
||||
self.request["Op"] = "ping"
|
||||
del self.request["PV"]
|
||||
handler = pyzor.server.RequestHandler(self.request)
|
||||
|
||||
self.expected_response["Code"] = "400"
|
||||
self.expected_response["Diag"] = "Bad request"
|
||||
self.check_response(handler)
|
||||
|
||||
def test_handle_unsupported_version(self):
|
||||
"""Tests handling an request with an unsupported version specified"""
|
||||
self.request["Op"] = "ping"
|
||||
self.request["PV"] = str(pyzor.proto_version + 2)
|
||||
handler = pyzor.server.RequestHandler(self.request)
|
||||
|
||||
self.expected_response["Code"] = "505"
|
||||
self.expected_response["Diag"] = "Version Not Supported"
|
||||
self.check_response(handler)
|
||||
|
||||
def test_handle_not_implemented(self):
|
||||
"""Tests handling an request with an unimplemented command"""
|
||||
self.request["Op"] = "notimplemented"
|
||||
acl = {pyzor.anonymous_user: "notimplemented"}
|
||||
handler = pyzor.server.RequestHandler(self.request, acl=acl)
|
||||
|
||||
self.expected_response["Code"] = "501"
|
||||
self.expected_response["Diag"] = "Not implemented"
|
||||
self.check_response(handler)
|
||||
|
||||
def test_handle_unauthorized(self):
|
||||
"""Tests handling an request with an unauthorized command"""
|
||||
self.request["Op"] = "report"
|
||||
acl = {pyzor.anonymous_user: ("ping", "check")}
|
||||
handler = pyzor.server.RequestHandler(self.request, acl=acl)
|
||||
|
||||
self.expected_response["Code"] = "403"
|
||||
self.expected_response["Diag"] = "Forbidden"
|
||||
self.check_response(handler)
|
||||
|
||||
def test_handle_account(self):
|
||||
"""Tests handling an request where user is not anonymous"""
|
||||
self.request["Op"] = "ping"
|
||||
self.request["User"] = "testuser"
|
||||
acl = {"testuser": ("ping", "check")}
|
||||
accounts = {"testuser": "testkey"}
|
||||
|
||||
mock_vs = lambda x, y: None
|
||||
real_vs = pyzor.account.verify_signature
|
||||
pyzor.account.verify_signature = mock_vs
|
||||
try:
|
||||
handler = pyzor.server.RequestHandler(self.request, acl=acl,
|
||||
accounts=accounts)
|
||||
self.check_response(handler)
|
||||
finally:
|
||||
pyzor.account.verify_signature = real_vs
|
||||
|
||||
def test_handle_unknown_account(self):
|
||||
"""Tests handling an request where user is unkwown"""
|
||||
self.request["Op"] = "ping"
|
||||
self.request["User"] = "testuser"
|
||||
acl = {"testuser": ("ping", "check")}
|
||||
accounts = {}
|
||||
|
||||
self.expected_response["Code"] = "401"
|
||||
self.expected_response["Diag"] = "Unauthorized"
|
||||
|
||||
def mock_vs(x, y):
|
||||
pass
|
||||
real_vs = pyzor.account.verify_signature
|
||||
pyzor.account.verify_signature = mock_vs
|
||||
try:
|
||||
handler = pyzor.server.RequestHandler(self.request, acl=acl,
|
||||
accounts=accounts)
|
||||
self.check_response(handler)
|
||||
finally:
|
||||
pyzor.account.verify_signature = real_vs
|
||||
|
||||
def test_handle_invalid_signature(self):
|
||||
"""Tests handling an request where user key is invalid"""
|
||||
self.request["Op"] = "ping"
|
||||
self.request["User"] = "testuser"
|
||||
acl = {"testuser": ("ping", "check")}
|
||||
accounts = {"testuser": ("ping", "check")}
|
||||
|
||||
self.expected_response["Code"] = "401"
|
||||
self.expected_response["Diag"] = "Unauthorized"
|
||||
|
||||
def mock_vs(x, y):
|
||||
raise pyzor.SignatureError("Invalid signature.")
|
||||
real_vs = pyzor.account.verify_signature
|
||||
pyzor.account.verify_signature = mock_vs
|
||||
try:
|
||||
handler = pyzor.server.RequestHandler(self.request, acl=acl,
|
||||
accounts=accounts)
|
||||
self.check_response(handler)
|
||||
finally:
|
||||
pyzor.account.verify_signature = real_vs
|
||||
|
||||
def suite():
|
||||
"""Gather all the tests from this module in a test suite."""
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.makeSuite(RequestHandlerTest))
|
||||
return test_suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
341
mail/spamassassin/pyzor-0.7.0/tests/util/__init__.py
Normal file
341
mail/spamassassin/pyzor-0.7.0/tests/util/__init__.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""This package contains various utilities use in the pyzor tests."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import shutil
|
||||
import unittest
|
||||
import subprocess
|
||||
import ConfigParser
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
msg = """Newsgroups:
|
||||
Date: Wed, 10 Apr 2002 22:23:51 -0400 (EDT)
|
||||
From: Frank Tobin <ftobin@neverending.org>
|
||||
Fcc: sent-mail
|
||||
Message-ID: <20020410222350.E16178@palanthas.neverending.org>
|
||||
X-Our-Headers: X-Bogus,Anon-To
|
||||
X-Bogus: aaron7@neverending.org
|
||||
MIME-Version: 1.0
|
||||
Content-Type: TEXT/PLAIN; charset=US-ASCII
|
||||
|
||||
Test Email
|
||||
"""
|
||||
|
||||
digest = "7421216f915a87e02da034cc483f5c876e1a1338"
|
||||
|
||||
_dt_decode = lambda x: None if x == 'None' else datetime.strptime(x, "%a %b %d %H:%M:%S %Y")
|
||||
|
||||
class PyzorTestBase(unittest.TestCase):
|
||||
"""Test base that starts the pyzord daemon in setUpClass with specified
|
||||
arguments. The daemon is killed in tearDownClass. This also create the
|
||||
necessary files and the homedir.
|
||||
"""
|
||||
pyzord = None
|
||||
_args = {"homedir": "--homedir",
|
||||
"engine": "-e",
|
||||
"dsn": "--dsn",
|
||||
"address": "-a",
|
||||
"port": "-p",
|
||||
"threads": "--threads",
|
||||
"max_threads": "--max-threads",
|
||||
"processes": "--processes",
|
||||
"max_processes": "--max-processes",
|
||||
"db_connections": "--db-connections",
|
||||
"password_file": "--password-file",
|
||||
"access_file": "--access-file",
|
||||
"cleanup_age": "--cleanup-age",
|
||||
"log_file": "--log-file",
|
||||
}
|
||||
homedir = "./pyzor-test/"
|
||||
address = "127.0.0.1"
|
||||
port = "9999"
|
||||
threads = "False"
|
||||
access_file = "pyzord.access"
|
||||
password_file = "pyzord.passwd"
|
||||
log_file = "pyzord-test.log"
|
||||
|
||||
access = """check report ping pong info whitelist : alice : deny
|
||||
check report ping pong info whitelist : bob : allow
|
||||
ALL : dan : allow
|
||||
pong info whitelist : dan : deny
|
||||
"""
|
||||
passwd = """alice : fc7f1cad729b5f3862b2ef192e2d9e0d0d4bd515
|
||||
bob : cf88277c5d4abdc0a3f56f416011966d04a3f462
|
||||
dan : c1a50281fc43e860fe78c16c73b9618ada59f959
|
||||
"""
|
||||
servers = """127.0.0.1:9999
|
||||
"""
|
||||
accounts_alice = """127.0.0.1 : 9999 : alice : d28f86151e80a9accba4a4eba81c460532384cd6,fc7f1cad729b5f3862b2ef192e2d9e0d0d4bd515
|
||||
"""
|
||||
accounts_bob = """127.0.0.1 : 9999 : bob : de6ef568787256bf5f55909dc0c398e49b5c9808,cf88277c5d4abdc0a3f56f416011966d04a3f462
|
||||
"""
|
||||
accounts_chuck = """127.0.0.1 : 9999 : bob : de6ef568787256bf5f55909dc0c398e49b5c9808,af88277c5d4abdc0a3f56f416011966d04a3f462
|
||||
"""
|
||||
accounts_dan = """127.0.0.1 : 9999 : dan : 1cc2efa77d8833d83556e0cc4fa617c64eebc7fb,c1a50281fc43e860fe78c16c73b9618ada59f959
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def write_homedir_file(cls, name, content):
|
||||
if not name or not content:
|
||||
return
|
||||
with open(os.path.join(cls.homedir, name), "w") as f:
|
||||
f.write(content)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(PyzorTestBase, cls).setUpClass()
|
||||
try:
|
||||
os.mkdir(cls.homedir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
cls.write_homedir_file(cls.access_file, cls.access)
|
||||
cls.write_homedir_file(cls.password_file, cls.passwd)
|
||||
cls.write_homedir_file(cls.password_file, cls.passwd)
|
||||
|
||||
cls.write_homedir_file("servers", cls.servers)
|
||||
cls.write_homedir_file("alice", cls.accounts_alice)
|
||||
cls.write_homedir_file("bob", cls.accounts_bob)
|
||||
cls.write_homedir_file("chuck", cls.accounts_chuck)
|
||||
cls.write_homedir_file("dan", cls.accounts_dan)
|
||||
|
||||
args = ["pyzord"]
|
||||
for key, value in cls._args.iteritems():
|
||||
option = getattr(cls, key, None)
|
||||
if option:
|
||||
args.append(value)
|
||||
args.append(option)
|
||||
cls.pyzord = subprocess.Popen(args)
|
||||
time.sleep(0.3) # allow time to initialize server
|
||||
|
||||
def setUp(self):
|
||||
unittest.TestCase.setUp(self)
|
||||
self.client_args = {"--homedir": self.homedir,
|
||||
"--servers-file": "servers",
|
||||
"-t": None, # timeout
|
||||
"-r": None, # report threshold
|
||||
"-w": None, # whitelist threshold
|
||||
"-s": None, # style
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(PyzorTestBase, cls).tearDownClass()
|
||||
cls.pyzord.kill()
|
||||
shutil.rmtree(cls.homedir, True)
|
||||
|
||||
def check_pyzor(self, cmd, user, input=None,
|
||||
code=None, exit_code=None, counts=()):
|
||||
"""Call the pyzor client with the specified args from self.client_args
|
||||
and verifies the response.
|
||||
"""
|
||||
args = ["pyzor"]
|
||||
if user:
|
||||
args.append("--accounts-file")
|
||||
args.append(user)
|
||||
for key, value in self.client_args.iteritems():
|
||||
if value:
|
||||
args.append(key)
|
||||
args.append(value)
|
||||
args.append(cmd)
|
||||
pyzor = subprocess.Popen(args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
if input:
|
||||
stdout, stderr = pyzor.communicate(input.encode("utf8"))
|
||||
else:
|
||||
stdout, stderr = pyzor.communicate()
|
||||
|
||||
if stderr:
|
||||
self.fail(stderr)
|
||||
if code is not None:
|
||||
try:
|
||||
stdout = stdout.decode("utf8")
|
||||
results = stdout.strip().split("\t")
|
||||
status = eval(results[1])
|
||||
except Exception as e:
|
||||
self.fail("Parsing error: %s of %r" % (e, stdout))
|
||||
self.assertEqual(status[0], code, status)
|
||||
|
||||
if counts:
|
||||
self.assertEqual(counts, (int(results[2]), int(results[3])))
|
||||
|
||||
if exit_code is not None:
|
||||
self.assertEqual(exit_code, pyzor.returncode)
|
||||
return stdout
|
||||
|
||||
def check_pyzor_multiple(self, cmd, user, input=None,
|
||||
code=None, exit_code=None, counts=()):
|
||||
"""Call the pyzor client with the specified args from self.client_args
|
||||
and verifies the response.
|
||||
"""
|
||||
args = ["pyzor"]
|
||||
if user:
|
||||
args.append("--accounts-file")
|
||||
args.append(user)
|
||||
for key, value in self.client_args.iteritems():
|
||||
if value:
|
||||
args.append(key)
|
||||
args.append(value)
|
||||
args.append(cmd)
|
||||
pyzor = subprocess.Popen(args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
if input:
|
||||
stdout, stderr = pyzor.communicate(input.encode("utf8"))
|
||||
else:
|
||||
stdout, stderr = pyzor.communicate()
|
||||
|
||||
if stderr:
|
||||
self.fail(stderr)
|
||||
|
||||
stdout = stdout.decode("utf8")
|
||||
for i, line in enumerate(stdout.splitlines()):
|
||||
try:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
results = line.strip().split("\t")
|
||||
except Exception as e:
|
||||
self.fail("Parsing error: %s of %r" % (e, stdout))
|
||||
if code is not None:
|
||||
try:
|
||||
status = eval(results[1])
|
||||
except Exception as e:
|
||||
self.fail("Parsing error: %s of %r" % (e, stdout))
|
||||
self.assertEqual(status[0], code[i], status)
|
||||
if counts:
|
||||
self.assertEqual((int(results[2]), int(results[3])),
|
||||
counts[i])
|
||||
|
||||
if exit_code is not None:
|
||||
self.assertEqual(exit_code, pyzor.returncode)
|
||||
return stdout
|
||||
|
||||
def get_record(self, input, user="bob"):
|
||||
"""Uses `pyzor info` to get the record data."""
|
||||
stdout = self.check_pyzor("info", user, input, code=200, exit_code=0)
|
||||
info = stdout.splitlines()[1:]
|
||||
record = {}
|
||||
try:
|
||||
for line in info:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
key, value = line.split(":", 1)
|
||||
record[key.strip()] = value.strip()
|
||||
except Exception as e:
|
||||
self.fail("Error parsing %r: %s" % (info, e))
|
||||
return record
|
||||
|
||||
def check_fuzzy_date(self, date1, date2=None, seconds=5):
|
||||
"""Check if the given date is almost equal to now."""
|
||||
date1 = _dt_decode(date1)
|
||||
if not date2:
|
||||
date2 = datetime.now()
|
||||
delta = abs((date2 - date1).total_seconds())
|
||||
if delta > seconds:
|
||||
self.fail("Delta %s is too big: %s, %s" % (delta , date1, date2))
|
||||
|
||||
class PyzorTest(object):
|
||||
"""MixIn class for PyzorTestBase that performs a series of basic tests."""
|
||||
def test_ping(self):
|
||||
self.check_pyzor("ping", "bob")
|
||||
|
||||
def test_pong(self):
|
||||
input = "Test1 pong1 Test2"
|
||||
self.check_pyzor("pong", "bob", input=input, code=200, exit_code=0,
|
||||
counts=(sys.maxint, 0))
|
||||
|
||||
def test_check(self):
|
||||
input = "Test1 check1 Test2"
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(0, 0))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["Count"], "0")
|
||||
|
||||
def test_report(self):
|
||||
input = "Test1 report1 Test2"
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=0,
|
||||
counts=(1, 0))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["Count"], "1")
|
||||
self.check_fuzzy_date(r["Entered"])
|
||||
|
||||
def test_report_update(self):
|
||||
input = "Test1 report update1 Test2"
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=0,
|
||||
counts=(1, 0))
|
||||
time.sleep(1)
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=0,
|
||||
counts=(2, 0))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["Count"], "2")
|
||||
self.assertNotEqual(r["Entered"], r["Updated"])
|
||||
self.check_fuzzy_date(r["Updated"])
|
||||
|
||||
def test_whitelist(self):
|
||||
input = "Test1 white list1 Test2"
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(0, 1))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["WL-Count"], "1")
|
||||
self.check_fuzzy_date(r["WL-Entered"])
|
||||
|
||||
def test_whitelist_update(self):
|
||||
input = "Test1 white list update1 Test2"
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(0, 1))
|
||||
time.sleep(1)
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(0, 2))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["WL-Count"], "2")
|
||||
self.assertNotEqual(r["WL-Entered"], r["WL-Updated"])
|
||||
self.check_fuzzy_date(r["WL-Updated"])
|
||||
|
||||
def test_report_whitelist(self):
|
||||
input = "Test1 white list report1 Test2"
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(1, 1))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["Count"], "1")
|
||||
self.check_fuzzy_date(r["Entered"])
|
||||
self.assertEqual(r["WL-Count"], "1")
|
||||
self.check_fuzzy_date(r["WL-Entered"])
|
||||
|
||||
def test_report_whitelist_update(self):
|
||||
input = "Test1 white list report update1 Test2"
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(1, 1))
|
||||
time.sleep(1)
|
||||
self.check_pyzor("whitelist", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("report", "bob", input=input, code=200, exit_code=0)
|
||||
self.check_pyzor("check", "bob", input=input, code=200, exit_code=1,
|
||||
counts=(2, 2))
|
||||
r = self.get_record(input)
|
||||
self.assertEqual(r["Count"], "2")
|
||||
self.assertNotEqual(r["Entered"], r["Updated"])
|
||||
self.check_fuzzy_date(r["Updated"])
|
||||
|
||||
self.assertEqual(r["WL-Count"], "2")
|
||||
self.assertNotEqual(r["WL-Entered"], r["WL-Updated"])
|
||||
self.check_fuzzy_date(r["WL-Updated"])
|
||||
|
||||
Reference in New Issue
Block a user