<commit_before>"""Basic selenium tests for MicroMasters"""
from base64 import b64encode
from datetime import timedelta
import logging
import os
import socket
from subprocess import (
    check_call,
    DEVNULL,
)
from tempfile import mkstemp
from unittest.mock import patch
from urllib.parse import (
    ParseResult,
    urlparse,
)

from django.conf import settings
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.core.management import call_command
from django.db import connection
from django.db.models.signals import post_save
from factory.django import mute_signals
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver import (
    DesiredCapabilities,
    Remote,
)
from selenium.webdriver.support.wait import WebDriverWait

from backends.edxorg import EdxOrgOAuth2
from courses.factories import CourseRunFactory
from dashboard.models import (
    ProgramEnrollment,
    UserCacheRefreshTime,
)
from ecommerce.models import (
    Coupon,
    UserCoupon,
)
from financialaid.models import (
    Tier,
    TierProgram,
)
from micromasters.utils import now_in_utc
from profiles.factories import ProfileFactory
from search.indexing_api import (
    delete_index,
    recreate_index,
)


log = logging.getLogger(__name__)


def _make_absolute_url(relative_url, absolute_base):
    """
    Create an absolute URL for selenium testing given a relative URL

    Args:
        relative_url (str): A relative URL
        absolute_base (str): An absolute URL which contains the http port and scheme we need

    Returns:
        str: An absolute URL pointing to the live server instance
    """
    # Swap out the hostname, which was set to 0.0.0.0 to allow external connections
    # Change it to use ip of this container instead
    absolute_pieces = urlparse(absolute_base)
    relative_pieces = urlparse(relative_url)
    host = socket.gethostbyname(socket.gethostname())
    return ParseResult(
        absolute_pieces.scheme,
        "{host}:{port}".format(host=host, port=absolute_pieces.port),
        relative_pieces.path,
        relative_pieces.params,
        relative_pieces.query,
        relative_pieces.fragment,
    ).geturl()


class CustomWebDriverWait(WebDriverWait):
    """WebDriverWait with extra custom methods"""

    def click(self, method, retries=3):
        """Ignore StaleElementReferenceExceptions when clicking an item"""
        while True:
            try:
                return self.until(method).click()
            except StaleElementReferenceException:
                if retries > 0:
                    retries -= 1
                else:
                    raise


class SeleniumTestsBase(StaticLiveServerTestCase):
    """Base class for selenium tests"""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        # Patch functions so we don't contact edX
        cls.patchers = []
        cls.patchers.append(patch('ecommerce.views.enroll_user_on_success', autospec=True))
        for patcher in cls.patchers:
            patcher.start()

        # Clear and repopulate database using migrations
        cls.clear_db()
        call_command("migrate", noinput=True)

        # ensure index exists. This uses the database so it must come after all migrations are run.
        recreate_index()

        # Dump the database so we can do a quick revert and skip migrations
        cls.database_dump_path = cls.dump_db()
        cls.restore_db()

        # Start a selenium server running chrome
        capabilities = DesiredCapabilities.CHROME.copy()
        capabilities['chromeOptions'] = {
            'binary': os.getenv('CHROME_BIN', '/usr/bin/google-chrome-stable'),
            'args': ['--no-sandbox'],
        }

        # Setup selenium remote connection here. This should happen at the end of setUpClass
        # because if an exception is raised here, tearDownClass will not get executed, which will leave
        # a connection to selenium open preventing it from rerunning the test without restarting the grid.
        cls.selenium = Remote(
            os.getenv('SELENIUM_URL', 'http://chrome:5555/wd/hub'),
            capabilities,
        )
        cls.selenium.implicitly_wait(10)

    def setUp(self):
        super().setUp()

        # Ensure Elasticsearch index exists
        recreate_index()

        self.user = self.create_user()
        self.password = "<PASSWORD>"
        self.user.set_password(self.password)
        self.user.save()

        # Update profile to pass validation so we don't get redirected to the signup page
        profile = self.user.profile
        profile.phone_number = '+93-23-232-3232'
        profile.filled_out = True
        profile.agreed_to_terms_of_service = True
        profile.save()

        self.edx_username = self.user.social_auth.get(provider=EdxOrgOAuth2.name).uid

        # Create a live program with valid prices and financial aid
        run = CourseRunFactory.create(
            course__program__live=True,
            course__program__financial_aid_availability=True,
            course__program__price=1000,
        )
        self.program = program = run.course.program
        TierProgram.objects.create(
            tier=Tier.objects.create(name="$0 discount"),
            current=True,
            discount_amount=0,
            income_threshold=35000,
            program=program,
        )
        TierProgram.objects.create(
            tier=Tier.objects.create(name="$0 threshold"),
            current=True,
            discount_amount=150,
            income_threshold=0,
            program=program,
        )
        # Make a 100% off coupon. By setting the price to $0 we can avoid dealing with Cybersource
        coupon = Coupon(
            amount=1,
            amount_type=Coupon.PERCENT_DISCOUNT,
            coupon_type=Coupon.STANDARD,
        )
        coupon.content_object = program
        coupon.save()
        # Attach coupon and program to user
        UserCoupon.objects.create(coupon=coupon, user=self.user)
        ProgramEnrollment.objects.create(program=run.course.program, user=self.user)

        self.assert_console_logs()

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()

        for patcher in cls.patchers:
            patcher.stop()<commit_msg>Handle reuse_db option (#3247)<commit_after>"""Basic selenium tests for MicroMasters"""
from base64 import b64encode
from datetime import timedelta
import logging
import os
import socket
from subprocess import (
    check_call,
    DEVNULL,
)
from tempfile import mkstemp
from unittest.mock import patch
from urllib.parse import (
    ParseResult,
    urlparse,
)

from django.conf import settings
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.core.management import call_command
from django.db import connection
from django.db.models.signals import post_save
from factory.django import mute_signals
import pytest
from selenium.common.exceptions import (
    ElementNotVisibleException,
    StaleElementReferenceException,
    WebDriverException,
)
from selenium.webdriver import (
    DesiredCapabilities,
    Remote,
)
from selenium.webdriver.support.wait import WebDriverWait

from backends.edxorg import EdxOrgOAuth2
from courses.factories import CourseRunFactory
from dashboard.models import (
    ProgramEnrollment,
    UserCacheRefreshTime,
)
from ecommerce.models import (
    Coupon,
    UserCoupon,
)
from financialaid.models import (
    Tier,
    TierProgram,
)
from micromasters.utils import now_in_utc
from profiles.factories import ProfileFactory
from search.indexing_api import (
    delete_index,
    recreate_index,
)


log = logging.getLogger(__name__)


def _make_absolute_url(relative_url, absolute_base):
    """
    Create an absolute URL for selenium testing given a relative URL

    Args:
        relative_url (str): A relative URL
        absolute_base (str): An absolute URL which contains the http port and scheme we need

    Returns:
        str: An absolute URL pointing to the live server instance
    """
    # Swap out the hostname, which was set to 0.0.0.0 to allow external connections
    # Change it to use ip of this container instead
    absolute_pieces = urlparse(absolute_base)
    relative_pieces = urlparse(relative_url)
    host = socket.gethostbyname(socket.gethostname())
    return ParseResult(
        absolute_pieces.scheme,
        "{host}:{port}".format(host=host, port=absolute_pieces.port),
        relative_pieces.path,
        relative_pieces.params,
        relative_pieces.query,
        relative_pieces.fragment,
    ).geturl()


class CustomWebDriverWait(WebDriverWait):
    """WebDriverWait with extra custom methods"""

    def click(self, method, retries=3):
        """Ignore StaleElementReferenceExceptions when clicking an item"""
        while True:
            try:
                return self.until(method).click()
            except (StaleElementReferenceException, ElementNotVisibleException):
                if retries > 0:
                    retries -= 1
                else:
                    raise
            except WebDriverException as ex:
                if 'not clickable at point' not in ex.msg:
                    raise
                elif retries > 0:
                    retries -= 1
                else:
                    raise


class SeleniumTestsBase(StaticLiveServerTestCase):
    """Base class for selenium tests"""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        # Patch functions so we don't contact edX
        cls.patchers = []
        cls.patchers.append(patch('ecommerce.views.enroll_user_on_success', autospec=True))
        for patcher in cls.patchers:
            patcher.start()

        # Clear and repopulate database using migrations
        if not pytest.config.option.reuse_db:
            cls.clear_db()
        call_command("migrate", noinput=True)

        # ensure index exists. This uses the database so it must come after all migrations are run.
        recreate_index()

        # Dump the database so we can do a quick revert and skip migrations
        cls.database_dump_path = cls.dump_db()
        cls.restore_db()

        # Start a selenium server running chrome
        capabilities = DesiredCapabilities.CHROME.copy()
        capabilities['chromeOptions'] = {
            'binary': os.getenv('CHROME_BIN', '/usr/bin/google-chrome-stable'),
            'args': ['--no-sandbox'],
        }

        # Setup selenium remote connection here. This should happen at the end of setUpClass
        # because if an exception is raised here, tearDownClass will not get executed, which will leave
        # a connection to selenium open preventing it from rerunning the test without restarting the grid.
        cls.selenium = Remote(
            os.getenv('SELENIUM_URL', 'http://chrome:5555/wd/hub'),
            capabilities,
        )
        cls.selenium.implicitly_wait(10)

    def setUp(self):
        super().setUp()

        self.restore_db()
        # Ensure Elasticsearch index exists
        recreate_index()

        self.user = self.create_user()
        self.password = "<PASSWORD>"
        self.user.set_password(self.password)
        self.user.save()

        # Update profile to pass validation so we don't get redirected to the signup page
        profile = self.user.profile
        profile.phone_number = '+1-800-888-8888'
        profile.country = 'US'
        profile.state_or_territory = 'US-MA'
        profile.postal_code = '02142'
        profile.filled_out = True
        profile.agreed_to_terms_of_service = True
        profile.save()

        self.edx_username = self.user.social_auth.get(provider=EdxOrgOAuth2.name).uid

        # Create a live program with valid prices and financial aid
        run = CourseRunFactory.create(
            course__program__live=True,
            course__program__financial_aid_availability=True,
            course__program__price=1000,
        )
        self.program = program = run.course.program
        TierProgram.objects.create(
            tier=Tier.objects.create(name="$0 discount"),
            current=True,
            discount_amount=0,
            income_threshold=35000,
            program=program,
        )
        TierProgram.objects.create(
            tier=Tier.objects.create(name="$0 threshold"),
            current=True,
            discount_amount=150,
            income_threshold=0,
            program=program,
        )
        # Make a 100% off coupon. By setting the price to $0 we can avoid dealing with Cybersource
        coupon = Coupon(
            amount=1,
            amount_type=Coupon.PERCENT_DISCOUNT,
            coupon_type=Coupon.STANDARD,
        )
        coupon.content_object = program
        coupon.save()
        # Attach coupon and program to user
        UserCoupon.objects.create(coupon=coupon, user=self.user)
        ProgramEnrollment.objects.create(program=run.course.program, user=self.user)

        # Iterate through browser logs to empty them so we start with a clean slate
        list(self.selenium.get_log("browser"))

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()

        for patcher in cls.patchers:
            patcher.stop()
{'fraction_non_alphanumeric': 0.07628711010898158, 'fraction_numerical': 0.008493047726418639, 'mean_word_length': 2.5539529914529915, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7449937', 'n_tokens_mistral': 3766, 'n_tokens_neox': 3589, 'n_words': 997}