diff --git a/cms/envs/common.py b/cms/envs/common.py
index 35566f69a29b3d34acb815e6b524d726a4a5e3c7..eb4ca9fed6d3334d6cd0fb310df741ffd3617c39 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -1082,7 +1082,7 @@ INSTALLED_APPS = [
     'courseware',
     'survey',
     'lms.djangoapps.verify_student.apps.VerifyStudentConfig',
-    'lms.djangoapps.completion.apps.CompletionAppConfig',
+    'completion',
 
     # Microsite configuration application
     'microsite_configuration',
diff --git a/lms/djangoapps/completion/__init__.py b/lms/djangoapps/completion/__init__.py
deleted file mode 100644
index e6065f215cf4f6d7f957e07d234d24b3f07c6ef5..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""
-Completion App
-"""
-
-default_app_config = 'lms.djangoapps.completion.apps.CompletionAppConfig'
diff --git a/lms/djangoapps/completion/api/urls.py b/lms/djangoapps/completion/api/urls.py
deleted file mode 100644
index 276d4f949afa2a5ecd98683c0a37112e3e083aaa..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/api/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""
-Api URLs.
-"""
-from django.conf.urls import include, url
-
-urlpatterns = [
-    url(r'^v1/', include('lms.djangoapps.completion.api.v1.urls', namespace='v1')),
-]
diff --git a/lms/djangoapps/completion/api/v1/tests/__init__.py b/lms/djangoapps/completion/api/v1/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/lms/djangoapps/completion/api/v1/urls.py b/lms/djangoapps/completion/api/v1/urls.py
deleted file mode 100644
index 18d6a5a0a036fcf7b222ff2b0935d029ec96a548..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/api/v1/urls.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
-API v1 URLs.
-"""
-from django.conf.urls import include, url
-
-from . import views
-
-urlpatterns = [
-    url(r'^completion-batch', views.CompletionBatchView.as_view(), name='completion-batch'),
-]
diff --git a/lms/djangoapps/completion/api/v1/views.py b/lms/djangoapps/completion/api/v1/views.py
deleted file mode 100644
index 5c0cd03e5af51e9929d95fde97a3299c6fe23064..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/api/v1/views.py
+++ /dev/null
@@ -1,137 +0,0 @@
-""" API v1 views. """
-from django.contrib.auth.models import User
-from django.core.exceptions import ValidationError, ObjectDoesNotExist
-from django.utils.translation import ugettext as _
-from django.db import DatabaseError
-
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions
-from rest_framework import status
-
-from opaque_keys.edx.keys import CourseKey, UsageKey
-from opaque_keys import InvalidKeyError
-from six import text_type
-
-from lms.djangoapps.completion.models import BlockCompletion
-from openedx.core.djangoapps.content.course_structures.models import CourseStructure
-from openedx.core.lib.api.permissions import IsStaffOrOwner
-from student.models import CourseEnrollment
-from completion import waffle
-
-
-class CompletionBatchView(APIView):
-    """
-    Handles API requests to submit batch completions.
-    """
-    permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner,)
-    REQUIRED_KEYS = ['username', 'course_key', 'blocks']
-
-    def _validate_and_parse(self, batch_object):
-        """
-        Performs validation on the batch object to make sure it is in the proper format.
-
-        Parameters:
-            * batch_object: The data provided to a POST. The expected format is the following:
-            {
-                "username": "username",
-                "course_key": "course-key",
-                "blocks": {
-                    "block_key1": 0.0,
-                    "block_key2": 1.0,
-                    "block_key3": 1.0,
-                }
-            }
-
-
-        Return Value:
-            * tuple: (User, CourseKey, List of tuples (UsageKey, completion_float)
-
-        Raises:
-
-            django.core.exceptions.ValidationError:
-                If any aspect of validation fails a ValidationError is raised.
-
-            ObjectDoesNotExist:
-                If a database object cannot be found an ObjectDoesNotExist is raised.
-        """
-        if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING):
-            raise ValidationError(
-                _("BlockCompletion.objects.submit_batch_completion should not be called when the feature is disabled.")
-            )
-
-        for key in self.REQUIRED_KEYS:
-            if key not in batch_object:
-                raise ValidationError(_("Key '{key}' not found.".format(key=key)))
-
-        username = batch_object['username']
-        user = User.objects.get(username=username)
-
-        course_key = batch_object['course_key']
-        try:
-            course_key_obj = CourseKey.from_string(course_key)
-        except InvalidKeyError:
-            raise ValidationError(_("Invalid course key: {}").format(course_key))
-        course_structure = CourseStructure.objects.get(course_id=course_key_obj)
-
-        if not CourseEnrollment.is_enrolled(user, course_key_obj):
-            raise ValidationError(_('User is not enrolled in course.'))
-
-        blocks = batch_object['blocks']
-        block_objs = []
-        for block_key in blocks:
-            if block_key not in course_structure.structure['blocks'].keys():
-                raise ValidationError(_("Block with key: '{key}' is not in course {course}")
-                                      .format(key=block_key, course=course_key))
-
-            block_key_obj = UsageKey.from_string(block_key)
-            completion = float(blocks[block_key])
-            block_objs.append((block_key_obj, completion))
-
-        return user, course_key_obj, block_objs
-
-    def post(self, request, *args, **kwargs):
-        """
-        Inserts a batch of completions.
-
-        REST Endpoint Format:
-        {
-          "username": "username",
-          "course_key": "course-key",
-          "blocks": {
-            "block_key1": 0.0,
-            "block_key2": 1.0,
-            "block_key3": 1.0,
-          }
-        }
-
-        **Returns**
-
-        A Response object, with an appropriate status code.
-
-        If successful, status code is 200.
-        {
-           "detail" : _("ok")
-        }
-
-        Otherwise, a 400 or 404 may be returned, and the "detail" content will explain the error.
-
-        """
-        batch_object = request.data or {}
-        try:
-            user, course_key, blocks = self._validate_and_parse(batch_object)
-            BlockCompletion.objects.submit_batch_completion(user, course_key, blocks)
-        except (ValidationError, ValueError) as exc:
-            return Response({
-                "detail": exc.message,
-            }, status=status.HTTP_400_BAD_REQUEST)
-        except ObjectDoesNotExist as exc:
-            return Response({
-                "detail": text_type(exc),
-            }, status=status.HTTP_404_NOT_FOUND)
-        except DatabaseError as exc:
-            return Response({
-                "detail": text_type(exc),
-            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-        return Response({"detail": _("ok")}, status=status.HTTP_200_OK)
diff --git a/lms/djangoapps/completion/apps.py b/lms/djangoapps/completion/apps.py
deleted file mode 100644
index a684576ea56f20d61755a5a2c3de85c30958d2eb..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/apps.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-App Configuration for Completion
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
-from django.apps import AppConfig
-
-
-class CompletionAppConfig(AppConfig):
-    """
-    App Configuration for Completion
-    """
-    name = 'lms.djangoapps.completion'
-    verbose_name = 'Completion'
-
-    def ready(self):
-        from . import handlers  # pylint: disable=unused-variable
diff --git a/lms/djangoapps/completion/handlers.py b/lms/djangoapps/completion/handlers.py
deleted file mode 100644
index f7b70e20ad0181daeb73be899dcd7bec7dd94b1c..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/handlers.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-Signal handlers to trigger completion updates.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from django.contrib.auth.models import User
-from django.dispatch import receiver
-
-from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED
-from opaque_keys.edx.keys import CourseKey, UsageKey
-from xblock.completable import XBlockCompletionMode
-from xblock.core import XBlock
-
-from .models import BlockCompletion
-from . import waffle
-
-
-@receiver(PROBLEM_WEIGHTED_SCORE_CHANGED)
-def scorable_block_completion(sender, **kwargs):  # pylint: disable=unused-argument
-    """
-    When a problem is scored, submit a new BlockCompletion for that block.
-    """
-    if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING):
-        return
-    course_key = CourseKey.from_string(kwargs['course_id'])
-    block_key = UsageKey.from_string(kwargs['usage_id'])
-    block_cls = XBlock.load_class(block_key.block_type)
-    if getattr(block_cls, 'completion_mode', XBlockCompletionMode.COMPLETABLE) != XBlockCompletionMode.COMPLETABLE:
-        return
-    if getattr(block_cls, 'has_custom_completion', False):
-        return
-    user = User.objects.get(id=kwargs['user_id'])
-    if kwargs.get('score_deleted'):
-        completion = 0.0
-    else:
-        completion = 1.0
-    BlockCompletion.objects.submit_completion(
-        user=user,
-        course_key=course_key,
-        block_key=block_key,
-        completion=completion,
-    )
diff --git a/lms/djangoapps/completion/migrations/0001_initial.py b/lms/djangoapps/completion/migrations/0001_initial.py
deleted file mode 100644
index 9d1a99b10e79cd0ed09cb8daf952f36e890bb881..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/migrations/0001_initial.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.utils.timezone
-from django.conf import settings
-import model_utils.fields
-from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField
-
-import lms.djangoapps.completion.models
-
-# pylint: disable=ungrouped-imports
-try:
-    from django.models import BigAutoField  # New in django 1.10
-except ImportError:
-    from openedx.core.djangolib.fields import BigAutoField
-# pylint: enable=ungrouped-imports
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='BlockCompletion',
-            fields=[
-                ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
-                ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
-                ('id', BigAutoField(serialize=False, primary_key=True)),
-                ('course_key', CourseKeyField(max_length=255)),
-                ('block_key', UsageKeyField(max_length=255)),
-                ('block_type', models.CharField(max_length=64)),
-                ('completion', models.FloatField(validators=[lms.djangoapps.completion.models.validate_percent])),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
-            ],
-        ),
-        migrations.AlterUniqueTogether(
-            name='blockcompletion',
-            unique_together=set([('course_key', 'block_key', 'user')]),
-        ),
-        migrations.AlterIndexTogether(
-            name='blockcompletion',
-            index_together=set([('course_key', 'block_type', 'user'), ('user', 'course_key', 'modified')]),
-        ),
-    ]
diff --git a/lms/djangoapps/completion/migrations/0002_auto_20180125_1510.py b/lms/djangoapps/completion/migrations/0002_auto_20180125_1510.py
deleted file mode 100644
index f446854de0d9c6c272993c3e99470780e23eb677..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/migrations/0002_auto_20180125_1510.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('completion', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AlterModelOptions(
-            name='blockcompletion',
-            options={'get_latest_by': 'modified'},
-        ),
-    ]
diff --git a/lms/djangoapps/completion/migrations/__init__.py b/lms/djangoapps/completion/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/lms/djangoapps/completion/models.py b/lms/djangoapps/completion/models.py
deleted file mode 100644
index e6d7716f85988e23f7d4e99993fe0980fb5c5792..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/models.py
+++ /dev/null
@@ -1,233 +0,0 @@
-"""
-Completion tracking and aggregation models.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from django.contrib.auth.models import User
-from django.core.exceptions import ValidationError
-from django.db import models, transaction
-from django.utils.translation import ugettext as _
-from model_utils.models import TimeStampedModel
-from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField
-from opaque_keys.edx.keys import CourseKey
-
-from . import waffle
-
-# pylint: disable=ungrouped-imports
-try:
-    from django.models import BigAutoField  # New in django 1.10
-except ImportError:
-    from openedx.core.djangolib.fields import BigAutoField
-# pylint: enable=ungrouped-imports
-
-
-def validate_percent(value):
-    """
-    Verify that the passed value is between 0.0 and 1.0.
-    """
-    if not 0.0 <= value <= 1.0:
-        raise ValidationError(_('{value} must be between 0.0 and 1.0').format(value=value))
-
-
-class BlockCompletionManager(models.Manager):
-    """
-    Custom manager for BlockCompletion model.
-
-    Adds submit_completion and submit_batch_completion methods.
-    """
-
-    def submit_completion(self, user, course_key, block_key, completion):
-        """
-        Update the completion value for the specified record.
-
-        Parameters:
-            * user (django.contrib.auth.models.User): The user for whom the
-              completion is being submitted.
-            * course_key (opaque_keys.edx.keys.CourseKey): The course in
-              which the submitted block is found.
-            * block_key (opaque_keys.edx.keys.UsageKey): The block that has had
-              its completion changed.
-            * completion (float in range [0.0, 1.0]): The fractional completion
-              value of the block (0.0 = incomplete, 1.0 = complete).
-
-        Return Value:
-            (BlockCompletion, bool): A tuple comprising the created or updated
-            BlockCompletion object and a boolean value indicating whether the
-            object was newly created by this call.
-
-        Raises:
-
-            ValueError:
-                If the wrong type is passed for one of the parameters.
-
-            django.core.exceptions.ValidationError:
-                If a float is passed that is not between 0.0 and 1.0.
-
-            django.db.DatabaseError:
-                If there was a problem getting, creating, or updating the
-                BlockCompletion record in the database.
-
-                This will also be a more specific error, as described here:
-                https://docs.djangoproject.com/en/1.11/ref/exceptions/#database-exceptions.
-                IntegrityError and OperationalError are relatively common
-                subclasses.
-        """
-
-        # Raise ValueError to match normal django semantics for wrong type of field.
-        if not isinstance(course_key, CourseKey):
-            raise ValueError(
-                "course_key must be an instance of `opaque_keys.edx.keys.CourseKey`.  Got {}".format(type(course_key))
-            )
-        try:
-            block_type = block_key.block_type
-        except AttributeError:
-            raise ValueError(
-                "block_key must be an instance of `opaque_keys.edx.keys.UsageKey`.  Got {}".format(type(block_key))
-            )
-
-        if waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING):
-            obj, is_new = self.get_or_create(
-                user=user,
-                course_key=course_key,
-                block_type=block_type,
-                block_key=block_key,
-                defaults={'completion': completion},
-            )
-            if not is_new and obj.completion != completion:
-                obj.completion = completion
-                obj.full_clean()
-                obj.save()
-        else:
-            # If the feature is not enabled, this method should not be called.  Error out with a RuntimeError.
-            raise RuntimeError(
-                "BlockCompletion.objects.submit_completion should not be called when the feature is disabled."
-            )
-        return obj, is_new
-
-    @transaction.atomic()
-    def submit_batch_completion(self, user, course_key, blocks):
-        """
-        Performs a batch insertion of completion objects.
-
-        Parameters:
-            * user (django.contrib.auth.models.User): The user for whom the
-              completions are being submitted.
-            * course_key (opaque_keys.edx.keys.CourseKey): The course in
-              which the submitted blocks are found.
-            * blocks: A list of tuples of UsageKey to float completion values.
-              (float in range [0.0, 1.0]): The fractional completion
-              value of the block (0.0 = incomplete, 1.0 = complete).
-
-        Return Value:
-            Dict of (BlockCompletion, bool): A dictionary with a
-            BlockCompletion object key and a value of bool. The boolean value
-            indicates whether the object was newly created by this call.
-
-        Raises:
-
-            ValueError:
-                If the wrong type is passed for one of the parameters.
-
-            django.core.exceptions.ValidationError:
-                If a float is passed that is not between 0.0 and 1.0.
-
-            django.db.DatabaseError:
-                If there was a problem getting, creating, or updating the
-                BlockCompletion record in the database.
-        """
-        block_completions = {}
-        for block, completion in blocks:
-            (block_completion, is_new) = self.submit_completion(user, course_key, block, completion)
-            block_completions[block_completion] = is_new
-        return block_completions
-
-
-class BlockCompletion(TimeStampedModel, models.Model):
-    """
-    Track completion of completable blocks.
-
-    A completion is unique for each (user, course_key, block_key).
-
-    The block_type field is included separately from the block_key to
-    facilitate distinct aggregations of the completion of particular types of
-    block.
-
-    The completion value is stored as a float in the range [0.0, 1.0], and all
-    calculations are performed on this float, though current practice is to
-    only track binary completion, where 1.0 indicates that the block is
-    complete, and 0.0 indicates that the block is incomplete.
-    """
-    id = BigAutoField(primary_key=True)  # pylint: disable=invalid-name
-    user = models.ForeignKey(User)
-    course_key = CourseKeyField(max_length=255)
-
-    # note: this usage key may not have the run filled in for
-    # old mongo courses.  Use the full_block_key property
-    # instead when you want to use/compare the usage_key.
-    block_key = UsageKeyField(max_length=255)
-    block_type = models.CharField(max_length=64)
-    completion = models.FloatField(validators=[validate_percent])
-
-    objects = BlockCompletionManager()
-
-    @property
-    def full_block_key(self):
-        """
-        Returns the "correct" usage key value with the run filled in.
-        """
-        if self.block_key.run is None:
-            return self.block_key.replace(course_key=self.course_key)  # pylint: disable=unexpected-keyword-arg, no-value-for-parameter
-        else:
-            return self.block_key
-
-    @classmethod
-    def get_course_completions(cls, user, course_key):
-        """
-        query all completions for course/user pair
-
-        Return value:
-            dict[BlockKey] = float
-        """
-        course_block_completions = cls.objects.filter(
-            user=user,
-            course_key=course_key,
-        )
-        # will not return if <= 0.0
-        return {completion.block_key: completion.completion for completion in course_block_completions}
-
-    @classmethod
-    def get_latest_block_completed(cls, user, course_key):
-        """
-        query latest completion for course/user pair
-
-        Return value:
-            obj: block completion
-        """
-        try:
-            latest_modified_block_completion = cls.objects.filter(
-                user=user,
-                course_key=course_key,
-            ).latest()
-        except cls.DoesNotExist:
-            return
-        return latest_modified_block_completion
-
-    class Meta(object):
-        index_together = [
-            ('course_key', 'block_type', 'user'),
-            ('user', 'course_key', 'modified'),
-        ]
-
-        unique_together = [
-            ('course_key', 'block_key', 'user')
-        ]
-        get_latest_by = 'modified'
-
-    def __unicode__(self):
-        return 'BlockCompletion: {username}, {course_key}, {block_key}: {completion}'.format(
-            username=self.user.username,
-            course_key=self.course_key,
-            block_key=self.block_key,
-            completion=self.completion,
-        )
diff --git a/lms/djangoapps/completion/services.py b/lms/djangoapps/completion/services.py
deleted file mode 100644
index d92db3f3511b12ebb9b703c0509b11d299d3b889..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/services.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-Runtime service for communicating completion information to the xblock system.
-"""
-
-from .models import BlockCompletion
-from . import waffle
-
-
-class CompletionService(object):
-    """
-    Service for handling completions for a user within a course.
-
-    Exposes
-
-    * self.completion_tracking_enabled() -> bool
-    * self.visual_progress_enabled() -> bool
-    * self.get_completions(candidates)
-    * self.vertical_is_complete(vertical_item)
-
-    Constructor takes a user object and course_key as arguments.
-    """
-    def __init__(self, user, course_key):
-        self._user = user
-        self._course_key = course_key
-
-    def completion_tracking_enabled(self):
-        """
-        Exposes ENABLE_COMPLETION_TRACKING waffle switch to XModule runtime
-
-        Return value:
-
-            bool -> True if completion tracking is enabled.
-        """
-        return waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING)
-
-    def visual_progress_enabled(self):
-        """
-        Exposes VISUAL_PROGRESS_ENABLED waffle switch to XModule runtime
-
-        Return value:
-
-            bool -> True if VISUAL_PROGRESS flag is enabled.
-        """
-        return waffle.visual_progress_enabled(self._course_key)
-
-    def get_completions(self, candidates):
-        """
-        Given an iterable collection of block_keys in the course, returns a
-        mapping of the block_keys to the present completion values of their
-        associated blocks.
-
-        If a completion is not found for a given block in the current course,
-        0.0 is returned.  The service does not attempt to verify that the block
-        exists within the course.
-
-        Parameters:
-
-            candidates: collection of BlockKeys within the current course.
-
-        Return value:
-
-            dict[BlockKey] -> float: Mapping blocks to their completion value.
-        """
-        completion_queryset = BlockCompletion.objects.filter(
-            user=self._user,
-            course_key=self._course_key,
-            block_key__in=candidates,
-        )
-        completions = {
-            block.full_block_key: block.completion for block in completion_queryset  # pylint: disable=not-an-iterable
-        }
-        for candidate in candidates:
-            if candidate not in completions:
-                completions[candidate] = 0.0
-        return completions
-
-    def vertical_is_complete(self, item):
-        """
-        Calculates and returns whether a particular vertical is complete.
-        The logic in this method is temporary, and will go away once the
-        completion API is able to store a first-order notion of completeness
-        for parent blocks (right now it just stores completion for leaves-
-        problems, HTML, video, etc.).
-        """
-        if item.location.block_type != 'vertical':
-            raise ValueError('The passed in xblock is not a vertical type!')
-
-        if not self.completion_tracking_enabled():
-            return None
-
-        # this is temporary local logic and will be removed when the whole course tree is included in completion
-        child_locations = [
-            child.location for child in item.get_children() if child.location.block_type != 'discussion'
-        ]
-        completions = self.get_completions(child_locations)
-        for child_location in child_locations:
-            if completions[child_location] < 1.0:
-                return False
-        return True
diff --git a/lms/djangoapps/completion/test_utils.py b/lms/djangoapps/completion/test_utils.py
deleted file mode 100644
index f0d8cb00392ebf3dfcb07f8d84dea0b1f3427c5d..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/test_utils.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-Common functionality to support writing tests around completion.
-"""
-
-from . import waffle
-
-
-class CompletionWaffleTestMixin(object):
-    """
-    Common functionality for completion waffle tests.
-    """
-    def override_waffle_switch(self, override):
-        """
-        Override the setting of the ENABLE_COMPLETION_TRACKING waffle switch
-        for the course of the test.
-
-        Parameters:
-            override (bool): True if tracking should be enabled.
-        """
-        _waffle_overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, override)
-        _waffle_overrider.__enter__()
-        self.addCleanup(_waffle_overrider.__exit__, None, None, None)
diff --git a/lms/djangoapps/completion/tests/__init__.py b/lms/djangoapps/completion/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/lms/djangoapps/completion/waffle.py b/lms/djangoapps/completion/waffle.py
deleted file mode 100644
index c7869995215f6b72c57e6c53b6473f7c6fe52317..0000000000000000000000000000000000000000
--- a/lms/djangoapps/completion/waffle.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""
-This module contains various configuration settings via
-waffle switches for the completion app.
-"""
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
-from openedx.core.djangoapps.theming.helpers import get_current_site
-from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace
-
-# Namespace
-WAFFLE_NAMESPACE = 'completion'
-
-# Switches
-# Full name: completion.enable_completion_tracking
-# Indicates whether or not to track completion of individual blocks.  Keeping
-# this disabled will prevent creation of BlockCompletion objects in the
-# database, as well as preventing completion-related network access by certain
-# xblocks.
-ENABLE_COMPLETION_TRACKING = 'enable_completion_tracking'
-
-# Full name completion.enable_visual_progress
-# Overrides completion.enable_course_visual_progress
-# Acts as a global override -- enable visual progress indicators
-# sitewide.
-ENABLE_VISUAL_PROGRESS = 'enable_visual_progress'
-
-# Full name completion.enable_course_visual_progress
-# Acts as a course-by-course enabling of visual progress
-# indicators, e.g. updated 'resume button' functionality
-ENABLE_COURSE_VISUAL_PROGRESS = 'enable_course_visual_progress'
-
-# SiteConfiguration visual progress enablement
-ENABLE_SITE_VISUAL_PROGRESS = 'enable_site_visual_progress'
-
-
-def waffle():
-    """
-    Returns the namespaced, cached, audited Waffle class for completion.
-    """
-    return WaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix='completion: ')
-
-
-def waffle_flag():
-    """
-    Returns the namespaced, cached, audited Waffle flags dictionary for Completion.
-    """
-    namespace = WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'completion: ')
-    return {
-        # By default, disable visual progress. Can be enabled on a course-by-course basis.
-        # And overridden site-globally by ENABLE_VISUAL_PROGRESS
-        ENABLE_COURSE_VISUAL_PROGRESS: CourseWaffleFlag(
-            namespace,
-            ENABLE_COURSE_VISUAL_PROGRESS,
-            flag_undefined_default=False
-        )
-    }
-
-
-def visual_progress_enabled(course_key):
-    """
-    Exposes varia of visual progress feature.
-        ENABLE_COMPLETION_TRACKING, current_site.configuration, AND
-        enable_course_visual_progress OR enable_visual_progress
-
-    :return:
-
-        bool -> True if site/course/global enabled for visual progress tracking
-    """
-    if not waffle().is_enabled(ENABLE_COMPLETION_TRACKING):
-        return
-
-    try:
-        current_site = get_current_site()
-        if not current_site.configuration.get_value(ENABLE_SITE_VISUAL_PROGRESS, False):
-            return
-    except SiteConfiguration.DoesNotExist:
-        return
-
-    # Site-aware global override
-    if not waffle().is_enabled(ENABLE_VISUAL_PROGRESS):
-        # Course enabled
-        return waffle_flag()[ENABLE_COURSE_VISUAL_PROGRESS].is_enabled(course_key)
-
-    return True
diff --git a/lms/djangoapps/course_api/blocks/transformers/block_completion.py b/lms/djangoapps/course_api/blocks/transformers/block_completion.py
index 09c2f8d0f7962b16426842bf860e64a4116c5e81..93275856483d3fad1cd5ee436904c1c6dab1af3e 100644
--- a/lms/djangoapps/course_api/blocks/transformers/block_completion.py
+++ b/lms/djangoapps/course_api/blocks/transformers/block_completion.py
@@ -3,8 +3,8 @@ Block Completion Transformer
 """
 
 from xblock.completable import XBlockCompletionMode as CompletionMode
+from completion.models import BlockCompletion
 
-from lms.djangoapps.completion.models import BlockCompletion
 from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
 
 
diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_block_completion.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_block_completion.py
index bf546c7eff94452483b4f441fd8f58f1d0b966d1..baf7e138f3ab5bdaaa8e7fc25850cbb7af9af1ab 100644
--- a/lms/djangoapps/course_api/blocks/transformers/tests/test_block_completion.py
+++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_block_completion.py
@@ -1,11 +1,11 @@
 """
 Tests for BlockCompletionTransformer.
 """
+from completion.models import BlockCompletion
+from completion.test_utils import CompletionWaffleTestMixin
 from xblock.core import XBlock
 from xblock.completable import CompletableXBlockMixin, XBlockCompletionMode
 
-from lms.djangoapps.completion.models import BlockCompletion
-from lms.djangoapps.completion.test_utils import CompletionWaffleTestMixin
 from lms.djangoapps.course_api.blocks.transformers.block_completion import BlockCompletionTransformer
 from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase, TransformerRegistryTestMixin
 from student.tests.factories import UserFactory
@@ -39,7 +39,7 @@ class StubCompletableXBlock(XBlock, CompletableXBlockMixin):
     pass
 
 
-class BlockCompletionTransformerTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase, CompletionWaffleTestMixin):
+class BlockCompletionTransformerTestCase(TransformerRegistryTestMixin, CompletionWaffleTestMixin, ModuleStoreTestCase):
     """
     Tests behaviour of BlockCompletionTransformer
     """
@@ -49,6 +49,7 @@ class BlockCompletionTransformerTestCase(TransformerRegistryTestMixin, ModuleSto
     def setUp(self):
         super(BlockCompletionTransformerTestCase, self).setUp()
         self.user = UserFactory.create(password='test')
+        # Set ENABLE_COMPLETION_TRACKING waffle switch to True
         self.override_waffle_switch(True)
 
     @XBlock.register_temp_plugin(StubAggregatorXBlock, identifier='aggregator')
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 5f698c31edf62ae0dd74aa6cebd4d4ab147cfa26..e60b72012ab628d0439ca55a90bd38f6f3d0735e 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -8,6 +8,8 @@ import logging
 from collections import OrderedDict
 from functools import partial
 
+from completion.models import BlockCompletion
+from completion import waffle as completion_waffle
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.cache import cache
@@ -40,8 +42,6 @@ from courseware.masquerade import (
 from courseware.model_data import DjangoKeyValueStore, FieldDataCache
 from edxmako.shortcuts import render_to_string
 from eventtracking import tracker
-from lms.djangoapps.completion.models import BlockCompletion
-from lms.djangoapps.completion import waffle as completion_waffle
 from lms.djangoapps.grades.signals.signals import SCORE_PUBLISHED
 from lms.djangoapps.lms_xblock.field_data import LmsFieldData
 from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 2020194512a9bdc034cc28ef67bab4bc2e06cb65..0a454cf77bb11ae9ba776019ef4b208249c52831 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -11,6 +11,8 @@ import ddt
 import pytest
 import pytz
 from bson import ObjectId
+from completion.models import BlockCompletion
+from completion import waffle as completion_waffle
 from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
 from django.core.urlresolvers import reverse
@@ -46,8 +48,6 @@ from courseware.module_render import get_module_for_descriptor, hash_resource
 from courseware.tests.factories import GlobalStaffFactory, StudentModuleFactory, UserFactory
 from courseware.tests.test_submitting_problems import TestSubmittingProblems
 from courseware.tests.tests import LoginEnrollmentTestCase
-from lms.djangoapps.completion.models import BlockCompletion
-from lms.djangoapps.completion import waffle as completion_waffle
 from lms.djangoapps.lms_xblock.field_data import LmsFieldData
 from openedx.core.djangoapps.credit.api import set_credit_requirement_status, set_credit_requirements
 from openedx.core.djangoapps.credit.models import CreditCourse
@@ -664,7 +664,7 @@ class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCas
                 content_type='application/json',
             )
             request.user = self.mock_user
-            with patch('lms.djangoapps.completion.models.BlockCompletionManager.submit_completion') as mock_complete:
+            with patch('completion.models.BlockCompletionManager.submit_completion') as mock_complete:
                 render.handle_xblock_callback(
                     request,
                     unicode(course.id),
diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py
index 66f1f7d8b2ca8bce3a373eae36d393ceaf736c55..52179f10e0de00d962a6bd52e8285af2f64ee19a 100644
--- a/lms/djangoapps/lms_xblock/runtime.py
+++ b/lms/djangoapps/lms_xblock/runtime.py
@@ -1,14 +1,14 @@
 """
 Module implementing `xblock.runtime.Runtime` functionality for the LMS
 """
-import xblock.reference.plugins
+from completion.services import CompletionService
 from django.conf import settings
 from django.core.urlresolvers import reverse
+import xblock.reference.plugins
 
 from badges.service import BadgingService
 from badges.utils import badges_enabled
 from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
-from lms.djangoapps.completion.services import CompletionService
 from openedx.core.djangoapps.user_api.course_tag import api as user_course_tag_api
 from openedx.core.lib.url_utils import quote_slashes
 from openedx.core.lib.xblock_utils import xblock_local_resource_url
diff --git a/lms/envs/common.py b/lms/envs/common.py
index f4e062bc213dc8419260507ff47e444346730276..0478c1fcadbdb6cec19c1e62662b6cfa6e04c99e 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -2347,9 +2347,6 @@ INSTALLED_APPS = [
     # Course Goals
     'lms.djangoapps.course_goals',
 
-    # Completion
-    'lms.djangoapps.completion.apps.CompletionAppConfig',
-
     # Features
     'openedx.features.course_bookmarks',
     'openedx.features.course_experience',
diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py
index 97845799efdb78339558ccd4df7d15c7d181a660..34a6a6f70044012600fbe0c9b4b86d5dc4c464e8 100644
--- a/openedx/features/course_experience/tests/views/test_course_outline.py
+++ b/openedx/features/course_experience/tests/views/test_course_outline.py
@@ -4,6 +4,9 @@ Tests for the Course Outline view and supporting views.
 import datetime
 import json
 
+from completion import waffle
+from completion.models import BlockCompletion
+from completion.test_utils import CompletionWaffleTestMixin
 from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
 from mock import Mock, patch
@@ -11,9 +14,6 @@ from six import text_type
 
 from courseware.tests.factories import StaffFactory
 from gating import api as lms_gating_api
-from lms.djangoapps.completion import waffle
-from lms.djangoapps.completion.models import BlockCompletion
-from lms.djangoapps.completion.test_utils import CompletionWaffleTestMixin
 from lms.djangoapps.course_api.blocks.transformers.milestones import MilestonesAndSpecialExamsTransformer
 from milestones.tests.utils import MilestonesTestCaseMixin
 from opaque_keys.edx.keys import CourseKey, UsageKey
@@ -325,7 +325,6 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
         """
         super(TestCourseOutlineResumeCourse, self).setUp()
         self.client.login(username=self.user.username, password=TEST_PASSWORD)
-        self.override_waffle_switch(False)
 
     def visit_sequential(self, course, chapter, sequential):
         """
@@ -393,7 +392,7 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
         ),
         active=True
     )
-    @patch('lms.djangoapps.completion.waffle.get_current_site')
+    @patch('completion.waffle.get_current_site')
     def test_resume_course_with_completion_api(self, get_patched_current_site):
         """
         Tests completion API resume button functionality
diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py
index b748f0ad50f8e8e57dedbe2bd47c336d89c93fca..518e91c4f677c00c348eed796a4d3e55020fc4aa 100644
--- a/openedx/features/course_experience/utils.py
+++ b/openedx/features/course_experience/utils.py
@@ -1,12 +1,12 @@
 """
 Common utilities for the course experience, including course outline.
 """
-from lms.djangoapps.completion.models import BlockCompletion
-from lms.djangoapps.completion.waffle import visual_progress_enabled
+from completion.models import BlockCompletion
+from completion.waffle import visual_progress_enabled
+
 from lms.djangoapps.course_api.blocks.api import get_blocks
 from lms.djangoapps.course_blocks.utils import get_student_module_as_dict
 from opaque_keys.edx.keys import CourseKey, UsageKey
-from opaque_keys.edx.locator import BlockUsageLocator
 from openedx.core.djangoapps.request_cache.middleware import request_cached
 from xmodule.modulestore.django import modulestore
 
@@ -50,7 +50,6 @@ def get_course_outline_block_tree(request, course_id):
         Mark 'most recent completed block as 'resume_block'
 
         """
-
         last_completed_child_position = BlockCompletion.get_latest_block_completed(user, course_key)
 
         if last_completed_child_position:
@@ -76,11 +75,11 @@ def get_course_outline_block_tree(request, course_id):
         :return:
             block: course_outline_root_block block object or child block
         """
-        locatable_block_string = BlockUsageLocator.from_string(block['id'])
+        block_key = block.serializer.instance
 
-        if course_block_completions.get(locatable_block_string):
+        if course_block_completions.get(block_key):
             block['complete'] = True
-            if locatable_block_string == latest_completion.block_key:
+            if block_key == latest_completion.full_block_key:
                 block['resume_block'] = True
 
         if block.get('children'):
diff --git a/lms/djangoapps/completion/api/__init__.py b/openedx/tests/completion_integration/README.rst
similarity index 100%
rename from lms/djangoapps/completion/api/__init__.py
rename to openedx/tests/completion_integration/README.rst
diff --git a/lms/djangoapps/completion/api/v1/__init__.py b/openedx/tests/completion_integration/__init__.py
similarity index 100%
rename from lms/djangoapps/completion/api/v1/__init__.py
rename to openedx/tests/completion_integration/__init__.py
diff --git a/lms/djangoapps/completion/tests/test_handlers.py b/openedx/tests/completion_integration/test_handlers.py
similarity index 78%
rename from lms/djangoapps/completion/tests/test_handlers.py
rename to openedx/tests/completion_integration/test_handlers.py
index 21330d8a1f096c0ef5d82f81c6101e7772693527..6f426016bba23e26ffea1c8e1860db0ad829753b 100644
--- a/lms/djangoapps/completion/tests/test_handlers.py
+++ b/openedx/tests/completion_integration/test_handlers.py
@@ -1,24 +1,22 @@
 """
-Test signal handlers.
+Test signal handlers for completion.
 """
 
 from datetime import datetime
 
+from completion import handlers
+from completion.models import BlockCompletion
+from completion.test_utils import CompletionSetUpMixin
 import ddt
 from django.test import TestCase
 from mock import patch
-from opaque_keys.edx.keys import CourseKey
 from pytz import utc
 import six
 from xblock.completable import XBlockCompletionMode
 from xblock.core import XBlock
 
 from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED
-from student.tests.factories import UserFactory
-
-from .. import handlers
-from ..models import BlockCompletion
-from ..test_utils import CompletionWaffleTestMixin
+from openedx.core.djangolib.testing.utils import skip_unless_lms
 
 
 class CustomScorableBlock(XBlock):
@@ -40,17 +38,16 @@ class ExcludedScorableBlock(XBlock):
 
 
 @ddt.ddt
-class ScorableCompletionHandlerTestCase(CompletionWaffleTestMixin, TestCase):
+@skip_unless_lms
+class ScorableCompletionHandlerTestCase(CompletionSetUpMixin, TestCase):
     """
     Test the signal handler
     """
+    COMPLETION_SWITCH_ENABLED = True
 
     def setUp(self):
         super(ScorableCompletionHandlerTestCase, self).setUp()
-        self.course_key = CourseKey.from_string('edx/course/beta')
-        self.scorable_block_key = self.course_key.make_usage_key(block_type='problem', block_id='red')
-        self.user = UserFactory.create()
-        self.override_waffle_switch(True)
+        self.block_key = self.course_key.make_usage_key(block_type='problem', block_id='red')
 
     def call_scorable_block_completion_handler(self, block_key, score_deleted=None):
         """
@@ -81,11 +78,11 @@ class ScorableCompletionHandlerTestCase(CompletionWaffleTestMixin, TestCase):
     )
     @ddt.unpack
     def test_handler_submits_completion(self, score_deleted, expected_completion):
