Example #1
0
 def setUp(self):
     super(TestUploadMintHistoryTask, self).setUp()
     self.uploader = MintFileUploader()
     # Create necessary foreign key objects
     self.txn_type = AutoFixture(spending_models.TransactionType).create_one()
     self.category = AutoFixture(spending_models.Category).create_one()
     person = AutoFixture(person_models.Person).create_one()
     self.account = AutoFixture(account_models.Account, generate_fk=True).create_one()
     
     self.account_map = account_models.AccountNameMap(
         name='Account Map Name',
         user=person,
         account=self.account
     )
     self.account_map.save()
Example #2
0
class TestUploadMintHistoryTask(TestCase):
    ''' Test the upload Mint.com history (csv file) process
    '''
    fake_filename = 'FakeFileNameExample.txt'

    def setUp(self):
        super(TestUploadMintHistoryTask, self).setUp()
        self.uploader = MintFileUploader()
        # Create necessary foreign key objects
        self.txn_type = AutoFixture(spending_models.TransactionType).create_one()
        self.category = AutoFixture(spending_models.Category).create_one()
        person = AutoFixture(person_models.Person).create_one()
        self.account = AutoFixture(account_models.Account, generate_fk=True).create_one()
        
        self.account_map = account_models.AccountNameMap(
            name='Account Map Name',
            user=person,
            account=self.account
        )
        self.account_map.save()

    def tearDown(self):
        ''' Remove any test files as needed
        '''
        super(TestUploadMintHistoryTask, self).tearDown()
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        if os.path.exists(filename):
            os.remove(filename)


    def prepare_default_upload_file_data(self):
        ''' Helper function to create a default sample row of data  
            from the default csv Mint history file download
        '''
        return {
            MintFileUploader.file_fields.date                   : '01/01/2013',
            MintFileUploader.file_fields.description            : 'Some Description goes here',
            MintFileUploader.file_fields.original_description   : 'The Original Description -- often hard to read',
            MintFileUploader.file_fields.amount                 : '123.45',
            MintFileUploader.file_fields.transaction_type       : self.txn_type.name,
            MintFileUploader.file_fields.category               : self.category.name,
            MintFileUploader.file_fields.account                : self.account_map.name,
            MintFileUploader.file_fields.notes                  : ''
        }

    def write_example_upload_file(self, filename, upload_data, repeat_rows=1):
        # Write the test data to a file
        ofile = open(filename, 'w')
        ofile.write('\t'.join(upload_data.keys()) + '\n')
        for i in range(repeat_rows):
            ofile.write('\t'.join([upload_data[k] for k in upload_data.keys()]) + '\n')
        ofile.close()

    def get_default_create_txn_data(self):
        ''' Setup a dictionary of default values for required fields to create a Transaction object
        '''
        upload_data = self.prepare_default_upload_file_data()
        date = datetime.datetime.strptime(upload_data[self.uploader.file_fields.date], '%m/%d/%Y')
        date = date.strftime('%Y-%m-%d')
        return {
            'post_date': date,
            'amount': upload_data[self.uploader.file_fields.amount],
            'account': self.account,
            'type': self.txn_type,
            'category': self.category,
            'description': upload_data[self.uploader.file_fields.description],
            'orig_description': upload_data[self.uploader.file_fields.original_description],
        }

    #---- TEST FILE INPUT ----#
    def test_will_throw_error_on_dne_file_input_to_uploader(self):
        ''' Must throw exception if input file to Uploader does not exist
        '''
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.assertFalse(os.path.exists(filename))

        # A file that DNE throws error
        self.assertRaises(
            exceptions.HeydollarInvalidUploadFile,
            self.uploader.upload,
            filename
        )

    def test_will_throw_error_on_bad_format_file_input_to_uploader(self):
        ''' Must throw exception if input file to Uploader has unexpected format
        '''
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.assertFalse(os.path.exists(filename))
        
        # Write unexpected column headers and data
        ofile = open(filename, 'w')
        ofile.write('\t'.join(['Header 1', 'Header 2']) + '\n')
        ofile.write('\t'.join(['String data', 'Other string']) + '\n')
        ofile.close()
        self.assertTrue(os.path.exists(filename))
        # Invalid file format must throw error
        self.assertRaises(
            exceptions.HeydollarInvalidUploadFile,
            self.uploader.upload,
            filename
        )

    def test_can_parse_upload_file_date_column_to_date_object(self):
        ''' Must be able to convert a date string in upload file to date object
        '''
        upload_data = self.prepare_default_upload_file_data()
        # Test parsing of format MM/DD/YYYY
        upload_data[self.uploader.file_fields.date] = '04/30/2013'
        db_data = self.uploader.map_row_to_db_format(upload_data)
        self.assertEqual(db_data[self.uploader.field_map[self.uploader.file_fields.date]], '2013-04-30')

    def test_can_parse_upload_file_description_field_including_delimiter_to_string(self):
        ''' Must be able to convert a string object in upload file to string, even
            if it contains a '\t' or other delimiter character
        '''
        upload_data = self.prepare_default_upload_file_data()
        # Prepare string with delimiter
        test_string = 'A test with \t tabs'
        upload_data[self.uploader.file_fields.description] = '"' + test_string + '"'
        # Write the test data to a file
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data)
        # Upload the test data to database
        self.uploader.upload(filename)
        # Filter for the uploaded Txn from database
        txn = spending_models.Transaction.objects.order_by('-pk')[0]
        self.assertEqual(txn.description, test_string)

    def test_can_parse_upload_file_amount_field_to_decimal(self):
        ''' Must be able to convert an amount string to decimal
        '''
        upload_data = self.prepare_default_upload_file_data()
        test_amount = decimal.Decimal(102.34)
        upload_data[self.uploader.file_fields.amount] = str(test_amount)
        db_data = self.uploader.map_row_to_db_format(upload_data)
        self.assertEqual(db_data[self.uploader.field_map[self.uploader.file_fields.amount]], test_amount)

    def test_can_parse_upload_file_category_field_to_new_category_object(self):
        ''' Must be able to convert category string to a new Category data object
            if none exists
        '''
        upload_data = self.prepare_default_upload_file_data()
        test_name = 'A New Category'
        # Confirm this is a new Category
        self.assertEqual(spending_models.Category.objects.filter(name=test_name).count(), 0)

        upload_data[self.uploader.file_fields.category] = test_name
        db_data = self.uploader.map_row_to_db_format(upload_data)
        # Confirm this new Category was created
        ctgy = spending_models.Category.objects.filter(name=test_name)[0]
        self.assertEqual(db_data[self.uploader.field_map[self.uploader.file_fields.category]], ctgy)

    def test_can_parse_upload_file_category_field_to_existing_category_object(self):
        ''' Must be able to convert category string to an existing Category data object
            if it exists
        '''
        upload_data = self.prepare_default_upload_file_data()
        test_amount = decimal.Decimal(102.34)
        upload_data[self.uploader.file_fields.amount] = str(test_amount)
        db_data = self.uploader.map_row_to_db_format(upload_data)
        self.assertEqual(db_data[self.uploader.field_map[self.uploader.file_fields.amount]], test_amount)

    def test_throw_error_for_upload_file_account_field_of_new_account_object(self):
        ''' Must throw Exception for an account string of an Account object that does not exist
        '''
        upload_data = self.prepare_default_upload_file_data()
        test_name = 'A New Account Name'
        # Confirm this is a new Account
        self.assertEqual(account_models.AccountNameMap.objects.filter(name=test_name).count(), 0)
        # Uploading with this account must throw an Error
        upload_data[self.uploader.file_fields.account] = test_name
        self.assertRaises(
            exceptions.HeydollarDoesNotExist,
            self.uploader.map_row_to_db_format,
            upload_data
        )

    def test_can_parse_upload_file_account_field_to_existing_account_object(self):
        ''' Must be able to convert account string to an existing Account data object
            if it exists
        '''
        upload_data = self.prepare_default_upload_file_data()
        test_account_map = self.account_map
        upload_data[self.uploader.file_fields.account] = test_account_map.name
        db_data = self.uploader.map_row_to_db_format(upload_data)
        self.assertEqual(db_data[self.uploader.field_map[self.uploader.file_fields.account]], test_account_map.account)

    def test_can_insert_new_upload_file_row_to_database(self):
        ''' Must insert record to database when uploading new transaction data
        '''
        upload_data = self.prepare_default_upload_file_data()
        # Write the test data to a file
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data)
        # Upload the test data to database
        txn_cnt = spending_models.Transaction.objects.count()
        self.uploader.upload(filename)
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt+1)

    def test_can_update_existing_upload_file_row_to_database(self):
        ''' Must update record in database when uploading existing transaction data
        '''
        upload_data = self.prepare_default_upload_file_data()
        new_notes = 'New notes to be saved'
        upload_data[self.uploader.file_fields.notes]=new_notes
        # Write the test data to a file
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data)
        # Create an Entry in database with test data
        txn_data = self.get_default_create_txn_data()
        txn = spending_models.Transaction(**txn_data)
        txn.notes = 'Old notes to be edited'
        txn.save()
        # Upload the test data to database
        txn_cnt = spending_models.Transaction.objects.count()
        self.uploader.upload(filename)
        txn = spending_models.Transaction.objects.get(pk=txn.pk)
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt)
        self.assertEqual(txn.notes, new_notes)

    def test_must_insert_duplicate_upload_file_row_to_database(self):
        ''' Must insert new record to database when uploading duplicate transaction data
            (ie, the same unique record fields occur multiple times in one upload file)
        '''
        upload_data = self.prepare_default_upload_file_data()
        # Write the test data to a file
        duplicate_cnt = 2
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data, repeat_rows=duplicate_cnt)
        # Upload the test data to database
        txn_cnt = spending_models.Transaction.objects.count()
        self.uploader.upload(filename)
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt+duplicate_cnt)

    def test_can_differentiate_duplicate_database_entries_by_notes_field(self):
        ''' Must select proper database entry to update (as needed) by matching notes field
        '''
        upload_data = self.prepare_default_upload_file_data()
        txn_cnt = spending_models.Transaction.objects.count()
        # Create 2 duplicate Transactions
        txn_data = self.get_default_create_txn_data()
        txn_data['notes'] = 'First Txn Notes'
        txn1 = spending_models.Transaction(**txn_data)
        txn1.save()
        notes2 = 'Second Txn Notes'
        txn_data['notes'] = notes2
        txn2 = spending_models.Transaction(**txn_data)
        txn2.save()
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt+2)
        # Write data to a test file, with EDITED category for Txn2
        upload_data[self.uploader.file_fields.notes] = notes2
        category2 = AutoFixture(spending_models.Category).create_one()
        upload_data[self.uploader.file_fields.category] = category2.name
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data)
        # Upload the test data to database. Should find and revise Txn2
        txn_cnt = spending_models.Transaction.objects.count()
        self.uploader.upload(filename)
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt)
        txn2 = spending_models.Transaction.objects.get(pk=txn2.pk)
        print upload_data
        print self.category
        print category2
        self.assertEqual(txn2.category, category2)

    def test_throw_error_for_ambiguous_duplicate_entry_updates(self):
        ''' Must throw Exception when attempts to update memo field for duplicate row
        '''
        upload_data = self.prepare_default_upload_file_data()
        txn_cnt = spending_models.Transaction.objects.count()
        # Create 2 duplicate Transactions
        txn_data = self.get_default_create_txn_data()
        txn_data['notes'] = 'First Txn Notes'
        txn1 = spending_models.Transaction(**txn_data)
        txn1.save()
        notes2 = 'Second Txn Notes'
        txn_data['notes'] = notes2
        txn2 = spending_models.Transaction(**txn_data)
        txn2.save()
        self.assertEqual(spending_models.Transaction.objects.count(), txn_cnt+2)
        # Write data to a test file, with THIRD notes field
        notes3 = 'Third Txn Notes'
        upload_data[self.uploader.file_fields.notes] = notes3
        filename = os.path.abspath(os.path.sep.join([settings.PROJECT_PATH, self.fake_filename]))
        self.write_example_upload_file(filename, upload_data)
        # Upload should throw Error.  Impossible to know if Txn1 or Txn2 notes changed
        self.assertRaises(
            exceptions.HeydollarAmbiguousEntry,
            self.uploader.upload,
            filename
        )