Commit 3bbc4bf5 by Renzo Lucioni

Use MySQL when running tests on Travis

We run Docker containers on Travis, including one for MySQL. However, we haven't actually be using it! These changes update the test settings required to make the switch from SQLite to MySQL, fix a variety of bugs in tests that were hidden by SQLite, and introduce new Make targets which make it easy to run tests locally in the same way that Travis runs them.
parent 3a369fe2
......@@ -35,5 +35,12 @@ services:
- "es"
- "memcache"
environment:
TEST_ELASTICSEARCH_URL: "http://es:9200"
CONN_MAX_AGE: 60
DB_ENGINE: "django.db.backends.mysql"
DB_HOST: "db"
DB_NAME: "discovery"
DB_PASSWORD: "password"
DB_PORT: "3306"
DB_USER: "discov001"
ENABLE_DJANGO_TOOLBAR: 1
TEST_ELASTICSEARCH_URL: "http://es:9200"
"""
Tests for affiliate tracking cookies.
"""
from unittest import TestCase
from django.test import TestCase
from selenium import webdriver
from acceptance_tests.config import (
......
""" Tests to validate configuration of the API gateway. """
from unittest import TestCase
import ddt
from django.test import TestCase
import requests
from acceptance_tests.config import API_GATEWAY_CATALOG_ROOT, API_ACCESS_TOKEN, CATALOG_ID
......
......@@ -24,6 +24,7 @@ from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.models import User
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin
from course_discovery.apps.course_metadata.choices import CourseRunStatus, ProgramStatus
from course_discovery.apps.course_metadata.models import CourseRun, Program
from course_discovery.apps.course_metadata.tests.factories import (
......@@ -64,7 +65,7 @@ def get_uuids(items):
return [str(item.uuid) for item in items]
class CatalogSerializerTests(TestCase):
class CatalogSerializerTests(ElasticsearchTestMixin, TestCase):
def test_data(self):
user = UserFactory()
catalog = CatalogFactory(query='*:*', viewers=[user]) # We intentionally use a query for all Courses.
......
......@@ -4,6 +4,7 @@ import urllib
import ddt
import pytz
from django.conf import settings
from django.db.models.functions import Lower
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase, APIRequestFactory
......@@ -24,7 +25,10 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
super(CourseRunViewSetTests, self).setUp()
self.user = UserFactory(is_staff=True, is_superuser=True)
self.client.force_authenticate(self.user)
self.partner = PartnerFactory()
# DEFAULT_PARTNER_ID is used explicitly here to avoid issues with differences in
# auto-incrementing behavior across databases. Otherwise, it's not safe to assume
# that the partner created here will always have id=DEFAULT_PARTNER_ID.
self.partner = PartnerFactory(id=settings.DEFAULT_PARTNER_ID)
self.course_run = CourseRunFactory(course__partner=self.partner)
self.course_run_2 = CourseRunFactory(course__partner=self.partner)
self.refresh_index()
......
......@@ -153,9 +153,12 @@ class CourseRunSearchViewSetTests(DefaultPartnerMixin, SerializationMixin, Login
# Verify all course runs are returned
self.assertEqual(response_data['objects']['count'], 4)
expected = [self.serialize_course_run(course_run) for course_run in
[archived, current, starting_soon, upcoming]]
self.assertEqual(response_data['objects']['results'], expected)
for run in [archived, current, starting_soon, upcoming]:
serialized = self.serialize_course_run(run)
# Force execution of lazy function.
serialized['availability'] = serialized['availability'].strip()
self.assertIn(serialized, response_data['objects']['results'])
self.assert_response_includes_availability_facets(response_data)
......@@ -244,8 +247,10 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin
response = self.get_search_response()
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content.decode('utf-8'))
self.assertListEqual(response_data['objects']['results'],
[self.serialize_course_run(course_run), self.serialize_program(program)])
self.assertListEqual(
response_data['objects']['results'],
[self.serialize_program(program), self.serialize_course_run(course_run)]
)
def test_hidden_runs_excluded(self):
"""Search results should not include hidden runs."""
......@@ -277,8 +282,10 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin
response = self.get_search_response()
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content.decode('utf-8'))
self.assertListEqual(response_data['objects']['results'],
[self.serialize_program(program), self.serialize_course_run(course_run)])
self.assertListEqual(
response_data['objects']['results'],
[self.serialize_course_run(course_run), self.serialize_program(program)]
)
# Filter results by partner
response = self.get_search_response({'partner': other_partner.short_code})
......@@ -324,7 +331,7 @@ class TypeaheadSearchViewTests(DefaultPartnerMixin, TypeaheadSerializationMixin,
function_score = {
'functions': [
{'filter': {'term': {'pacing_type_exact': 'self_paced'}}, 'weight': 1.0},
{'filter': {'term': {'type_exact': 'micromasters'}}, 'weight': 1.0},
{'filter': {'term': {'type_exact': 'MicroMasters'}}, 'weight': 1.0},
{'linear': {'start': {'origin': 'now', 'scale': '1d', 'decay': 0.95}}, 'weight': 5.0}
],
'boost': 1.0, 'score_mode': 'sum', 'boost_mode': 'sum',
......
import itertools
import ddt
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.test import TestCase, LiveServerTestCase
from selenium import webdriver
......@@ -188,6 +189,9 @@ class AdminTests(TestCase):
class ProgramAdminFunctionalTests(LiveServerTestCase):
""" Functional Tests for Admin page."""
# Required for access to initial data loaded in migrations (e.g., LanguageTags).
serialized_rollback = True
create_view_name = 'admin:course_metadata_program_add'
edit_view_name = 'admin:course_metadata_program_change'
......@@ -218,6 +222,19 @@ class ProgramAdminFunctionalTests(LiveServerTestCase):
def setUp(self):
super().setUp()
# ContentTypeManager uses a cache to speed up ContentType retrieval. This
# cache persists across tests. This is fine in the context of a regular
# TestCase which uses a transaction to reset the database between tests.
# However, it becomes a problem in subclasses of TransactionTestCase which
# truncate all tables to reset the database between tests. When tables are
# truncated, ContentType objects in the ContentTypeManager's cache become
# stale. Attempting to use these stale objects in tests such as the ones
# below, which create LogEntry objects as a side-effect of interacting with
# the admin, will result in IntegrityErrors on databases that check foreign
# key constraints (e.g., MySQL). Preemptively clearing the cache prevents
# stale ContentType objects from being used.
ContentType.objects.clear_cache()
self.course_runs = factories.CourseRunFactory.create_batch(2)
self.courses = [course_run.course for course_run in self.course_runs]
......
......@@ -6,6 +6,7 @@ import mock
from dateutil.parser import parse
from django.conf import settings
from django.db import IntegrityError
from django.db.models.functions import Lower
from django.test import TestCase
from factory.fuzzy import FuzzyText
from freezegun import freeze_time
......@@ -13,6 +14,7 @@ import responses
from course_discovery.apps.core.models import Currency
from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin
from course_discovery.apps.core.utils import SearchQuerySetWrapper
from course_discovery.apps.course_metadata.choices import ProgramStatus
from course_discovery.apps.course_metadata.models import (
......@@ -29,7 +31,7 @@ from course_discovery.apps.ietf_language_tags.models import LanguageTag
# pylint: disable=no-member
class CourseTests(TestCase):
class CourseTests(ElasticsearchTestMixin, TestCase):
""" Tests for the `Course` model. """
def setUp(self):
......@@ -44,9 +46,16 @@ class CourseTests(TestCase):
""" Verify the method returns a filtered queryset of courses. """
title = 'Some random title'
courses = factories.CourseFactory.create_batch(3, title=title)
courses = sorted(courses, key=lambda course: course.key)
# Sort lowercase keys to prevent different sort orders due to casing.
# For example, sorted(['a', 'Z']) gives ['Z', 'a'], but an ordered
# queryset containing the same values may give ['a', 'Z'] depending
# on the database backend in use.
courses = sorted(courses, key=lambda course: course.key.lower())
query = 'title:' + title
actual = list(Course.search(query).order_by('key'))
# Use Lower() to force a case-insensitive sort.
actual = list(Course.search(query).order_by(Lower('key')))
self.assertEqual(actual, courses)
def test_course_run_update_caught_exception(self):
......
"""Tests API Serializers."""
from unittest import TestCase
from django.test import RequestFactory
from django.test import RequestFactory, TestCase
from rest_framework.exceptions import ValidationError
from course_discovery.apps.core.tests.factories import UserFactory
......
......@@ -43,7 +43,8 @@ class CourseRoleAssignmentViewTests(TestCase):
# Create three internal user course roles for internal users against a course
# so we can test change role assignment on these roles.
for user, role in zip(self.other_internal_users, PublisherUserRole.choices):
roles = [role for role, __ in PublisherUserRole.choices]
for user, role in zip(self.other_internal_users, roles):
factories.CourseUserRoleFactory(course=self.course, user=user, role=role)
self.client.login(username=self.internal_user.username, password=USER_PASSWORD)
......
......@@ -142,5 +142,5 @@ def send_email_for_course_creation(course, course_run):
email_msg.send()
except Exception: # pylint: disable=broad-except
logger.exception(
'Failed to send email notifications for course creation course run id [%s]', course_run.course.id
'Failed to send email notifications for creation of course [%s]', course_run.course.id
)
......@@ -233,8 +233,8 @@ class CourseCreatedEmailTests(TestCase):
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for course creation course run id [{}]'.format(
self.course_run.id
'Failed to send email notifications for creation of course [{}]'.format(
self.course_run.course.id
)
)
)
......
......@@ -361,8 +361,8 @@ class CreateUpdateCourseViewTests(TestCase):
self.assertEqual(Seat.objects.all().count(), count)
def _assert_test_data(self, response, course, expected_type, expected_price):
# DRY method to assert response and data.
run_detail_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course.id})
course_run = course.publisher_course_runs.get()
run_detail_path = reverse('publisher:publisher_course_run_detail', kwargs={'pk': course_run.id})
self.assertRedirects(
response,
......@@ -370,25 +370,22 @@ class CreateUpdateCourseViewTests(TestCase):
status_code=302,
target_status_code=200
)
self.assertEqual(course.organizations.first(), self.organization_extension.organization)
self.assertEqual(len(course.course_user_roles.all()), 3)
self.assertEqual(course.course_user_roles.filter(role=PublisherUserRole.CourseTeam).count(), 1)
course_run = course.publisher_course_runs.all()[0]
self.assertEqual(self.course_run.language, course_run.language)
self.assertEqual(self.course_run.contacted_partner_manager, course_run.contacted_partner_manager)
self.assertEqual(self.course_run.pacing_type, course_run.pacing_type)
self.assertEqual(course_run.start.strftime("%Y-%m-%d %H:%M:%S"), self.start_date_time)
self.assertEqual(course_run.end.strftime("%Y-%m-%d %H:%M:%S"), self.end_date_time)
seat = course_run.seats.all()[0]
seat = course_run.seats.first()
self.assertEqual(seat.type, expected_type)
self.assertEqual(seat.price, expected_price)
self._assert_records(2)
response = self.client.get(run_detail_path)
self.assertEqual(response.status_code, 200)
# django-taggit stores data without any order. For test .
self.assertEqual(course.organizations.first(), self.organization_extension.organization)
self.assertTrue(len(course.course_user_roles.all()), 2)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(
str(mail.outbox[0].subject), 'New Studio instance request for {title}'.format(title=course.title)
......@@ -1011,8 +1008,8 @@ class CourseRunDetailTests(TestCase):
self.course.organizations.add(organization)
factories.OrganizationExtensionFactory(organization=organization)
# create three course user roles for internal users
for user, role in zip([pc_user, marketing_user, publisher_user], PublisherUserRole.choices):
roles = [role for role, __ in PublisherUserRole.choices]
for user, role in zip([pc_user, marketing_user, publisher_user], roles):
factories.CourseUserRoleFactory(course=self.course, user=user, role=role)
response = self.client.get(self.page_url)
......
from unittest import TestCase
from django.test import TestCase
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory, force_authenticate
......
import os
from course_discovery.settings.base import *
# noinspection PyUnresolvedReferences
from course_discovery.settings.shared.test import *
INSTALLED_APPS += [
'django_nose',
'course_discovery.apps.edx_catalog_extensions',
]
# IN-MEMORY TEST DATABASE
TEST_NON_SERIALIZED_APPS = [
# Prevents the issue described at https://code.djangoproject.com/ticket/23727.
'django.contrib.contenttypes',
# Because of the bug linked above, loading serialized data for this app in a
# TransactionTestCase with serialized_rollback=True will cause IntegrityErrors
# on databases that check foreign key constraints (e.g., MySQL, not SQLite).
# The app's models contain foreign keys referencing content types that no longer
# exist when serialized data is loaded. This is a variant of the issue described
# at https://code.djangoproject.com/ticket/10827.
'django.contrib.auth',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.environ.get('DB_NAME', ':memory:'),
'USER': os.environ.get('DB_USER', ''),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': os.environ.get('DB_HOST', ''),
'PORT': os.environ.get('DB_PORT', ''),
'CONN_MAX_AGE': int(os.environ.get('CONN_MAX_AGE', 0)),
},
}
# END IN-MEMORY TEST DATABASE
JWT_AUTH['JWT_SECRET_KEY'] = 'course-discovery-jwt-secret-key'
......
......@@ -37,6 +37,7 @@ elasticsearch>=1.0.0,<2.0.0
html2text==2016.9.19
jsonfield==1.0.3
markdown==2.6.6
mysqlclient==1.3.9
pillow==3.4.2
pycountry==1.20
python-dateutil==2.5.3
......
......@@ -5,7 +5,6 @@ certifi==2016.9.26
django-ses==0.8.1
gevent==1.1.2
gunicorn==19.6.0
mysqlclient==1.3.9
newrelic==2.74.0.54
nodeenv==1.0.0
python-memcached==1.58
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment