예제 #1
0
    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})
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
 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
예제 #7
0
    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
예제 #8
0
    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."
예제 #9
0
    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)
예제 #10
0
    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)
예제 #11
0
    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"
예제 #12
0
    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
예제 #13
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)
예제 #14
0
    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
예제 #15
0
    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)
예제 #16
0
    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"
예제 #17
0
    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)
예제 #18
0
    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)
예제 #19
0
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
예제 #20
0
    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)
예제 #21
0
    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
예제 #22
0
    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
예제 #23
0
    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
예제 #24
0
    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,
        )
예제 #25
0
    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)
예제 #26
0
    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)
예제 #27
0
    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)
예제 #28
0
    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)
예제 #29
0
    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
예제 #30
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]