Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
b7e8af65
Commit
b7e8af65
authored
Aug 30, 2013
by
Jason Bau
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #841 from edx/jbau/bulk-email-faster-tests
Jbau/bulk email faster tests
parents
8d7d8194
4f9e5109
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
17 additions
and
172 deletions
+17
-172
lms/djangoapps/bulk_email/tests/fake_smtp.py
+0
-88
lms/djangoapps/bulk_email/tests/smtp_server_thread.py
+0
-47
lms/djangoapps/bulk_email/tests/test_err_handling.py
+17
-37
No files found.
lms/djangoapps/bulk_email/tests/fake_smtp.py
deleted
100755 → 0
View file @
8d7d8194
"""
Fake SMTP Server used for testing error handling for sending email.
We could have mocked smptlib to raise connection errors, but this simulates
connection errors from an SMTP server.
"""
import
smtpd
import
socket
import
asyncore
import
asynchat
import
errno
class
FakeSMTPChannel
(
smtpd
.
SMTPChannel
):
"""
A fake SMTPChannel for sending fake error response through socket.
This causes smptlib to raise an SMTPConnectError.
Adapted from http://hg.python.org/cpython/file/2.7/Lib/smtpd.py
"""
# Disable pylint warnings that arise from subclassing SMTPChannel
# and calling init -- overriding SMTPChannel's init to return error
# message but keeping the rest of the class.
# pylint: disable=W0231, W0233
def
__init__
(
self
,
server
,
conn
,
addr
):
asynchat
.
async_chat
.
__init__
(
self
,
conn
)
self
.
__server
=
server
self
.
__conn
=
conn
self
.
__addr
=
addr
self
.
__line
=
[]
self
.
__state
=
self
.
COMMAND
self
.
__greeting
=
0
self
.
__mailfrom
=
None
self
.
__rcpttos
=
[]
self
.
__data
=
''
self
.
__fqdn
=
socket
.
getfqdn
()
try
:
self
.
__peer
=
conn
.
getpeername
()
except
socket
.
error
,
err
:
# a race condition may occur if the other end is closing
# before we can get the peername
self
.
close
()
if
err
[
0
]
!=
errno
.
ENOTCONN
:
raise
return
self
.
push
(
'421 SMTP Server error: too many concurrent sessions, please try again later.'
)
self
.
set_terminator
(
'
\r\n
'
)
class
FakeSMTPServer
(
smtpd
.
SMTPServer
):
"""A fake SMTP server for generating different smptlib exceptions."""
def
__init__
(
self
,
*
args
,
**
kwargs
):
smtpd
.
SMTPServer
.
__init__
(
self
,
*
args
,
**
kwargs
)
self
.
errtype
=
None
self
.
response
=
None
def
set_errtype
(
self
,
errtype
,
response
=
''
):
"""Specify the type of error to cause smptlib to raise, with optional response string.
`errtype` -- "DATA": The server will cause smptlib to throw SMTPDataError.
"CONN": The server will cause smptlib to throw SMTPConnectError.
"DISCONN": The server will cause smptlib to throw SMTPServerDisconnected.
"""
self
.
errtype
=
errtype
self
.
response
=
response
def
handle_accept
(
self
):
if
self
.
errtype
==
"DISCONN"
:
self
.
accept
()
elif
self
.
errtype
==
"CONN"
:
pair
=
self
.
accept
()
if
pair
is
not
None
:
conn
,
addr
=
pair
_channel
=
FakeSMTPChannel
(
self
,
conn
,
addr
)
else
:
smtpd
.
SMTPServer
.
handle_accept
(
self
)
def
process_message
(
self
,
*
_args
,
**
_kwargs
):
if
self
.
errtype
==
"DATA"
:
# After failing on the first email, succeed on the rest.
self
.
errtype
=
None
return
self
.
response
else
:
return
None
def
serve_forever
(
self
):
"""Start the server running until close() is called on the server."""
asyncore
.
loop
()
lms/djangoapps/bulk_email/tests/smtp_server_thread.py
deleted
100644 → 0
View file @
8d7d8194
"""
Defines a class for a thread that runs a Fake SMTP server, used for testing
error handling from sending email.
"""
import
threading
from
bulk_email.tests.fake_smtp
import
FakeSMTPServer
class
FakeSMTPServerThread
(
threading
.
Thread
):
"""
Thread for running a fake SMTP server
"""
def
__init__
(
self
,
host
,
port
):
self
.
host
=
host
self
.
port
=
port
self
.
is_ready
=
threading
.
Event
()
self
.
error
=
None
self
.
server
=
None
super
(
FakeSMTPServerThread
,
self
)
.
__init__
()
def
start
(
self
):
self
.
daemon
=
True
super
(
FakeSMTPServerThread
,
self
)
.
start
()
self
.
is_ready
.
wait
()
if
self
.
error
:
raise
self
.
error
# pylint: disable=E0702
def
stop
(
self
):
"""
Stop the thread by closing the server instance.
Wait for the server thread to terminate.
"""
if
hasattr
(
self
,
'server'
):
self
.
server
.
close
()
self
.
join
()
def
run
(
self
):
"""
Sets up the test smtp server and handle requests.
"""
try
:
self
.
server
=
FakeSMTPServer
((
self
.
host
,
self
.
port
),
None
)
self
.
is_ready
.
set
()
self
.
server
.
serve_forever
()
except
Exception
,
exc
:
# pylint: disable=W0703
self
.
error
=
exc
self
.
is_ready
.
set
()
lms/djangoapps/bulk_email/tests/test_err_handling.py
View file @
b7e8af65
"""
"""
Unit tests for handling email sending errors
Unit tests for handling email sending errors
"""
"""
from
itertools
import
cycle
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.management
import
call_command
from
django.core.management
import
call_command
...
@@ -14,24 +14,16 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF
...
@@ -14,24 +14,16 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF
from
bulk_email.models
import
CourseEmail
from
bulk_email.models
import
CourseEmail
from
bulk_email.tasks
import
delegate_email_batches
from
bulk_email.tasks
import
delegate_email_batches
from
bulk_email.tests.smtp_server_thread
import
FakeSMTPServerThread
from
mock
import
patch
,
Mock
from
mock
import
patch
,
Mock
from
smtplib
import
SMTPDataError
,
SMTPServerDisconnected
,
SMTPConnectError
from
smtplib
import
SMTPDataError
,
SMTPServerDisconnected
,
SMTPConnectError
TEST_SMTP_PORT
=
1025
class
EmailTestException
(
Exception
):
class
EmailTestException
(
Exception
):
pass
pass
@override_settings
(
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
,
EMAIL_BACKEND
=
'django.core.mail.backends.smtp.EmailBackend'
,
EMAIL_HOST
=
'localhost'
,
EMAIL_PORT
=
TEST_SMTP_PORT
)
class
TestEmailErrors
(
ModuleStoreTestCase
):
class
TestEmailErrors
(
ModuleStoreTestCase
):
"""
"""
Test that errors from sending email are handled properly.
Test that errors from sending email are handled properly.
...
@@ -44,26 +36,18 @@ class TestEmailErrors(ModuleStoreTestCase):
...
@@ -44,26 +36,18 @@ class TestEmailErrors(ModuleStoreTestCase):
# load initial content (since we don't run migrations as part of tests):
# load initial content (since we don't run migrations as part of tests):
call_command
(
"loaddata"
,
"course_email_template.json"
)
call_command
(
"loaddata"
,
"course_email_template.json"
)
self
.
smtp_server_thread
=
FakeSMTPServerThread
(
'localhost'
,
TEST_SMTP_PORT
)
self
.
smtp_server_thread
.
start
()
self
.
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
})
self
.
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
})
def
tearDown
(
self
):
def
tearDown
(
self
):
self
.
smtp_server_thread
.
stop
()
patch
.
stopall
()
patch
.
stopall
()
@patch
(
'bulk_email.tasks.get_connection'
,
autospec
=
True
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
def
test_data_err_retry
(
self
,
retry
):
def
test_data_err_retry
(
self
,
retry
,
get_conn
):
"""
"""
Test that celery handles transient SMTPDataErrors by retrying.
Test that celery handles transient SMTPDataErrors by retrying.
"""
"""
self
.
smtp_server_thread
.
server
.
set_errtype
(
get_conn
.
return_value
.
send_messages
.
side_effect
=
SMTPDataError
(
455
,
"Throttling: Sending rate exceeded"
)
"DATA"
,
"454 Throttling failure: Daily message quota exceeded."
)
test_email
=
{
test_email
=
{
'action'
:
'Send email'
,
'action'
:
'Send email'
,
'to_option'
:
'myself'
,
'to_option'
:
'myself'
,
...
@@ -78,17 +62,15 @@ class TestEmailErrors(ModuleStoreTestCase):
...
@@ -78,17 +62,15 @@ class TestEmailErrors(ModuleStoreTestCase):
exc
=
kwargs
[
'exc'
]
exc
=
kwargs
[
'exc'
]
self
.
assertTrue
(
type
(
exc
)
==
SMTPDataError
)
self
.
assertTrue
(
type
(
exc
)
==
SMTPDataError
)
@patch
(
'bulk_email.tasks.get_connection'
,
autospec
=
True
)
@patch
(
'bulk_email.tasks.course_email_result'
)
@patch
(
'bulk_email.tasks.course_email_result'
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
def
test_data_err_fail
(
self
,
retry
,
result
):
def
test_data_err_fail
(
self
,
retry
,
result
,
get_conn
):
"""
"""
Test that celery handles permanent SMTPDataErrors by failing and not retrying.
Test that celery handles permanent SMTPDataErrors by failing and not retrying.
"""
"""
self
.
smtp_server_thread
.
server
.
set_errtype
(
get_conn
.
return_value
.
send_messages
.
side_effect
=
cycle
([
SMTPDataError
(
554
,
"Email address is blacklisted"
),
"DATA"
,
None
])
"554 Message rejected: Email address is not verified."
)
students
=
[
UserFactory
()
for
_
in
xrange
(
settings
.
EMAILS_PER_TASK
)]
students
=
[
UserFactory
()
for
_
in
xrange
(
settings
.
EMAILS_PER_TASK
)]
for
student
in
students
:
for
student
in
students
:
CourseEnrollmentFactory
.
create
(
user
=
student
,
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
.
create
(
user
=
student
,
course_id
=
self
.
course
.
id
)
...
@@ -106,18 +88,16 @@ class TestEmailErrors(ModuleStoreTestCase):
...
@@ -106,18 +88,16 @@ class TestEmailErrors(ModuleStoreTestCase):
# Test that after the rejected email, the rest still successfully send
# Test that after the rejected email, the rest still successfully send
((
sent
,
fail
,
optouts
),
_
)
=
result
.
call_args
((
sent
,
fail
,
optouts
),
_
)
=
result
.
call_args
self
.
assertEquals
(
optouts
,
0
)
self
.
assertEquals
(
optouts
,
0
)
self
.
assertEquals
(
fail
,
1
)
self
.
assertEquals
(
fail
,
settings
.
EMAILS_PER_TASK
/
2
)
self
.
assertEquals
(
sent
,
settings
.
EMAILS_PER_TASK
-
1
)
self
.
assertEquals
(
sent
,
settings
.
EMAILS_PER_TASK
/
2
)
@patch
(
'bulk_email.tasks.get_connection'
,
autospec
=
True
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
def
test_disconn_err_retry
(
self
,
retry
):
def
test_disconn_err_retry
(
self
,
retry
,
get_conn
):
"""
"""
Test that celery handles SMTPServerDisconnected by retrying.
Test that celery handles SMTPServerDisconnected by retrying.
"""
"""
self
.
smtp_server_thread
.
server
.
set_errtype
(
get_conn
.
return_value
.
open
.
side_effect
=
SMTPServerDisconnected
(
425
,
"Disconnecting"
)
"DISCONN"
,
"Server disconnected, please try again later."
)
test_email
=
{
test_email
=
{
'action'
:
'Send email'
,
'action'
:
'Send email'
,
'to_option'
:
'myself'
,
'to_option'
:
'myself'
,
...
@@ -131,13 +111,13 @@ class TestEmailErrors(ModuleStoreTestCase):
...
@@ -131,13 +111,13 @@ class TestEmailErrors(ModuleStoreTestCase):
exc
=
kwargs
[
'exc'
]
exc
=
kwargs
[
'exc'
]
self
.
assertTrue
(
type
(
exc
)
==
SMTPServerDisconnected
)
self
.
assertTrue
(
type
(
exc
)
==
SMTPServerDisconnected
)
@patch
(
'bulk_email.tasks.get_connection'
,
autospec
=
True
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
@patch
(
'bulk_email.tasks.course_email.retry'
)
def
test_conn_err_retry
(
self
,
retry
):
def
test_conn_err_retry
(
self
,
retry
,
get_conn
):
"""
"""
Test that celery handles SMTPConnectError by retrying.
Test that celery handles SMTPConnectError by retrying.
"""
"""
# SMTP reply is already specified in fake SMTP Channel created
get_conn
.
return_value
.
open
.
side_effect
=
SMTPConnectError
(
424
,
"Bad Connection"
)
self
.
smtp_server_thread
.
server
.
set_errtype
(
"CONN"
)
test_email
=
{
test_email
=
{
'action'
:
'Send email'
,
'action'
:
'Send email'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment