# The Mia! Accounting Project. # Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/22 # Copyright (c) 2023 imacat. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """The test for the options. """ import datetime as dt import unittest import httpx from flask import Flask from accounting.utils.next_uri import encode_next from test_site import db from testlib import NEXT_URI, Accounts, create_test_app, get_client, \ get_csrf_token PREFIX: str = "/accounting/options" """The URL prefix for the option management.""" class OptionTestCase(unittest.TestCase): """The option test case.""" def setUp(self) -> None: """Sets up the test. This is run once per test. :return: None. """ self.__app: Flask = create_test_app() """The Flask application.""" with self.__app.app_context(): from accounting.models import Option Option.query.delete() self.__encoded_next_uri: str = encode_next(NEXT_URI) """The encoded next URI.""" self.__client: httpx.Client = get_client(self.__app, "admin") """The user client.""" self.__csrf_token: str = get_csrf_token(self.__client) """The CSRF token.""" def test_nobody(self) -> None: """Test the permission as nobody. :return: None. """ client: httpx.Client = get_client(self.__app, "nobody") csrf_token: str = get_csrf_token(client) detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" edit_uri: str = f"{PREFIX}/edit?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" response: httpx.Response response = client.get(detail_uri) self.assertEqual(response.status_code, 403) response = client.get(edit_uri) self.assertEqual(response.status_code, 403) response = client.post(update_uri, data=self.__get_form(csrf_token)) self.assertEqual(response.status_code, 403) def test_viewer(self) -> None: """Test the permission as viewer. :return: None. """ client: httpx.Client = get_client(self.__app, "viewer") csrf_token: str = get_csrf_token(client) detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" edit_uri: str = f"{PREFIX}/edit?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" response: httpx.Response response = client.get(detail_uri) self.assertEqual(response.status_code, 403) response = client.get(edit_uri) self.assertEqual(response.status_code, 403) response = client.post(update_uri, data=self.__get_form(csrf_token)) self.assertEqual(response.status_code, 403) def test_editor(self) -> None: """Test the permission as editor. :return: None. """ client: httpx.Client = get_client(self.__app, "editor") csrf_token: str = get_csrf_token(client) detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" edit_uri: str = f"{PREFIX}/edit?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" response: httpx.Response response = client.get(detail_uri) self.assertEqual(response.status_code, 403) response = client.get(edit_uri) self.assertEqual(response.status_code, 403) response = client.post(update_uri, data=self.__get_form(csrf_token)) self.assertEqual(response.status_code, 403) def test_admin(self) -> None: """Test the permission as administrator. :return: None. """ detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" edit_uri: str = f"{PREFIX}/edit?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" response: httpx.Response response = self.__client.get(detail_uri) self.assertEqual(response.status_code, 200) response = self.__client.get(edit_uri) self.assertEqual(response.status_code, 200) response = self.__client.post(update_uri, data=self.__get_form()) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) def test_set(self) -> None: """Test to set the options. :return: None. """ from accounting.utils.options import options detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" edit_uri: str = f"{PREFIX}/edit?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" form: dict[str, str] response: httpx.Response # Empty currency code form = self.__get_form() form["default_currency_code"] = " " response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Non-existing currency code form = self.__get_form() form["default_currency_code"] = "ZZZ" response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Empty current account form = self.__get_form() form["default_ie_account_code"] = " " response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Non-existing current account form = self.__get_form() form["default_ie_account_code"] = "9999-999" response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Not a current account form = self.__get_form() form["default_ie_account_code"] = Accounts.MEAL response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item name empty form = self.__get_form() key = [x for x in form if x.endswith("-name")][0] form[key] = " " response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item account empty form = self.__get_form() key = [x for x in form if x.endswith("-account_code")][0] form[key] = " " response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item non-expense account form = self.__get_form() key = [x for x in form if x.startswith("recurring-expense-") and x.endswith("-account_code")][0] form[key] = Accounts.SERVICE response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item non-income account form = self.__get_form() key = [x for x in form if x.startswith("recurring-income-") and x.endswith("-account_code")][0] form[key] = Accounts.UTILITIES response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item payable expense form = self.__get_form() key = [x for x in form if x.startswith("recurring-expense-") and x.endswith("-account_code")][0] form[key] = Accounts.PAYABLE response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item receivable income form = self.__get_form() key = [x for x in form if x.startswith("recurring-income-") and x.endswith("-account_code")][0] form[key] = Accounts.RECEIVABLE response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Recurring item description template empty form = self.__get_form() key = [x for x in form if x.endswith("-description_template")][0] form[key] = " " response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], edit_uri) # Success, with malformed order with self.__app.app_context(): self.assertEqual(options.default_currency_code, "USD") self.assertEqual(options.default_ie_account_code, "1111-001") self.assertEqual(len(options.recurring.expenses), 0) self.assertEqual(len(options.recurring.incomes), 0) response = self.__client.post(update_uri, data=self.__get_form()) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): self.assertEqual(options.default_currency_code, "EUR") self.assertEqual(options.default_ie_account_code, "0000-000") self.assertEqual(len(options.recurring.expenses), 4) self.assertEqual(options.recurring.expenses[0].account_code, Accounts.INSURANCE) self.assertEqual(options.recurring.expenses[1].account_code, Accounts.POSTAGE) self.assertEqual(options.recurring.expenses[2].account_code, Accounts.UTILITIES) self.assertEqual(options.recurring.expenses[3].account_code, Accounts.RENT_EXPENSE) self.assertEqual(len(options.recurring.incomes), 2) self.assertEqual(options.recurring.incomes[0].account_code, Accounts.SERVICE) self.assertEqual(options.recurring.incomes[1].account_code, Accounts.DONATION) # Success, with no recurring data form = self.__get_form() form = {x: form[x] for x in form if not x.startswith("recurring-")} response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): self.assertEqual(len(options.recurring.expenses), 0) self.assertEqual(len(options.recurring.incomes), 0) def test_update_not_modified(self) -> None: """Tests that the data is not modified. :return: None. """ from accounting.models import Option detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" form: dict[str, str] option: Option | None resource: httpx.Response response = self.__client.post(update_uri, data=self.__get_form()) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): option = db.session.get(Option, "recurring") self.assertIsNotNone(option) timestamp: dt.datetime \ = option.created_at - dt.timedelta(seconds=5) option.created_at = timestamp option.updated_at = timestamp db.session.commit() # The recurring setting was not modified form = self.__get_form() form["default_currency_code"] = "JPY" response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): option = db.session.get(Option, "recurring") self.assertIsNotNone(option) self.assertEqual(option.created_at, timestamp) self.assertEqual(option.updated_at, timestamp) # The recurring setting was modified form = self.__get_form() key: str = [x for x in form if x.startswith("recurring-expense-") and x.endswith("-account_code")][0] form[key] = Accounts.MEAL response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): option = db.session.get(Option, "recurring") self.assertIsNotNone(option) self.assertLess(option.created_at, option.updated_at) def test_created_updated_by(self) -> None: """Tests the created-by and updated-by record. :return: None. """ from accounting.models import Option from accounting.utils.user import get_user_pk admin_username, editor_username = "admin", "editor" detail_uri: str = f"{PREFIX}?next={self.__encoded_next_uri}" update_uri: str = f"{PREFIX}/update" option: Option | None response: httpx.Response response = self.__client.post(update_uri, data=self.__get_form()) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): editor_pk: int = get_user_pk(editor_username) option = db.session.get(Option, "recurring") self.assertIsNotNone(option) option.created_by_id = editor_pk option.updated_by_id = editor_pk db.session.commit() form: dict[str, str] = self.__get_form() key: str = [x for x in form if x.startswith("recurring-expense-") and x.endswith("-account_code")][0] form[key] = Accounts.MEAL response = self.__client.post(update_uri, data=form) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], detail_uri) with self.__app.app_context(): option = db.session.get(Option, "recurring") self.assertIsNotNone(option) self.assertEqual(option.created_by.username, editor_username) self.assertEqual(option.updated_by.username, admin_username) def __get_form(self, csrf_token: str | None = None) -> dict[str, str]: """Returns the option form. :param csrf_token: The CSRF token. :return: The option form. """ if csrf_token is None: csrf_token = self.__csrf_token return {"csrf_token": csrf_token, "next": self.__encoded_next_uri, "default_currency_code": "EUR", "default_ie_account_code": "0000-000", "recurring-expense-1-name": "Water bill", "recurring-expense-1-account_code": Accounts.UTILITIES, "recurring-expense-1-description_template": "Water bill for {last_bimonthly_name}", "recurring-expense-3-no": "16", "recurring-expense-3-name": "Phone bill", "recurring-expense-3-account_code": Accounts.POSTAGE, "recurring-expense-3-description_template": "Phone bill for {last_month_name}", "recurring-expense-12-name": "Rent", "recurring-expense-12-account_code": Accounts.RENT_EXPENSE, "recurring-expense-12-description_template": "Rent for {this_month_name}", "recurring-expense-26-no": "7", "recurring-expense-26-name": "Insurance", "recurring-expense-26-account_code": Accounts.INSURANCE, "recurring-expense-26-description_template": "Insurance for {last_month_name}", "recurring-income-2-no": "12", "recurring-income-2-name": "Donation", "recurring-income-2-account_code": Accounts.DONATION, "recurring-income-2-description_template": "Donation for {this_month_name}", "recurring-income-15-no": "4", "recurring-income-15-name": "Payroll", "recurring-income-15-account_code": Accounts.SERVICE, "recurring-income-15-description_template": "Payroll for {last_month_name}"}