def get_ip_open_where_display_index(track_url_obj):
    """
    Maps logical combinations of track_url object properties to a user interface display object choice.
    Meant to be used for UI select menu.
    :param track_url_obj: a track URL object as returned by InterventionPoint.track_urls()
    :return an index mapping to a specific select option for an html dropdown selectmenu list
    """

    if not track_url_obj.open_as_tab and track_url_obj.is_canvas_page:
        return 0  # <option value="canvasPage">
    if not track_url_obj.open_as_tab and not track_url_obj.is_canvas_page:
        return 1  # <option value="externalPage">
    if track_url_obj.open_as_tab:
        return 2  # <option value="newTab"> INTERVENTION_POINT_URL_TAG = 'intervention_point_url_' DEPLOY_OPTION_TAG = 'is_canvas_page_for_track_' class InterventionPoint(CourseObject):
    """ This model stores the configuration of an intervention point"""
    name = models.CharField(max_length=NAME_CHAR_LIMIT)
    notes = models.TextField()
    experiment = models.ForeignKey(Experiment, related_name="intervention_points")
    tracks = models.ManyToManyField(Track, through="InterventionPointUrl")
    
    def is_missing_urls(self):
        if (Track.objects.filter(course_id=self.course_id, experiment=self.experiment).count()
            != self.tracks.count()):
            return True
        for intervention_point_url in InterventionPointUrl.objects.filter(intervention_point=self):
            if not intervention_point_url.url:
                return True
        return False
    
    class Meta:
        unique_together = (('experiment', 'name'),)
    
    def track_urls(self):
        """ This gets the InterventionPointUrls for an InterventionPoint in the
            default databse order of Tracks and uses a mock InterventionPointUrl
            with only the track attribute set in the event an InterventionPointUrl
            is missing for that track """
        ip_urls = self.interventionpointurl_set.all()
        track_urls = []
        for track in self.experiment.tracks.all():
            for ip_url in ip_urls:
                if ip_url.track == track:
                    track_urls.append(ip_url)
                    break
            else:
                track_urls.append(InterventionPointUrl(track=track))
        return track_urls class InterventionPointUrl(TimestampedModel):
    """ This model stores the URL of a single intervention """
    url = models.URLField(max_length=URL_CHAR_LIMIT)
    track = models.ForeignKey(Track)
    intervention_point = models.ForeignKey(InterventionPoint)
    open_as_tab = models.BooleanField(default=False)
    is_canvas_page = models.BooleanField(default=False)
    
    class Meta:
        unique_together = (('track', 'intervention_point'),) class ExperimentStudent(CourseObject):
    """ This model stores which track a student is in for a given experiment
        within a given course.  A real-world student can be represented by
        multiple ExperimentStudent objects, and will have a separate object for
        each experiment they are in. """
    student_id = models.CharField(max_length=128, db_index=True)
    student_name = models.CharField(max_length=256, db_index=True, null=True)
    experiment = models.ForeignKey(Experiment, related_name="students")
    track = models.ForeignKey(Track)
    
    class Meta:
        unique_together = (('experiment', 'student_id'),) class Experiment(CourseObject):
    UNIFORM_RANDOM = 1
    WEIGHTED_PROBABILITY_RANDOM = 2
    CSV_UPLOAD = 3
    REVERSE_API = 4
    
    ASSIGNMENT_ENUM_TYPES = (
        (UNIFORM_RANDOM, "uniform_random"),
        (WEIGHTED_PROBABILITY_RANDOM, "weighted_probability_random"),
        (CSV_UPLOAD, "csv_upload"),
        (REVERSE_API, "reverse_api"),
    )
    
    name = models.CharField(max_length=NAME_CHAR_LIMIT)
    notes = models.TextField()
    tracks_finalized = models.BooleanField(default=False)
    assignment_method = models.IntegerField(max_length=1, default=1,
                                            choices=ASSIGNMENT_ENUM_TYPES,)
    
    class Meta:
        unique_together = (('course_id', 'name'),)
    
    @classmethod
    def get_all_started_csv(cls):
        return cls.objects.filter(tracks_finalized=True,
                                  assignment_method=Experiment.CSV_UPLOAD)
    
    def get_course_notification(self):
        """ The CourseNotification object should exist, at the point at which
            an experiment has been created under the same course, so this method
            will raise an exception if the CourseNotification object doesn't
            exist yet."""
        return CourseNotification.objects.get(course_id=self.course_id)
    
    def assert_not_finalized(self):
        if self.tracks_finalized:
            raise EXPERIMENT_TRACKS_ALREADY_FINALIZED
    
    def new_track(self, track_name):
        return Track.objects.create(course_id=self.course_id, experiment=self,
                                    name=track_name)
    
    def get_incomplete_intervention_point_names(self):
        return [point.name for point in self.intervention_points.all() if point.is_missing_urls()]
    
    def to_json(self):
        """ Converts the experiment and its associated tracks to json,
            in the form expected by the edit_experiment.html template and
            by edit_experiment.js """
        experiment_dict = {
            "id": self.id,
            "name": self.name,
            "notes": self.notes,
            "uniformRandom": bool(self.assignment_method == self.UNIFORM_RANDOM),
            "csvUpload": bool(self.assignment_method == self.CSV_UPLOAD),
            "tracks": [{"id": t.id,
                        "weighting": t.get_weighting(),
                        "name": t.name,
                        # databaseName stores the track name in the database for collision comparison
                        "databaseName": t.name,
                        # newName and editing are used to hold temporary values for track name editing
                        "newName" : t.name,
                        "editing": False,
                        "deleteURL": reverse('ab_testing_tool_delete_track', args=(t.id,))}
                       for t in self.tracks.all()],
        }
        return json.dumps(experiment_dict)
    
    def copy(self, new_name):
        tracks = self.tracks.all()
        intervention_points = self.intervention_points.all()
        # Copy Experiment
        self.save_as_new_object(name=new_name, tracks_finalized=False)
        
        # Copy Tracks
        track_id_mapping = {}
        for track in tracks:
            original_track_id = track.id
            track.save_as_new_object(experiment=self)
            track_id_mapping[original_track_id] = track
            # Copy TrackProbabilityWeight, if any
            try:
                weight = TrackProbabilityWeight.objects.get(track_id=original_track_id)
                weight.save_as_new_object(track=track)
            except TrackProbabilityWeight.DoesNotExist:
                pass
        
        # Copy InterventionPoints
        for intervention_point in intervention_points:
            orig_ip_id = intervention_point.id
            intervention_point.save_as_new_object(experiment=self)
            # Copy InterventionPointUrls, if any
            for ip_url in InterventionPointUrl.objects.filter(intervention_point_id=orig_ip_id):
                ip_url.save_as_new_object(track=track_id_mapping[ip_url.track.id],
                                          intervention_point=intervention_point)
    
    @classmethod
    def get_placeholder_course_experiment(cls, course_id):
        """ Gets or creates a single experiment for the course.  Placeholder
            method until interface supports multiple experiments.
            TODO: Remove once multiple experiments are supported """
        return Experiment.objects.get_or_create(course_id=course_id, name="Experiment 1")[0] class SessionTestCase(TestCase):
    def setUp(self):
        # http://code.djangoproject.com/ticket/10899
        settings.SESSION_ENGINE = "django.contrib.sessions.backends.file"
        engine = import_module(settings.SESSION_ENGINE)
        store = engine.SessionStore()
        store.save()
        self.session = store
        self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
        
        settings.LOGGING = {}
        
        lti_launch = {}
        lti_launch["roles"] = ADMINS
        lti_launch["custom_canvas_course_id"] = TEST_COURSE_ID
        lti_launch["lis_course_offering_sourcedid"] = "92345"
        lti_launch["custom_canvas_api_domain"] = TEST_DOMAIN
        lti_launch["launch_presentation_return_url"] = TEST_DOMAIN
        lti_launch["custom_canvas_user_login_id"] = TEST_STUDENT_ID
        lti_launch["lis_person_name_full"] = TEST_STUDENT_NAME
        lti_launch["context_title"] = "Course title"
        lti_launch["lis_person_contact_email_primary"] = TEST_EMAIL
        session = self.client.session
        session["LTI_LAUNCH"] = lti_launch
        session.save()
        
        self.request = RequestMock()
        self.request.session = session
        
        # Patches api functions for all tests; can be overridden by re-patching
        # the particular api call for a particular test
        patchers = [
            patch(LIST_MODULES, return_value=APIReturn([])),
            patch(LIST_ITEMS, return_value=APIReturn([])),
            patch(GET_TOKEN, return_value="MOCK_TOKEN"),
        ]
        for patcher in patchers:
            patcher.start()
            self.addCleanup(patcher.stop)
    
    def set_lti_param(self, param_name, param_value):
        """ Convenience method to set an LIT_LAUNCH parameter in the mock session """
        session = self.client.session
        session["LTI_LAUNCH"][param_name] = param_value
        session.save()
    
    def set_roles(self, roles):
        self.set_lti_param("roles", roles)
    
    def assertOkay(self, response):
        self.assertEqual(response.status_code, 200)
    
    def assertError(self, response, exception_instance):
        self.assertTemplateUsed(response, ERROR_TEMPLATE)
        if isinstance(exception_instance, RenderableError):
            self.assertEqual(response.status_code, exception_instance.status_code)
        else:
            self.assertEqual(response.status_code, DEFAULT_ERROR_STATUS)
        self.assertIn("message", response.context)
        self.assertEqual(response.context["message"], str(exception_instance))
    
    def assertSameIds(self, iterable_1, iterable_2, duplicates_allowed=False):
        """ Checks that the two iterables have the same set of .id attributes
            among their items; if duplicates_allowed is False, the test
            will pass if either iterable contains duplicate_ids within itself """
        ids_1 = [i.id for i in iterable_1]
        ids_2 = [i.id for i in iterable_2]
        if not duplicates_allowed:
            self.assertEqual(len(set(ids_1)), len(ids_1))
            self.assertEqual(len(set(ids_2)), len(ids_2))
        self.assertEqual(set(ids_1), set(ids_2))
    
    def assertRaisesSpecific(self, exception_instance, func, *args, **kwargs):
        """ Asserts that the function with the passed args and kwargs,
            raises an exception that matches the exception_instance.
            Match means has the same message, and is either the same class as
            exception_instance or a child of exception_instance's class. """
        error = False
        self.assertRaises(exception_instance.__class__, func, *args, **kwargs)
        try:
            func(*args, **kwargs)
            error = True
            
        except Exception as e:
            self.assertEquals(str(e), str(exception_instance))
        if error:
            raise Exception("Function either raises no exception or is "
                            "non-deterministic")
    
    def create_test_experiment(self, course_id=TEST_COURSE_ID, name="testexperiment",
                               assignment_method=Experiment.UNIFORM_RANDOM, **kwargs):
        return Experiment.objects.create(name=name, course_id=course_id,
                                         assignment_method=assignment_method,
                                         **kwargs)
    
    def create_test_track(self, course_id=TEST_COURSE_ID, name="testtrack", experiment=None):
        if not experiment:
            experiment = Experiment.get_placeholder_course_experiment(course_id)
        return Track.objects.create(name=name, course_id=course_id,
                                    experiment=experiment)
    
    def create_test_track_weight(self, course_id=TEST_COURSE_ID, weighting=1,
                                 track=None, experiment=None):
        if not experiment:
            experiment = Experiment.get_placeholder_course_experiment(course_id)
        if not track:
            track = Track.objects.create(name="testtrack", course_id=course_id,
                                         experiment=experiment)
        return TrackProbabilityWeight.objects.create(
                course_id=course_id, weighting=weighting, track=track,
                experiment=experiment)
    
    def create_test_intervention_point(self, course_id=TEST_COURSE_ID, name="testip",
                                       experiment=None):
        if not experiment:
            experiment = Experiment.get_placeholder_course_experiment(course_id)
        return InterventionPoint.objects.create(name=name, course_id=course_id,
                                                experiment=experiment) TEST_COURSE_ID = "12345" TEST_OTHER_COURSE_ID = "5555555" NONEXISTENT_INTERVENTION_POINT_ID = 12345678987654321 #111111111^2 class APIReturn(object):
    """ Spoofs returned response from Canvas SDK. Has response.ok property and
        JSON contents """
    def __init__(self, obj, ok=True):
        self.obj = obj
        self.text = dumps(obj)
        self.ok = ok
        
    def json(self):
        return self.obj LIST_MODULES = "canvas_sdk.methods.modules.list_modules" TEST_STUDENT_ID = "70707707" NO_URL_FOR_TRACK = Renderable403(STUDENT_VISIBLE_ERROR) UNAUTHORIZED_ACCESS = Renderable403("Sorry, you do not have access to this course. If you feel that this is an error, please contact a member of your local academic support staff.") EXPERIMENT_TRACKS_NOT_FINALIZED = Renderable403(STUDENT_VISIBLE_ERROR) NO_TRACKS_FOR_EXPERIMENT = Renderable403(STUDENT_VISIBLE_ERROR) DELETING_INSTALLED_INTERVENTION_POINT = Renderable400("Deleting an installed Intervention Point is not allowed.") UNIQUE_NAME_ERROR = Renderable400("Another intervention point with this name was found in the database. Names must be unique. Ensure you are using a different name and please try again.")
