From 2b5a8f02b20e30a25a759ea95d382e65acb27107 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 13 Nov 2020 19:04:27 +0000 Subject: [PATCH 01/15] experimentations with github workflows --- .github/workflows/build.yml | 58 ++++++++++++++++++++++++++ .github/workflows/windows_test.yml | 29 +++++++++++++ README.md | 5 ++- opal/core/test_runner.py | 4 +- opal/tests/js_config/karma_defaults.js | 4 +- opal/tests/test_core_test_runner.py | 12 +++--- runtests.py | 6 +-- 7 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/windows_test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..f3d239775 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,58 @@ +on: [push, pull_request] +jobs: + # run python/js tests, coveralls and lint + integration-test: + name: Integration test ${{ matrix.os }} python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + services: + postgres: + image: postgres:11 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: ci_db_test + ports: + - 5432:5432 + strategy: + fail-fast: false + matrix: + os: + - ubuntu-16.04 + python-version: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up ruby for coveralls + uses: actions/setup-ruby@v1 + - name: Set up js + uses: actions/setup-node@v1 + - name: Install js dependencies + run: npm install jasmine-core@2.3.4 karma@1.5 karma-coverage@1.1.1 karma-jasmine@0.3.8 karma-firefox-launcher@2.1.0 karma-coveralls@1.1.2 + - name: Install opal + run: python setup.py develop + - name: Install dependencies + run: pip install -r test-requirements.txt + - run: gem install coveralls-lcov + - name: run tests + run: opal test --coverage + - name: flake8 + run: flake8 + - run: find . -iname '*.info' -exec cp '{}' coverage \; + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage/lcov.info + + + + + diff --git a/.github/workflows/windows_test.yml b/.github/workflows/windows_test.yml new file mode 100644 index 000000000..f31401a45 --- /dev/null +++ b/.github/workflows/windows_test.yml @@ -0,0 +1,29 @@ +on: [push, pull_request] +jobs: + # make sure the python side of things works on windows + other-os-tests: + name: Test ${{ matrix.os }} Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - windows-latest + python-version: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install opal + run: python setup.py develop + - name: Install dependencies + run: pip install -r test-requirements.txt + - name: run tests + run: opal test py \ No newline at end of file diff --git a/README.md b/README.md index 2708783fa..12f753cbe 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ Opal ==== -[![Build Status](https://travis-ci.org/openhealthcare/opal.svg?branch=v0.11.0)](https://travis-ci.org/openhealthcare/opal) +![Integration tests](https://github.com/fredkingham/opal/workflows/.github/workflows/build.yml/badge.svg) + [![Coverage Status](https://coveralls.io/repos/github/openhealthcare/opal/badge.svg?branch=v0.11.0)](https://coveralls.io/github/openhealthcare/opal?branch=v0.11.0) -[![PyPI version](https://badge.fury.io/py/opal.svg)](https://badge.fury.io/py/opal) +[![PyPI version](https://badge.fury.io/py/opal.svg)](https://badge.fury.io/py/opal) Opal is a full stack web framework that makes building digital tools for health care easy. diff --git a/opal/core/test_runner.py b/opal/core/test_runner.py index 438d3e78a..4ded35847 100644 --- a/opal/core/test_runner.py +++ b/opal/core/test_runner.py @@ -10,7 +10,7 @@ from opal.utils import write -TRAVIS = os.environ.get('TRAVIS', False) +GITHUB_ACTION = os.environ.get('GITHUB_WORKFLOW', False) def _has_file(where, filename): @@ -84,7 +84,7 @@ def _run_js_tests(args): # to a string env["OPAL_LOCATION"] = str(args.opal_location) - if TRAVIS: + if GITHUB_ACTION: karma = './node_modules/karma/bin/karma' else: karma = 'karma' diff --git a/opal/tests/js_config/karma_defaults.js b/opal/tests/js_config/karma_defaults.js index aa29b451a..20a9bdf8b 100644 --- a/opal/tests/js_config/karma_defaults.js +++ b/opal/tests/js_config/karma_defaults.js @@ -63,8 +63,8 @@ module.exports = function(includedFiles, baseDir, coverageFiles){ } ]; - if(process.env.TRAVIS){ - browsers = ["Firefox"]; + if(process.env.GITHUB_WORKFLOW){ + browsers = ["FirefoxHeadless"]; plugins.push("karma-firefox-launcher"); plugins.push("karma-coveralls"); if(useCoverage){ diff --git a/opal/tests/test_core_test_runner.py b/opal/tests/test_core_test_runner.py index 9acb7ed1a..7ae80bef0 100644 --- a/opal/tests/test_core_test_runner.py +++ b/opal/tests/test_core_test_runner.py @@ -148,10 +148,10 @@ def test_run_tests_for_unknown_config(self, sysexit, writer, has_file): class RunJSTestsTestCase(OpalTestCase): def setUp(self): - self.TRAVIS = test_runner.TRAVIS + self.GITHUB_ACTION = test_runner.GITHUB_ACTION def tearDown(self): - test_runner.TRAVIS = self.TRAVIS + test_runner.GITHUB_ACTION = self.GITHUB_ACTION @patch('subprocess.check_call') def test_run_tests(self, check_call): @@ -160,7 +160,7 @@ def test_run_tests(self, check_call): mock_args.coverage = False mock_args.test = None mock_args.failfast = False - test_runner.TRAVIS = False + test_runner.GITHUB_ACTION = False test_runner._run_js_tests(mock_args) self.assertEqual( ['karma', 'start', 'config/karma.conf.js', '--single-run'], @@ -168,13 +168,13 @@ def test_run_tests(self, check_call): ) @patch('subprocess.check_call') - def test_run_tests_travis(self, check_call): + def test_run_tests_github(self, check_call): mock_args = MagicMock(name="args") mock_args.userland_here = ffs.Path('.') mock_args.coverage = False mock_args.test = None mock_args.failfast = False - test_runner.TRAVIS = True + test_runner.GITHUB_ACTION = True test_runner._run_js_tests(mock_args) self.assertEqual( [ @@ -198,7 +198,7 @@ def test_run_tests_failfast(self, check_call): mock_args.coverage = False mock_args.test = None mock_args.failfast = True - test_runner.TRAVIS = False + test_runner.GITHUB_ACTION = False test_runner._run_js_tests(mock_args) self.assertEqual( [ diff --git a/runtests.py b/runtests.py index 38c93b7f7..2e37ba1db 100644 --- a/runtests.py +++ b/runtests.py @@ -112,13 +112,13 @@ } ) -if 'TRAVIS' in os.environ: +if os.environ.get('GITHUB_WORKFLOW') == 'tests': test_settings_config["DATABASES"] = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'travis_ci_test', + 'NAME': 'ci_db_test', 'USER': 'postgres', - 'PASSWORD': '', + 'PASSWORD': 'postgres', 'HOST': 'localhost', } } From 9ac99d527c9f6be22281c3b8a6ce7234b3a34c1e Mon Sep 17 00:00:00 2001 From: fredkingham Date: Mon, 16 Nov 2020 09:16:29 +0000 Subject: [PATCH 02/15] Remove the old travis file --- .travis.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8f32c81b4..000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: python -dist: xenial -python: - - "3.5" - - "3.6" - - "3.7" - -services: - - postgresql - - xvfb -install: - - python setup.py develop - - gem install coveralls-lcov - - pip install -r test-requirements.txt - - npm install jasmine-core@2.3.4 karma@1.5 karma-coverage@1.1.1 karma-jasmine@0.3.8 karma-firefox-launcher@1.0.0 karma-coveralls@1.1.2 - - gem install rake -before_script: - - psql -c 'create database travis_ci_test;' -U postgres -script: - - opal test --coverage - - flake8 -after_success: - - ls -lha coverage - - coveralls-lcov -v -n coverage/Firefox\ 56.0.0\ \(Linux\ 0.0.0\)/lcov.info > coverage/coverage.json - - cat coverage/coverage.json - - coveralls debug --merge=coverage/coverage.json - - coveralls --merge=coverage/coverage.json -notifications: - slack: ohcuk:6spaME3CB7f2PGrMAcklYWqp -sudo: false From 77df11bfdc0532194b2c18502caa769529a1474f Mon Sep 17 00:00:00 2001 From: fredkingham Date: Mon, 16 Nov 2020 09:42:37 +0000 Subject: [PATCH 03/15] Coverralls tweaks to merge js and python --- .github/workflows/build.yml | 15 +++++++++------ opal/tests/test_core_test_runner.py | 1 + runtests.py | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3d239775..878c7a09e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,23 +34,26 @@ jobs: uses: actions/setup-ruby@v1 - name: Set up js uses: actions/setup-node@v1 + - run: gem install coveralls-lcov + - run: gem install rake - name: Install js dependencies run: npm install jasmine-core@2.3.4 karma@1.5 karma-coverage@1.1.1 karma-jasmine@0.3.8 karma-firefox-launcher@2.1.0 karma-coveralls@1.1.2 - name: Install opal run: python setup.py develop + env: + OPAL_TEST_USE_POSTGRES: 1 - name: Install dependencies run: pip install -r test-requirements.txt - run: gem install coveralls-lcov + - run: gem install rake - name: run tests run: opal test --coverage - name: flake8 run: flake8 - - run: find . -iname '*.info' -exec cp '{}' coverage \; - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: coverage/lcov.info + - run: coveralls-lcov -v -n coverage/Firefox\ 82.0.0\ \(Ubuntu\ 0.0.0\)/lcov.info > coverage/coverage.json + - run: coveralls --merge=coverage/coverage.json + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" diff --git a/opal/tests/test_core_test_runner.py b/opal/tests/test_core_test_runner.py index 7ae80bef0..ab1136f01 100644 --- a/opal/tests/test_core_test_runner.py +++ b/opal/tests/test_core_test_runner.py @@ -10,6 +10,7 @@ from opal.core import test_runner + class RunPyTestsTestCase(OpalTestCase): @patch('subprocess.check_call') diff --git a/runtests.py b/runtests.py index 2e37ba1db..51005863e 100644 --- a/runtests.py +++ b/runtests.py @@ -43,7 +43,7 @@ 'reversion.middleware.RevisionMiddleware' ), INSTALLED_APPS=( - 'django.contrib.auth', + 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.staticfiles', 'django.contrib.sessions', @@ -112,7 +112,7 @@ } ) -if os.environ.get('GITHUB_WORKFLOW') == 'tests': +if os.environ.get('USE_POSTGRES'): test_settings_config["DATABASES"] = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', From ae5a7f4596476d65e02ba6d95f7bc3e52fc66829 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Thu, 19 Nov 2020 15:00:57 +0000 Subject: [PATCH 04/15] remove the github windows test and combine things into work workflow build file that tests everything --- .github/workflows/build.yml | 27 +++++++++++++++++++++++++++ .github/workflows/windows_test.yml | 29 ----------------------------- 2 files changed, 27 insertions(+), 29 deletions(-) delete mode 100644 .github/workflows/windows_test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 878c7a09e..240640589 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,33 @@ jobs: - run: coveralls --merge=coverage/coverage.json env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" + # make sure the python side of things works on windows + other-os-tests: + name: Test ${{ matrix.os }} Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - windows-latest + python-version: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install opal + run: python setup.py develop + - name: Install dependencies + run: pip install -r test-requirements.txt + - name: run tests + run: opal test py diff --git a/.github/workflows/windows_test.yml b/.github/workflows/windows_test.yml deleted file mode 100644 index f31401a45..000000000 --- a/.github/workflows/windows_test.yml +++ /dev/null @@ -1,29 +0,0 @@ -on: [push, pull_request] -jobs: - # make sure the python side of things works on windows - other-os-tests: - name: Test ${{ matrix.os }} Python ${{ matrix.python-version }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - windows-latest - python-version: - - 3.6 - - 3.7 - - 3.8 - - 3.9 - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install opal - run: python setup.py develop - - name: Install dependencies - run: pip install -r test-requirements.txt - - name: run tests - run: opal test py \ No newline at end of file From 5076533414f0205d2ba5a4e5362e996a26e4578b Mon Sep 17 00:00:00 2001 From: fredkingham Date: Thu, 19 Nov 2020 15:04:03 +0000 Subject: [PATCH 05/15] Add a comment as to why we are detecting the github workflow attribute --- opal/core/test_runner.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opal/core/test_runner.py b/opal/core/test_runner.py index 4ded35847..69efdb473 100644 --- a/opal/core/test_runner.py +++ b/opal/core/test_runner.py @@ -10,6 +10,9 @@ from opal.utils import write +# We're using the GITHUB_WORKFLOW env variable +# to determine that we are running in a github action. +# GITHUB_WORKFLOW is set in the env by github. GITHUB_ACTION = os.environ.get('GITHUB_WORKFLOW', False) From 0fe2645b99449521e8a7f4b0d6aec592eb3cef34 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Thu, 19 Nov 2020 15:36:50 +0000 Subject: [PATCH 06/15] Updates the readme to look at the opal repo's build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12f753cbe..dc1dfa3ee 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Opal ==== -![Integration tests](https://github.com/fredkingham/opal/workflows/.github/workflows/build.yml/badge.svg) +![Build](https://github.com/openhealthcare/opal/workflows/.github/workflows/build.yml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/openhealthcare/opal/badge.svg?branch=v0.11.0)](https://coveralls.io/github/openhealthcare/opal?branch=v0.11.0) From 93300561540cfaad7b2fc0f7b1b37ae32cc77dd4 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Thu, 19 Nov 2020 15:38:10 +0000 Subject: [PATCH 07/15] Remove appveyor this is covered by the gh action --- .appveyor.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 9250aa563..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -environment: - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - PYTHON: "C:\\Python36-x64" - PYTHON_VERSION: "3.6.*" - PYTHON_ARCH: "64" - -install: - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - "echo %PYTHON%" - # - "python -m pip install --disable-pip-version-check --user --upgrade pip" - - "python setup.py develop" - - "pip install -r test-requirements.txt" - -build: off - -test_script: - - "python runtests.py" From 70ed01ebd0f3219182394ba4bdc0e7f44242d77f Mon Sep 17 00:00:00 2001 From: fredkingham Date: Tue, 24 Nov 2020 19:13:45 +0000 Subject: [PATCH 08/15] Remove the duplicate line in the definition --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 240640589..fd947a15a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,8 +34,6 @@ jobs: uses: actions/setup-ruby@v1 - name: Set up js uses: actions/setup-node@v1 - - run: gem install coveralls-lcov - - run: gem install rake - name: Install js dependencies run: npm install jasmine-core@2.3.4 karma@1.5 karma-coverage@1.1.1 karma-jasmine@0.3.8 karma-firefox-launcher@2.1.0 karma-coveralls@1.1.2 - name: Install opal From ab211de930a867ed16decce0ce041b40dbfa2dbd Mon Sep 17 00:00:00 2001 From: fredkingham Date: Wed, 25 Nov 2020 09:14:37 +0000 Subject: [PATCH 09/15] Use the coverralls secret for pushing to coveralls --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd947a15a..1fa85b0bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,6 +52,7 @@ jobs: - run: coveralls --merge=coverage/coverage.json env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} # make sure the python side of things works on windows other-os-tests: name: Test ${{ matrix.os }} Python ${{ matrix.python-version }} From be53e74cefa79e20e5876e496306f2df6ca67660 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Wed, 9 Dec 2020 10:36:28 +0000 Subject: [PATCH 10/15] try and avoid the tyranny of depending on firefox versions to find lcov.info --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fa85b0bd..abfaaf9a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,8 @@ jobs: run: opal test --coverage - name: flake8 run: flake8 - - run: coveralls-lcov -v -n coverage/Firefox\ 82.0.0\ \(Ubuntu\ 0.0.0\)/lcov.info > coverage/coverage.json + - name: combine coveralls + run: find coverage -name "lcov.info" -exec coveralls-lcov -v -n {} \; > coverage/coverage.json - run: coveralls --merge=coverage/coverage.json env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" From 097a1d44107945911f58e4efee620684af40252e Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 26 Aug 2022 12:27:04 +0100 Subject: [PATCH 11/15] Fixes the Kombu version to 5.2.3 Celery allows Kombu 5.3 to be imported but that includes an import that does not work for python 3.6.9 (although celery should be supporting 3.6) --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index e51441c82..e4f413934 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,8 @@ ] }, install_requires=[ + # We pin kombu because kombu 5.3 imports does not work on python 3.6.9 + 'kombu==5.2.3', 'ffs>=0.0.8.2', 'Jinja2==2.10.1', 'django==2.2.16', From 70563acaa4449c8c638465c5b0970412d7ded56d Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 26 Aug 2022 13:02:44 +0100 Subject: [PATCH 12/15] Change to use pip install --editable rather than python setup.py develop --- .github/workflows/build.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abfaaf9a4..b0b29aa49 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Install js dependencies run: npm install jasmine-core@2.3.4 karma@1.5 karma-coverage@1.1.1 karma-jasmine@0.3.8 karma-firefox-launcher@2.1.0 karma-coveralls@1.1.2 - name: Install opal - run: python setup.py develop + run: pip install -e . env: OPAL_TEST_USE_POSTGRES: 1 - name: Install dependencies @@ -76,13 +76,8 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install opal - run: python setup.py develop + run: pip install -e . - name: Install dependencies run: pip install -r test-requirements.txt - name: run tests run: opal test py - - - - - From 4fb63ef8fd69aacfabeecd46454da86979c015bd Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 26 Aug 2022 13:20:16 +0100 Subject: [PATCH 13/15] Downgrade Kombu to 5.1.0 Kombu 5.2.3 cannot be found on windows --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e4f413934..01dcd2a9f 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ }, install_requires=[ # We pin kombu because kombu 5.3 imports does not work on python 3.6.9 - 'kombu==5.2.3', + 'kombu==5.1.0', 'ffs>=0.0.8.2', 'Jinja2==2.10.1', 'django==2.2.16', From 76475e60ecd57912980de1597bf6cebad790947a Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 26 Aug 2022 13:37:16 +0100 Subject: [PATCH 14/15] Change the github actions to run on 18.04 We have no servers still running on ubuntu 16 and github don't support it, so use a more recent version of ubuntu --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0b29aa49..a9a724ddc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: - - ubuntu-16.04 + - ubuntu-18.04 python-version: - 3.6 - 3.7 From 06eddd4d45acdb9b80875e5879a9b82957f9d9b5 Mon Sep 17 00:00:00 2001 From: fredkingham Date: Fri, 26 Aug 2022 16:23:48 +0100 Subject: [PATCH 15/15] Testing coverrals, this should fail coverralls --- opal/core/search/tests/test_api.py | 30 - opal/core/search/tests/test_extract.py | 574 -------------- opal/core/search/tests/test_search_query.py | 796 -------------------- opal/core/search/tests/test_search_rule.py | 212 ------ opal/core/search/tests/test_tasks.py | 25 - opal/core/search/tests/test_views.py | 501 ------------ 6 files changed, 2138 deletions(-) delete mode 100644 opal/core/search/tests/test_api.py delete mode 100644 opal/core/search/tests/test_extract.py delete mode 100644 opal/core/search/tests/test_search_query.py delete mode 100644 opal/core/search/tests/test_search_rule.py delete mode 100644 opal/core/search/tests/test_tasks.py delete mode 100644 opal/core/search/tests/test_views.py diff --git a/opal/core/search/tests/test_api.py b/opal/core/search/tests/test_api.py deleted file mode 100644 index a6754da21..000000000 --- a/opal/core/search/tests/test_api.py +++ /dev/null @@ -1,30 +0,0 @@ -from opal.core.test import OpalTestCase -from unittest.mock import patch -from opal.core.search import api -from rest_framework.reverse import reverse -from rest_framework import status - - -class ExtractSchemaTestCase(OpalTestCase): - - @patch('opal.core.search.api.schemas') - def test_records(self, schemas): - schemas.extract_schema.return_value = [{}] - self.assertEqual([{}], api.ExtractSchemaViewSet().list(None).data) - - -class LoginRequredTestCase(OpalTestCase): - """ - we expect almost all views to 401 - """ - def setUp(self): - self.patient, self.episode = self.new_patient_and_episode_please() - self.request = self.rf.get("/") - - def test_401(self): - url = reverse('extract-schema-list', request=self.request) - response = self.client.get(url) - self.assertEqual( - response.status_code, - 401 - ) diff --git a/opal/core/search/tests/test_extract.py b/opal/core/search/tests/test_extract.py deleted file mode 100644 index 4acaa6b85..000000000 --- a/opal/core/search/tests/test_extract.py +++ /dev/null @@ -1,574 +0,0 @@ -""" -Unittests for opal.core.search.extract -""" -import datetime -import json -import os -from pathlib import Path - -from django.urls import reverse -from django.test import override_settings -from django.utils import timezone -from unittest.mock import mock_open, patch, Mock, MagicMock - -from opal.core.test import OpalTestCase -from opal import models -from opal.tests.models import ( - Colour, PatientColour, Demographics, HatWearer, HouseOwner -) -from opal.core.search import extract -from six import u -import tempfile - - -MOCKING_FILE_NAME_OPEN = "opal.core.search.extract.open" - - -class TestViewPOSTTestCase(OpalTestCase): - - def test_check_view(self): - # a vanilla check to make sure that the view returns a zip file - url = reverse("extract_download") - post_data = { - "criteria": - json.dumps([{ - "combine": "and", - "column": "demographics", - "field": "Surname", - "queryType": "Contains", - "query": "a", - "lookup_list": [], - }]) - } - - self.assertTrue( - self.client.login(username=self.user.username, password=self.PASSWORD) - ) - - response = self.client.post(url, post_data) - - self.assertEqual(response.status_code, 200) - - @override_settings(EXTRACT_ASYNC=True) - def test_check_view_with_sync_extract(self): - url = reverse("extract_download") - post_data = { - "criteria": - json.dumps([{ - "combine": "and", - "column": "demographics", - "field": "Surname", - "queryType": "Contains", - "query": "a", - "lookup_list": [], - }]) - } - - self.assertTrue( - self.client.login(username=self.user.username, password=self.PASSWORD) - ) - - response = self.client.post(url, json.dumps(post_data), content_type='appliaction/json') - self.assertEqual(response.status_code, 200) - - -class PatientEpisodeTestCase(OpalTestCase): - def setUp(self): - self.patient, self.episode = self.new_patient_and_episode_please() - Demographics.objects.all().update( - patient=self.patient, - hospital_number='12345678', - first_name="Alice", - surname="Alderney", - date_of_birth=datetime.date(1976, 1, 1) - ) - super(PatientEpisodeTestCase, self).setUp() - - def mocked_extract(self, some_fun, args): - m = mock_open() - with patch(MOCKING_FILE_NAME_OPEN, m, create=True): - some_fun(*args) - - -class GenerateFilesTestCase(OpalTestCase): - @patch('opal.core.search.extract.subrecords') - @patch('opal.core.search.extract.CsvRenderer.write_to_file') - @patch('opal.core.search.extract.write_data_dictionary') - def test_generate_csv_files( - self, write_data_dictionary, write_to_file, subrecords - ): - patient, episode = self.new_patient_and_episode_please() - subrecords.return_value = [HatWearer, HouseOwner] - HatWearer.objects.create(name="Indiana", episode=episode) - HouseOwner.objects.create(patient=patient) - results = extract.generate_csv_files( - "somewhere", models.Episode.objects.all(), self.user - ) - expected = [ - (os.path.join('somewhere', 'data_dictionary.html'), 'data_dictionary.html'), - (os.path.join('somewhere', 'episodes.csv'), 'episodes.csv'), - (os.path.join('somewhere', 'hat_wearer.csv'), 'hat_wearer.csv'), - (os.path.join('somewhere', 'house_owner.csv'), 'house_owner.csv') - ] - self.assertEqual(expected, results) - self.assertEqual( - write_data_dictionary.call_args[0][0], - os.path.join('somewhere', 'data_dictionary.html'), - ) - self.assertEqual( - write_to_file.call_args[0], (os.path.join('somewhere', 'house_owner.csv'),) - ) - - @patch('opal.core.search.extract.subrecords') - @patch('opal.core.search.extract.EpisodeSubrecordCsvRenderer') - @patch('opal.core.search.extract.CsvRenderer.write_to_file') - @patch('opal.core.search.extract.write_data_dictionary') - def test_exclude_subrecords( - self, write_data_dictionary, write_to_file, csv_renderer, subrecords - ): - subrecords.return_value = [Colour] - extract.generate_csv_files( - "somewhere", models.Episode.objects.all(), self.user - ) - self.assertEqual(csv_renderer.call_count, 0) - - -class ZipArchiveTestCase(OpalTestCase): - @patch('opal.core.search.extract.tempfile') - @patch('opal.core.search.extract.generate_csv_files') - @patch('opal.core.search.extract.shutil.make_archive') - @patch('opal.core.search.extract.application.get_app') - def test_zip_file_writes( - self, get_app, make_archive, generate_csv_files, their_tempfile - ): - """ - generate_zip_files does the followig - * Creates an extract directory in a temp directory - * Calls the function that creates/writes the csvs to that extract directory - * Writes all files within the extract directory to a zip directory - - This tests that by mocking out the creates/writes function to put a file in - and makes sure that get's written to a zip directory. - """ - with tempfile.TemporaryDirectory() as csv_dir: - with tempfile.TemporaryDirectory() as zip_dir: - their_tempfile.TemporaryDirectory.return_value.__enter__.return_value = csv_dir - their_tempfile.mkdtemp.return_value = zip_dir - root_dir = None - - application = MagicMock() - modify_extract_fun = MagicMock() - application.get_modify_extract_functions.return_value = [modify_extract_fun] - get_app.return_value = application - - def _generate_csv_files(_root_dir, episodes, user): - nonlocal root_dir - root_dir = _root_dir - generate_csv_files.side_effect = _generate_csv_files - episode_qs = models.Episode.objects.all() - make_archive.return_value = 'extract.zip' - result = extract.zip_archive(episode_qs, 'this', self.user) - make_archive_call_args = make_archive.call_args - - # the directory that will contain extract.zip - zip_archive_dir = Path(root_dir).parent.absolute() - expected_zip_name = os.path.join(zip_archive_dir, 'extract') - make_archive_call_args.assert_called_once_with( - expected_zip_name, 'zip', root_dir - ) - modify_extract_fun.assert_called_once_with( - episode_qs, root_dir, self.user, - ) - self.assertEqual(os.path.basename(result), "extract.zip") - - -class AsyncExtractTestCase(OpalTestCase): - @patch('opal.core.search.tasks.extract.delay') - def test_async(self, delay): - extract.async_extract(self.user, 'THIS') - delay.assert_called_with(self.user, 'THIS') - - -class TestBasicCsvRenderer(PatientEpisodeTestCase): - def test_init(self): - renderer = extract.CsvRenderer(Colour, Colour.objects.all(), self.user) - self.assertEqual(renderer.model, Colour) - renderer = extract.CsvRenderer(Colour, Colour.objects.all(), self.user) - self.assertEqual(renderer.fields, renderer.get_field_names_to_render()) - - def test_0_count(self): - renderer = extract.CsvRenderer(Colour, Colour.objects.all(), self.user) - self.assertEqual(renderer.count(), 0) - - def test_1_count(self): - Colour.objects.create(name="Blue", episode=self.episode) - renderer = extract.CsvRenderer(Colour, Colour.objects.all(), self.user) - self.assertEqual(renderer.count(), 1) - - def test_set_fields(self): - renderer = extract.CsvRenderer(Colour, Colour.objects.all(), self.user, fields=[ - "name", "episode_id" - ]) - self.assertEqual(renderer.fields, ["name", "episode_id"]) - self.assertEqual( - renderer.get_headers(), - ["Name", "Episode"] - ) - - def test_get_field_names_to_render(self): - with patch.object(Colour, "_get_fieldnames_to_extract") as field_names: - field_names.return_value = ["id", "name", "consistency_token"] - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - self.assertEqual( - renderer.fields, - ["id", "name"] - ) - - def test_get_field_names_to_render_order(self): - with patch.object(Colour, "_get_fieldnames_to_extract") as field_names: - field_names.return_value = ["name", "id", "patient_id", "consistency_token", "episode_id"] - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - self.assertEqual( - renderer.fields, - ["id", "patient_id", "episode_id", "name"] - ) - - - def test_fields_uses_fields_arg(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - self.assertEqual( - renderer.fields, - ["name"] - ) - - def test_get_headers(self): - with patch.object(Colour, "_get_fieldnames_to_extract") as field_names: - field_names.return_value = ["name", "consistency_token"] - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - self.assertEqual( - renderer.get_headers(), - ["Name"] - ) - - def test_get_headers_uses_fields_arg(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - self.assertEqual( - renderer.get_headers(), - ["Name"] - ) - - def test_serialize_list_simple(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - result = renderer.serialize_value(["hello", "there"]) - self.assertEqual( - result, - "hello; there" - ) - - def test_serialize_list_complex(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - - dt = timezone.make_aware(datetime.datetime(2017, 2, 9, 10, 00, 16)) - result = renderer.serialize_value([{"hello": dt}]) - self.assertEqual( - result, - '[{"hello": "2017-02-09T10:00:16Z"}]' - ) - - def test_serialize_dict(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - - dt = timezone.make_aware(datetime.datetime(2017, 2, 9, 10, 00, 16)) - result = renderer.serialize_value({"hello": dt}) - self.assertEqual( - result, - '{"hello": "2017-02-09T10:00:16Z"}' - ) - - def test_serialize_number(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - - result = renderer.serialize_value(1) - self.assertEqual(result, "1") - - def test_serialize_string(self): - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - result = renderer.serialize_value("hello") - self.assertEqual(result, "hello") - - def test_list_render(self): - _, episode = self.new_patient_and_episode_please() - colour = Colour.objects.create(name="Blue", episode=episode) - normal_to_dict = colour.to_dict(self.user) - normal_to_dict["name"] = ["onions", "kettles"] - with patch.object(colour, "to_dict") as to_dicted: - to_dicted.return_value = normal_to_dict - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - result = renderer.get_row(colour) - self.assertIn("onions; kettles", result) - - def test_to_dict_render(self): - _, episode = self.new_patient_and_episode_please() - colour = Colour.objects.create(name="Blue", episode=episode) - normal_to_dict = colour.to_dict(self.user) - normal_to_dict["name"] = {"RGB": "50, 168, 82", "Hex": "#32a852"} - with patch.object(colour, "to_dict") as to_dicted: - to_dicted.return_value = normal_to_dict - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - result = renderer.get_row(colour) - self.assertIn(json.dumps(normal_to_dict["name"]), result) - - def test_get_row(self): - with patch.object(Colour, "_get_fieldnames_to_extract") as field_names: - _, episode = self.new_patient_and_episode_please() - colour = Colour.objects.create(name="Blue", episode=episode) - field_names.return_value = ["name", "consistency_token"] - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - self.assertEqual( - renderer.get_row(colour), - ["Blue"] - ) - - def test_get_row_uses_fields_arg(self): - _, episode = self.new_patient_and_episode_please() - colour = Colour.objects.create(name="Blue", episode=episode) - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user, fields=["name"] - ) - self.assertEqual( - renderer.get_row(colour), - ["Blue"] - ) - - def test_get_rows(self): - _, episode = self.new_patient_and_episode_please() - colour = Colour.objects.create(name="Blue", episode=episode) - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - with patch.object(renderer, "get_row") as get_row: - get_row.return_value = ["some_row"] - result = list(renderer.get_rows()) - - get_row.assert_called_with(colour) - self.assertEqual(result, [["some_row"]]) - - @patch("opal.core.search.extract.csv") - def test_write_to_file(self, csv): - _, episode = self.new_patient_and_episode_please() - Colour.objects.create(name="Blue", episode=episode) - renderer = extract.CsvRenderer( - Colour, Colour.objects.all(), self.user - ) - with patch.object(renderer, "get_rows"): - with patch.object(renderer, "get_headers"): - renderer.get_rows.return_value = [["row"]] - renderer.get_headers.return_value = ["header"] - self.mocked_extract(renderer.write_to_file, ["some file"]) - self.assertEqual(renderer.get_rows.call_count, 1) - self.assertEqual(renderer.get_headers.call_count, 1) - self.assertEqual(csv.writer().writerow.call_count, 2) - self.assertEqual(csv.writer().writerow.mock_calls[0][1][0], ["header"]) - self.assertEqual(csv.writer().writerow.mock_calls[1][1][0], ["row"]) - - -class TestEpisodeCsvRenderer(PatientEpisodeTestCase): - - def test_init(self): - renderer = extract.EpisodeCsvRenderer( - models.Episode, - models.Episode.objects.all(), - self.user - ) - self.assertEqual(renderer.model, models.Episode) - - def test_headers(self): - expected = { - "Start", - "End", - "Created", - "Updated", - "Created By", - "Updated By", - "Patient", - "Tagging" - } - renderer = extract.EpisodeCsvRenderer( - models.Episode, - models.Episode.objects.all(), - self.user - ) - self.assertEqual(len(expected - set(renderer.get_headers())), 0) - - def test_get_row(self): - renderer = extract.EpisodeCsvRenderer( - models.Episode, - models.Episode.objects.all(), - self.user - ) - self.episode.set_tag_names(["trees"], self.user) - # make sure we keep historic tags - self.episode.set_tag_names(["leaves"], self.user) - row = renderer.get_row(self.episode) - self.assertTrue( - "trees;leaves" in row or "leaves;trees" in row - ) - - -@patch.object(PatientColour, "_get_fieldnames_to_extract") -class TestPatientSubrecordCsvRenderer(PatientEpisodeTestCase): - - def setUp(self): - self.patient, self.episode = self.new_patient_and_episode_please() - self.patient_colour = PatientColour.objects.create( - name="blue", patient=self.patient - ) - self.pid_str = str(self.patient.id) - self.eid_str = str(self.episode.id) - - def test_get_row(self, field_names_to_extract): - field_names_to_extract.return_value = [ - "id", "patient_id", "episode_id", "name", "consistency_token" - ] - renderer = extract.PatientSubrecordCsvRenderer( - PatientColour, - models.Episode.objects.all(), - self.user - ) - rendered = renderer.get_row(self.patient_colour, self.episode.id) - self.assertEqual( - [ - str(self.patient_colour.id), - self.pid_str, - self.eid_str, - "blue" - ], rendered - ) - - def test_get_rows(self, field_names_to_extract): - field_names_to_extract.return_value = [ - "id", "patient_id", "episode_id", "name", "consistency_token", - ] - renderer = extract.PatientSubrecordCsvRenderer( - PatientColour, - models.Episode.objects.all(), - self.user - ) - rendered = list( - renderer.get_rows() - ) - expected = [[str(self.patient_colour.id), self.pid_str, self.eid_str, "blue"]] - self.assertEqual(expected, rendered) - - def test_get_rows_same_patient(self, field_names_to_extract): - self.patient.create_episode() - first_episode = self.patient.episode_set.first() - last_episode = self.patient.episode_set.last() - field_names_to_extract.return_value = [ - "id", "patient_id", "episode_id", "name", "consistency_token" - ] - - renderer = extract.PatientSubrecordCsvRenderer( - PatientColour, - models.Episode.objects.all(), - self.user - ) - rendered = list( - renderer.get_rows() - ) - self.assertEqual([ - [ - str(self.patient_colour.id), - str(self.patient.id), - str(first_episode.id), - "blue" - ], - [ - str(self.patient_colour.id), - str(self.patient.id), - str(last_episode.id), - "blue" - ], - ], rendered) - - -@patch.object(Colour, "_get_fieldnames_to_extract") -class TestEpisodeSubrecordCsvRenderer(PatientEpisodeTestCase): - def setUp(self): - self.patient, self.episode = self.new_patient_and_episode_please() - self.colour = Colour.objects.create( - name="blue", episode=self.episode - ) - self.eid_str = str(self.episode.id) - self.pid_str = str(self.patient.id) - - def test_get_row(self, field_names_to_extract): - field_names_to_extract.return_value = [ - "id", "episode_id", "name", "consistency_token" - ] - renderer = extract.EpisodeSubrecordCsvRenderer( - Colour, models.Episode.objects.all(), self.user - ) - rendered = renderer.get_row(self.colour) - expected = [str(self.colour.id), self.pid_str, self.eid_str, "blue"] - self.assertEqual(expected, rendered) - - -@patch('opal.core.search.extract.subrecords') -class GetDataDictionaryTestCase(OpalTestCase): - def test_excludes_from_data_dictionary(self, subrecords): - subrecords.return_value = [Colour] - result = extract.get_data_dictionary() - result.pop('Episode') - # without result we should be empty - self.assertFalse(bool(result)) - - def test_episode_data_dictionary(self, subrecords): - subrecords.return_value = [] - dd = extract.get_data_dictionary() - episode = dd.pop('Episode') - start = next(i for i in episode if i["display_name"] == 'Start') - self.assertEqual( - start['type_display_name'], 'Date' - ) - - def test_subrecord_data_dictionary(self, subrecords): - subrecords.return_value = [HatWearer] - dd = extract.get_data_dictionary() - hat_wearer = dd.pop(HatWearer.get_display_name()) - hats = next(i for i in hat_wearer if i["display_name"] == 'Hats') - self.assertEqual( - hats['type_display_name'], 'Some of the Hats' - ) - wearing_a_hat = next( - i for i in hat_wearer if i["display_name"] == 'Wearing A Hat' - ) - self.assertEqual( - wearing_a_hat['type_display_name'], 'Either True or False' - ) diff --git a/opal/core/search/tests/test_search_query.py b/opal/core/search/tests/test_search_query.py deleted file mode 100644 index 5441b5fb3..000000000 --- a/opal/core/search/tests/test_search_query.py +++ /dev/null @@ -1,796 +0,0 @@ -""" -Unittests for opal.core.search.queries -""" -from datetime import date - -from django.db import transaction -from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType -from unittest.mock import patch, MagicMock -from reversion import revisions as reversion -from opal.tests.episodes import RestrictedEpisodeCategory - -from opal.core.search.search_rule import SearchRule -from opal.models import Synonym, Gender - -from opal.core.test import OpalTestCase - -from opal.core.search import queries - -from opal.tests import models as testmodels - - -# don't remove this, we use it to discover the restricted episode category -from opal.tests.episodes import RestrictedEpisodeCategory # NOQA - - -class PatientSummaryTestCase(OpalTestCase): - - def test_update_sets_start(self): - patient, episode = self.new_patient_and_episode_please() - summary = queries.PatientSummary(episode) - self.assertEqual(None, summary.start) - the_date = date(day=27, month=1, year=1972) - episode2 = patient.create_episode(start=the_date) - summary.update(episode2) - self.assertEqual(summary.start, the_date) - - def test_update_sets_end(self): - patient, episode = self.new_patient_and_episode_please() - summary = queries.PatientSummary(episode) - self.assertEqual(None, summary.start) - the_date = date(day=27, month=1, year=1972) - episode2 = patient.create_episode(end=the_date) - summary.update(episode2) - self.assertEqual(summary.end, the_date) - - -class QueryBackendTestCase(OpalTestCase): - - def test_fuzzy_query(self): - with self.assertRaises(NotImplementedError): - queries.QueryBackend(self.user, 'aquery').fuzzy_query() - - def test_get_episodes(self): - with self.assertRaises(NotImplementedError): - queries.QueryBackend(self.user, 'aquery').get_episodes() - - def test_description(self): - with self.assertRaises(NotImplementedError): - queries.QueryBackend(self.user, 'aquery').description() - - def test_get_patients(self): - with self.assertRaises(NotImplementedError): - queries.QueryBackend(self.user, 'aquery').get_patients() - - def test_get_patient_summaries(self): - with self.assertRaises(NotImplementedError): - queries.QueryBackend(self.user, 'aquery').get_patient_summaries() - - -class DatabaseQueryTestCase(OpalTestCase): - DATE_OF_BIRTH = date(day=27, month=1, year=1977) - DATE_OF_EPISODE = date(day=1, month=2, year=2015) - - def setUp(self): - self.patient, self.episode = self.new_patient_and_episode_please() - self.episode.date_of_episode = self.DATE_OF_EPISODE - self.episode.start = self.DATE_OF_EPISODE - self.episode.end = self.DATE_OF_EPISODE - self.episode.save() - self.demographics = self.patient.demographics() - self.demographics.first_name = "Sally" - self.demographics.surname = "Stevens" - self.demographics.sex = "Female" - self.demographics.hospital_number = "0" - self.demographics.date_of_birth = self.DATE_OF_BIRTH - self.demographics.save() - - self.name_criteria = [ - { - u'column': u'demographics', - u'field': u'Surname', - u'combine': u'and', - u'query': u'Stevens', - u'queryType': u'Equals' - } - ] - - def test_episodes_for_boolean_fields(self): - criteria = dict( - column='demographics', field='Death Indicator', - combine='and', query='false', queryType='Equals' - ) - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_number_fields_greater_than(self): - testmodels.FavouriteNumber.objects.create( - patient=self.patient, number=10 - ) - criteria = dict( - column='favourite_number', - field='number', - combine='and', - query=1, - queryType='Greater Than' - ) - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - criteria["query"] = 100 - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([], query.get_episodes()) - - def test_episodes_for_number_fields_less_than(self): - testmodels.FavouriteNumber.objects.create( - patient=self.patient, number=10 - ) - criteria = dict( - column='favourite_number', - field='number', - combine='and', - query=11, - queryType='Less Than' - ) - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - criteria["query"] = 1 - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([], query.get_episodes()) - - def test_episodes_for_boolean_fields_episode_subrecord(self): - criteria = dict( - column='hat_wearer', field='Wearing A Hat', - combine='and', query='true', queryType='Equals' - ) - hatwearer = testmodels.HatWearer( - episode=self.episode, wearing_a_hat=True - ) - hatwearer.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_date_fields(self): - criteria = dict( - column='dog_owner', field='Ownership Start Date', - combine='and', query='1/12/1999', queryType='Equals' - ) - dogowner = testmodels.DogOwner( - episode=self.episode, ownership_start_date=date(1999, 12, 1)) - dogowner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_date_fields_patient_subrecord(self): - criteria = dict( - column='birthday', field='Birth Date', - combine='and', query='1/12/1999', queryType='Equals' - ) - birthday = testmodels.Birthday( - patient=self.patient, birth_date=date(1999, 12, 1)) - birthday.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_date_fields_before(self): - criteria = dict( - column='dog_owner', field='Ownership Start Date', - combine='and', query='1/12/2000', queryType='Before' - ) - dogowner = testmodels.DogOwner( - episode=self.episode, ownership_start_date=date(1999, 12, 1)) - dogowner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_date_fields_equals(self): - criteria = dict( - column='dog_owner', field='Ownership Start Date', - combine='and', query='2/12/2000', queryType='Equals' - ) - # should be ignored because its too early - _, other_episode = self.new_patient_and_episode_please() - testmodels.DogOwner.objects.create( - episode=other_episode, ownership_start_date=date(2000, 12, 1) - ) - - # the expected episode - testmodels.DogOwner.objects.create( - episode=self.episode, ownership_start_date=date(2000, 12, 2) - ) - - # should be ignored because its too late - _, other_episode_2 = self.new_patient_and_episode_please() - testmodels.DogOwner.objects.create( - episode=other_episode_2, ownership_start_date=date(2000, 12, 3) - ) - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_date_fields_after(self): - criteria = dict( - column='dog_owner', field='Ownership Start Date', - combine='and', query='1/12/1998', queryType='After' - ) - dogowner = testmodels.DogOwner( - episode=self.episode, ownership_start_date=date(1999, 12, 1)) - dogowner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_m2m_fields(self): - criteria = dict( - column='hat_wearer', field='Hats', - combine='and', query='Bowler', queryType='Equals' - ) - - bowler = testmodels.Hat(name='Bowler') - bowler.save() - - hatwearer = testmodels.HatWearer(episode=self.episode) - hatwearer.save() - hatwearer.hats.add(bowler) - hatwearer.save() - - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_m2m_fields_equals_with_synonyms(self): - criteria = dict( - column='hat_wearer', field='Hats', - combine='and', query='Derby', queryType='Equals' - ) - - bowler = testmodels.Hat.objects.create(name='Bowler') - content_type = ContentType.objects.get_for_model(testmodels.Hat) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=bowler.id, - name="Derby" - ) - - hatwearer = testmodels.HatWearer(episode=self.episode) - hatwearer.save() - hatwearer.hats.add(bowler) - hatwearer.save() - - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_m2m_fields_contains_synonym_and_name(self): - criteria = dict( - column='hat_wearer', field='Hats', - combine='and', query='Der', queryType='Contains' - ) - - bowler = testmodels.Hat.objects.create(name='Bowler') - content_type = ContentType.objects.get_for_model(testmodels.Hat) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=bowler.id, - name="Derby" - ) - - hatwearer = testmodels.HatWearer(episode=self.episode) - hatwearer.save() - hatwearer.hats.add(bowler) - hatwearer.save() - - # now we add another episode with an actual hat - derbishire = testmodels.Hat.objects.create(name='derbishire') - _, other_episode = self.new_patient_and_episode_please() - - hatwearer = testmodels.HatWearer(episode=other_episode) - hatwearer.save() - hatwearer.hats.add(derbishire) - hatwearer.save() - - query = queries.DatabaseQuery(self.user, [criteria]) - expected = set([self.episode.id, other_episode.id]) - found = set([i.id for i in query.get_episodes()]) - self.assertEqual(expected, found) - - def test_fuzzy_query(self): - """ It should return the patients that - match the criteria ordered in by - their related episode id descending - """ - patient_1, episode_1 = self.new_patient_and_episode_please() - patient_2, episode_2 = self.new_patient_and_episode_please() - patient_3, episode_3 = self.new_patient_and_episode_please() - testmodels.Demographics.objects.filter( - patient__in=[patient_1, patient_2, patient_3] - ).update( - first_name="tree" - ) - patient_2.create_episode() - # this patient, episode should not be found - self.new_patient_and_episode_please() - query = queries.DatabaseQuery(self.user, "tree") - patients = query.fuzzy_query() - - # expectation is that patient 2 comes last as - # they have the most recent episode - self.assertEqual( - list(patients), - [patient_2, patient_3, patient_1] - ) - - def test_distinct_episodes_for_m2m_fields_containing_synonsyms_and_names( - self - ): - criteria = dict( - column='hat_wearer', field='Hats', - combine='and', query='Der', queryType='Contains' - ) - - bowler = testmodels.Hat.objects.create(name='Bowler') - content_type = ContentType.objects.get_for_model(testmodels.Hat) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=bowler.id, - name="Derby" - ) - - hatwearer = testmodels.HatWearer(episode=self.episode) - hatwearer.save() - hatwearer.hats.add(bowler) - hatwearer.save() - - derbishire = testmodels.Hat.objects.create(name='derbishire') - hatwearer.hats.add(derbishire) - hatwearer.save() - - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_m2m_fields_patient_subrecord(self): - criteria = dict( - column='favourite_dogs', field='Dogs', - combine='and', query='Dalmation', queryType='Equals' - ) - - dalmation = testmodels.Dog(name='Dalmation') - dalmation.save() - - favouritedogs = testmodels.FavouriteDogs.objects.create( - patient=self.patient - ) - - favouritedogs.dogs.add(dalmation) - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_fkorft_fields_for_patient_subrecord(self): - criteria = dict( - column='demographics', field='sex', - combine='and', query='Unknown', queryType='Equals' - ) - unknown = Gender(name='Unknown') - unknown.save() - demographics = self.patient.demographics() - demographics.sex = 'Unknown' - demographics.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_fkorft_fields_for_patient_subrecord_with_multiple_episodes(self): - criteria = dict( - column='demographics', field='sex', - combine='and', query='Unknown', queryType='Equals' - ) - unknown = Gender(name='Unknown') - unknown.save() - demographics = self.patient.demographics() - demographics.sex = 'Unknown' - demographics.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_for_fkorft_fields_exact_episode_subrecord(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='Dalmation', queryType='Equals' - ) - - dalmation = testmodels.Dog(name='Dalmation') - dalmation.save() - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_for_exact_fkorft_synonym(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='Dalmation', queryType='Equals' - ) - - spotted_dog = testmodels.Hat.objects.create(name='Spotted Dog') - content_type = ContentType.objects.get_for_model(testmodels.Hat) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=spotted_dog.id, - name="Dalmation" - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_for_exact_fkorft_free_text(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dalmation', queryType='Equals' - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_for_fkorft_fields_contains_episode_subrecord(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dal', queryType='Contains' - ) - - dalmation = testmodels.Dog(name='Dalmation') - dalmation.save() - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_fkorft_for_contains_synonym(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dal', queryType='Contains' - ) - - spotted_dog = testmodels.Dog.objects.create(name='Spotted Dog') - content_type = ContentType.objects.get_for_model(testmodels.Dog) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=spotted_dog.id, - name="Dalmation" - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_fkorft_for_contains_ft(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dal', queryType='Contains' - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episode_fkorft_for_contains_synonym_name_and_ft(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dal', queryType='Contains' - ) - - spotted_dog = testmodels.Dog.objects.create(name='Spotted Dog') - content_type = ContentType.objects.get_for_model(testmodels.Dog) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=spotted_dog.id, - name="Dalmation" - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - - _, episode_2 = self.new_patient_and_episode_please() - hound_owner = testmodels.HoundOwner.objects.create( - episode=episode_2 - ) - hound_owner.dog = "Dalwinion" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - self.assertEqual([self.episode, episode_2], query.get_episodes()) - - def test_episode_fkorft_contains_distinct(self): - criteria = dict( - column='hound_owner', field='dog', - combine='and', query='dal', queryType='Contains' - ) - - spotted_dog = testmodels.Dog.objects.create(name='Spotted Dog') - content_type = ContentType.objects.get_for_model(testmodels.Dog) - Synonym.objects.get_or_create( - content_type=content_type, - object_id=spotted_dog.id, - name="Dalmation" - ) - - hound_owner = testmodels.HoundOwner.objects.create( - episode=self.episode - ) - hound_owner.dog = "Dalmation" - hound_owner.save() - episode_2 = self.patient.create_episode() - - hound_owner = testmodels.HoundOwner.objects.create( - episode=episode_2 - ) - hound_owner.dog = "Dalwinion" - hound_owner.save() - query = queries.DatabaseQuery(self.user, [criteria]) - expected = set([self.episode.id, episode_2.id]) - found = set(i.id for i in query.get_episodes()) - self.assertEqual(expected, found) - - def test_episodes_for_criteria_episode_subrecord_string_field(self): - criteria = [ - { - u'column': u'hat_wearer', - u'field': u'Name', - u'combine': u'and', - u'query': u'Bowler', - u'queryType': u'Equals' - } - ] - query = queries.DatabaseQuery(self.user, criteria) - res = query.episodes_for_criteria(criteria[0]) - self.assertEqual([], list(res)) - - def test_episodes_for_criteria_search_rule_used(self): - criteria = [ - { - u'column': u'hat_wearer', - u'field': u'Name', - u'combine': u'and', - u'query': u'Bowler', - u'queryType': u'Equals' - } - ] - - class HatWearerQuery(object): - def query(self, given_query): - pass - - with patch.object(SearchRule, "get") as search_rule_get: - with patch.object(HatWearerQuery, "query") as hat_wearer_query: - search_rule_get.return_value = HatWearerQuery - query = queries.DatabaseQuery(self.user, criteria) - query.episodes_for_criteria(criteria[0]) - search_rule_get.assert_called_once_with("hat_wearer") - hat_wearer_query.assert_called_once_with(criteria[0]) - - def test_episodes_without_restrictions_no_matches(self): - query = queries.DatabaseQuery(self.user, self.name_criteria) - query.query = [] - result = query._episodes_without_restrictions() - self.assertEqual([], result) - - def test_episodes_without_restrictions(self): - query = queries.DatabaseQuery(self.user, self.name_criteria) - result = query._episodes_without_restrictions() - self.assertEqual(self.episode, list(result)[0]) - - def test_filter_restricted_only_user(self): - self.user.profile.restricted_only = True - self.user.profile.save() - self.patient.create_episode(category_name='Inpatient') - query = queries.DatabaseQuery(self.user, self.name_criteria) - self.assertEqual([], query.get_episodes()) - - def test_filter_in_restricted_episode_types(self): - self.user.profile.restricted_only = True - self.user.profile.save() - episode2 = self.patient.create_episode(category_name='Restricted') - self.assertEqual('Restricted', episode2.category_name) - - query = queries.DatabaseQuery(self.user, self.name_criteria) - self.assertEqual([episode2], query.get_episodes()) - - def test_get_old_episode(self): - # episode's with old tags that have subsequently been removed - # should still be qiried - - team_query = [dict( - column="tagging", - field='other_team', - combine='and', - query=None, - lookup_list=[], - queryType=None - )] - - with transaction.atomic(), reversion.create_revision(): - other_episode = self.patient.create_episode() - other_episode.set_tag_names(['other_team'], self.user) - query = queries.DatabaseQuery(self.user, team_query) - - self.assertEqual([other_episode], query.get_episodes()) - - with transaction.atomic(), reversion.create_revision(): - other_episode.set_tag_names([], self.user) - - self.assertEqual([other_episode], query.get_episodes()) - - def test_gets_mine_only(self): - # an episode tagged with 'mine' should return - # only episodes that I have tagged with mine - team_query = [dict( - column="tagging", - field='mine', - combine='and', - query=None, - lookup_list=[], - queryType=None - )] - - other_user = User.objects.create(username="other") - _, other_users_episode = self.new_patient_and_episode_please() - - with transaction.atomic(), reversion.create_revision(): - episode = self.patient.create_episode() - episode.set_tag_names(['mine'], self.user) - other_users_episode.set_tag_names(['mine'], other_user) - query = queries.DatabaseQuery(self.user, team_query) - - self.assertEqual([episode], query.get_episodes()) - - with transaction.atomic(), reversion.create_revision(): - episode.set_tag_names([], self.user) - - self.assertEqual([episode], query.get_episodes()) - - def test_get_episodes(self): - query = queries.DatabaseQuery(self.user, self.name_criteria) - self.assertEqual([self.episode], query.get_episodes()) - - def test_get_episodes_multi_query(self): - criteria = [ - { - u'column': u'demographics', - u'field': u'Sex', - u'combine': u'and', - u'query': u'Female', - u'queryType': u'Equals' - }, - self.name_criteria[0] - ] - query = queries.DatabaseQuery(self.user, criteria) - self.assertEqual([self.episode], query.get_episodes()) - - def test_get_episodes_searching_ft_or_fk_field(self): - criteria = [ - { - u'column': u'demographics', - u'field': u'Sex', - u'combine': u'and', - u'query': u'Female', - u'queryType': u'Equals' - } - ] - query = queries.DatabaseQuery(self.user, criteria) - self.assertEqual([self.episode], query.get_episodes()) - - def test_episodes_searching_fk_or_ft_fields_with_synonym_values(self): - criteria = [ - { - u'column': u'demographics', - u'field': u'Sex', - u'combine': u'and', - u'query': u'F', - u'queryType': u'Equals' - } - ] - female = Gender.objects.create(name="Female") - ct = ContentType.objects.get_for_model(Gender) - Synonym.objects.create(content_type=ct, name="F", object_id=female.id) - demographics = self.patient.demographics() - demographics.sex = "F" - demographics.save() - self.assertEqual("Female", demographics.sex) - query = queries.DatabaseQuery(self.user, criteria) - self.assertEqual([self.episode], query.get_episodes()) - - def test_get_episodes_searching_episode_subrecord_ft_or_fk_fields(self): - criteria = [ - { - u'column': u'dog_owner', - u'field': u'Dog', - u'combine': u'and', - u'query': u'Terrier', - u'queryType': u'Equals' - } - ] - dog_owner = testmodels.DogOwner.objects.create(episode=self.episode) - dog_owner.dog = 'Terrier' - dog_owner.save() - query = queries.DatabaseQuery(self.user, criteria) - self.assertEqual([self.episode], query.get_episodes()) - - def test_get_patient_summaries(self): - query = queries.DatabaseQuery(self.user, self.name_criteria) - summaries = query.get_patient_summaries() - expected = [{ - 'count': 1, - 'hospital_number': u'0', - 'date_of_birth': self.DATE_OF_BIRTH, - 'first_name': u'Sally', - 'surname': u'Stevens', - 'end': self.DATE_OF_EPISODE, - 'start': self.DATE_OF_EPISODE, - 'patient_id': self.patient.id, - 'categories': [u'Inpatient'] - }] - self.assertEqual(expected, summaries) - - def test_update_patient_summaries(self): - """ with a patient with multiple episodes - we expect it to aggregate these into summaries - """ - start_date = date(day=1, month=2, year=2014) - self.patient.create_episode( - start=start_date - ) - end_date = date(day=1, month=2, year=2016) - self.patient.create_episode( - end=end_date - ) - query = queries.DatabaseQuery(self.user, self.name_criteria) - summaries = query.get_patient_summaries() - expected = [{ - 'count': 3, - 'hospital_number': u'0', - 'date_of_birth': self.DATE_OF_BIRTH, - 'first_name': u'Sally', - 'surname': u'Stevens', - 'end': end_date, - 'start': start_date, - 'patient_id': self.patient.id, - 'categories': [u'Inpatient'] - }] - self.assertEqual(expected, summaries) - - -class CreateQueryTestCase(OpalTestCase): - - @patch('opal.core.search.queries.stringport') - def test_from_settings(self, stringport): - mock_backend = MagicMock('Mock Backend') - stringport.return_value = mock_backend - - with self.settings(OPAL_SEARCH_BACKEND='mybackend'): - backend = queries.create_query(self.user, []) - self.assertEqual(mock_backend.return_value, backend) - mock_backend.assert_called_with(self.user, []) diff --git a/opal/core/search/tests/test_search_rule.py b/opal/core/search/tests/test_search_rule.py deleted file mode 100644 index 90a8699a7..000000000 --- a/opal/core/search/tests/test_search_rule.py +++ /dev/null @@ -1,212 +0,0 @@ -from unittest.mock import MagicMock, patch -import datetime - -from opal.core.test import OpalTestCase -from opal.core.search import search_rule - - -class SearchRuleFieldTestCase(OpalTestCase): - def setUp(self, *args, **kwargs): - class SomeSearchRuleField(search_rule.SearchRuleField): - lookup_list = "some_list" - enum = [1, 2, 3] - description = "its a custom field" - display_name = "custom field you know" - field_type = "string" - slug = "some_slug" - self.custom_field = SomeSearchRuleField() - super(SearchRuleFieldTestCase, self).setUp(*args, **kwargs) - - def test_slug_if_slug_provided(self): - self.assertEqual(self.custom_field.get_slug(), "some_slug") - - def test_slug_no_display_name(self): - class SomeSearchRuleField(search_rule.SearchRuleField): - lookup_list = "some_list" - enum = [1, 2, 3] - description = "its a custom field" - field_type = "string" - custom_field = SomeSearchRuleField() - with self.assertRaises(ValueError) as v: - custom_field.get_slug() - - self.assertTrue( - "Must set display_name for" in str(v.exception) - ) - - def test_slug_if_slug_not_provided(self): - class SomeOtherSearchRuleField(search_rule.SearchRuleField): - lookuplist = "some_list" - enum = [1, 2, 3] - description = "its a custom field" - display_name = "custom field you know" - self.assertEqual( - SomeOtherSearchRuleField().get_slug(), "customfieldyouknow" - ) - - def test_query(self): - with self.assertRaises(NotImplementedError) as nie: - self.custom_field.query("some query") - - self.assertEqual("please implement a query", str(nie.exception)) - - def test_to_dict(self): - expected = dict( - lookup_list="some_list", - enum=[1, 2, 3], - description="its a custom field", - name="some_slug", - title='custom field you know', - type="string" - ) - self.assertEqual( - self.custom_field.to_dict(), - expected - ) - - -class SearchRuleTestCase(OpalTestCase): - def test_get(self): - some_mock_query = MagicMock() - with patch.object(search_rule.SearchRule, "list") as get_list: - get_list.return_value = [some_mock_query] - some_mock_query.get_slug.return_value = "tree" - result = search_rule.SearchRule.get("tree") - self.assertEqual(result, some_mock_query) - - def test_get_if_missing(self): - some_mock_query = MagicMock() - with patch.object(search_rule.SearchRule, "list") as get_list: - get_list.return_value = [some_mock_query] - some_mock_query.get_slug.return_value = "tree" - result = search_rule.SearchRule.get("onion") - self.assertIsNone(result) - - def test_get_fields(self): - self.assertEqual(search_rule.SearchRule().get_fields(), []) - - def test_query(self): - some_mock_query = MagicMock() - - with patch.object( - search_rule.SearchRule, "get_fields" - ) as get_fields: - get_fields.return_value = [some_mock_query] - some_mock_query.get_slug.return_value = "tree" - some_mock_query().query.return_value = "some_result" - query = dict(field="tree") - result = search_rule.SearchRule().query(query) - self.assertEqual(result, "some_result") - some_mock_query().query.assert_called_once_with(query) - - -class EpisodeQueryTestCase(OpalTestCase): - def setUp(self, *args, **kwargs): - super(EpisodeQueryTestCase, self).setUp(*args, **kwargs) - _, self.episode = self.new_patient_and_episode_please() - self.episode.start = datetime.date(2017, 1, 1) - self.episode.end = datetime.date(2017, 1, 5) - self.episode.save() - self.episode_query = search_rule.EpisodeQuery() - - def test_episode_end_start(self): - query_end = dict( - queryType="Before", - query="1/8/2017", - field="end" - ) - self.assertEqual( - list(self.episode_query.query(query_end))[0], self.episode - ) - - def test_episode_end_when_none(self): - self.episode.end = None - self.episode.save() - query_end = dict( - queryType="Before", - query="1/8/2017", - field="end" - ) - self.assertEqual( - list(self.episode_query.query(query_end)), [] - ) - - def test_episode_end_after(self): - query_end = dict( - queryType="After", - query="1/8/2015", - field="end" - ) - self.assertEqual( - list(self.episode_query.query(query_end))[0], self.episode - ) - - def test_episode_end_not_found(self): - query_end = dict( - queryType="Before", - query="1/8/2010", - field="end" - ) - self.assertEqual( - list(self.episode_query.query(query_end)), [] - ) - - def test_episode_end_wrong_query_param(self): - query_end = dict( - queryType="asdfsadf", - query="1/8/2010", - field="end" - ) - with self.assertRaises(search_rule.SearchException): - self.episode_query.query(query_end) - - def test_episode_start_before(self): - query_end = dict( - queryType="Before", - query="1/8/2017", - field="start" - ) - self.assertEqual( - list(self.episode_query.query(query_end))[0], self.episode - ) - - def test_episode_start_after(self): - query_end = dict( - queryType="After", - query="1/8/2015", - field="start" - ) - self.assertEqual( - list(self.episode_query.query(query_end))[0], self.episode - ) - - def test_episode_start_when_none(self): - self.episode.start = None - self.episode.save() - query_end = dict( - queryType="Before", - query="1/8/2017", - field="start" - ) - self.assertEqual( - list(self.episode_query.query(query_end)), [] - ) - - def test_episode_start_not_found(self): - query_end = dict( - queryType="Before", - query="1/8/2011", - field="start" - ) - self.assertEqual( - list(self.episode_query.query(query_end)), [] - ) - - def test_episode_start_wrong_query_param(self): - query_end = dict( - queryType="asdfsadf", - query="1/8/2010", - field="start" - ) - with self.assertRaises(search_rule.SearchException): - self.episode_query.query(query_end) diff --git a/opal/core/search/tests/test_tasks.py b/opal/core/search/tests/test_tasks.py deleted file mode 100644 index 61b173add..000000000 --- a/opal/core/search/tests/test_tasks.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Unittests for the opal.core.search.tasks module -""" -from unittest.mock import patch -from opal.core.test import OpalTestCase - -from opal.core.search import tasks - - -class ExtractTestCase(OpalTestCase): - - @patch('opal.core.search.extract.zip_archive') - def test_extract(self, zip_archive): - zip_archive.return_value = 'Help' - criteria = [ - { - u'column': u'demographics', - u'field': u'surname', - u'combine': u'and', - u'query': u'Stevens', - u'queryType': u'Equals' - } - ] - fname = tasks.extract(self.user.id, criteria) - self.assertEqual('Help', fname) diff --git a/opal/core/search/tests/test_views.py b/opal/core/search/tests/test_views.py deleted file mode 100644 index 72fa4e959..000000000 --- a/opal/core/search/tests/test_views.py +++ /dev/null @@ -1,501 +0,0 @@ -""" -unittests for opal.core.search.views -""" -import json -from datetime import date - -from django.core.serializers.json import DjangoJSONEncoder -from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import PermissionDenied -from unittest.mock import patch, mock_open - -from opal import models -from opal.tests import models as tmodels -from opal.core.test import OpalTestCase -from opal.core.search import views - - -class BaseSearchTestCase(OpalTestCase): - - def create_patient(self, first_name, last_name, hospital_number): - patient, episode = self.new_patient_and_episode_please() - demographics = patient.demographics() - demographics.first_name = first_name - demographics.surname = last_name - demographics.hospital_number = hospital_number - demographics.save() - return patient, episode - - def setUp(self): - self.patient, self.episode = self.create_patient( - "Sean", "Connery", "007" - ) - - def get_logged_in_request(self, url=None): - if url is None: - url = "/" - request = self.rf.get(url) - request.user = self.user - return request - - def get_not_logged_in_request(self, url=None): - if url is None: - url = "/" - request = self.rf.get(url) - request.user = AnonymousUser() - return request - - def get_response(self, url=None, view=None): - request = self.get_logged_in_request(url) - if view is None: - view = self.view - return self.view(request) - - def tearDown(self): - self.patient.delete() - super(BaseSearchTestCase, self).tearDown() - - -class PatientSearchTestCase(BaseSearchTestCase): - - def setUp(self): - self.url = '/search/patient/' - self.view = views.patient_search_view - super(PatientSearchTestCase, self).setUp() - - def test_not_logged_in(self): - request = self.get_not_logged_in_request() - with self.assertRaises(PermissionDenied): - self.view(request) - - # Searching for a patient that doesn't exist by Hospital Number - def test_patient_does_not_exist_number(self): - url = '%s?hospital_number=notareanumber' % self.url - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual([], data) - - # Searching for a patient that exists by Hospital Number - def test_patient_exists_number(self): - url = '/search/patient/?hospital_number=007' - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - expected = [self.patient.to_dict(self.user)] - - expected = json.loads(json.dumps(expected, cls=DjangoJSONEncoder)) - self.assertEqual(expected, data) - - def test_patient_number_with_hash(self): - demographics = self.patient.demographics() - demographics.hospital_number = "#007" - demographics.save() - url = '/search/patient/?hospital_number=%23007' - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - expected = [self.patient.to_dict(self.user)] - expected = json.loads(json.dumps(expected, cls=DjangoJSONEncoder)) - self.assertEqual(expected, data) - - def test_patient_number_with_slash(self): - demographics = self.patient.demographics() - demographics.hospital_number = "/007" - demographics.save() - url = '/search/patient/?hospital_number=%2F007' - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - expected = [self.patient.to_dict(self.user)] - expected = json.loads(json.dumps(expected, cls=DjangoJSONEncoder)) - self.assertEqual(expected, data) - - def test_patient_number_with_question_mark(self): - demographics = self.patient.demographics() - demographics.hospital_number = "?007" - demographics.save() - url = '/search/patient/?hospital_number=%3F007' - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - expected = [self.patient.to_dict(self.user)] - expected = json.loads(json.dumps(expected, cls=DjangoJSONEncoder)) - self.assertEqual(expected, data) - - def test_patient_number_with_ampersand(self): - demographics = self.patient.demographics() - demographics.hospital_number = "&007" - demographics.save() - url = '/search/patient/?hospital_number=%26007' - resp = self.get_response(url) - data = json.loads(resp.content.decode('UTF-8')) - expected = [self.patient.to_dict(self.user)] - expected = json.loads(json.dumps(expected, cls=DjangoJSONEncoder)) - self.assertEqual(expected, data) - - - # TODO: - # Searching for a patient that exists but only has episodes that are - # restricted teams that the user is not a member of. - - def test_must_provide_hospital_number(self): - url = "/search/patient/" - resp = self.get_response(url) - self.assertEqual(400, resp.status_code) - - -class SimpleSearchViewTestCase(BaseSearchTestCase): - maxDiff = None - - def setUp(self): - super(SimpleSearchViewTestCase, self).setUp() - self.url = '/search/simple/' - self.view = views.simple_search_view - self.expected = { - u'page_number': 1, - u'object_list': [{ - u'count': 1, - u'first_name': u'Sean', - u'surname': u'Connery', - u'end': u'15/10/2015', - u'patient_id': self.patient.id, - u'hospital_number': u'007', - u'date_of_birth': None, - u'start': u'15/10/2015', - u'categories': [u'Inpatient'] - }], - u'total_count': 1, - u'total_pages': 1, - } - - self.empty_expected = { - "page_number": 1, - "object_list": [], - "total_pages": 1, - "total_count": 0 - } - dt = date( - day=15, month=10, year=2015 - ) - self.episode.date_of_episode = dt - self.episode.start = dt - self.episode.end = dt - self.episode.save() - - def test_not_logged_in(self): - request = self.get_not_logged_in_request() - with self.assertRaises(PermissionDenied): - self.view(request) - - def test_must_provide_name_or_hospital_number(self): - resp = self.get_response(self.url) - self.assertEqual(400, resp.status_code) - - # Searching for a patient that exists by partial name match - def test_patient_exists_partial_name(self): - resp = self.get_response("%s?query=Co" % self.url) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.expected, data) - - # Searching for a patient that exists by partial HN match - def test_patient_exists_partial_number(self): - resp = self.get_response('%s?query=07' % self.url) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.expected, data) - - # Searching for a patient that exists by name - def test_patient_exists_name(self): - resp = self.get_response('%s?query=Connery' % self.url) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.expected, data) - - # Searching for a patient that doesn't exist by Hospital Number - def test_patient_does_not_exist_number(self): - resp = self.get_response('%s?query=notareanumber' % self.url) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.empty_expected, data) - - # Searching for a patient that doesn't exist by name - def test_patient_does_not_exist_name(self): - request = self.rf.get('%s/?query=notareaname' % self.url) - request.user = self.user - resp = self.view(request) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.empty_expected, data) - - # Searching for a patient that exists by Hospital Number - def test_patient_exists_number(self): - request = self.rf.get('%s/?query=007' % self.url) - request.user = self.user - resp = self.view(request) - data = json.loads(resp.content.decode('UTF-8')) - self.assertEqual(self.expected, data) - - # searching by James Bond should only yield James Bond - def test_incomplete_matching(self): - james_patient, sam_episode = self.create_patient( - "James", "Bond", "23412" - ) - sam_patient, sam_episode = self.create_patient( - "Samantha", "Bond", "23432" - ) - blofeld_patient, blofeld_episode = self.create_patient( - "Ernst", "Blofeld", "23422" - ) - resp = self.get_response('{}/?query=James%20Bond'.format(self.url)) - data = json.loads(resp.content.decode('UTF-8'))["object_list"] - self.assertEqual(len(data), 1) - self.assertEqual(data[0]["first_name"], "James") - self.assertEqual(data[0]["surname"], "Bond") - - def test_number_of_queries(self): - """ Pagination should make sure we - do the same number of queries - despite the number of results. - """ - # we need to make sure we're all logged in before we start - self.assertIsNotNone(self.user) - for i in range(100): - self.create_patient( - "James", "Bond", str(i) - ) - - with self.assertNumQueries(26): - self.get_response('{}/?query=Bond'.format(self.url)) - - for i in range(20): - self.create_patient( - "James", "Blofelt", str(i) - ) - - with self.assertNumQueries(26): - self.get_response('{}/?query=Blofelt'.format(self.url)) - - def test_with_multiple_patient_episodes(self): - self.patient.create_episode() - blofeld_patient, blofeld_episode = self.create_patient( - "Ernst", "Blofeld", "23422" - ) - response = json.loads( - self.get_response( - '{}/?query=Blofeld'.format(self.url) - ).content.decode('UTF-8') - ) - expected = { - "total_pages": 1, - "object_list": [{ - "count": 1, - "first_name": "Ernst", - "surname": "Blofeld", - "start": None, - "patient_id": blofeld_patient.id, - "hospital_number": "23422", - "date_of_birth": None, - "end": None, - "categories": ["Inpatient"] - }], - "page_number": 1, - "total_count": 1 - } - self.assertEqual(response, expected) - - @patch('opal.core.search.views.PAGINATION_AMOUNT', 2) - def test_patients_more_than_pagination_amount(self): - """ - Prior to 0.16.0 the implementation would sometimes - return results with fewer than the number of results - that should have been on that page. - - This is because a subquery was paginating by the - number of episodes rather than the number of - patients. - """ - blofeld_patient, blofeld_episode = self.create_patient( - "Ernst", "Blofeld", "23422" - ) - blofeld_patient.create_episode() - - response = json.loads( - self.get_response( - '{}/?query=o'.format(self.url) - ).content.decode('UTF-8') - ) - self.assertEqual(len(response["object_list"]), 2) - - -class SearchTemplateTestCase(OpalTestCase): - - def test_search_template_view(self): - self.assertStatusCode('/search/templates/search.html/', 200) - - -class ExtractSearchViewTestCase(BaseSearchTestCase): - - def test_not_logged_in_post(self): - view = views.ExtractSearchView() - view.request = self.get_not_logged_in_request() - with self.assertRaises(PermissionDenied): - view.post() - - def test_post(self): - data = json.dumps([ - { - u'page_number': 1, - u'column': u'demographics', - u'field': u'Surname', - u'combine': u'and', - u'query': u'Connery', - u'queryType': u'Equals' - } - ]) - request = self.rf.post('extract') - request.user = self.user - view = views.ExtractSearchView() - view.request = request - with patch.object(view.request, 'read') as mock_read: - mock_read.return_value = data - - resp = json.loads(view.post().content.decode('UTF-8')) - self.assertEqual(1, resp['total_count']) - self.assertEqual(self.patient.id, resp['object_list'][0]['patient_id']) - - def test_post_with_no_data(self): - data = json.dumps([]) - request = self.rf.post('extract') - request.user = self.user - view = views.ExtractSearchView() - view.request = request - with patch.object(view.request, 'read') as mock_read: - mock_read.return_value = data - resp = view.post() - self.assertEqual(resp.status_code, 400) - self.assertEqual(json.loads(resp.content.decode('UTF-8')), dict( - error="No search criteria provied" - )) - - -class FilterViewTestCase(BaseSearchTestCase): - - def test_not_logged_in_dispatch(self): - view = views.FilterView() - view.request = self.get_not_logged_in_request() - - with self.assertRaises(PermissionDenied): - view.dispatch() - - def test_logged_in_dispatch(self): - # we should error if we're logged in - view = views.FilterView() - request = self.get_logged_in_request() - view.request = self.get_logged_in_request() - response = view.dispatch(request) - self.assertEqual(response.status_code, 200) - - def test_get(self): - filter = models.Filter.objects.create( - user=self.user, name='testfilter', criteria='[]' - ) - self.assertEqual(1, models.Filter.objects.count()) - - view = views.FilterView() - view.request = self.rf.get('/filter') - view.request.user = self.user - - data = json.loads(view.get().content.decode('UTF-8')) - self.assertEqual( - [{'name': filter.name, 'criteria': [], 'id': filter.id}], data - ) - - def test_post(self): - view = views.FilterView() - view.request = self.rf.post('/filter') - view.request.user = self.user - with patch.object(view.request, 'read') as mock_read: - mock_read.return_value = '{"name": "posttestfilter", "criteria": "[]"}' - - self.assertEqual(0, models.Filter.objects.count()) - view.post() - self.assertEqual(1, models.Filter.objects.count()) - - -class FilterDetailViewTestCase(BaseSearchTestCase): - - def setUp(self): - super(FilterDetailViewTestCase, self).setUp() - self.filt = models.Filter(user=self.user, name='testfilter', criteria='[]') - self.filt.save() - - def test_get(self): - request = self.get_logged_in_request('/filter/1/') - data = json.loads(views.FilterDetailView.as_view()( - request, pk=self.filt.pk).content.decode('UTF-8')) - self.assertEqual({'name': 'testfilter', - 'criteria': [], - 'id': self.filt.id}, data) - - def test_filter_detail_no_filter(self): - view = views.FilterDetailView() - view.request = self.get_logged_in_request() - response = view.dispatch(pk=323) - self.assertEqual(404, response.status_code) - - def test_not_logged_in_dispatch(self): - view = views.FilterDetailView() - view.request = self.get_not_logged_in_request() - - with self.assertRaises(PermissionDenied): - view.dispatch() - - def test_put(self): - view = views.FilterDetailView() - view.request = self.get_logged_in_request() - with patch.object(view.request, "read") as criteria: - criteria.return_value = json.dumps( - {'criteria': [], 'name': 'My Name'} - ) - view.filter = self.filt - resp = view.put() - self.assertEqual(200, resp.status_code) - - def test_delete(self): - view = views.FilterDetailView() - view.request = self.get_logged_in_request() - view.filter = self.filt - view.delete() - self.assertEqual(0, models.Filter.objects.count()) - - -class ExtractResultViewTestCase(BaseSearchTestCase): - - @patch('celery.result.AsyncResult') - def test_get(self, async_result): - view = views.ExtractResultView() - view.request = self.get_logged_in_request() - async_result.return_value.state = 'The State' - resp = view.get(task_id=490) - self.assertEqual(200, resp.status_code) - - -class ExtractFileView(BaseSearchTestCase): - - def test_get_not_logged_in(self): - view = views.ExtractFileView() - view.request = self.get_not_logged_in_request() - with self.assertRaises(PermissionDenied): - resp = view.get(task_id=8902321890) - - @patch('celery.result.AsyncResult') - def test_get(self, async_result): - view = views.ExtractFileView() - view.request = self.get_logged_in_request() - async_result.return_value.state = 'SUCCESS' - async_result.return_value.get.return_value = 'foo.txt' - - m = mock_open(read_data='This is a file') - with patch('opal.core.search.views.open', m, create=True) as m: - resp = view.get(task_id=437878) - self.assertEqual(200, resp.status_code) - - @patch('celery.result.AsyncResult') - def test_get_not_successful(self, async_result): - view = views.ExtractFileView() - view.request = self.get_logged_in_request("/") - async_result.return_value.state = 'FAILURE' - with self.assertRaises(ValueError): - view.get(task_id=8902321890)