old_contents: """TestCase for conceptual tests.
  
  ConceptTestCases are designed to be natural language tests that help
  students understand high-level understanding. As such, these test cases
  focus mainly on unlocking. When used in the grading protocol,
  ConceptTestCases simply display the answer if already unlocked.
  """
  
  from client.models import serialize
  from client.protocols import grading
  from client.protocols import unlock
  from client.utils import formatting
  
  class ConceptCase(grading.GradedTestCase, unlock.UnlockTestCase):
      """TestCase for conceptual questions."""
  
      type = 'concept'
  
      REQUIRED = {
          'type': serialize.STR,
          'question': serialize.STR,
          'answer': serialize.STR,
      }
      OPTIONAL = {
          'locked': serialize.BOOL_FALSE,
          'choices': serialize.SerializeArray(serialize.STR),
          'never_lock': serialize.BOOL_FALSE,
          'hidden': serialize.BOOL_FALSE,
      }
  
      def __init__(self, **fields):
          super().__init__(**fields)
          self['question'] = formatting.dedent(self['question'])
          self['answer'] = formatting.dedent(self['answer'])
  
  
      ######################################
      # Protocol interface implementations #
      ######################################
  
      def on_grade(self, logger, verbose, interact, timeout):
          """Implements the GradedTestCase interface."""
          print('Q: ' + self['question'])
          print('A: ' + self['answer'])
          print()
          return False
  
      def should_grade(self):
          return not self['locked']
  
      def on_unlock(self, logger, interact_fn):
          """Implements the UnlockTestCase interface."""
          print('Q: ' + self['question'])
          print()
          answer = interact_fn(self['answer'], self['choices'])
          self['answer'] = answer
          self['locked'] = False
  
      def on_lock(self, hash_fn):
          #TODO(soumya): Make this a call to normalize after it's moved to an appropriate place.
          if self['choices']:
              self['answer'] = hash_fn("".join(self['answer']))
          else:
              self['answer'] = hash_fn(self['answer'])
          self['locked'] = True
  
  
new_contents: """Case for conceptual tests.
  
  ConceptCases are designed to be natural language tests that help
  students understand high-level understanding. As such, these test cases
  focus mainly on unlocking.
  """
  
  from client.models import core
  from client.sources.common import case
  
  class ConceptCase(case.LockableCase):
      question = core.String()
      answer = core.String()
      choices = core.List(type=str, optional=True)
  
      def run(self, logger):
          """Runs the test conceptual test case.
  
          RETURNS:
          bool; True if the test case passes, False otherwise.
          """
          # TODO(albert): print question and answer if verbose
          # print('Q: ' + self['question'])
          # print('A: ' + self['answer'])
          # print()
          return True
  
      def lock(self, hash_fn):
          if self.choices is not core.NoValue:
              # TODO(albert): ask Soumya why join is used
              self.answer = hash_fn("".join(self.answer))
          else:
              self.answer = hash_fn(self.answer)
          self.locked = True
  
      def unlock(self, interact):
          """Unlocks the conceptual test case."""
          if self.locked == core.NoValue:
              # TODO(albert): determine best initial setting.
              self.locked = False
          if self.locked:
              # TODO(albert): perhaps move ctrl-c handling here
              # TODO(albert): print question
              # print('Q: ' + self['question'])
              # print()
              answer = interact(self.answer, self.choices)
              if answer != self.answer:
                  # Answer was presumably unlocked
                  self.locked = False
                  self.answer = answer
  
subject: Add concept case