from django.core.urlresolvers import reverse
from mock import patch, Mock
from ab_tool.views.intervention_point_pages import get_ip_open_where_display_index
from ab_tool.constants import INTERVENTION_POINT_URL_TAG, DEPLOY_OPTION_TAG
from ab_tool.models import (InterventionPoint, InterventionPointUrl,
    ExperimentStudent, Experiment)
from ab_tool.tests.common import (SessionTestCase, TEST_COURSE_ID,
    TEST_OTHER_COURSE_ID, NONEXISTENT_INTERVENTION_POINT_ID, APIReturn, LIST_MODULES,
    TEST_STUDENT_ID)
from ab_tool.exceptions import (NO_URL_FOR_TRACK,  UNAUTHORIZED_ACCESS,
    EXPERIMENT_TRACKS_NOT_FINALIZED, NO_TRACKS_FOR_EXPERIMENT,
    DELETING_INSTALLED_INTERVENTION_POINT, UNIQUE_NAME_ERROR)
    
    def test_deploy_intervention_point_student_created(self):
        """ Tests deploy intervention_point for student creates student object and assigns
            track to that student object """
        self.set_roles([])
        Experiment.get_placeholder_course_experiment(TEST_COURSE_ID).update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point()
        track = self.create_test_track(name="track1")
        students = ExperimentStudent.objects.filter(course_id=TEST_COURSE_ID,
                                               student_id=TEST_STUDENT_ID)
        self.assertEqual(students.count(), 0)
        InterventionPointUrl.objects.create(intervention_point=intervention_point, track=track,
                                url="http://www.example.com")
        self.client.get(reverse("ab_testing_tool_deploy_intervention_point", args=(intervention_point.id,)))
        student = ExperimentStudent.objects.get(course_id=TEST_COURSE_ID,
                                            student_id=TEST_STUDENT_ID)
        self.assertEqual(student.track.name, "track1")
    
    def test_deploy_intervention_point_student_redirect(self):
        """ Tests deploy intervention_point for student redirects to the correct url """
        self.set_roles([])
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        experiment.update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point(name="intervention_point1")
        track = self.create_test_track(name="track1")
        intervention_point2 = self.create_test_intervention_point(name="intervention_point2")
        track2 = self.create_test_track(name="track2")
        ExperimentStudent.objects.create(course_id=TEST_COURSE_ID, experiment=experiment,
                                         student_id=TEST_STUDENT_ID, track=track)
        InterventionPointUrl.objects.create(intervention_point=intervention_point, track=track,
                                            url="http://www.example.com")
        InterventionPointUrl.objects.create(intervention_point=intervention_point2, track=track2,
                                            url="http://www.incorrect-domain.com")
        response = self.client.get(reverse("ab_testing_tool_deploy_intervention_point",
                                           args=(intervention_point.id,)))
        # Can't use assertRedirects because it is an external domain
        self.assertEqual(response._headers['location'],
                         ("Location", "http://www.example.com"))
        self.assertEqual(response.status_code, 302)
    
    def test_deploy_intervention_point_no_url(self):
        """ Tests deploy intervention_point with existing InterventionPointUrl
            for student with NO_URL_FOR_TRACK error """
        self.set_roles([])
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        experiment.update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point()
        track = self.create_test_track()
        ExperimentStudent.objects.create(course_id=TEST_COURSE_ID, experiment=experiment,
                                         student_id=TEST_STUDENT_ID, track=track)
        InterventionPointUrl.objects.create(intervention_point=intervention_point,
                                            track=track, url="")
        response = self.client.get(reverse("ab_testing_tool_deploy_intervention_point",
                                           args=(intervention_point.id,)))
        self.assertError(response, NO_URL_FOR_TRACK)
    
    def test_deploy_intervention_point_with_no_interventionpointurl_created(self):
        """ Tests deploy intervention_point with no InterventionPointUrl
            for student with NO_URL_FOR_TRACK error """
        self.set_roles([])
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        experiment.update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point()
        track = self.create_test_track()
        ExperimentStudent.objects.create(course_id=TEST_COURSE_ID, experiment=experiment,
                                         student_id=TEST_STUDENT_ID, track=track)
        response = self.client.get(reverse("ab_testing_tool_deploy_intervention_point",
                                           args=(intervention_point.id,)))
        self.assertError(response, NO_URL_FOR_TRACK)
    
    def test_deploy_intervention_point_with_url_as_new_tab(self):
        """ Tests deploy intervention_point successfully loads url as new tab"""
        self.set_roles([])
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        experiment.update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point()
        track = self.create_test_track()
        ExperimentStudent.objects.create(course_id=TEST_COURSE_ID, experiment=experiment,
                                         student_id=TEST_STUDENT_ID, track=track)
        InterventionPointUrl.objects.create(intervention_point=intervention_point,
                                            track=track, url="www.google.com", open_as_tab=True)
        response = self.client.get(reverse("ab_testing_tool_deploy_intervention_point",
                                           args=(intervention_point.id,)), follow=True)
        self.assertTemplateUsed(response, "ab_tool/new_tab_redirect.html")
    
    def test_deploy_intervention_point_with_url_as_canvas_page(self):
        """ Tests deploy intervention_point successfully loads url in a window redirect"""
        self.set_roles([])
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        experiment.update(tracks_finalized=True)
        intervention_point = self.create_test_intervention_point()
        track = self.create_test_track()
        ExperimentStudent.objects.create(course_id=TEST_COURSE_ID, experiment=experiment,
                                         student_id=TEST_STUDENT_ID, track=track)
        InterventionPointUrl.objects.create(intervention_point=intervention_point,
                                            track=track, url="www.google.com", is_canvas_page=True)
        response = self.client.get(reverse("ab_testing_tool_deploy_intervention_point",
                                           args=(intervention_point.id,)), follow=True)
        self.assertTemplateUsed(response, "ab_tool/window_redirect.html")
    
    def test_submit_create_intervention_point(self):
        """ Tests that create_intervention_point creates an InterventionPoint
            object verified by DB count """
        num_intervention_points = InterventionPoint.objects.count()
        data = {"name": "intervention_point", "notes": "hi"}
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        url = reverse("ab_testing_tool_submit_create_intervention_point", args=(experiment.id,))
        response = self.client.post(url, data, follow=True)
        self.assertOkay(response)
        self.assertEqual(num_intervention_points + 1, InterventionPoint.objects.count())
    
    def test_submit_create_intervention_point_with_same_name_raises_exception(self):
        """ Tests that create_intervention_point with same name as existing intervention
            point raises exception"""
        intervention_point = self.create_test_intervention_point(name="SAME_NAME")
        num_intervention_points = InterventionPoint.objects.count()
        data = {"name": intervention_point.name, "notes": "hi"}
        experiment = Experiment.get_placeholder_course_experiment(TEST_COURSE_ID)
        url = reverse("ab_testing_tool_submit_create_intervention_point", args=(experiment.id,))
        response = self.client.post(url, data, follow=True)

        self.assertError(response, UNIQUE_NAME_ERROR)