text: #!/usr/bin/env python
  """TcEx Framework Test Generation Module."""
  # standard library
  import copy
  import json
  import os
  
  from ..app_config_object.templates import (
      CustomTemplates,
      DownloadTemplates,
      TestProfileTemplates,
      ValidationTemplates,
  )
  from ..profile import Interactive, Profile
  from .bin import Bin
  
  
  class Test(Bin):
      """Create testing files for ThreatConnect Exchange App.
  
      Args:
          _args (namespace): The argparser args Namespace.
      """
  
      def __init__(self, _args):
          """Initialize Class properties."""
          super().__init__(_args)
  
          # properties
          self.profile = Profile(
              default_args={}, feature=self.args.feature, name=self.args.profile_name
          )
          self.custom_templates = CustomTemplates(self.profile, self.args.branch)
          self.download_template = DownloadTemplates(self.profile, self.args.branch)
          self.profile_interactive = Interactive(self.profile)
          self.test_profile_template = TestProfileTemplates(self.profile, self.args.branch)
          self.validation_templates = ValidationTemplates(self.profile, self.args.branch)
  
      def add_negative_profile(self, profile_name, inputs, fail_on_error=None):
          """Create a negative profile."""
          # build profile name
          exit_code = 1  # default exit code is 1
          if fail_on_error is not None:
              if fail_on_error is False:
                  exit_code = 0
              profile_name = f'{profile_name}_foe_{str(fail_on_error).lower()}'
  
          # get profile data and update with new inputs
          profile_data = self.profile.contents
          profile_data['exit_codes'] = [exit_code]
          profile_data['exit_message'] = None
          profile_data['inputs'] = inputs
          profile_data['outputs'] = None
          profile_data['environments'] = [*self.profile.environments, 'negative']
  
          # create a meaningful profile name
          new_profile = Profile(default_args={}, feature=self.args.feature, name=profile_name)
          new_profile.add(profile_data=profile_data)
  
      def create_dirs(self):
          """Create tcex.d directory and sub directories."""
          for d in [
              self.profile.test_directory,
              self.profile.feature_directory,
              self.profile.directory,
          ]:
              if not os.path.isdir(d):
                  os.makedirs(d)
  
          # create __init__ files
          self.create_dirs_init()
  
      def create_dirs_init(self):
          """Create the __init__.py file under dir."""
          for d in [self.profile.test_directory, self.profile.feature_directory]:
              if os.path.isdir(d):
                  with open(os.path.join(d, '__init__.py'), 'a'):
                      os.utime(os.path.join(d, '__init__.py'), None)
  
      def create_negative_profiles(self):
          """Create negative profiles using interactive profile base."""
          for inputs in self.profile.profile_inputs:
              for name, value in inputs.get('required', {}).items():
                  ij_data = self.profile.ij.params_dict.get(name, {})
                  # create a profile for each pb data type
                  for pb_data_type in ij_data.get('playbookDataType', []):
                      for negative_type in self.negative_inputs.get(pb_data_type.lower(), []):
                          # the value is pre-staged in test_case_playbook_common.py
                          value = f'#App:1234:{negative_type}!{pb_data_type}'
                          profile_name = f'negative_{name}_{pb_data_type.lower()}_{negative_type}'
                          # modify copy so original is preserved for next interation
                          new_inputs = copy.deepcopy(inputs)
                          new_inputs['required'][name] = value
  
                          if 'fail_on_error' in inputs.get('optional', {}):
                              # handle fail on error
                              for b in [False, True]:
                                  new_inputs['optional']['fail_on_error'] = b
                                  self.add_negative_profile(profile_name, new_inputs, b)
                          else:
                              self.add_negative_profile(profile_name, new_inputs)
  
      def interactive_profile(self, negative=False):
          """Present interactive profile inputs."""
          self.profile_interactive.present()
          profile_data = {
              'exit_codes': self.profile_interactive.exit_codes,
              'inputs': self.profile_interactive.inputs,
              'stage': self.profile_interactive.staging_data,
          }
          self.profile.add(profile_data=profile_data)
  
          if negative:
              # if user specified negative arg then create negative test profiles
              self.create_negative_profiles()
  
      @staticmethod
      def load_legacy_profiles(staging_files):
          """Load staging data to migrate legacy templates."""
          staging_data = {}
          for sf in staging_files:
              with open(sf) as fh:
                  data = json.load(fh)
              for d in data:
                  staging_data[d.get('variable')] = d.get('data')
          return staging_data
  
      def migrate_profile(self):
          """Migrate legacy profile to new framework."""
          data = []
          profile_file = os.path.join(self.app_path, 'tcex.d', 'profiles', self.args.profile_file)
          if os.path.isfile(self.args.profile_file):
              with open(self.args.profile_file) as fh:
                  data = json.load(fh)
          elif os.path.isfile(profile_file):
              with open(profile_file) as fh:
                  data = json.load(fh)
          else:
              self.handle_error(f'Error reading in profile file: {self.args.profile_file}', True)
  
          for d in data:
              profile_data = {
                  'exit_codes': d.get('exit_codes'),
                  'exit_message': None,
                  'inputs': d.get('args', {}).get('app'),
                  'stage': {'kvstore': self.load_legacy_profiles(d.get('data_files', []))},
              }
  
              # add profile
              self.profile.add(profile_data=profile_data, profile_name=d.get('profile_name'))
  
      @property
      def negative_inputs(self):
          """Return dict of negative inputs."""
          return {
              'binary': ['empty', 'null'],
              'binaryarray': ['empty', 'null'],
              'keyvalue': ['null'],
              'keyvaluearray': ['null'],
              'string': ['empty', 'null'],
              'stringarray': ['empty', 'null'],
              'tcentity': ['null'],
              'tcentityarray': ['null'],
          }
  