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
2c80b1b4
Commit
2c80b1b4
authored
Oct 27, 2017
by
Nimisha Asthagiri
Committed by
GitHub
Oct 27, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16339 from edx/cale/dry-schedule-tests
Dry schedule tests
parents
764e598f
b461ce0c
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
454 additions
and
720 deletions
+454
-720
openedx/core/djangoapps/schedules/management/commands/__init__.py
+5
-3
openedx/core/djangoapps/schedules/management/commands/send_course_update.py
+2
-8
openedx/core/djangoapps/schedules/management/commands/send_recurring_nudge.py
+2
-8
openedx/core/djangoapps/schedules/management/commands/send_upgrade_reminder.py
+2
-7
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
+377
-0
openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py
+16
-1
openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py
+21
-310
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
+29
-383
No files found.
openedx/core/djangoapps/schedules/management/commands/__init__.py
View file @
2c80b1b4
...
@@ -9,6 +9,7 @@ from openedx.core.djangoapps.schedules.utils import PrefixedDebugLoggerMixin
...
@@ -9,6 +9,7 @@ from openedx.core.djangoapps.schedules.utils import PrefixedDebugLoggerMixin
class
SendEmailBaseCommand
(
PrefixedDebugLoggerMixin
,
BaseCommand
):
class
SendEmailBaseCommand
(
PrefixedDebugLoggerMixin
,
BaseCommand
):
async_send_task
=
None
# define in subclass
async_send_task
=
None
# define in subclass
offsets
=
()
# define in subclass
def
add_arguments
(
self
,
parser
):
def
add_arguments
(
self
,
parser
):
parser
.
add_argument
(
parser
.
add_argument
(
...
@@ -37,9 +38,6 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand):
...
@@ -37,9 +38,6 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand):
override_recipient_email
=
options
.
get
(
'override_recipient_email'
)
override_recipient_email
=
options
.
get
(
'override_recipient_email'
)
self
.
send_emails
(
site
,
current_date
,
override_recipient_email
)
self
.
send_emails
(
site
,
current_date
,
override_recipient_email
)
def
send_emails
(
self
,
*
args
,
**
kwargs
):
raise
NotImplementedError
def
enqueue
(
self
,
day_offset
,
site
,
current_date
,
override_recipient_email
=
None
):
def
enqueue
(
self
,
day_offset
,
site
,
current_date
,
override_recipient_email
=
None
):
self
.
async_send_task
.
enqueue
(
self
.
async_send_task
.
enqueue
(
site
,
site
,
...
@@ -47,3 +45,7 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand):
...
@@ -47,3 +45,7 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand):
day_offset
,
day_offset
,
override_recipient_email
,
override_recipient_email
,
)
)
def
send_emails
(
self
,
*
args
,
**
kwargs
):
for
offset
in
self
.
offsets
:
self
.
enqueue
(
offset
,
*
args
,
**
kwargs
)
openedx/core/djangoapps/schedules/management/commands/send_course_update.py
View file @
2c80b1b4
...
@@ -4,11 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleCourseUpdate
...
@@ -4,11 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleCourseUpdate
class
Command
(
SendEmailBaseCommand
):
class
Command
(
SendEmailBaseCommand
):
async_send_task
=
ScheduleCourseUpdate
async_send_task
=
ScheduleCourseUpdate
log_prefix
=
'Course Update'
def
__init__
(
self
,
*
args
,
**
kwargs
):
offsets
=
xrange
(
-
7
,
-
77
,
-
7
)
super
(
Command
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Upgrade Reminder'
def
send_emails
(
self
,
*
args
,
**
kwargs
):
for
day_offset
in
xrange
(
-
7
,
-
77
,
-
7
):
self
.
enqueue
(
day_offset
,
*
args
,
**
kwargs
)
openedx/core/djangoapps/schedules/management/commands/send_recurring_nudge.py
View file @
2c80b1b4
...
@@ -4,11 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleRecurringNudge
...
@@ -4,11 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleRecurringNudge
class
Command
(
SendEmailBaseCommand
):
class
Command
(
SendEmailBaseCommand
):
async_send_task
=
ScheduleRecurringNudge
async_send_task
=
ScheduleRecurringNudge
log_prefix
=
'Scheduled Nudge'
def
__init__
(
self
,
*
args
,
**
kwargs
):
offsets
=
(
-
3
,
-
10
)
super
(
Command
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Scheduled Nudge'
def
send_emails
(
self
,
*
args
,
**
kwargs
):
for
day_offset
in
(
-
3
,
-
10
):
self
.
enqueue
(
day_offset
,
*
args
,
**
kwargs
)
openedx/core/djangoapps/schedules/management/commands/send_upgrade_reminder.py
View file @
2c80b1b4
...
@@ -4,10 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleUpgradeReminder
...
@@ -4,10 +4,5 @@ from openedx.core.djangoapps.schedules.tasks import ScheduleUpgradeReminder
class
Command
(
SendEmailBaseCommand
):
class
Command
(
SendEmailBaseCommand
):
async_send_task
=
ScheduleUpgradeReminder
async_send_task
=
ScheduleUpgradeReminder
log_prefix
=
'Upgrade Reminder'
def
__init__
(
self
,
*
args
,
**
kwargs
):
offsets
=
(
2
,)
super
(
Command
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Upgrade Reminder'
def
send_emails
(
self
,
*
args
,
**
kwargs
):
self
.
enqueue
(
2
,
*
args
,
**
kwargs
)
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
0 → 100644
View file @
2c80b1b4
from
copy
import
deepcopy
import
datetime
import
ddt
import
logging
import
attr
from
django.conf
import
settings
from
freezegun
import
freeze_time
from
mock
import
Mock
,
patch
import
pytz
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
edx_ace.channel
import
ChannelType
from
edx_ace.utils.date
import
serialize
from
edx_ace.test_utils
import
StubPolicy
,
patch_channels
,
patch_policies
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteConfigurationFactory
,
SiteFactory
from
openedx.core.djangoapps.schedules
import
resolvers
,
tasks
from
openedx.core.djangoapps.schedules.resolvers
import
_get_datetime_beginning_of_day
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
SITE_QUERY
=
1
ORG_DEADLINE_QUERY
=
1
SCHEDULES_QUERY
=
1
COURSE_MODES_QUERY
=
1
GLOBAL_DEADLINE_SWITCH_QUERY
=
1
COMMERCE_CONFIG_QUERY
=
1
NUM_QUERIES_NO_ORG_LIST
=
1
NUM_QUERIES_NO_MATCHING_SCHEDULES
=
SITE_QUERY
+
SCHEDULES_QUERY
NUM_QUERIES_WITH_MATCHES
=
(
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
COURSE_MODES_QUERY
)
NUM_QUERIES_FIRST_MATCH
=
(
NUM_QUERIES_WITH_MATCHES
+
GLOBAL_DEADLINE_SWITCH_QUERY
+
ORG_DEADLINE_QUERY
+
COMMERCE_CONFIG_QUERY
)
LOG
=
logging
.
getLogger
(
__name__
)
@ddt.ddt
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
class
ScheduleSendEmailTestBase
(
SharedModuleStoreTestCase
):
__test__
=
False
ENABLED_CACHES
=
[
'default'
]
has_course_queries
=
False
def
setUp
(
self
):
super
(
ScheduleSendEmailTestBase
,
self
)
.
setUp
()
site
=
SiteFactory
.
create
()
self
.
site_config
=
SiteConfigurationFactory
.
create
(
site
=
site
)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
def
_calculate_bin_for_user
(
self
,
user
):
return
user
.
id
%
self
.
tested_task
.
num_bins
def
_get_dates
(
self
,
offset
=
None
):
current_day
=
_get_datetime_beginning_of_day
(
datetime
.
datetime
.
now
(
pytz
.
UTC
))
offset
=
offset
or
self
.
expected_offsets
[
0
]
target_day
=
current_day
+
datetime
.
timedelta
(
days
=
offset
)
return
current_day
,
offset
,
target_day
def
_get_template_overrides
(
self
):
templates_override
=
deepcopy
(
settings
.
TEMPLATES
)
templates_override
[
0
][
'OPTIONS'
][
'string_if_invalid'
]
=
"TEMPLATE WARNING - MISSING VARIABLE [
%
s]"
return
templates_override
def
test_command_task_binding
(
self
):
self
.
assertEqual
(
self
.
tested_command
.
async_send_task
,
self
.
tested_task
)
def
test_handle
(
self
):
with
patch
.
object
(
self
.
tested_command
,
'async_send_task'
)
as
mock_send
:
test_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
self
.
tested_command
()
.
handle
(
date
=
'2017-08-01'
,
site_domain_name
=
self
.
site_config
.
site
.
domain
)
for
offset
in
self
.
expected_offsets
:
mock_send
.
enqueue
.
assert_any_call
(
self
.
site_config
.
site
,
test_day
,
offset
,
None
)
@patch.object
(
tasks
,
'ace'
)
def
test_resolver_send
(
self
,
mock_ace
):
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
with
patch
.
object
(
self
.
tested_task
,
'apply_async'
)
as
mock_apply_async
:
self
.
tested_task
.
enqueue
(
self
.
site_config
.
site
,
current_day
,
offset
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
target_day
),
offset
,
0
,
None
),
retry
=
False
,
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
target_day
),
offset
,
self
.
tested_task
.
num_bins
-
1
,
None
),
retry
=
False
,
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
resolvers
,
'set_custom_metric'
)
def
test_schedule_bin
(
self
,
schedule_count
,
mock_metric
,
mock_ace
):
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
schedules
=
[
ScheduleFactory
.
create
(
start
=
target_day
,
upgrade_deadline
=
target_day
,
enrollment__course__self_paced
=
True
,
)
for
_
in
range
(
schedule_count
)
]
bins_in_use
=
frozenset
((
self
.
_calculate_bin_for_user
(
s
.
enrollment
.
user
))
for
s
in
schedules
)
is_first_match
=
True
course_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
if
self
.
has_course_queries
else
0
target_day_str
=
serialize
(
target_day
)
for
b
in
range
(
self
.
tested_task
.
num_bins
):
LOG
.
debug
(
'Running bin
%
d'
,
b
)
expected_queries
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
if
b
in
bins_in_use
:
if
is_first_match
:
expected_queries
=
(
# Since this is the first match, we need to cache all of the config models, so we run a
# query for each of those...
NUM_QUERIES_FIRST_MATCH
+
course_queries
)
is_first_match
=
False
else
:
expected_queries
=
NUM_QUERIES_WITH_MATCHES
expected_queries
+=
NUM_QUERIES_NO_ORG_LIST
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
target_day_str
,
day_offset
=
offset
,
bin_num
=
b
,
))
num_schedules
=
mock_metric
.
call_args
[
0
][
1
]
if
b
in
bins_in_use
:
self
.
assertGreater
(
num_schedules
,
0
)
else
:
self
.
assertEqual
(
num_schedules
,
0
)
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
schedule_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
def
test_no_course_overview
(
self
):
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
schedule
=
ScheduleFactory
.
create
(
start
=
target_day
,
upgrade_deadline
=
target_day
,
enrollment__course__self_paced
=
True
,
)
schedule
.
enrollment
.
course_id
=
CourseKey
.
from_string
(
'edX/toy/Not_2012_Fall'
)
schedule
.
enrollment
.
save
()
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
for
b
in
range
(
self
.
tested_task
.
num_bins
):
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
b
,
))
# There is no database constraint that enforces that enrollment.course_id points
# to a valid CourseOverview object. However, in that case, schedules isn't going
# to attempt to address it, and will instead simply skip those users.
# This happens 'transparently' because django generates an inner-join between
# enrollment and course_overview, and thus will skip any rows where course_overview
# is null.
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
0
)
@ddt.data
(
True
,
False
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
,
'Message'
)
def
test_deliver_config
(
self
,
is_enabled
,
mock_message
,
mock_ace
):
schedule_config_kwargs
=
{
'site'
:
self
.
site_config
.
site
,
self
.
deliver_config
:
is_enabled
,
}
ScheduleConfigFactory
.
create
(
**
schedule_config_kwargs
)
mock_msg
=
Mock
()
self
.
deliver_task
(
self
.
site_config
.
site
.
id
,
mock_msg
)
if
is_enabled
:
self
.
assertTrue
(
mock_ace
.
send
.
called
)
else
:
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
True
,
False
)
def
test_enqueue_config
(
self
,
is_enabled
):
schedule_config_kwargs
=
{
'site'
:
self
.
site_config
.
site
,
self
.
enqueue_config
:
is_enabled
,
}
ScheduleConfigFactory
.
create
(
**
schedule_config_kwargs
)
current_datetime
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
with
patch
.
object
(
self
.
tested_task
,
'apply_async'
)
as
mock_apply_async
:
self
.
tested_task
.
enqueue
(
self
.
site_config
.
site
,
current_datetime
,
3
)
if
is_enabled
:
self
.
assertTrue
(
mock_apply_async
.
called
)
else
:
self
.
assertFalse
(
mock_apply_async
.
called
)
@patch.object
(
tasks
,
'ace'
)
@ddt.data
(
(([
'filtered_org'
],
[],
1
)),
(([],
[
'filtered_org'
],
2
))
)
@ddt.unpack
def
test_site_config
(
self
,
this_org_list
,
other_org_list
,
expected_message_count
,
mock_ace
):
filtered_org
=
'filtered_org'
unfiltered_org
=
'unfiltered_org'
this_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
this_org_list
})
other_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
other_org_list
})
for
config
in
(
this_config
,
other_config
):
ScheduleConfigFactory
.
create
(
site
=
config
.
site
)
user1
=
UserFactory
.
create
(
id
=
self
.
tested_task
.
num_bins
)
user2
=
UserFactory
.
create
(
id
=
self
.
tested_task
.
num_bins
*
2
)
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
ScheduleFactory
.
create
(
upgrade_deadline
=
target_day
,
start
=
target_day
,
enrollment__course__org
=
filtered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
upgrade_deadline
=
target_day
,
start
=
target_day
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
upgrade_deadline
=
target_day
,
start
=
target_day
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user2
,
)
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
this_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
0
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
expected_message_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
True
,
False
)
def
test_course_end
(
self
,
has_course_ended
):
user1
=
UserFactory
.
create
(
id
=
self
.
tested_task
.
num_bins
)
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
schedule
=
ScheduleFactory
.
create
(
start
=
target_day
,
upgrade_deadline
=
target_day
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
)
schedule
.
enrollment
.
course
.
start
=
current_day
-
datetime
.
timedelta
(
days
=
30
)
end_date_offset
=
-
2
if
has_course_ended
else
2
schedule
.
enrollment
.
course
.
end
=
current_day
+
datetime
.
timedelta
(
days
=
end_date_offset
)
schedule
.
enrollment
.
course
.
save
()
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
0
,
))
if
has_course_ended
:
self
.
assertFalse
(
mock_schedule_send
.
apply_async
.
called
)
else
:
self
.
assertTrue
(
mock_schedule_send
.
apply_async
.
called
)
@patch.object
(
tasks
,
'ace'
)
def
test_multiple_enrollments
(
self
,
mock_ace
):
user
=
UserFactory
.
create
()
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
num_courses
=
3
for
course_index
in
range
(
num_courses
):
ScheduleFactory
.
create
(
start
=
target_day
,
upgrade_deadline
=
target_day
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user
,
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
)
course_queries
=
num_courses
if
self
.
has_course_queries
else
0
expected_query_count
=
NUM_QUERIES_FIRST_MATCH
+
course_queries
+
NUM_QUERIES_NO_ORG_LIST
with
self
.
assertNumQueries
(
expected_query_count
,
table_blacklist
=
WAFFLE_TABLES
):
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
1
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
def
test_templates
(
self
,
message_count
):
for
offset
in
self
.
expected_offsets
:
self
.
_assert_template_for_offset
(
offset
,
message_count
)
self
.
clear_caches
()
def
_assert_template_for_offset
(
self
,
offset
,
message_count
):
current_day
,
offset
,
target_day
=
self
.
_get_dates
(
offset
)
user
=
UserFactory
.
create
()
for
course_index
in
range
(
message_count
):
ScheduleFactory
.
create
(
start
=
target_day
,
upgrade_deadline
=
target_day
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user
,
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
)
patch_policies
(
self
,
[
StubPolicy
([
ChannelType
.
PUSH
])])
mock_channel
=
Mock
(
name
=
'test_channel'
,
channel_type
=
ChannelType
.
EMAIL
)
patch_channels
(
self
,
[
mock_channel
])
sent_messages
=
[]
with
self
.
settings
(
TEMPLATES
=
self
.
_get_template_overrides
()):
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
)
num_expected_queries
=
NUM_QUERIES_NO_ORG_LIST
+
NUM_QUERIES_FIRST_MATCH
if
self
.
has_course_queries
:
num_expected_queries
+=
message_count
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
self
.
assertEqual
(
len
(
sent_messages
),
1
)
with
self
.
assertNumQueries
(
2
):
for
args
in
sent_messages
:
self
.
deliver_task
(
*
args
)
self
.
assertEqual
(
mock_channel
.
deliver
.
call_count
,
1
)
for
(
_name
,
(
_msg
,
email
),
_kwargs
)
in
mock_channel
.
deliver
.
mock_calls
:
for
template
in
attr
.
astuple
(
email
):
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"{{"
,
template
)
self
.
assertNotIn
(
"}}"
,
template
)
openedx/core/djangoapps/schedules/management/commands/tests/test_
base
.py
→
openedx/core/djangoapps/schedules/management/commands/tests/test_
send_email_base_command
.py
View file @
2c80b1b4
...
@@ -4,7 +4,7 @@ from unittest import skipUnless
...
@@ -4,7 +4,7 @@ from unittest import skipUnless
import
ddt
import
ddt
import
pytz
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
mock
import
patch
from
mock
import
patch
,
DEFAULT
,
Mock
from
openedx.core.djangoapps.schedules.management.commands
import
SendEmailBaseCommand
from
openedx.core.djangoapps.schedules.management.commands
import
SendEmailBaseCommand
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
,
SiteConfigurationFactory
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
,
SiteConfigurationFactory
...
@@ -29,3 +29,18 @@ class TestSendEmailBaseCommand(CacheIsolationTestCase):
...
@@ -29,3 +29,18 @@ class TestSendEmailBaseCommand(CacheIsolationTestCase):
datetime
.
datetime
(
2017
,
9
,
29
,
tzinfo
=
pytz
.
UTC
),
datetime
.
datetime
(
2017
,
9
,
29
,
tzinfo
=
pytz
.
UTC
),
None
None
)
)
def
test_send_emails
(
self
):
with
patch
.
multiple
(
self
.
command
,
offsets
=
(
1
,
3
,
5
),
enqueue
=
DEFAULT
,
):
arg
=
Mock
(
name
=
'arg'
)
kwarg
=
Mock
(
name
=
'kwarg'
)
self
.
command
.
send_emails
(
arg
,
kwarg
=
kwarg
)
self
.
assertFalse
(
arg
.
called
)
self
.
assertFalse
(
kwarg
.
called
)
for
offset
in
self
.
command
.
offsets
:
self
.
command
.
enqueue
.
assert_any_call
(
offset
,
arg
,
kwarg
=
kwarg
)
openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py
View file @
2c80b1b4
import
datetime
import
datetime
import
itertools
from
copy
import
deepcopy
from
unittest
import
skipUnless
from
unittest
import
skipUnless
import
attr
import
ddt
import
ddt
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
edx_ace.channel
import
ChannelType
from
edx_ace.test_utils
import
StubPolicy
,
patch_channels
,
patch_policies
from
edx_ace.utils.date
import
serialize
from
edx_ace.utils.date
import
serialize
from
edx_ace.message
import
Message
from
edx_ace.message
import
Message
from
mock
import
Mock
,
patch
from
mock
import
patch
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
import
pytz
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.tests.factories
import
CourseModeFactory
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.content.course_overviews.tests.factories
import
CourseOverviewFactory
from
openedx.core.djangoapps.schedules
import
tasks
from
openedx.core.djangoapps.schedules
import
resolvers
,
tasks
from
openedx.core.djangoapps.schedules.management.commands
import
send_recurring_nudge
as
nudge
from
openedx.core.djangoapps.schedules.management.commands
import
send_recurring_nudge
as
nudge
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteConfigurationFactory
,
SiteFactory
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleFactory
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
,
skip_unless_lms
,
FilteredQueryCountMixin
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
# 1) Load the current django site
# 2) Query the schedules to find all of the template context information
NUM_QUERIES_NO_MATCHING_SCHEDULES
=
2
# 3) Query all course modes for all courses in returned schedules
NUM_QUERIES_WITH_MATCHES
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
1
# 4) Load the non-matching site configurations
NUM_QUERIES_NO_ORG_LIST
=
1
NUM_COURSE_MODES_QUERIES
=
1
@ddt.ddt
@ddt.ddt
@skip_unless_lms
@skip_unless_lms
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
"Can't test schedules if the app isn't installed"
)
"Can't test schedules if the app isn't installed"
)
class
TestSendRecurringNudge
(
FilteredQueryCountMixin
,
CacheIsolationTestCase
):
class
TestSendRecurringNudge
(
ScheduleSendEmailTestBase
):
# pylint: disable=protected-access
__test__
=
True
ENABLED_CACHES
=
[
'default'
]
def
setUp
(
self
):
super
(
TestSendRecurringNudge
,
self
)
.
setUp
()
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
1
,
15
,
44
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
1
,
17
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
2
,
15
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
site
=
SiteFactory
.
create
()
self
.
site_config
=
SiteConfigurationFactory
.
create
(
site
=
site
)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
@patch.object
(
nudge
.
Command
,
'async_send_task'
)
def
test_handle
(
self
,
mock_send
):
test_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
nudge
.
Command
()
.
handle
(
date
=
'2017-08-01'
,
site_domain_name
=
self
.
site_config
.
site
.
domain
)
for
day
in
(
-
3
,
-
10
):
mock_send
.
enqueue
.
assert_any_call
(
self
.
site_config
.
site
,
test_day
,
day
,
None
)
@patch.object
(
tasks
,
'ace'
)
def
test_resolver_send
(
self
,
mock_ace
):
current_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
with
patch
.
object
(
tasks
.
ScheduleRecurringNudge
,
'apply_async'
)
as
mock_apply_async
:
tasks
.
ScheduleRecurringNudge
.
enqueue
(
self
.
site_config
.
site
,
current_day
,
-
3
)
test_day
=
current_day
+
datetime
.
timedelta
(
days
=-
3
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
test_day
),
-
3
,
0
,
None
),
retry
=
False
,
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
test_day
),
-
3
,
resolvers
.
RECURRING_NUDGE_NUM_BINS
-
1
,
None
),
retry
=
False
,
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
def
test_schedule_bin
(
self
,
schedule_count
,
mock_schedule_send
,
mock_ace
):
schedules
=
[
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
18
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Bin'
)
)
for
i
in
range
(
schedule_count
)
]
bins_in_use
=
frozenset
((
s
.
enrollment
.
user
.
id
%
resolvers
.
RECURRING_NUDGE_NUM_BINS
)
for
s
in
schedules
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
18
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
for
b
in
range
(
resolvers
.
RECURRING_NUDGE_NUM_BINS
):
expected_queries
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
NUM_QUERIES_NO_ORG_LIST
if
b
in
bins_in_use
:
# to fetch course modes for valid schedules
expected_queries
+=
NUM_COURSE_MODES_QUERIES
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
b
,
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
schedule_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
def
test_no_course_overview
(
self
,
mock_schedule_send
):
schedule
=
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
34
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__user
=
UserFactory
.
create
(),
)
schedule
.
enrollment
.
course_id
=
CourseKey
.
from_string
(
'edX/toy/Not_2012_Fall'
)
schedule
.
enrollment
.
save
()
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
for
b
in
range
(
resolvers
.
RECURRING_NUDGE_NUM_BINS
):
with
self
.
assertNumQueries
(
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
NUM_QUERIES_NO_ORG_LIST
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
b
))
# There is no database constraint that enforces that enrollment.course_id points
# to a valid CourseOverview object. However, in that case, schedules isn't going
# to attempt to address it, and will instead simply skip those users.
# This happens 'transparently' because django generates an inner-join between
# enrollment and course_overview, and thus will skip any rows where course_overview
# is null.
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
0
)
@patch.object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
def
test_send_after_course_end
(
self
,
mock_schedule_send
):
user1
=
UserFactory
.
create
(
id
=
resolvers
.
RECURRING_NUDGE_NUM_BINS
)
schedule_start
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
34
,
30
,
tzinfo
=
pytz
.
UTC
)
day_command_is_run
=
schedule_start
+
datetime
.
timedelta
(
days
=
3
)
schedule
=
ScheduleFactory
.
create
(
start
=
schedule_start
,
enrollment__user
=
user1
,
)
schedule
.
enrollment
.
course
.
start
=
schedule_start
-
datetime
.
timedelta
(
days
=
30
)
schedule
.
enrollment
.
course
.
end
=
day_command_is_run
-
datetime
.
timedelta
(
days
=
1
)
schedule
.
enrollment
.
course
.
save
()
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
0
,
))
self
.
assertFalse
(
mock_schedule_send
.
apply_async
.
called
)
@patch.object
(
tasks
,
'ace'
)
def
test_delivery_disabled
(
self
,
mock_ace
):
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
,
deliver_recurring_nudge
=
False
)
mock_msg
=
Mock
()
tasks
.
_recurring_nudge_schedule_send
(
self
.
site_config
.
site
.
id
,
mock_msg
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'apply_async'
)
def
test_enqueue_disabled
(
self
,
mock_ace
,
mock_apply_async
):
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
,
enqueue_recurring_nudge
=
False
)
current_datetime
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
tasks
.
ScheduleRecurringNudge
.
enqueue
(
self
.
site_config
.
site
,
current_datetime
,
3
)
self
.
assertFalse
(
mock_apply_async
.
called
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
@ddt.data
(
(([
'filtered_org'
],
[],
1
)),
(([],
[
'filtered_org'
],
2
))
)
@ddt.unpack
def
test_site_config
(
self
,
this_org_list
,
other_org_list
,
expected_message_count
,
mock_schedule_send
,
mock_ace
):
filtered_org
=
'filtered_org'
unfiltered_org
=
'unfiltered_org'
this_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
this_org_list
})
other_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
other_org_list
})
for
config
in
(
this_config
,
other_config
):
ScheduleConfigFactory
.
create
(
site
=
config
.
site
)
user1
=
UserFactory
.
create
(
id
=
resolvers
.
RECURRING_NUDGE_NUM_BINS
)
user2
=
UserFactory
.
create
(
id
=
resolvers
.
RECURRING_NUDGE_NUM_BINS
*
2
)
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
filtered_org
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
unfiltered_org
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
unfiltered_org
,
enrollment__user
=
user2
,
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
tzinfo
=
pytz
.
UTC
)
# pylint: disable=protected-access
test_datetime_str
=
serialize
(
test_datetime
)
tested_task
=
tasks
.
ScheduleRecurringNudge
deliver_task
=
tasks
.
_recurring_nudge_schedule_send
expected_queries
=
NUM_QUERIES_WITH_MATCHES
tested_command
=
nudge
.
Command
if
not
this_org_list
:
deliver_config
=
'deliver_recurring_nudge'
expected_queries
+=
NUM_QUERIES_NO_ORG_LIST
enqueue_config
=
'enqueue_recurring_nudge'
expected_offsets
=
(
-
3
,
-
10
)
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
this_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
0
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
expected_message_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
def
test_multiple_enrollments
(
self
,
mock_schedule_send
,
mock_ace
):
user
=
UserFactory
.
create
()
schedules
=
[
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__user
=
user
,
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
for
course_num
in
(
1
,
2
,
3
)
]
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
with
self
.
assertNumQueries
(
NUM_QUERIES_WITH_MATCHES
+
NUM_QUERIES_NO_ORG_LIST
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
user
.
id
%
resolvers
.
RECURRING_NUDGE_NUM_BINS
,
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
1
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
*
itertools
.
product
((
1
,
10
,
100
),
(
-
3
,
-
10
)))
@ddt.unpack
def
test_templates
(
self
,
message_count
,
day
):
user
=
UserFactory
.
create
()
schedules
=
[
ScheduleFactory
.
create
(
start
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__user
=
user
,
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
for
course_num
in
range
(
message_count
)
]
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
patch_policies
(
self
,
[
StubPolicy
([
ChannelType
.
PUSH
])])
mock_channel
=
Mock
(
name
=
'test_channel'
,
channel_type
=
ChannelType
.
EMAIL
)
patch_channels
(
self
,
[
mock_channel
])
sent_messages
=
[]
with
self
.
settings
(
TEMPLATES
=
self
.
_get_template_overrides
()):
with
patch
.
object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
)
with
self
.
assertNumQueries
(
NUM_QUERIES_WITH_MATCHES
+
NUM_QUERIES_NO_ORG_LIST
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
day
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
self
.
assertEqual
(
len
(
sent_messages
),
1
)
# Load the site
# Check the schedule config
with
self
.
assertNumQueries
(
2
):
for
args
in
sent_messages
:
tasks
.
_recurring_nudge_schedule_send
(
*
args
)
self
.
assertEqual
(
mock_channel
.
deliver
.
call_count
,
1
)
for
(
_name
,
(
_msg
,
email
),
_kwargs
)
in
mock_channel
.
deliver
.
mock_calls
:
for
template
in
attr
.
astuple
(
email
):
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"{{"
,
template
)
self
.
assertNotIn
(
"}}"
,
template
)
def
test_user_in_course_with_verified_coursemode_receives_upsell
(
self
):
def
test_user_in_course_with_verified_coursemode_receives_upsell
(
self
):
user
=
UserFactory
.
create
()
user
=
UserFactory
.
create
()
...
@@ -344,8 +64,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -344,8 +64,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
user
,
user
,
schedule
.
enrollment
.
course
.
org
schedule
.
enrollment
.
course
.
org
]
]
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
tasks
.
ScheduleRecurringNudge
,
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
self
.
tested_task
,
stubbed_send_task
=
patch
.
object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
),
stubbed_send_task
=
patch
.
object
(
self
.
tested_task
,
'async_send_task'
),
bin_task_params
=
bin_task_parameters
)
bin_task_params
=
bin_task_parameters
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
...
@@ -376,8 +96,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -376,8 +96,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
user
,
user
,
schedule
.
enrollment
.
course
.
org
schedule
.
enrollment
.
course
.
org
]
]
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
tasks
.
ScheduleRecurringNudge
,
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
self
.
tested_task
,
stubbed_send_task
=
patch
.
object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
),
stubbed_send_task
=
patch
.
object
(
self
.
tested_task
,
'async_send_task'
),
bin_task_params
=
bin_task_parameters
)
bin_task_params
=
bin_task_parameters
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
...
@@ -415,8 +135,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -415,8 +135,8 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
user
,
user
,
schedule
.
enrollment
.
course
.
org
schedule
.
enrollment
.
course
.
org
]
]
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
tasks
.
ScheduleRecurringNudge
,
sent_messages
=
self
.
_stub_sender_and_collect_sent_messages
(
bin_task
=
self
.
tested_task
,
stubbed_send_task
=
patch
.
object
(
tasks
.
ScheduleRecurringNudge
,
'async_send_task'
),
stubbed_send_task
=
patch
.
object
(
self
.
tested_task
,
'async_send_task'
),
bin_task_params
=
bin_task_parameters
)
bin_task_params
=
bin_task_parameters
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
...
@@ -440,15 +160,6 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -440,15 +160,6 @@ class TestSendRecurringNudge(FilteredQueryCountMixin, CacheIsolationTestCase):
return
sent_messages
return
sent_messages
def
_get_template_overrides
(
self
):
templates_override
=
deepcopy
(
settings
.
TEMPLATES
)
templates_override
[
0
][
'OPTIONS'
][
'string_if_invalid'
]
=
"TEMPLATE WARNING - MISSING VARIABLE [
%
s]"
return
templates_override
def
_calculate_bin_for_user
(
self
,
user
):
return
user
.
id
%
resolvers
.
RECURRING_NUDGE_NUM_BINS
def
_contains_upsell_attribute
(
self
,
msg_attr
):
def
_contains_upsell_attribute
(
self
,
msg_attr
):
msg
=
Message
.
from_string
(
msg_attr
)
msg
=
Message
.
from_string
(
msg_attr
)
tmp
=
msg
.
context
[
"show_upsell"
]
return
msg
.
context
[
"show_upsell"
]
return
msg
.
context
[
"show_upsell"
]
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
View file @
2c80b1b4
import
datetime
from
copy
import
deepcopy
import
logging
import
logging
from
unittest
import
skipUnless
from
unittest
import
skipUnless
import
attr
import
ddt
import
ddt
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
edx_ace
import
Message
from
edx_ace
import
Message
from
freezegun
import
freeze_time
from
edx_ace.channel
import
ChannelType
from
edx_ace.test_utils
import
StubPolicy
,
patch_channels
,
patch_policies
from
edx_ace.utils.date
import
serialize
from
edx_ace.utils.date
import
serialize
from
mock
import
Mock
,
patch
from
mock
import
Mock
,
patch
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
openedx.core.djangoapps.schedules
import
tasks
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.schedules
import
resolvers
,
tasks
from
openedx.core.djangoapps.schedules.management.commands
import
send_upgrade_reminder
as
reminder
from
openedx.core.djangoapps.schedules.management.commands
import
send_upgrade_reminder
as
reminder
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteConfigurationFactory
,
SiteFactory
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleFactory
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
SITE_QUERY
=
1
SCHEDULES_QUERY
=
1
COURSE_MODES_QUERY
=
1
GLOBAL_DEADLINE_SWITCH_QUERY
=
1
COMMERCE_CONFIG_QUERY
=
1
NUM_QUERIES_NO_ORG_LIST
=
1
NUM_QUERIES_NO_MATCHING_SCHEDULES
=
SITE_QUERY
+
SCHEDULES_QUERY
NUM_QUERIES_WITH_MATCHES
=
(
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
COURSE_MODES_QUERY
)
NUM_QUERIES_FIRST_MATCH
=
(
NUM_QUERIES_WITH_MATCHES
+
GLOBAL_DEADLINE_SWITCH_QUERY
+
COMMERCE_CONFIG_QUERY
)
LOG
=
logging
.
getLogger
(
__name__
)
LOG
=
logging
.
getLogger
(
__name__
)
...
@@ -58,378 +24,58 @@ LOG = logging.getLogger(__name__)
...
@@ -58,378 +24,58 @@ LOG = logging.getLogger(__name__)
@skip_unless_lms
@skip_unless_lms
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
"Can't test schedules if the app isn't installed"
)
"Can't test schedules if the app isn't installed"
)
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
class
TestUpgradeReminder
(
ScheduleSendEmailTestBase
):
class
TestUpgradeReminder
(
SharedModuleStoreTestCase
):
__test__
=
True
ENABLED_CACHES
=
[
'default'
]
@classmethod
def
setUpClass
(
cls
):
super
(
TestUpgradeReminder
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'test'
,
display_name
=
'Test Course'
,
self_paced
=
True
,
start
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
-
datetime
.
timedelta
(
days
=
30
),
)
cls
.
course_overview
=
CourseOverview
.
get_from_id
(
cls
.
course
.
id
)
def
setUp
(
self
):
super
(
TestUpgradeReminder
,
self
)
.
setUp
()
CourseModeFactory
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
30
),
)
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
15
,
44
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
17
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
2
,
15
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
site
=
SiteFactory
.
create
()
self
.
site_config
=
SiteConfigurationFactory
.
create
(
site
=
site
)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
tested_task
=
tasks
.
ScheduleUpgradeReminder
deliver_task
=
tasks
.
_upgrade_reminder_schedule_send
tested_command
=
reminder
.
Command
deliver_config
=
'deliver_upgrade_reminder'
enqueue_config
=
'enqueue_upgrade_reminder'
expected_offsets
=
(
2
,)
@patch.object
(
reminder
.
Command
,
'async_send_task'
)
has_course_queries
=
True
def
test_handle
(
self
,
mock_send
):
test_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
reminder
.
Command
()
.
handle
(
date
=
'2017-08-01'
,
site_domain_name
=
self
.
site_config
.
site
.
domain
)
mock_send
.
enqueue
.
assert_called_with
(
self
.
site_config
.
site
,
test_day
,
2
,
None
)
@ddt.data
(
True
,
False
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
,
'ace'
)
def
test_resolver_send
(
self
,
mock_ace
):
def
test_verified_learner
(
self
,
is_verified
,
mock_ace
):
current_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
user
=
UserFactory
.
create
(
id
=
self
.
tested_task
.
num_bins
)
test_day
=
current_day
+
datetime
.
timedelta
(
days
=
2
)
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
15
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'apply_async'
)
as
mock_apply_async
:
tasks
.
ScheduleUpgradeReminder
.
enqueue
(
self
.
site_config
.
site
,
current_day
,
2
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
test_day
),
2
,
0
,
None
),
retry
=
False
,
)
mock_apply_async
.
assert_any_call
(
(
self
.
site_config
.
site
.
id
,
serialize
(
test_day
),
2
,
resolvers
.
UPGRADE_REMINDER_NUM_BINS
-
1
,
None
),
retry
=
False
,
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
def
test_schedule_bin
(
self
,
schedule_count
,
mock_schedule_send
,
mock_ace
):
upgrade_deadline
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
schedules
=
[
ScheduleFactory
.
create
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__course
=
self
.
course_overview
,
)
for
i
in
range
(
schedule_count
)
]
bins_in_use
=
frozenset
((
self
.
_calculate_bin_for_user
(
s
.
enrollment
.
user
))
for
s
in
schedules
)
is_first_match
=
True
course_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
org_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
.
org
for
s
in
schedules
))
test_datetime
=
upgrade_deadline
test_datetime_str
=
serialize
(
test_datetime
)
for
b
in
range
(
resolvers
.
UPGRADE_REMINDER_NUM_BINS
):
LOG
.
debug
(
'Running bin
%
d'
,
b
)
expected_queries
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
if
b
in
bins_in_use
:
if
is_first_match
:
expected_queries
=
(
# Since this is the first match, we need to cache all of the config models, so we run a query
# for each of those...
NUM_QUERIES_FIRST_MATCH
+
course_switch_queries
+
org_switch_queries
)
is_first_match
=
False
else
:
expected_queries
=
NUM_QUERIES_WITH_MATCHES
expected_queries
+=
NUM_QUERIES_NO_ORG_LIST
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
b
,
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
schedule_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
def
test_no_course_overview
(
self
,
mock_schedule_send
):
schedule
=
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
34
,
30
,
tzinfo
=
pytz
.
UTC
),
)
schedule
.
enrollment
.
course_id
=
CourseKey
.
from_string
(
'edX/toy/Not_2012_Fall'
)
schedule
.
enrollment
.
save
()
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
20
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
for
b
in
range
(
resolvers
.
UPGRADE_REMINDER_NUM_BINS
):
with
self
.
assertNumQueries
(
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
NUM_QUERIES_NO_ORG_LIST
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
b
,
))
# There is no database constraint that enforces that enrollment.course_id points
# to a valid CourseOverview object. However, in that case, schedules isn't going
# to attempt to address it, and will instead simply skip those users.
# This happens 'transparently' because django generates an inner-join between
# enrollment and course_overview, and thus will skip any rows where course_overview
# is null.
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
0
)
@patch.object
(
tasks
,
'ace'
)
def
test_delivery_disabled
(
self
,
mock_ace
):
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
,
deliver_upgrade_reminder
=
False
)
mock_msg
=
Mock
()
tasks
.
_upgrade_reminder_schedule_send
(
self
.
site_config
.
site
.
id
,
mock_msg
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'apply_async'
)
def
test_enqueue_disabled
(
self
,
mock_ace
,
mock_apply_async
):
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
,
enqueue_upgrade_reminder
=
False
)
current_day
=
datetime
.
datetime
(
2017
,
8
,
1
,
tzinfo
=
pytz
.
UTC
)
tasks
.
ScheduleUpgradeReminder
.
enqueue
(
self
.
site_config
.
site
,
current_day
,
day_offset
=
3
,
)
self
.
assertFalse
(
mock_apply_async
.
called
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
@ddt.data
(
(([
'filtered_org'
],
[],
1
)),
(([],
[
'filtered_org'
],
2
))
)
@ddt.unpack
def
test_site_config
(
self
,
this_org_list
,
other_org_list
,
expected_message_count
,
mock_schedule_send
,
mock_ace
):
filtered_org
=
'filtered_org'
unfiltered_org
=
'unfiltered_org'
this_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
this_org_list
})
other_config
=
SiteConfigurationFactory
.
create
(
values
=
{
'course_org_filter'
:
other_org_list
})
for
config
in
(
this_config
,
other_config
):
ScheduleConfigFactory
.
create
(
site
=
config
.
site
)
user1
=
UserFactory
.
create
(
id
=
resolvers
.
UPGRADE_REMINDER_NUM_BINS
)
user2
=
UserFactory
.
create
(
id
=
resolvers
.
UPGRADE_REMINDER_NUM_BINS
*
2
)
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
filtered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
)
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
upgrade_deadline
=
target_day
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user2
,
enrollment__user
=
user
,
enrollment__mode
=
CourseMode
.
VERIFIED
if
is_verified
else
CourseMode
.
AUDIT
,
)
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
tzinfo
=
pytz
.
UTC
)
self
.
tested_task
.
apply
(
kwargs
=
dict
(
test_datetime_str
=
serialize
(
test_datetime
)
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
course_switch_queries
=
1
self
.
assertEqual
(
mock_ace
.
send
.
called
,
not
is_verified
)
org_switch_queries
=
1
expected_queries
=
NUM_QUERIES_FIRST_MATCH
+
course_switch_queries
+
org_switch_queries
if
not
this_org_list
:
expected_queries
+=
NUM_QUERIES_NO_ORG_LIST
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
site_id
=
this_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=-
3
,
bin_num
=
0
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
expected_message_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
def
test_multiple_enrollments
(
self
,
mock_schedule_send
,
mock_ace
):
user
=
UserFactory
.
create
()
schedules
=
[
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
for
course_num
in
(
1
,
2
,
3
)
]
course_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
org_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
.
org
for
s
in
schedules
))
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
expected_query_count
=
(
NUM_QUERIES_FIRST_MATCH
+
course_switch_queries
+
org_switch_queries
+
NUM_QUERIES_NO_ORG_LIST
)
with
self
.
assertNumQueries
(
expected_query_count
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
1
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
def
test_templates
(
self
,
message_count
):
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
future_datetime
=
now
+
datetime
.
timedelta
(
days
=
21
)
user
=
UserFactory
.
create
()
schedules
=
[
ScheduleFactory
.
create
(
upgrade_deadline
=
future_datetime
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__end
=
future_datetime
+
datetime
.
timedelta
(
days
=
30
),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
for
course_num
in
range
(
message_count
)
]
for
schedule
in
schedules
:
CourseModeFactory
(
course_id
=
schedule
.
enrollment
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
future_datetime
)
course_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
org_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
.
org
for
s
in
schedules
))
test_datetime
=
future_datetime
test_datetime_str
=
serialize
(
test_datetime
)
patch_policies
(
self
,
[
StubPolicy
([
ChannelType
.
PUSH
])])
mock_channel
=
Mock
(
name
=
'test_channel'
,
channel_type
=
ChannelType
.
EMAIL
)
patch_channels
(
self
,
[
mock_channel
])
sent_messages
=
[]
with
self
.
settings
(
TEMPLATES
=
self
.
_get_template_overrides
()):
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
)
# we execute one query per course to see if it's opted out of dynamic upgrade deadlines
num_expected_queries
=
(
NUM_QUERIES_FIRST_MATCH
+
NUM_QUERIES_NO_ORG_LIST
+
course_switch_queries
+
org_switch_queries
)
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
self
.
assertEqual
(
len
(
sent_messages
),
1
)
# Load the site (which we query per message sent)
# Check the schedule config
with
self
.
assertNumQueries
(
2
):
for
args
in
sent_messages
:
tasks
.
_upgrade_reminder_schedule_send
(
*
args
)
self
.
assertEqual
(
mock_channel
.
deliver
.
call_count
,
1
)
for
(
_name
,
(
_msg
,
email
),
_kwargs
)
in
mock_channel
.
deliver
.
mock_calls
:
for
template
in
attr
.
astuple
(
email
):
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"{{"
,
template
)
self
.
assertNotIn
(
"}}"
,
template
)
def
_get_template_overrides
(
self
):
templates_override
=
deepcopy
(
settings
.
TEMPLATES
)
templates_override
[
0
][
'OPTIONS'
][
'string_if_invalid'
]
=
"TEMPLATE WARNING - MISSING VARIABLE [
%
s]"
return
templates_override
def
_calculate_bin_for_user
(
self
,
user
):
return
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
@patch.object
(
tasks
,
'_upgrade_reminder_schedule_send'
)
def
test_dont_send_to_verified_learner
(
self
,
mock_schedule_send
):
upgrade_deadline
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
ScheduleFactory
.
create
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__user
=
UserFactory
.
create
(
id
=
resolvers
.
UPGRADE_REMINDER_NUM_BINS
),
enrollment__course
=
self
.
course_overview
,
enrollment__mode
=
CourseMode
.
VERIFIED
,
)
test_datetime_str
=
serialize
(
datetime
.
datetime
.
now
(
pytz
.
UTC
))
tasks
.
ScheduleUpgradeReminder
.
delay
(
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
0
,
org_list
=
[
self
.
course
.
org
],
)
self
.
assertFalse
(
mock_schedule_send
.
called
)
self
.
assertFalse
(
mock_schedule_send
.
apply_async
.
called
)
def
test_filter_out_verified_schedules
(
self
):
def
test_filter_out_verified_schedules
(
self
):
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
current_day
,
offset
,
target_day
=
self
.
_get_dates
()
future_datetime
=
now
+
datetime
.
timedelta
(
days
=
21
)
user
=
UserFactory
.
create
()
user
=
UserFactory
.
create
()
schedules
=
[
schedules
=
[
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
future_datetime
,
upgrade_deadline
=
target_day
,
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__self_paced
=
True
,
enrollment__course__end
=
future_datetime
+
datetime
.
timedelta
(
days
=
30
),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
i
)),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
i
)),
enrollment__mode
=
CourseMode
.
VERIFIED
if
i
in
(
0
,
3
)
else
CourseMode
.
AUDIT
,
enrollment__mode
=
CourseMode
.
VERIFIED
if
i
in
(
0
,
3
)
else
CourseMode
.
AUDIT
,
)
)
for
i
in
range
(
5
)
for
i
in
range
(
5
)
]
]
for
schedule
in
schedules
:
CourseModeFactory
(
course_id
=
schedule
.
enrollment
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
future_datetime
)
test_datetime
=
future_datetime
test_datetime_str
=
serialize
(
test_datetime
)
sent_messages
=
[]
sent_messages
=
[]
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
self
.
tested_task
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
[
1
])
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
[
1
])
tasks
.
ScheduleUpgradeReminder
.
apply
(
kwargs
=
dict
(
self
.
tested_task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
))
))
...
...
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