-        self.call_scorable_block_completion_handler(self.scorable_block_key, score_deleted)
+        self.call_scorable_block_completion_handler(self.block_key, score_deleted)
         completion = BlockCompletion.objects.get(
             user=self.user,
             course_key=self.course_key,
-            block_key=self.scorable_block_key,
+            block_key=self.block_key,
         )
         self.assertEqual(completion.completion, expected_completion)
 
@@ -112,14 +109,12 @@ class ScorableCompletionHandlerTestCase(CompletionWaffleTestMixin, TestCase):
         self.assertFalse(completion.exists())
 
     def test_signal_calls_handler(self):
-        user = UserFactory.create()
-
-        with patch('lms.djangoapps.completion.handlers.scorable_block_completion') as mock_handler:
+        with patch('completion.handlers.scorable_block_completion') as mock_handler:
             PROBLEM_WEIGHTED_SCORE_CHANGED.send_robust(
                 sender=self,
-                user_id=user.id,
+                user_id=self.user.id,
                 course_id=six.text_type(self.course_key),
-                usage_id=six.text_type(self.scorable_block_key),
+                usage_id=six.text_type(self.block_key),
                 weighted_earned=0.0,
                 weighted_possible=3.0,
                 modified=datetime.utcnow().replace(tzinfo=utc),
@@ -128,17 +123,17 @@ class ScorableCompletionHandlerTestCase(CompletionWaffleTestMixin, TestCase):
         mock_handler.assert_called()
 
 
-class DisabledCompletionHandlerTestCase(CompletionWaffleTestMixin, TestCase):
+@skip_unless_lms
+class DisabledCompletionHandlerTestCase(CompletionSetUpMixin, TestCase):
     """
     Test that disabling the ENABLE_COMPLETION_TRACKING waffle switch prevents
     the signal handler from submitting a completion.
     """
+    COMPLETION_SWITCH_ENABLED = False
+
     def setUp(self):
         super(DisabledCompletionHandlerTestCase, self).setUp()
-        self.user = UserFactory.create()
-        self.course_key = CourseKey.from_string("course-v1:a+valid+course")
-        self.block_key = self.course_key.make_usage_key(block_type="video", block_id="mah-video")
-        self.override_waffle_switch(False)
+        self.block_key = self.course_key.make_usage_key(block_type='problem', block_id='red')
 
     def test_disabled_handler_does_not_submit_completion(self):
         handlers.scorable_block_completion(
diff --git a/lms/djangoapps/completion/tests/test_models.py b/openedx/tests/completion_integration/test_models.py
similarity index 79%
rename from lms/djangoapps/completion/tests/test_models.py
rename to openedx/tests/completion_integration/test_models.py
index f3843cbda95c01a6bb6177027ec18a786a5a5b01..99de2a9e63769a3af04fa1f1e40712d3eb07ee1d 100644
--- a/lms/djangoapps/completion/tests/test_models.py
+++ b/openedx/tests/completion_integration/test_models.py
@@ -4,15 +4,17 @@ Test models, managers, and validators.
 
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+from completion import models, waffle
+from completion.test_utils import CompletionWaffleTestMixin
 from django.core.exceptions import ValidationError
 from django.test import TestCase
-
 from opaque_keys.edx.keys import CourseKey, UsageKey
-from student.tests.factories import CourseEnrollmentFactory, UserFactory
 
-from .. import models, waffle
+from openedx.core.djangolib.testing.utils import skip_unless_lms
+from student.tests.factories import CourseEnrollmentFactory, UserFactory
 
 
+@skip_unless_lms
 class PercentValidatorTestCase(TestCase):
     """
     Test that validate_percent only allows floats (and ints) between 0.0 and 1.0.
@@ -26,7 +28,10 @@ class PercentValidatorTestCase(TestCase):
             self.assertRaises(ValidationError, models.validate_percent, value)
 
 
-class CompletionSetUpMixin(object):
+class CompletionSetUpMixin(CompletionWaffleTestMixin):
+    """
+    Mixin that provides helper to create test BlockCompletion object.
+    """
     def set_up_completion(self):
         self.user = UserFactory()
         self.block_key = UsageKey.from_string(u'block-v1:edx+test+run+type@video+block@doggos')
@@ -39,6 +44,7 @@ class CompletionSetUpMixin(object):
         )
 
 
+@skip_unless_lms
 class SubmitCompletionTestCase(CompletionSetUpMixin, TestCase):
     """
     Test that BlockCompletion.objects.submit_completion has the desired
@@ -46,9 +52,7 @@ class SubmitCompletionTestCase(CompletionSetUpMixin, TestCase):
     """
     def setUp(self):
         super(SubmitCompletionTestCase, self).setUp()
-        _overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True)
-        _overrider.__enter__()
-        self.addCleanup(_overrider.__exit__, None, None, None)
+        self.override_waffle_switch(True)
         self.set_up_completion()
 
     def test_changed_value(self):
@@ -114,22 +118,17 @@ class SubmitCompletionTestCase(CompletionSetUpMixin, TestCase):
         self.assertEqual(models.BlockCompletion.objects.count(), 1)
 
 
+@skip_unless_lms
 class CompletionDisabledTestCase(CompletionSetUpMixin, TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        super(CompletionDisabledTestCase, cls).setUpClass()
-        cls.overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, False)
-        cls.overrider.__enter__()
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.overrider.__exit__(None, None, None)
-        super(CompletionDisabledTestCase, cls).tearDownClass()
-
+    """
+    Tests that completion API is not called when the feature is disabled.
+    """
     def setUp(self):
         super(CompletionDisabledTestCase, self).setUp()
+        # insert one completion record...
         self.set_up_completion()
+        # ...then disable the feature.
+        self.override_waffle_switch(False)
 
     def test_cannot_call_submit_completion(self):
         self.assertEqual(models.BlockCompletion.objects.count(), 1)
@@ -143,17 +142,15 @@ class CompletionDisabledTestCase(CompletionSetUpMixin, TestCase):
         self.assertEqual(models.BlockCompletion.objects.count(), 1)
 
 
-class SubmitBatchCompletionTestCase(TestCase):
+@skip_unless_lms
+class SubmitBatchCompletionTestCase(CompletionWaffleTestMixin, TestCase):
     """
     Test that BlockCompletion.objects.submit_batch_completion has the desired
     semantics.
     """
-
     def setUp(self):
         super(SubmitBatchCompletionTestCase, self).setUp()
-        _overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True)
-        _overrider.__enter__()
-        self.addCleanup(_overrider.__exit__, None, None, None)
+        self.override_waffle_switch(True)
 
         self.block_key = UsageKey.from_string('block-v1:edx+test+run+type@video+block@doggos')
         self.course_key_obj = CourseKey.from_string('course-v1:edx+test+run')
@@ -188,13 +185,14 @@ class SubmitBatchCompletionTestCase(TestCase):
         self.assertEqual(model.completion, 1.0)
 
 
-class BatchCompletionMethodTests(TestCase):
-
+@skip_unless_lms
+class BatchCompletionMethodTests(CompletionWaffleTestMixin, TestCase):
+    """
+    Tests for the classmethods that retrieve course/block completion data.
+    """
     def setUp(self):
         super(BatchCompletionMethodTests, self).setUp()
-        _overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True)
-        _overrider.__enter__()
-        self.addCleanup(_overrider.__exit__, None, None, None)
+        self.override_waffle_switch(True)
 
         self.user = UserFactory.create()
         self.other_user = UserFactory.create()
@@ -202,11 +200,13 @@ class BatchCompletionMethodTests(TestCase):
         self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904")
         self.block_keys = [UsageKey.from_string("i4x://edX/MOOC101/video/{}".format(number)) for number in xrange(5)]
 
-        self.submit_faux_completions()
+        self.submit_fake_completions()
 
-    def submit_faux_completions(self):
-        # Proper completions for the given runtime
-        for idx, block_key in enumerate(self.block_keys[0:3]):
+    def submit_fake_completions(self):
+        """
+        Submit completions for given runtime, run at setup
+        """
+        for idx, block_key in enumerate(self.block_keys[:3]):
             models.BlockCompletion.objects.submit_completion(
                 user=self.user,
                 course_key=self.course_key,
@@ -214,8 +214,7 @@ class BatchCompletionMethodTests(TestCase):
                 completion=1.0 - (0.2 * idx),
             )
 
-        # Wrong user
-        for idx, block_key in enumerate(self.block_keys[2:]):
+        for idx, block_key in enumerate(self.block_keys[2:]):  # Wrong user
             models.BlockCompletion.objects.submit_completion(
                 user=self.other_user,
                 course_key=self.course_key,
@@ -223,23 +222,23 @@ class BatchCompletionMethodTests(TestCase):
                 completion=0.9 - (0.2 * idx),
             )
 
-        # Wrong course
-        models.BlockCompletion.objects.submit_completion(
+        models.BlockCompletion.objects.submit_completion(  # Wrong course
             user=self.user,
             course_key=self.other_course_key,
             block_key=self.block_keys[4],
             completion=0.75,
         )
 
-    def test_get_course_completions(self):
+    def test_get_course_completions_missing_runs(self):
+        actual_completions = models.BlockCompletion.get_course_completions(self.user, self.course_key)
+        expected_block_keys = [key.replace(course_key=self.course_key) for key in self.block_keys[:3]]
+        expected_completions = dict(zip(expected_block_keys, [1.0, 0.8, 0.6]))
+        self.assertEqual(expected_completions, actual_completions)
 
+    def test_get_course_completions_empty_result_set(self):
         self.assertEqual(
-            models.BlockCompletion.get_course_completions(self.user, self.course_key),
-            {
-                self.block_keys[0]: 1.0,
-                self.block_keys[1]: 0.8,
-                self.block_keys[2]: 0.6,
-            },
+            models.BlockCompletion.get_course_completions(self.other_user, self.other_course_key),
+            {}
         )
 
     def test_get_latest_block_completed(self):
@@ -247,3 +246,6 @@ class BatchCompletionMethodTests(TestCase):
             models.BlockCompletion.get_latest_block_completed(self.user, self.course_key).block_key,
             self.block_keys[2]
         )
+
+    def test_get_latest_completed_none_exist(self):
+        self.assertIsNone(models.BlockCompletion.get_latest_block_completed(self.other_user, self.other_course_key))
diff --git a/lms/djangoapps/completion/tests/test_services.py b/openedx/tests/completion_integration/test_services.py
similarity index 95%
rename from lms/djangoapps/completion/tests/test_services.py
rename to openedx/tests/completion_integration/test_services.py
index 24563ba4b0aaa9b75960a8efc287ca75785f11cd..2027dd0d15d5c0071b8498298fc89d14885b5f0e 100644
--- a/lms/djangoapps/completion/tests/test_services.py
+++ b/openedx/tests/completion_integration/test_services.py
@@ -1,18 +1,20 @@
 """
 Tests of completion xblock runtime services
 """
+from completion.models import BlockCompletion
+from completion.services import CompletionService
+from completion.test_utils import CompletionWaffleTestMixin
 import ddt
 from opaque_keys.edx.keys import CourseKey
+
+from openedx.core.djangolib.testing.utils import skip_unless_lms
 from student.tests.factories import UserFactory
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 
-from ..models import BlockCompletion
-from ..services import CompletionService
-from ..test_utils import CompletionWaffleTestMixin
-
 
 @ddt.ddt
+@skip_unless_lms
 class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTestCase):
     """
     Test the data returned by the CompletionService.
diff --git a/lms/djangoapps/completion/api/v1/tests/test_views.py b/openedx/tests/completion_integration/test_views.py
similarity index 95%
rename from lms/djangoapps/completion/api/v1/tests/test_views.py
rename to openedx/tests/completion_integration/test_views.py
index 6b4fb08f474ab55651bbfe3c83f82568469fc666..f8b27d9f2b574c9ac646c4ac5e1de05185e969d1 100644
--- a/lms/djangoapps/completion/api/v1/tests/test_views.py
+++ b/openedx/tests/completion_integration/test_views.py
@@ -2,20 +2,22 @@
 """
 Test models, managers, and validators.
 """
-
+from completion import waffle
+from completion.test_utils import CompletionWaffleTestMixin
 import ddt
 from django.core.urlresolvers import reverse
-from rest_framework.test import APIClient, force_authenticate
+from rest_framework.test import APIClient
 
-from completion import waffle
 from student.tests.factories import UserFactory, CourseEnrollmentFactory
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 from openedx.core.djangoapps.content.course_structures.tasks import update_course_structure
+from openedx.core.djangolib.testing.utils import skip_unless_lms
 
 
 @ddt.ddt
-class CompletionBatchTestCase(ModuleStoreTestCase):
+@skip_unless_lms
+class CompletionBatchTestCase(CompletionWaffleTestMixin, ModuleStoreTestCase):
     """
     Test that BlockCompletion.objects.submit_batch_completion has the desired
     semantics.
@@ -33,9 +35,7 @@ class CompletionBatchTestCase(ModuleStoreTestCase):
         self.url = reverse('completion_api:v1:completion-batch')
 
         # Enable the waffle flag for all tests
-        _overrider = waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True)
-        _overrider.__enter__()
-        self.addCleanup(_overrider.__exit__, None, None, None)
+        self.override_waffle_switch(True)
 
         # Create course
         self.course = CourseFactory.create(org='TestX', number='101', display_name='Test')
diff --git a/openedx/tests/settings.py b/openedx/tests/settings.py
index 0438721228aae8e4043ace921afa63253ba891b0..9b3691d2301314c8f0af2a0f309c74f57a03d7d4 100644
--- a/openedx/tests/settings.py
+++ b/openedx/tests/settings.py
@@ -75,7 +75,9 @@ INSTALLED_APPS = (
     'openedx.core.djangoapps.self_paced',
     'milestones',
     'celery_utils',
-    'lms.djangoapps.completion.apps.CompletionAppConfig',
+
+    # Django 1.11 demands to have imported models supported by installed apps.
+    'completion',
 )
 
 LMS_ROOT_URL = 'http://localhost:8000'
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index bdbb8c78ef0df9f62360e814861c04b76c9bdbc4..f820b1f692d3c612b96200b89624b6fe8dde6424 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -56,6 +56,7 @@ git+https://github.com/cpennington/pylint-django@fix-field-inference-during-monk
 enum34==1.1.6
 edx-django-oauth2-provider==1.2.5
 edx-django-sites-extensions==2.3.0
+edx-completion==0.0.6
 edx-enterprise==0.65.7
 edx-milestones==0.1.13
 edx-oauth2-provider==1.2.2
diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt
index 5e7bff3d99128a785e1fcfc7e6f4141c8d25396a..81d48a6b18062dcef2acf7fe9a840000e6ce532f 100644
--- a/requirements/edx/github.txt
+++ b/requirements/edx/github.txt
@@ -98,7 +98,6 @@ git+https://github.com/edx/xblock-lti-consumer.git@v1.1.7#egg=lti_consumer-xbloc
 # This is here because all of the other XBlocks are located here. However, it is published to PyPI and will be installed that way
 xblock-review==1.1.4
 
-
 # Third Party XBlocks
 
 git+https://github.com/mitodl/edx-sga.git@d019b8a050c056db535e3ff13c93096145a932de#egg=edx-sga==0.7.1