Repository: Health-Informatics-UoN/Carrot-Mapper

Issue:
💡 Combine Github release workflows
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Is your proposal related to a problem or functionality gap?

Currently the release workflow for dev/test/production is 3 separate Github actions, working with Gitflow. So merging a new fix requires 3 separate PRs, artifacts, and deployments: https://github.com/Health-Informatics-UoN/CaRROT-Mapper/pulls?q=is%3Apr+is%3Aclosed



### Describe your proposal

I propose combining this into one workflow, utilising Github environments to deploy a single artifact from the main branch. An example here: https://github.com/Health-Informatics-UoN/rcc-monitor/actions/runs/7931015704


### Describe alternatives you've considered

_No response_

### I'm part of a Project Team

Yes - DRS

### Anything else?

It might not be done here - but I think it's worth also exploring when database migrations happen as part of CD, not sure if this currently a manual process?

### Are you willing to contribute to developing this feature?

✅ Yes, me or my team intend to do the development.


Discussion:

Resolving patch:
diff --git a/app/api/api/settings.py b/app/api/api/settings.py
--- a/app/api/api/settings.py
+++ b/app/api/api/settings.py
@@ -11,6 +11,7 @@
 """
 
 import os
+
 from dotenv import load_dotenv
 
 load_dotenv()
@@ -59,6 +60,7 @@
     "django_filters",
     "rest_framework.authtoken",
     "corsheaders",
+    "test",
 ]
 
 MIDDLEWARE = [
@@ -110,8 +112,7 @@
         "USER": os.getenv("COCONNECT_DB_USER"),
         "PASSWORD": os.getenv("COCONNECT_DB_PASSWORD"),
         "TEST": {
-            "NAME": "throwaway-db",
-            "MIRROR": "default",
+            "NAME": "throwawaydb",
         },
     }
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,111 @@
+name: test
+
+on: [pull_request]
+
+jobs:
+  test-workers:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ['3.8', '3.11']
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install Poetry
+        uses: snok/install-poetry@v1
+
+      - name: Load cached venv
+        id: cached-poetry-dependencies
+        uses: actions/cache@v3
+        with:
+          path: .venv
+          key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+
+      - name: Install dependencies
+        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+        working-directory: app/workers
+        run: poetry install --no-interaction --no-root
+
+      - name: Test with pytest Poetry
+        working-directory: app/workers
+        run: |
+          poetry run pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=sources test | tee pytest-coverage.txt
+
+      - name: Pytest coverage comment
+        uses: MishaKav/pytest-coverage-comment@main
+        with:
+          pytest-coverage-path: app/workers/pytest-coverage.txt
+          junitxml-path: app/workers/pytest.xml
+
+  test-django:
+      runs-on: ubuntu-latest
+      strategy:
+        fail-fast: false
+        matrix:
+          python-version: ['3.8', '3.11']
+          django-version: [ '3.1.14', '4.2']
+
+      services:
+        postgres:
+          image: postgres
+          env:
+            POSTGRES_PASSWORD: postgres
+            POSTGRES_DB: test
+          options: >-
+            --health-cmd pg_isready
+            --health-interval 10s
+            --health-timeout 5s
+            --health-retries 5
+          ports:
+            - 5432:5432
+
+      steps:
+        - uses: actions/checkout@v3
+
+        - name: Set up Python ${{ matrix.python-version }}
+          uses: actions/setup-python@v4
+          with:
+            python-version: ${{ matrix.python-version }}
+
+        - name: Install Poetry
+          uses: snok/install-poetry@v1
+
+        - name: Load cached venv
+          id: cached-poetry-dependencies
+          uses: actions/cache@v3
+          with:
+            path: .venv
+            key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+
+        - name: Install dependencies
+          if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+          working-directory: app/api
+          run: poetry install --no-interaction --no-root
+
+        - name: Install django ${{ matrix.django-version }}
+          working-directory: app/api
+          run: |
+            poetry run python -m venv .venv
+            source .venv/bin/activate
+            pip install "Django==${{ matrix.django-version }}"
+
+        - name: Test with Django
+          env:
+            SECRET_KEY: 'test'
+            ALLOWED_HOSTS: "['localhost']"
+            COCONNECT_DB_ENGINE: "django.db.backends.postgresql"
+            COCONNECT_DB_HOST: "localhost"
+            COCONNECT_DB_NAME: "test"
+            COCONNECT_DB_PASSWORD: "postgres"
+            COCONNECT_DB_PORT: "5432"
+            COCONNECT_DB_USER: "postgres"
+          working-directory: app/api
+          run: |
+            poetry run pytest
diff --git a/.github/workflows/test_ccom-funcs-test.yml b/.github/workflows/test_ccom-funcs-test.yml
deleted file mode 100644
--- a/.github/workflows/test_ccom-funcs-test.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action
-# More GitHub Actions for Azure: https://github.com/Azure/actions
-
-name: Build and deploy Python project to Azure Function App - ccom-funcs-test
-
-on:
-  push:
-    branches:
-      - test
-  workflow_dispatch:
-
-env:
-  AZURE_FUNCTIONAPP_PACKAGE_PATH: './app/workers' # set this to the path to your web app project, defaults to the repository root
-  PYTHON_VERSION: '3.8' # set this to the python version to use (supports 3.6, 3.7, 3.8)
-
-jobs:
-  build-and-deploy:
-    runs-on: ubuntu-latest
-    steps:
-    - name: 'Checkout GitHub Action'
-      uses: actions/checkout@v2
-
-    - name: Setup Python ${{ env.PYTHON_VERSION }} Environment
-      uses: actions/setup-python@v1
-      with:
-        python-version: ${{ env.PYTHON_VERSION }}
-
-    - name: 'Install Poetry'
-      uses: snok/install-poetry@v1
-
-    - name: 'Resolve Project Dependencies Using Poetry'
-      run: |
-        pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
-        poetry config virtualenvs.create false
-        poetry export --format requirements.txt --output requirements.txt
-        popd
-      shell: bash
-
-    - name: 'Install Dependencies Using Pip'
-      shell: bash
-      run: |
-        pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
-        python -m pip install --upgrade pip
-        pip install -r requirements.txt --target=".python_packages/lib/site-packages"
-        popd
-
-    - name: 'Run Azure Functions Action'
-      uses: Azure/functions-action@v1
-      id: fa
-      with:
-        app-name: 'ccom-funcs-test'
-        slot-name: 'production'
-        package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
-        publish-profile: ${{ secrets.AZURE_FUNCAPP_PUBLISH_PROFILE_TEST }}
diff --git a/.github/workflows/test_ccom-test.yml b/.github/workflows/test_ccom-test.yml
deleted file mode 100644
--- a/.github/workflows/test_ccom-test.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
-# More GitHub Actions for Azure: https://github.com/Azure/actions
-
-name: Build and deploy container app to Azure Web App - ccom-test
-
-on:
-  push:
-    branches:
-      - test
-  workflow_dispatch:
-
-jobs:
-  build:
-    runs-on: 'ubuntu-latest'
-
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v1
-
-    - name: Log in to registry
-      uses: docker/login-action@v1
-      with:
-        registry: https://ccomregtest.azurecr.io/
-        username: ${{ secrets.REGISTRY_USERNAME_TEST }}
-        password: ${{ secrets.REGISTRY_PASSWORD_TEST }}
-
-    - name: Build and push container image to registry
-      uses: docker/build-push-action@v2
-      with:
-        push: true
-        tags: ccomregtest.azurecr.io/${{ secrets.REGISTRY_USERNAME_TEST }}/test:${{ github.sha }}
-        file: ./app/Dockerfile.deploy
-
-  deploy:
-    runs-on: ubuntu-latest
-    needs: build
-    environment:
-      name: 'test'
-      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
-
-    steps:
-    - name: Deploy to Azure Web App
-      id: deploy-to-webapp
-      uses: azure/webapps-deploy@v2
-      with:
-        app-name: 'ccom-test'
-        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_TEST }}
-        images: 'ccomregtest.azurecr.io/${{ secrets.REGISTRY_USERNAME_TEST }}/test:${{ github.sha }}'
diff --git a/app/api/mapping/tests.py b/app/api/mapping/tests.py
deleted file mode 100644
--- a/app/api/mapping/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/app/api/test/__init__.py b/app/api/test/__init__.py
new file mode 100644
diff --git a/app/api/test/conftest.py b/app/api/test/conftest.py
new file mode 100644
--- /dev/null
+++ b/app/api/test/conftest.py
@@ -0,0 +1,74 @@
+from typing import Generator
+
+import psycopg2
+import pytest
+from data.models.concept import Concept
+from django.conf import settings
+from django.core.management import call_command
+from django.db import connection
+from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
+from pytest_django import DjangoDbBlocker
+
+
+def run_sql(db: str, sql: str):
+    """
+    Executes the given SQL query on the given database.
+
+    Args:
+        db: The name of the database to connect to.
+        sql: The SQL query to execute.
+
+    Returns:
+        None
+
+    Raises:
+        psycopg2.Error: If there is an error executing the SQL.
+    """
+    conn = psycopg2.connect(
+        dbname=db,
+        user=settings.DATABASES["default"]["USER"],
+        password=settings.DATABASES["default"]["PASSWORD"],
+        host=settings.DATABASES["default"]["HOST"],
+        port=settings.DATABASES["default"]["PORT"],
+    )
+    conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
+    cur = conn.cursor()
+    cur.execute(sql)
+    conn.close()
+
+
+@pytest.fixture(scope="session")
+def django_db_setup(
+    django_db_blocker: DjangoDbBlocker,
+    django_db_keepdb: bool,
+) -> Generator[None, None, None]:
+    """
+    Override Pytest db setup, to create the "omop.Concept" table.
+
+    This is necessary for the migrations to run succesfully in test,
+    as the migrations depend on this schema/table existing.
+    """
+    # Set the default db to the test one for ease.
+    db_name = settings.DATABASES["default"]["TEST"]["NAME"]
+
+    # Manually create the test DB + omop Schema if it doesn't exist, drop if it does.
+    run_sql("postgres", f"DROP DATABASE IF EXISTS {db_name}")
+    run_sql("postgres", f"CREATE DATABASE {db_name} TEMPLATE template0")
+    run_sql(db_name, "CREATE SCHEMA IF NOT EXISTS omop")
+
+    # Set the default to test for ease
+    settings.DATABASES["default"]["NAME"] = db_name
+
+    # Create the omop.Concept table
+    with django_db_blocker.unblock():
+        with connection.schema_editor() as schema_editor:
+            schema_editor.create_model(Concept)
+
+        # run the rest of the migrations
+        call_command("migrate", "--noinput")
+
+    yield
+
+    # If not keeping the test db, drop it
+    if not django_db_keepdb:
+        run_sql("postgres", f"DROP DATABASE {db_name}")
diff --git a/app/api/mapping/test_permissions.py b/app/api/test/test_permissions.py
similarity index 98%
rename from app/api/mapping/test_permissions.py
rename to app/api/test/test_permissions.py
--- a/app/api/mapping/test_permissions.py
+++ b/app/api/test/test_permissions.py
@@ -1,23 +1,22 @@
 import os
 from unittest import mock
-from django.test import TestCase
+
 from django.contrib.auth import get_user_model
-from rest_framework.generics import GenericAPIView
-from rest_framework.test import APIRequestFactory, force_authenticate
-from rest_framework.authtoken.models import Token
-from .permissions import (
-    has_editorship,
-    has_viewership,
-    is_admin,
+from django.test import TestCase
+from mapping.models import DataPartner, Dataset, Project, ScanReport, VisibilityChoices
+from mapping.permissions import (
     CanAdmin,
     CanEdit,
-    CanViewProject,
     CanView,
+    CanViewProject,
+    has_editorship,
+    has_viewership,
+    is_admin,
 )
-from .views import (
-    ProjectRetrieveView,
-)
-from .models import Project, Dataset, ScanReport, VisibilityChoices, DataPartner
+from mapping.views import ProjectRetrieveView
+from rest_framework.authtoken.models import Token
+from rest_framework.generics import GenericAPIView
+from rest_framework.test import APIRequestFactory, force_authenticate
 
 
 class TestHasViewership(TestCase):
@@ -534,7 +533,7 @@ def test_public_viewership(self):
     @mock.patch.dict(os.environ, {"AZ_FUNCTION_USER": "az_functions"}, clear=True)
     def test_az_function_user_perm(self):
         User = get_user_model()
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
 
         # Add the user to the request; this is not automatic
         self.request.user = az_user
@@ -638,7 +637,7 @@ def test_dataset_editor_can_edit_scanreport(self):
     @mock.patch.dict(os.environ, {"AZ_FUNCTION_USER": "az_functions"}, clear=True)
     def test_az_function_user_perm(self):
         User = get_user_model()
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
 
         # Add the user to the request; this is not automatic
         self.request.user = az_user
@@ -739,7 +738,7 @@ def test_author_can_admin_scanreport(self):
     @mock.patch.dict(os.environ, {"AZ_FUNCTION_USER": "az_functions"}, clear=True)
     def test_az_function_user_perm(self):
         User = get_user_model()
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
 
         # Add the user to the request; this is not automatic
         self.request.user = az_user
diff --git a/app/api/mapping/test_serializers.py b/app/api/test/test_serializers.py
similarity index 98%
rename from app/api/mapping/test_serializers.py
rename to app/api/test/test_serializers.py
--- a/app/api/mapping/test_serializers.py
+++ b/app/api/test/test_serializers.py
@@ -1,11 +1,12 @@
 import os
-from django.test import TestCase
+
 from django.contrib.auth import get_user_model
-from rest_framework.test import APIRequestFactory
+from django.test import TestCase
+from mapping.models import DataPartner, Dataset, Project, ScanReport, VisibilityChoices
+from mapping.serializers import DatasetEditSerializer, ScanReportEditSerializer
 from rest_framework.authtoken.models import Token
 from rest_framework.serializers import ValidationError
-from .models import Project, Dataset, ScanReport, VisibilityChoices, DataPartner
-from .serializers import ScanReportEditSerializer, DatasetEditSerializer
+from rest_framework.test import APIRequestFactory
 
 
 class TestScanReportEditSerializer(TestCase):
diff --git a/app/api/mapping/test_services_rules.py b/app/api/test/test_services_rules.py
similarity index 98%
rename from app/api/mapping/test_services_rules.py
rename to app/api/test/test_services_rules.py
--- a/app/api/mapping/test_services_rules.py
+++ b/app/api/test/test_services_rules.py
@@ -1,22 +1,25 @@
-from django.test import TestCase
+from datetime import date
+
+import pytest
+from data.models import Concept
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
-from data.models import Concept
-from .services_rules import analyse_concepts
-from rest_framework.authtoken.models import Token
-from .models import (
+from django.test import TestCase
+from mapping.models import (
     DataPartner,
+    Dataset,
     MappingRule,
     OmopField,
     OmopTable,
     Project,
-    Dataset,
     ScanReport,
     ScanReportConcept,
     ScanReportField,
     ScanReportTable,
     ScanReportValue,
 )
+from mapping.services_rules import analyse_concepts
+from rest_framework.authtoken.models import Token
 
 
 class TestMisalignedMappings(TestCase):
@@ -264,6 +267,7 @@ def setUp(self):
             concept=self.scan_report_concept_cough_desc,
         )
 
+    @pytest.mark.skip(reason="This test relies on a more complete OMOP db.")
     def test_analyse_concepts(self):
         data = analyse_concepts(self.scan_report1.id)
         test_data = data["data"][0]
diff --git a/app/api/mapping/test_views.py b/app/api/test/test_views.py
similarity index 82%
rename from app/api/mapping/test_views.py
rename to app/api/test/test_views.py
--- a/app/api/mapping/test_views.py
+++ b/app/api/test/test_views.py
@@ -1,23 +1,26 @@
 import os
+from datetime import date
 from unittest import mock
-from django.test import TestCase
+
+import pytest
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
-from rest_framework.test import APIRequestFactory, APIClient, force_authenticate
-from rest_framework.authtoken.models import Token
-from .views import DatasetListView, ScanReportListViewSet
-from .models import (
-    Project,
+from django.test import TestCase, TransactionTestCase
+from mapping.models import (
+    Concept,
+    DataPartner,
     Dataset,
+    Project,
     ScanReport,
-    VisibilityChoices,
-    DataPartner,
-    ScanReportTable,
+    ScanReportConcept,
     ScanReportField,
+    ScanReportTable,
     ScanReportValue,
-    ScanReportConcept,
-    Concept,
+    VisibilityChoices,
 )
+from mapping.views import DatasetListView, ScanReportListViewSet
+from rest_framework.authtoken.models import Token
+from rest_framework.test import APIClient, APIRequestFactory, force_authenticate
 
 
 class TestDatasetListView(TestCase):
@@ -76,7 +79,7 @@ def setUp(self):
 
     def test_dataset_returns(self):
         # Make the request for Datasets
-        request = self.factory.get(f"/api/datasets/")
+        request = self.factory.get("/api/datasets/")
         # Add user1 to the request; this is not automatic
         request.user = self.user1
         # Authenticate the user1
@@ -125,7 +128,7 @@ def test_dataset_returns(self):
     def test_dataset_filtering(self):
         # Make the request for the public_dataset1
         request = self.factory.get(
-            f"/api/datasets/", {"id__in": self.public_dataset1.id}
+            "/api/datasets/", {"id__in": self.public_dataset1.id}
         )
         # Add user1 to the request; this is not automatic
         request.user = self.user1
@@ -145,7 +148,7 @@ def test_dataset_filtering(self):
 
         # Make the request for the public_dataset3
         request = self.factory.get(
-            f"/api/datasets/", {"id__in": self.public_dataset3.id}
+            "/api/datasets/", {"id__in": self.public_dataset3.id}
         )
         # Add user1 to the request; this is not automatic
         request.user = self.user1
@@ -164,9 +167,11 @@ def test_dataset_filtering(self):
     @mock.patch.dict(os.environ, {"AZ_FUNCTION_USER": "az_functions"}, clear=True)
     def test_az_function_user_perm(self):
         User = get_user_model()
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
+        Token.objects.create(user=az_user)
+
         # Make the request for the Dataset
-        request = self.factory.get(f"/api/datasets/")
+        request = self.factory.get("/api/datasets/")
         # Add the user to the request; this is not automatic
         request.user = az_user
         # Authenticate az_user
@@ -373,7 +378,7 @@ def test_non_project_member_forbidden(self):
         self.assertEqual(response.status_code, 403)
 
 
-class TestScanReportListViewset(TestCase):
+class TestScanReportListViewset(TransactionTestCase):
     def setUp(self):
         # Set up Data Partner
         self.data_partner = DataPartner.objects.create(name="Silvan Elves")
@@ -547,6 +552,7 @@ def test_viewer_get(self):
         # Assert the observed results are the same as the expected
         self.assertListEqual(observed_objs, expected_objs)
 
+    @pytest.mark.skip(reason="TODO: Fails due to the API query.")
     def test_author_get(self):
         """Authors can see all public SRs and restricted SRs they are the author of."""
         User = get_user_model()
@@ -589,7 +595,7 @@ def test_az_function_user_get(self):
         User = get_user_model()
 
         # AZ_FUNCTION_USER
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
         self.project.members.add(az_user)
         self.scanreport3.author = az_user
         self.scanreport3.save()
@@ -681,34 +687,24 @@ def setUp(self):
             value="Value1",
             frequency=0,
         )
+        concept1 = Concept.objects.create(
+            concept_id=1,
+            concept_name="Test Concept",
+            domain_id="Test Domain",
+            vocabulary_id="Test Vocab",
+            concept_class_id="1",
+            concept_code="100",
+            valid_start_date=date.today(),
+            valid_end_date=date.today(),
+        )
         self.scanreportconcept1 = ScanReportConcept.objects.create(
-            concept=Concept(
-                concept_id=1,
-                concept_name="",
-                domain_id="",
-                vocabulary_id="",
-                concept_class_id="",
-                standard_concept="",
-                concept_code="",
-                valid_start_date="",
-                valid_end_date="",
-            ),
+            concept=concept1,
             content_type=ContentType(ScanReportField),
             object_id=self.scanreportfield1.id,
             content_object=self.scanreportfield1,
         )
         self.scanreportconcept2 = ScanReportConcept.objects.create(
-            concept=Concept(
-                concept_id=1,
-                concept_name="",
-                domain_id="",
-                vocabulary_id="",
-                concept_class_id="",
-                standard_concept="",
-                concept_code="",
-                valid_start_date="",
-                valid_end_date="",
-            ),
+            concept=concept1,
             content_type=ContentType(ScanReportValue),
             object_id=self.scanreportvalue1.id,
             content_object=self.scanreportvalue1,
@@ -736,33 +732,13 @@ def setUp(self):
             frequency=0,
         )
         self.scanreportconcept3 = ScanReportConcept.objects.create(
-            concept=Concept(
-                concept_id=1,
-                concept_name="",
-                domain_id="",
-                vocabulary_id="",
-                concept_class_id="",
-                standard_concept="",
-                concept_code="",
-                valid_start_date="",
-                valid_end_date="",
-            ),
+            concept=concept1,
             content_type=ContentType(ScanReportField),
             object_id=self.scanreportfield2.id,
             content_object=self.scanreportfield2,
         )
         self.scanreportconcept4 = ScanReportConcept.objects.create(
-            concept=Concept(
-                concept_id=1,
-                concept_name="",
-                domain_id="",
-                vocabulary_id="",
-                concept_class_id="",
-                standard_concept="",
-                concept_code="",
-                valid_start_date="",
-                valid_end_date="",
-            ),
+            concept=concept1,
             content_type=ContentType(ScanReportValue),
             object_id=self.scanreportvalue2.id,
             content_object=self.scanreportvalue2,
@@ -770,108 +746,16 @@ def setUp(self):
         # Set up API client
         self.client = APIClient()
 
-    def test_admin_user_get(self):
-        """Users who are admins of the parent dataset can see all public SRs
-        and restricted SRs whose parent dataset they are the admin of.
-        """
-        User = get_user_model()
-
-        # user who is an admin of the parent dataset
-        admin_user = User.objects.create(username="gandalf", password="fiwuenfwinefiw")
-        self.project.members.add(admin_user)
-        self.public_dataset.admins.add(admin_user)
-        self.restricted_dataset.admins.add(admin_user)
-
-        # Get data admin_user should be able to see
-        self.client.force_authenticate(admin_user)
-        admin_response = self.client.get(
-            "/api/scanreportactiveconceptfilter/?content_type=15"
-        )
-        self.assertEqual(admin_response.status_code, 200)
-        observed_objs = sorted([obj.get("id") for obj in admin_response.data])
-        print(observed_objs)
-        expected_objs = []
-
-        # Assert the observed results are the same as the expected
-        self.assertListEqual(observed_objs, expected_objs)
-
-    def test_editor_get(self):
-        """Users who are editors of the parent dataset can see all public SRs
-        and restricted SRs whose parent dataset they are an editor of.
-        """
-        User = get_user_model()
-
-        # user who is an editor of the parent dataset
-        editor_user = User.objects.create(username="gandalf", password="fiwuenfwinefiw")
-        self.project.members.add(editor_user)
-        self.public_dataset.editors.add(editor_user)
-        self.restricted_dataset.editors.add(editor_user)
-
-        # Get data editor_user should be able to see
-        self.client.force_authenticate(editor_user)
-        editor_response = self.client.get(
-            "/api/scanreportactiveconceptfilter/?content_type=15"
-        )
-        self.assertEqual(editor_response.status_code, 200)
-        observed_objs = sorted([obj.get("id") for obj in editor_response.data])
-        expected_objs = []
-
-        # Assert the observed results are the same as the expected
-        self.assertListEqual(observed_objs, expected_objs)
-
-    def test_viewer_get(self):
-        """Users who are viewers of the parent dataset can see all public SRs
-        and restricted SRs whose parent dataset they are a viewer of.
-        """
-        User = get_user_model()
-
-        # user who is a viewer of the parent dataset
-        viewer_user = User.objects.create(username="gandalf", password="fiwuenfwinefiw")
-        self.project.members.add(viewer_user)
-        self.public_dataset.viewers.add(viewer_user)
-        self.restricted_dataset.viewers.add(viewer_user)
-
-        # Get data viewer_user should be able to see
-        self.client.force_authenticate(viewer_user)
-        viewer_response = self.client.get(
-            "/api/scanreportactiveconceptfilter/?content_type=15"
-        )
-        self.assertEqual(viewer_response.status_code, 200)
-        observed_objs = sorted([obj.get("id") for obj in viewer_response.data])
-        expected_objs = []
-
-        # Assert the observed results are the same as the expected
-        self.assertListEqual(observed_objs, expected_objs)
-
-    def test_author_get(self):
-        """Authors can see all public SRs and restricted SRs they are the author of."""
-        User = get_user_model()
-
-        # user who is the author of a scan report
-        author_user = User.objects.create(username="gandalf", password="fiwuenfwinefiw")
-        self.project.members.add(author_user)
-        self.scanreport1.author = author_user
-        self.scanreport1.save()
-
-        # Get data admin_user should be able to see
-        self.client.force_authenticate(author_user)
-        author_response = self.client.get(
-            "/api/scanreportactiveconceptfilter/?content_type=15"
-        )
-        self.assertEqual(author_response.status_code, 200)
-        observed_objs = sorted([obj.get("id") for obj in author_response.data])
-        expected_objs = []
-
-        # Assert the observed results are the same as the expected
-        self.assertListEqual(observed_objs, expected_objs)
-
     @mock.patch.dict(os.environ, {"AZ_FUNCTION_USER": "az_functions"}, clear=True)
+    @pytest.mark.skip(
+        reason="Depends on hardcoded IDs, fix: https://github.com/Health-Informatics-UoN/CaRROT-Mapper/issues/637"
+    )
     def test_az_function_user_get(self):
         """AZ_FUNCTION_USER can see all public SRs and restricted SRs."""
         User = get_user_model()
 
         # AZ_FUNCTION_USER
-        az_user = User.objects.get(username=os.getenv("AZ_FUNCTION_USER"))
+        az_user = User.objects.create(username="az_functions")
         self.project.members.add(az_user)
         self.scanreport1.author = az_user
         self.scanreport1.save()