diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 87a93fef6..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: 2.1 - -orbs: - browser-tools: circleci/browser-tools@1.4.8 -jobs: - build: - machine: - image: ubuntu-2204:2023.04.2 - docker_layer_caching: true - - steps: - - checkout - - run: mkdir test-reports - - run: cp .env.ci .env - - run: - name: Build images - command: make build - - run: - name: Run containers - command: make up-detatched - - run: - name: Install npm packages - command: npm ci - - run: - name: Run postinstall script - command: npm run postinstall - - run: - name: Collect static - command: make collectstatic - - run: - name: Run Flake8 - command: make flake8 - - run: - name: Run isort - command: make isort-check - - run: - name: Run black - command: make black-check - - run: - name: Run tests - command: make pytest - - run: - name: Run BDD tests - command: make bdd - - - store_artifacts: - path: test-reports/ - destination: tr1 - - store_test_results: - path: test-reports/ diff --git a/.env.ci b/.env.ci index cd7345ba7..761b61594 100644 --- a/.env.ci +++ b/.env.ci @@ -3,3 +3,7 @@ DATABASE_URL=psql://postgres:postgres@db:5432/fido FLASK_CONFIG=testing SECRET_KEY=used_for_testing ALLOWED_HOSTS="*" +AUTHBROKER_CLIENT_ID= +AUTHBROKER_CLIENT_SECRET= +AUTHBROKER_URL= +DEBUG=False diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0affcb5c3..738de65ed 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # This repo is maintained by the Employee Experience team -* @uktrade/employee-experience \ No newline at end of file +* @uktrade/employee-experience diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..d9cef327f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: FFT CI + +on: + pull_request: + merge_group: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Create a .env file + run: cp .env.ci .env + + - name: Build images + run: make build + + - name: Run containers + run: make up-detatched + + - name: Install React app + run: npm ci + + - name: Collect static + run: make collectstatic + + - name: Run Flake8 + run: make flake8 + + - name: Run isort + run: make isort-check + + - name: Run black + run: make black-check + + - name: Run tests + run: make pytest + + - name: Run BDD tests + run: make bdd + + - name: Docker compose down + run: docker compose down diff --git a/config/test/__init__.py b/config/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/test/conftest.py b/config/test/conftest.py deleted file mode 100644 index c802caac8..000000000 --- a/config/test/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -# Used to detect if running from within a pytest run -# https://docs.pytest.org/en/latest/example/simple.html -# required because save in the meta model logchange (in core), save the changes to the log table, with the userid -# but the userid is not valid when we are running from test... - - -def pytest_configure(config): - import core - - print("In pytest_configure") - core._called_from_test = True - - -def pytest_unconfigure(config): - import core - - print("In pytest_unconfigure") - del core._called_from_test diff --git a/core/management/commands/create_stub_data.py b/core/management/commands/create_stub_data.py index 7285cc89d..ec046200e 100644 --- a/core/management/commands/create_stub_data.py +++ b/core/management/commands/create_stub_data.py @@ -1,6 +1,5 @@ from django.core.management.base import BaseCommand -import core from chartofaccountDIT.models import ( Analysis1, Analysis2, @@ -360,10 +359,8 @@ def create(self, what): # The modified save writes the current user to the log, but # the user is not available while we are running a command. # So set the test flag to stop writing to the log - core._called_from_test = True p = what() p.create() - del core._called_from_test self.stdout.write( self.style.SUCCESS( "Successfully completed stub data creation for {}.".format(p.name) @@ -371,10 +368,8 @@ def create(self, what): ) def clear(self, what): - core._called_from_test = True p = what() p.clear() - del core._called_from_test self.stdout.write( self.style.SUCCESS("Successfully cleared stub data for {}.".format(p.name)) ) diff --git a/end_of_month/test/test_end_of_month_process.py b/end_of_month/test/test_end_of_month_process.py index 3ed71ad51..8af3c485e 100644 --- a/end_of_month/test/test_end_of_month_process.py +++ b/end_of_month/test/test_end_of_month_process.py @@ -1,3 +1,4 @@ +import pytest from django.db.models import F from django.test import TestCase @@ -111,16 +112,7 @@ def test_end_of_month_mar(self): self.assertEqual(count, 129) -class ReadArchivedForecastTest(TestCase): - archived_figure = [] - - def setUp(self): - self.init_data = MonthlyFigureSetup() - self.init_data.setup_forecast() - - for period in range(0, 16): - self.archived_figure.append(0) - +class ReadArchivedForecastTestMixin: def get_period_total(self, period): data_model = forecast_budget_view_model[period] tot_q = data_model.objects.filter( @@ -152,17 +144,15 @@ def check_archive_period(self, tested_period): end_of_month_archive(tested_period) # run a query giving the full total archived_total = self.get_period_total(tested_period) - self.assertEqual(total_before, archived_total) + assert total_before == archived_total change_amount = tested_period * 10000 self.init_data.monthly_figure_update(tested_period + 1, change_amount) current_total = self.get_current_total() self.archived_figure[tested_period] = archived_total - self.assertNotEqual(current_total, archived_total) - self.assertEqual(current_total, (archived_total + change_amount)) + assert current_total != archived_total + assert current_total == (archived_total + change_amount) for period in range(1, tested_period + 1): - self.assertEqual( - self.archived_figure[period], self.get_period_total(period) - ) + assert self.archived_figure[period] == self.get_period_total(period) # The following tests check that the archived figures are not changed by # changing the current figures. @@ -226,6 +216,14 @@ def test_read_archived_figure_mar(self): self.check_archive_period(tested_period) +class TestReadArchivedForecast(ReadArchivedForecastTestMixin): + @pytest.fixture(autouse=True) + def _setup(self, db): + self.archived_figure = [0 for _ in range(16)] + self.init_data = MonthlyFigureSetup() + self.init_data.setup_forecast() + + class EndOfMonthBudgetTest(TestCase): def setUp(self): self.init_data = MonthlyFigureSetup() @@ -333,15 +331,7 @@ def test_end_of_month_mar(self): self.assertEqual(budget_total_count, 12) -class ReadArchivedBudgetTest(TestCase): - archived_figure = [] - - def setUp(self): - self.init_data = MonthlyFigureSetup() - self.init_data.setup_budget() - for period in range(0, 16): - self.archived_figure.append(0) - +class ReadArchivedBudgetTestMixin: def get_period_budget_total(self, period): data_model = forecast_budget_view_model[period] tot_q = data_model.objects.filter(financial_year=self.init_data.year_used) @@ -355,23 +345,20 @@ def check_archive_period(self, tested_period): end_of_month_archive(tested_period) # run a query giving the full total archived_total = self.get_period_budget_total(tested_period) - self.assertEqual(total_before, archived_total) + assert total_before == archived_total change_amount = tested_period * 10000 self.init_data.monthly_figure_update(tested_period + 1, change_amount, "budget") current_total = self.get_current_budget_total() self.archived_figure[tested_period] = archived_total - self.assertNotEqual(current_total, archived_total) - self.assertNotEqual(current_total, archived_total) - self.assertEqual(current_total, (archived_total + change_amount)) + assert current_total != archived_total + assert current_total == (archived_total + change_amount) for period in range(1, tested_period + 1): # Check the full total. It is saved in a different table, for convenience monthly_budget = MonthlyTotalBudget.objects.get(archived_period=period) - self.assertEqual(self.archived_figure[period], monthly_budget.amount) - # Check that nothig has corrupted the archived figures - self.assertEqual( - self.archived_figure[period], self.get_period_budget_total(period) - ) + assert self.archived_figure[period] == monthly_budget.amount + # Check that nothing has corrupted the archived figures + assert self.archived_figure[period] == self.get_period_budget_total(period) # The following tests check that the archived figures are not changed by # changing the current figures. @@ -433,3 +420,11 @@ def test_read_archived_figure_mar(self): tested_period = 12 self.test_read_archived_figure_feb() self.check_archive_period(tested_period) + + +class TestReadArchivedBudget(ReadArchivedBudgetTestMixin): + @pytest.fixture(autouse=True) + def _setup(self, db): + self.archived_figure = [0 for _ in range(16)] + self.init_data = MonthlyFigureSetup() + self.init_data.setup_budget() diff --git a/end_of_month/test/test_outturn_variance.py b/end_of_month/test/test_outturn_variance.py index 7b2dad2f0..610087b37 100644 --- a/end_of_month/test/test_outturn_variance.py +++ b/end_of_month/test/test_outturn_variance.py @@ -1,19 +1,17 @@ +import pytest from django.db.models import F -from django.test import TestCase from end_of_month.end_of_month_actions import end_of_month_archive from end_of_month.models import forecast_budget_view_model from end_of_month.test.test_utils import MonthlyFigureSetup -class ReadMonthlyVarianceTest(TestCase): - archived_figure = [] - - def setUp(self): +class TestReadMonthlyVariance: + @pytest.fixture(autouse=True) + def _setup(self, db): + self.archived_figure = [0 for _ in range(16)] self.init_data = MonthlyFigureSetup() self.init_data.setup_forecast() - for period in range(0, 16): - self.archived_figure.append(0) def get_period_total(self, period): data_model = forecast_budget_view_model[period] @@ -52,17 +50,17 @@ def check_archive_period(self, tested_period): end_of_month_archive(tested_period, True) # run a query giving the full total archived_total = self.get_period_total(tested_period) - self.assertEqual(total_before, archived_total) + assert total_before == archived_total previous_outurn = self.get_current_previous_outturn() - self.assertEqual(total_before, previous_outurn) + assert total_before == previous_outurn change_amount = tested_period * 10000 self.init_data.monthly_figure_update(tested_period + 1, change_amount) current_total = self.get_current_total() self.archived_figure[tested_period] = archived_total - self.assertNotEqual(current_total, previous_outurn) - self.assertEqual(current_total, (previous_outurn + change_amount)) + assert current_total != previous_outurn + assert current_total == (previous_outurn + change_amount) # The following tests check that the previous outturn figure is not changed by # changing the current figures. diff --git a/end_of_month/test/test_utils.py b/end_of_month/test/test_utils.py index 0854a2820..0c11d0337 100644 --- a/end_of_month/test/test_utils.py +++ b/end_of_month/test/test_utils.py @@ -124,9 +124,6 @@ def setup_budget(self): class SetFullYearArchive(MonthlyFigureSetup): - archived_forecast = [] - archived_budget = [] - def set_period_total(self, period): data_model = forecast_budget_view_model[period] tot_q = data_model.objects.all() @@ -164,6 +161,8 @@ def set_archive_period(self, last_archived_period=13): def __init__(self, last_archived_period=16, year=0): super().__init__(year) + self.archived_forecast = [] + self.archived_budget = [] self.setup_forecast() self.setup_budget() # prepares the lists used to store the totals diff --git a/future_years/test/test_end_of_month_archive_budget.py b/future_years/test/test_end_of_month_archive_budget.py index c083826fe..b93949c5c 100644 --- a/future_years/test/test_end_of_month_archive_budget.py +++ b/future_years/test/test_end_of_month_archive_budget.py @@ -1,15 +1,19 @@ +import pytest from django.test import TestCase from core.utils.generic_helpers import get_current_financial_year from end_of_month.end_of_month_actions import end_of_month_archive -from end_of_month.test.test_end_of_month_process import ReadArchivedBudgetTest +from end_of_month.test.test_end_of_month_process import ReadArchivedBudgetTestMixin from end_of_month.test.test_utils import MonthlyFigureSetup from forecast.models import BudgetMonthlyFigure -class ReadArchivedFutureDataForecast(ReadArchivedBudgetTest): - def setUp(self): - super().setUp() +class TestReadArchivedFutureDataForecast(ReadArchivedBudgetTestMixin): + @pytest.fixture(autouse=True) + def _setup(self, db): + self.archived_figure = [0 for _ in range(16)] + self.init_data = MonthlyFigureSetup() + self.init_data.setup_budget() current_year = get_current_financial_year() # Create a set of future budget data self.init_data.set_year(current_year + 2) diff --git a/future_years/test/test_end_of_month_archive_forecast.py b/future_years/test/test_end_of_month_archive_forecast.py index 1f25cc0d9..77f8ea41a 100644 --- a/future_years/test/test_end_of_month_archive_forecast.py +++ b/future_years/test/test_end_of_month_archive_forecast.py @@ -1,15 +1,19 @@ +import pytest from django.test import TestCase from core.utils.generic_helpers import get_current_financial_year from end_of_month.end_of_month_actions import end_of_month_archive -from end_of_month.test.test_end_of_month_process import ReadArchivedForecastTest +from end_of_month.test.test_end_of_month_process import ReadArchivedForecastTestMixin from end_of_month.test.test_utils import MonthlyFigureSetup from forecast.models import ForecastMonthlyFigure -class ReadArchivedFutureDataForecast(ReadArchivedForecastTest): - def setUp(self): - super().setUp() +class TestReadArchivedFutureDataForecast(ReadArchivedForecastTestMixin): + @pytest.fixture(autouse=True) + def _setup(self, db): + self.archived_figure = [0 for _ in range(16)] + self.init_data = MonthlyFigureSetup() + self.init_data.setup_forecast() current_year = get_current_financial_year() # Create a set of future forecast data self.init_data.set_year(current_year + 2) diff --git a/gifthospitality/management/commands/populate_gift_hospitality_table.py b/gifthospitality/management/commands/populate_gift_hospitality_table.py index 58b0f10cd..2cafc0d3f 100644 --- a/gifthospitality/management/commands/populate_gift_hospitality_table.py +++ b/gifthospitality/management/commands/populate_gift_hospitality_table.py @@ -1,6 +1,5 @@ from django.core.management.base import BaseCommand -import core from gifthospitality.models import ( GiftAndHospitalityCategory, GiftAndHospitalityClassification, @@ -264,10 +263,8 @@ def add_arguments(self, parser): ) def create(self, what): - core._called_from_test = True p = what() p.create() - del core._called_from_test self.stdout.write( self.style.SUCCESS( "Successfully completed G and H data creation for {}.".format(p.name) @@ -275,10 +272,8 @@ def create(self, what): ) def clear(self, what): - core._called_from_test = True p = what() p.clear() - del core._called_from_test self.stdout.write( self.style.SUCCESS( "Successfully cleared Gift and Hospitality data for {}.".format(p.name) diff --git a/makefile b/makefile index 0e4232423..e05019d12 100644 --- a/makefile +++ b/makefile @@ -77,7 +77,7 @@ flake8: docker-compose run --rm web flake8 $(file) bdd: - docker-compose exec web sh -c "python manage.py behave $(feature) --settings=config.settings.bdd --no-capture" + docker-compose exec -T web python manage.py behave $(feature) --settings=config.settings.bdd --no-capture elevate: docker-compose run --rm web python manage.py elevate_sso_user_permissions @@ -95,7 +95,7 @@ test: docker-compose run --rm web python manage.py test $(test) pytest: - docker-compose run --rm web pytest -raP --capture=sys --ignore=node_modules --ignore=front_end --ignore=features --ignore=staticfiles -n 4 + docker-compose run --rm web pytest --ignore=node_modules --ignore=front_end --ignore=features --ignore=staticfiles --random-order -n 4 -v black-check: docker-compose run --rm --no-deps web black --check . diff --git a/poetry.lock b/poetry.lock index cdace48a6..3744d9c07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2032,6 +2032,20 @@ pytest = ">=7.0.0" docs = ["sphinx", "sphinx-rtd-theme"] testing = ["Django", "django-configurations (>=2.0)"] +[[package]] +name = "pytest-random-order" +version = "1.1.1" +description = "Randomise the order in which pytest tests are run with some control over the randomness" +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "pytest-random-order-1.1.1.tar.gz", hash = "sha256:4472d7d34f1f1c5f3a359c4ffc5c13ed065232f31eca19c8844c1ab406e79080"}, + {file = "pytest_random_order-1.1.1-py3-none-any.whl", hash = "sha256:882727a8b597ecd06ede28654ffeb8a6d511a1e4abe1054cca7982f2e42008cd"}, +] + +[package.dependencies] +pytest = ">=3.0.0" + [[package]] name = "pytest-xdist" version = "3.5.0" @@ -2723,4 +2737,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "7895d28da3f884172d0dfbfb68d2f18cd1527b4da89d626929ffebd4e74c22d0" +content-hash = "e33a3191e593867bcd2e20fb01070e9ef7994f01a95a98949d8058e5abf9c4e2" diff --git a/pyproject.toml b/pyproject.toml index 580b53a74..bd59ae090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,6 +147,7 @@ wheel = "0.38.1" pep517 = "0.10.0" apipkg = "1.5" toml = "0.10.1" +pytest-random-order = "^1.1.1" [build-system]