def test_linkify_class_valid(self, client: "Client", django_user_model: "User"): login(client, django_user_model) tclass = mommy.make("budget.TransactionClass", name="foo") url = tables.linkify_class_by_name("foo") # Should link to a detail view for the existing class assert url == reverse("budget:class-detail", kwargs={"pk": tclass.pk})
def test_bulk_update_valid(self, client, django_user_model): url = reverse("budget:pattern-bulk-update") login(client, django_user_model) cat_model = apps.get_model("budget.Category") pattern_model = apps.get_model("budget.Pattern") assert pattern_model.objects.count() == 0 # Create the transaction class objects mommy.make("budget.TransactionClass", name="bills") mommy.make("budget.TransactionClass", name="discretionary") # Create the CSV df = pd.DataFrame( dict( Pattern=[".*state.*farm.*", ".*target.*"], Category=["Insurance", "Shopping"], Class=["Bills", "Discretionary"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) try: with open(temp_path, "rb") as f: r = client.post(url, {"csv": f}) assert r.status_code == 302 assert cat_model.objects.count() == 2 assert pattern_model.objects.count() == 2 finally: os.remove(temp_path)
def test_backup_create_no_transactions(self, client: "Client", django_user_model: "User"): # Setup login(client, django_user_model) bak_model = apps.get_model( "budget.CSVBackup") # type: Type[models.CSVBackup] assert bak_model.objects.count() == 0 bak = mommy.make(bak_model, csv=None) assert bak_model.objects.count() == 1 # Create the backup bak.create_backup() try: with open(bak.csv.path) as f: lines = list(f.readlines()) # No transactions, so should just be header assert len([ln for ln in lines if ln != "\n"]) == 1 assert lines[ 0] == "Account,Class,Category,Date,Amount,Description\n" finally: # Cleanup os.remove(bak.csv.path)
def test_backup_file_response(self, client: "Client", django_user_model: "User"): # Setup login(client, django_user_model) bak_model = apps.get_model( "budget.CSVBackup") # type: Type[models.CSVBackup] assert bak_model.objects.count() == 0 # Make a fake file s = "foo bar baz spam ham eggs" temp_path = os.path.join(settings.MEDIA_ROOT, "temp.txt") with open(temp_path, "w") as f: f.write(s) try: bak = mommy.make(bak_model, csv=temp_path) assert bak_model.objects.count() == 1 r = bak.file_response() text = "".join( line.decode("UTF-8") for line in r.streaming_content) assert text == s finally: # Cleanup os.remove(temp_path)
def test_bulk_update_invalid_csv(self, client, django_user_model): url = reverse("budget:pattern-bulk-update") login(client, django_user_model) pattern_model = apps.get_model("budget.Pattern") assert pattern_model.objects.count() == 0 # Create the CSV df = pd.DataFrame( dict( Pattern=[".*state.*farm.*", ".*target.*"], Category=["Insurance", "Shopping"], BadColumnName=["Bills", "Discretionary"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) try: with open(temp_path, "rb") as f: r = client.post(url, {"csv": f}) assert r.status_code == 200 assert pattern_model.objects.count() == 0 assert "columns expected but not found" in hr(r) finally: os.remove(temp_path)
def test_backup_purge_confirm_view(self, client: "Client", django_user_model: "User"): url = "budget:backup-purge-confirm" template = "budget/backup-purge-confirm.html" login(client, django_user_model) response = client.get(reverse(url)) tp_names = [t.name for t in response.templates] assert response.status_code == 200 and template in tp_names
def test_index_no_data(self, client, django_user_model): url = "budget:index" template = "budget/index.html" login(client, django_user_model) # Check the page response = client.get(reverse(url)) tp_names = [t.name for t in response.templates] assert response.status_code == 200 and template in tp_names
def test_backup_empty_file_response(self, client: "Client", django_user_model: "User"): # Setup login(client, django_user_model) bak_model = apps.get_model( "budget.CSVBackup") # type: Type[models.CSVBackup] assert bak_model.objects.count() == 0 bak = mommy.make(bak_model, csv=None) assert bak_model.objects.count() == 1 r = bak.file_response() text = "".join(line.decode("UTF-8") for line in r.streaming_content) assert text == "No CSV associated with selected backup."
def test_backup_download_view(self, client: "Client", django_user_model: "User"): # Setup url = "budget:backup-download" user = login(client, django_user_model) bak_model = apps.get_model("budget.CSVBackup") # type: Type[CSVBackup] assert bak_model.objects.count() == 0 # Make a fake file s = "foo bar baz spam ham eggs" temp_path = os.path.join(settings.MEDIA_ROOT, "temp.txt") with open(temp_path, "w") as f: f.write(s) try: bak = mommy.make(bak_model, user=user, csv=temp_path) assert bak_model.objects.count() == 1 r = client.get(reverse(url, kwargs={"pk": bak.pk})) text = "".join( line.decode("UTF-8") for line in r.streaming_content) assert text == s finally: # Cleanup os.remove(temp_path)
def test_backup_create_new_view(self, client: "Client", django_user_model: "User"): url = "budget:backup-addnew" # Setup user = login(client, django_user_model) bak_model = apps.get_model("budget.CSVBackup") # type: Type[CSVBackup] assert bak_model.objects.count() == 0 # Make some transactions tr_model = apps.get_model( "budget.Transaction") # type: Type[Transaction] assert tr_model.objects.count() == 0 mommy.make(tr_model, user=user, amount=5.54) mommy.make(tr_model, user=user, amount=3.99) assert tr_model.objects.count() == 2 # POST to the page r = client.post(reverse(url)) assert r.status_code == 302 assert bak_model.objects.count() == 1 obj = bak_model.objects.first() # type: CSVBackup try: # Check that the transactions are in the CSV df = pd.read_csv(obj.csv) assert df.shape == (2, 6) assert math.isclose(df["Amount"].sum(), 9.53, rel_tol=1e-9, abs_tol=0.0) finally: os.remove(obj.csv.path)
def test_pattern_update_view(self, client, django_user_model): # Setup url_pattern = "budget:pattern-update" template = "budget/pattern-update.html" user = login(client, django_user_model) # Create the dependency object cat_model = apps.get_model("budget.Category") # type: Type[Category] cat = mommy.make(cat_model) # Create the object model = apps.get_model("budget.Pattern") # type: Type[Pattern] assert model.objects.count() == 0 pat = mommy.make(model, user=user, pattern="OrigPattern", category=cat) url = reverse(url_pattern, kwargs={"pk": pat.pk}) # Test the update page for template response = client.get(url) assert response.status_code == 200 assert template in [t.name for t in response.templates] # Test the update page form form_data = dict(pattern="NewPattern", category=cat.id) response = client.post(url, data=form_data) assert response.status_code == 302 assert model.objects.get(pk=cat.pk).pattern == "NewPattern"
def delete_view_test(client, django_user_model, model, url, user_required=True, obj_params=None): # Make sure there are no existing objects model_cls = apps.get_model(*model.split(".")) model_cls.objects.all().delete() assert model_cls.objects.count() == 0 # Create the object and assert success if obj_params is None: obj_params = dict() user = login(client, django_user_model) if user_required: obj_params.update(user=user) obj = mommy.make(model, **obj_params) obj = create_recursive_dependencies(obj) obj.save() assert model_cls.objects.count() == 1 # Check the delete page response = client.get(reverse(url, kwargs={"pk": obj.id})) assert response.status_code == 200 and model_cls.objects.count() == 1 # Delete the object and verify response = client.post(reverse(url, kwargs={"pk": obj.id})) assert response.status_code == 302 and model_cls.objects.count() == 0
def test_upload_parse_transactions_wrong_header(self, client: "Client", django_user_model: "User"): # Setup user = login(client, django_user_model) Account = apps.get_model("budget.Account") Upload = apps.get_model("budget.Upload") acc = mommy.make( Account, user=user, date_col_name="date", amt_col_name="amt", desc_col_name="desc", ) # Make a fake csv df = pd.DataFrame( dict( date=["2018-11-10", "2018-11-11"], bad_amt_col_name=[5.54, 3.99], desc=["Eggs", "Spam"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) # Create the upload and try to parse the CSV ul = mommy.make(Upload, account=acc, user=user, csv=temp_path) result = ul.parse_transactions() assert "Not all specified columns" in result assert acc.num_transactions == 0 # Cleanup os.remove(temp_path)
def test_classify_view(self, client: "Client", django_user_model: "User"): url = "budget:classify" # Setup user = login(client, django_user_model) pat_model = apps.get_model("budget.Pattern") # type: Type[Pattern] tr_model = apps.get_model( "budget.Transaction") # type: Type[Transaction] assert pat_model.objects.count() == 0 assert tr_model.objects.count() == 0 # Make some transactions and a pattern for i in range(3): mommy.make(tr_model, user=user, description=f"transaction {i}", pattern=None) # type: Transaction assert tr_model.objects.count() == 3 pat = mommy.make(pat_model, user=user, pattern="transaction.*") # type: Pattern assert pat_model.objects.count() == 1 # GET the view and check that the pattern associated r = client.get(reverse(url)) assert r.status_code == 302 assert pat.num_transactions == 3
def test_upload_parse_transactions_str_in_value_col( self, client: "Client", django_user_model: "User"): # Setup user = login(client, django_user_model) Account = apps.get_model("budget.Account") Upload = apps.get_model("budget.Upload") acc = mommy.make( Account, user=user, date_col_name="date", amt_col_name="amt", desc_col_name="desc", ) # Make a fake csv df = pd.DataFrame( dict( date=["2018-11-10", "2018-11-11"], amt=["five dollars", 3.99], desc=["Eggs", "Spam"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) # Create the upload and try to parse the CSV ul = mommy.make(Upload, account=acc, user=user, csv=temp_path) with transaction.atomic(): result = ul.parse_transactions() assert "Validation error" in result assert acc.num_transactions == 0 # Cleanup os.remove(temp_path)
def test_category_update_view(self, client, django_user_model): # Setup url_pattern = "budget:category-update" template = "budget/category-update.html" user = login(client, django_user_model) # Create the dependency object cls_model = apps.get_model( "budget.TransactionClass") # type: Type[TransactionClass] cls = mommy.make(cls_model) # Create the object model = apps.get_model("budget.Category") # type: Type[Category] assert model.objects.count() == 0 cat = mommy.make(model, user=user, name="OrigName", class_field=cls) url = reverse(url_pattern, kwargs={"pk": cat.pk}) # Test the update page for template response = client.get(url) assert response.status_code == 200 assert template in [t.name for t in response.templates] # Test the update page form form_data = dict(name="NewName", class_field=cat.class_field_id) response = client.post(url, data=form_data) assert response.status_code == 302 assert model.objects.get(pk=cat.pk).name == "NewName"
def test_backup_create_view(self, client, django_user_model): url = "budget:backup-add" model = "budget.CSVBackup" template = "budget/backup-add.html" user = login(client, django_user_model) # Create file content = ",".join( ["Account", "Class", "Category", "Date", "Amount", "Description"]) csv = temp_file(content=content) try: obj_params = dict(creation_time=today_str(), csv=csv) self.create_view_test( client, model, url, template, user, obj_params=obj_params, file_field="csv", ) finally: os.remove(csv)
def test_upload_create_view(self, client, django_user_model): url = "budget:upload-add" model = "budget.Upload" template = "budget/upload-add.html" user = login(client, django_user_model) # Parents parent_models = ["budget.Account"] parents = parent_obj_set(parent_models) # Create file acc = parents["budget.Account"] content = ",".join( [acc.date_col_name, acc.amt_col_name, acc.desc_col_name]) csv = temp_file(content=content) try: obj_params = dict(upload_time=today_str(), account=acc.id, csv=csv) self.create_view_test( client, model, url, template, user, obj_params=obj_params, file_field="csv", ) finally: os.remove(csv)
def test_debt_budget_valid(client: "Client", django_user_model: "User"): user = login(client, django_user_model) tclass = mommy.make("budget.TransactionClass", name="debt") # type: Type[TransactionClass] budget = mommy.make("budget.Budget", class_field=tclass, user=user, value=100) # type: Type[Budget] assert get_debt_budget(user).pk == budget.pk
def test_backup_restore_clean(self, client: "Client", django_user_model: "User"): # Setup user = login(client, django_user_model) bak_model = apps.get_model( "budget.CSVBackup") # type: Type[models.CSVBackup] tr_model = apps.get_model( "budget.Transaction") # type: Type[models.Transaction] acc_model = apps.get_model( "budget.Account") # type: Type[models.Account] ul_model = apps.get_model("budget.Upload") # type: Type[models.Upload] for model in (bak_model, tr_model, acc_model, ul_model): assert model.objects.count() == 0 # Make a fake csv df = pd.DataFrame( dict( Account=["Checking", "Checking"], Class=["", ""], Category=["", ""], Date=["2018-11-10", "2018-11-11"], Amount=[5.54, 3.99], Description=["Eggs", "Spam"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) try: # Create the backup object and restore bak = mommy.make(bak_model, user=user, csv=temp_path) with transaction.atomic(): msg = bak.restore() # There should now be two transactions attached to one # upload and one account, and msg should be a success code assert tr_model.objects.count() == 2 assert (float( tr_model.objects.aggregate( Sum("amount"))["amount__sum"]) == 9.53) assert acc_model.objects.count() == 1 assert acc_model.objects.first().name == "Checking" assert acc_model.objects.first().num_transactions == 2 assert ul_model.objects.count() == 1 assert ul_model.objects.first().account.name == "Checking" assert ul_model.objects.first().num_transactions == 2 assert msg == "success" finally: # Cleanup os.remove(temp_path)
def test_backup_failed_restore_view(self, client: "Client", django_user_model: "User"): # Setup url = "budget:backup-restore" user = login(client, django_user_model) bak_model = apps.get_model("budget.CSVBackup") # type: Type[CSVBackup] assert bak_model.objects.count() == 0 # Create the backup object w/ no CSV and try to restore bak = mommy.make(bak_model, user=user, csv=None) r = client.post(reverse(url, kwargs={"pk": bak.pk})) msgs = r.cookies["messages"].value assert r.status_code == 302 assert "Restore failed: No CSV associated" in msgs
def test_index_no_budget(self, client, django_user_model): url = "budget:index" template = "budget/index.html" user = login(client, django_user_model) cls = mommy.make("budget.TransactionClass", name="Bills") cat = mommy.make("budget.Category", class_field=cls, user=user) ptrn = mommy.make("budget.Pattern", user=user, category=cat) for __ in range(10): mommy.make("budget.Transaction", user=user, pattern=ptrn) # Check the page response = client.get(reverse(url)) tp_names = [t.name for t in response.templates] assert response.status_code == 200 and template in tp_names
def test_thirteen_months_manager(self, client: "Client", django_user_model: "User"): model = "budget.Transaction" today = datetime.date.today() two_years_ago = today - datetime.timedelta(days=2 * DAYS_PER_YEAR) user = login(client, django_user_model) # Make objects old_trans = mommy.make(model, date=two_years_ago, user=user) new_trans = mommy.make(model, date=today, user=user) old_trans.save() new_trans.save() # Test Transaction = apps.get_model(model) qs = Transaction.objects.in_last_thirteen_months(user) assert new_trans in qs and old_trans not in qs
def test_budget_update_view(self, client, django_user_model): url = "budget:budget-update" model = "budget.Budget" template = "budget/budget-update.html" user = login(client, django_user_model) obj_params = dict(class_field=1, value=1000) self.update_view_test( client, model, url, template, user, user_required=True, obj_params=obj_params, create_recursive=False, )
def test_pattern_create_view(self, client, django_user_model): url = "budget:pattern-add" model = "budget.Pattern" template = "budget/pattern-add.html" user = login(client, django_user_model) # Parents parent_models = ["budget.Category"] parents = parent_obj_set(parent_models) obj_params = dict(pattern="TestObj", category=parents["budget.Category"].id) self.create_view_test(client, model, url, template, user, obj_params=obj_params)
def test_category_create_view(self, client, django_user_model): url = "budget:category-add" model = "budget.Category" template = "budget/category-add.html" user = login(client, django_user_model) # Parents parent_models = ["budget.TransactionClass"] parents = parent_obj_set(parent_models) obj_params = dict(name="TestObj", class_field=parents["budget.TransactionClass"].id) self.create_view_test(client, model, url, template, user, obj_params=obj_params)
def test_account_create_view(self, client, django_user_model): url = "budget:account-add" model = "budget.Account" template = "budget/account-add.html" user = login(client, django_user_model) obj_params = dict( name="TestObj", date_col_name="Date", amt_col_name="Amt", desc_col_name="Desc", ) self.create_view_test(client, model, url, template, user, obj_params=obj_params)
def test_backup_restore_str_in_value_col(self, client: "Client", django_user_model: "User"): # Setup user = login(client, django_user_model) bak_model = apps.get_model( "budget.CSVBackup") # type: Type[models.CSVBackup] tr_model = apps.get_model( "budget.Transaction") # type: Type[models.Transaction] acc_model = apps.get_model( "budget.Account") # type: Type[models.Account] ul_model = apps.get_model("budget.Upload") # type: Type[models.Upload] for model in (bak_model, tr_model, acc_model, ul_model): assert model.objects.count() == 0 # Make a fake csv df = pd.DataFrame( dict( Account=["Checking", "Checking"], Class=["", ""], Category=["", ""], Date=["2018-11-10", "2018-11-11"], Amount=["five dollars", 3.99], Description=["Eggs", "Spam"], )) temp_path = os.path.join(settings.MEDIA_ROOT, "temp.csv") df.to_csv(temp_path) try: # Create the backup object and restore bak = mommy.make(bak_model, user=user, csv=temp_path) with transaction.atomic(): msg = bak.restore() # It should have failed because of the incorrect date format assert tr_model.objects.count() == 0 assert acc_model.objects.count() == 0 assert ul_model.objects.count() == 0 assert "Validation error" in msg finally: # Cleanup os.remove(temp_path)
def test_backup_purge_view(self, client: "Client", django_user_model: "User"): url = "budget:backup-purge" # Setup user = login(client, django_user_model) bak_model = apps.get_model("budget.CSVBackup") # type: Type[CSVBackup] assert bak_model.objects.count() == 0 # Make some transactions tr_model = apps.get_model( "budget.Transaction") # type: Type[Transaction] assert tr_model.objects.count() == 0 mommy.make(tr_model, user=user, amount=5.54) mommy.make(tr_model, user=user, amount=3.99) assert tr_model.objects.count() == 2 # POST to the page r = client.post(reverse(url)) assert r.status_code == 302 assert tr_model.objects.count() == 0
def test_pattern_match_transactions(self, client: "Client", django_user_model: "User"): # Setup user = login(client, django_user_model) Pattern = apps.get_model("budget.Pattern") Transaction = apps.get_model("budget.Transaction") assert Pattern.objects.count() == 0 assert Transaction.objects.count() == 0 # Create the pattern and transactions p = mommy.make(Pattern, user=user, pattern=r".*wal[- ]?mart.*") shared_kwargs = dict(_model=Transaction, user=user, pattern=None) mommy.make(**shared_kwargs, id=1, description="WalMart") # Yes mommy.make(**shared_kwargs, id=2, description="Wal Mart") # Yes mommy.make(**shared_kwargs, id=3, description="WallMart") # No mommy.make(**shared_kwargs, id=4, description="Target") # No mommy.make(**shared_kwargs, id=5, description="Debit - Wal-Mart") # Yes # Validate matches p.match_transactions() assert [t.id for t in p.transaction_set.all()] == [1, 2, 5]