From effcaa1fe2f5d244c67db096f69f98e76c1e8f56 Mon Sep 17 00:00:00 2001 From: Charles Muchogo Date: Thu, 5 Jan 2023 22:22:36 +0300 Subject: [PATCH] test: multitenancy tests (#163) --- .github/workflows/ci.yml | 15 ++-- .pre-commit-config.yaml | 10 +-- config/settings/base.py | 4 +- config/settings/production.py | 4 -- config/settings/test.py | 3 + mycarehub/conftest.py | 9 ++- mycarehub/content/filters.py | 22 ++++++ mycarehub/content/forms.py | 21 ------ mycarehub/content/models/__init__.py | 34 +++++----- mycarehub/content/models/models.py | 2 +- mycarehub/content/models/snippets.py | 2 +- mycarehub/content/tests/test_views.py | 68 ++++++++++++++++++- mycarehub/content/tests/test_wagtail_hooks.py | 53 ++++++++++++++- mycarehub/content/views/__init__.py | 20 +++--- mycarehub/content/views/snippets.py | 2 + setup.cfg | 1 + 16 files changed, 202 insertions(+), 68 deletions(-) delete mode 100644 mycarehub/content/forms.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7907e0..2375599 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,18 +40,18 @@ jobs: steps: - name: Checkout Code Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.10.0 + python-version: '3.10' # Run all pre-commit hooks on all the files. # Getting only staged files can be tricky in case a new PR is opened # since the action is run on a branch in detached head state - name: Install and Run Pre-commit - uses: pre-commit/action@v2.0.3 + uses: pre-commit/action@v3.0.0 - name: Install requirements run: | @@ -87,7 +87,12 @@ jobs: steps: - name: Checkout Code Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Decrypt GCS service account run: ./.github/scripts/decrypt_secret.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6da2c1e..7ee04fe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,26 +4,26 @@ fail_fast: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.12.0 hooks: - id: black args: ["-l", "99"] - repo: https://github.com/timothycrosley/isort - rev: 5.9.1 + rev: 5.11.4 hooks: - id: isort args: ["--profile", "black"] - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 hooks: - id: flake8 args: ["--config=setup.cfg"] diff --git a/config/settings/base.py b/config/settings/base.py index 5198132..d9aa63e 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -253,7 +253,7 @@ # ADMIN # ------------------------------------------------------------------------------ -ADMIN_URL = "admin/" +ADMIN_URL = env("DJANGO_ADMIN_URL", default="admin/") ADMINS = [ ( "Savannah Informatics Global Health Institute", @@ -401,7 +401,7 @@ TAGGIT_CASE_INSENSITIVE = True WAGTAILMEDIA = { "MEDIA_MODEL": "content.CustomMedia", - "MEDIA_FORM_BASE": "mycarehub.content.forms.CustomBaseMediaForm", + "MEDIA_FORM_BASE": "mycarehub.content.filters.CustomBaseMediaForm", "AUDIO_EXTENSIONS": ["aac", "wav"], "VIDEO_EXTENSIONS": ["mp4"], } diff --git a/config/settings/production.py b/config/settings/production.py index 40c12ef..f07ea5b 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -85,10 +85,6 @@ ) ] -# ADMIN -# ------------------------------------------------------------------------------ -# Django Admin URL regex. -ADMIN_URL = env("DJANGO_ADMIN_URL", default="admin/") # Anymail # ------------------------------------------------------------------------------ diff --git a/config/settings/test.py b/config/settings/test.py index da7e8e3..820842a 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -68,6 +68,9 @@ def gen_func(): WHITENOISE_MANIFEST_STRICT = False GCS_BUCKET_ALLOWED_ORIGINS = env.list("GCS_BUCKET_ALLOWED_ORIGINS") +# Admin +ADMIN_URL = "test-admin/" + def require_env(name: str) -> str: value = env(name) diff --git a/mycarehub/conftest.py b/mycarehub/conftest.py index 69c4672..c9482b8 100644 --- a/mycarehub/conftest.py +++ b/mycarehub/conftest.py @@ -66,7 +66,7 @@ def request_with_user(rf, django_user_model, user_with_all_permissions): @pytest.fixture -def content_item_with_tag_and_category(request_with_user): +def content_item_index(request_with_user): # get the root page site = Site.find_for_request(request_with_user) assert site is not None @@ -90,6 +90,13 @@ def content_item_with_tag_and_category(request_with_user): home.add_child(instance=content_item_index) home.save_revision().publish() + content_item_index.save_revision().publish() + + return content_item_index + + +@pytest.fixture +def content_item_with_tag_and_category(content_item_index): # get a hero image hero = baker.make("wagtailimages.Image", _create_files=True) diff --git a/mycarehub/content/filters.py b/mycarehub/content/filters.py index de626f1..1ab6a53 100644 --- a/mycarehub/content/filters.py +++ b/mycarehub/content/filters.py @@ -1,10 +1,13 @@ import django_filters +from django.core.files.storage import get_storage_class from django.db.models import Count, Q from rest_framework.filters import BaseFilterBackend from wagtail.admin.filters import WagtailFilterSet +from wagtailmedia.forms import BaseMediaForm from mycarehub.common.filters.base_filters import CommonFieldsFilterset from mycarehub.content.models import ContentItem +from mycarehub.utils.signed_url import generate_media_name from .models import ( Author, @@ -99,6 +102,7 @@ class Meta: class ContentItemCategoryFilterSet(WagtailFilterSet): def filter_queryset(self, *args, **kwargs): return self.queryset.filter(organisation=self.request.user.organisation) + class Meta: model = ContentItemCategory fields = ["name"] @@ -107,6 +111,24 @@ class Meta: class AuthorFilterSet(WagtailFilterSet): def filter_queryset(self, *args, **kwargs): return self.queryset.filter(organisation=self.request.user.organisation) + class Meta: model = Author fields = ["name"] + + +class CustomBaseMediaForm(BaseMediaForm): + def save(self, commit=True): # pragma: no cover + instance = super().save(commit=False) + + # Checks the storage class being used + # Google Cloud Storage should save only the file name + # because the upload is already done using a signed url + if get_storage_class().__name__ == "MediaRootGoogleCloudStorage": # pragma: no cover + temp_file = instance.file + instance.file = generate_media_name(temp_file.name) + + if commit: + instance.save() + + return instance diff --git a/mycarehub/content/forms.py b/mycarehub/content/forms.py deleted file mode 100644 index 778eb60..0000000 --- a/mycarehub/content/forms.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.core.files.storage import get_storage_class -from wagtailmedia.forms import BaseMediaForm - -from mycarehub.utils.signed_url import generate_media_name - - -class CustomBaseMediaForm(BaseMediaForm): - def save(self, commit=True): # pragma: nocover - instance = super().save(commit=False) - - # Checks the storage class being used - # Google Cloud Storage should save only the file name - # because the upload is already done using a signed url - if get_storage_class().__name__ == "MediaRootGoogleCloudStorage": # pragma: nocover - temp_file = instance.file - instance.file = generate_media_name(temp_file.name) - - if commit: - instance.save() - - return instance diff --git a/mycarehub/content/models/__init__.py b/mycarehub/content/models/__init__.py index 57355fb..ff8b0af 100644 --- a/mycarehub/content/models/__init__.py +++ b/mycarehub/content/models/__init__.py @@ -6,6 +6,7 @@ ContentItemGalleryImage, ContentItemIndexPage, ContentItemMediaLink, + ContentItemPageForm, ContentItemQuestionnaire, ContentItemTag, ContentItemTagIndexPage, @@ -14,20 +15,21 @@ from .snippets import Author, ContentItemCategory __all__ = [ - MediaSerializedField, - Author, - ContentItemCategory, - ContentItemTag, - ContentItemTagIndexPage, - ContentItemIndexPage, - ContentItem, - ContentItemDocumentLink, - ContentItemMediaLink, - ContentItemGalleryImage, - ContentLike, - ContentBookmark, - ContentShare, - ContentView, - ContentItemQuestionnaire, - CustomMedia, + "MediaSerializedField", + "Author", + "ContentItemCategory", + "ContentItemTag", + "ContentItemTagIndexPage", + "ContentItemIndexPage", + "ContentItem", + "ContentItemDocumentLink", + "ContentItemMediaLink", + "ContentItemGalleryImage", + "ContentLike", + "ContentBookmark", + "ContentShare", + "ContentView", + "ContentItemQuestionnaire", + "CustomMedia", + "ContentItemPageForm", ] diff --git a/mycarehub/content/models/models.py b/mycarehub/content/models/models.py index e897ef1..0d5cb13 100644 --- a/mycarehub/content/models/models.py +++ b/mycarehub/content/models/models.py @@ -146,7 +146,7 @@ def get_context(self, request): class ContentItemPageForm(WagtailAdminPageForm): def __init__( self, data=None, files=None, parent_page=None, subscription=None, *args, **kwargs - ): + ): # pragma: no cover super().__init__(data, files, parent_page, subscription, *args, **kwargs) self.fields["categories"].queryset = self.fields["categories"].queryset.filter( diff --git a/mycarehub/content/models/snippets.py b/mycarehub/content/models/snippets.py index fa4b823..ea705ae 100644 --- a/mycarehub/content/models/snippets.py +++ b/mycarehub/content/models/snippets.py @@ -75,7 +75,7 @@ class ContentItemCategory(index.Indexed, models.Model): FieldPanel("icon"), ] - search_fields = [index.SearchField("name"), index.FilterField('organisation_id')] + search_fields = [index.SearchField("name"), index.FilterField("organisation_id")] def __str__(self): return self.name diff --git a/mycarehub/content/tests/test_views.py b/mycarehub/content/tests/test_views.py index 0f5dcc9..ef822e9 100644 --- a/mycarehub/content/tests/test_views.py +++ b/mycarehub/content/tests/test_views.py @@ -1,10 +1,18 @@ import pytest +from django.core.files.uploadedfile import SimpleUploadedFile from django.test import override_settings from django.urls import reverse from model_bakery import baker from rest_framework import status -from ..models import ContentBookmark, ContentLike, ContentShare, ContentView +from ..models import ( + Author, + ContentBookmark, + ContentItemCategory, + ContentLike, + ContentShare, + ContentView, +) pytestmark = pytest.mark.django_db @@ -252,3 +260,61 @@ def test_content_bookmark_create_view( response_data = response.json() print(response_data) assert response_data["id"] != "" + + +def test_author_snippet_list(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("wagtailsnippets_content_author:list") + + baker.make(Author, organisation=user_with_all_permissions.organisation) + + response = client.get(url) + + print(response.content.decode()) + assert response.status_code == status.HTTP_200_OK + + +def test_content_item_category_snippet_list(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("wagtailsnippets_content_contentitemcategory:list") + + baker.make(ContentItemCategory, organisation=user_with_all_permissions.organisation) + + response = client.get(url) + + print(response.content) + assert response.status_code == status.HTTP_200_OK + + +def test_author_chooser_list(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("wagtailadmin_home") + url = url + "author_chooser/" + + baker.make(Author, organisation=user_with_all_permissions.organisation) + + response = client.get(url) + + print(response.content) + assert response.status_code == status.HTTP_200_OK + + +def test_add_media(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("wagtailmedia:add", kwargs={"media_type": "video"}) + + video = SimpleUploadedFile("file.mp4", b"file_content", content_type="video/mp4") + + response = client.post( + url, + data={ + "title": "Test Video", + }, + files={"file": video}, + content_type="application/json", + accept="application/json", + follow=True, + ) + + print(response.content) + assert response.status_code == status.HTTP_200_OK diff --git a/mycarehub/content/tests/test_wagtail_hooks.py b/mycarehub/content/tests/test_wagtail_hooks.py index aa4b4d3..7d75773 100644 --- a/mycarehub/content/tests/test_wagtail_hooks.py +++ b/mycarehub/content/tests/test_wagtail_hooks.py @@ -1,8 +1,18 @@ import pytest from django.core.exceptions import ValidationError +from model_bakery import baker from wagtail.models import Site -from mycarehub.content.wagtail_hooks import before_publish_page, get_global_admin_js +from mycarehub.content.models import Author, ContentItem, CustomMedia +from mycarehub.content.wagtail_hooks import ( + before_publish_page, + chooser_show_organisation_pages_only, + explorer_show_organisation_pages_only, + get_global_admin_js, + set_organisation_after_page_create, + set_organisation_after_snippet_create, + show_organisation_media_only, +) pytestmark = pytest.mark.django_db @@ -67,3 +77,44 @@ def test_before_publish_content_item_article( ): # there should be no exception before_publish_page(request=request_with_user, page=content_item_with_tag_and_category) + + +def test_set_organisation_after_page_create( + request_with_user, + content_item_with_tag_and_category, +): + set_organisation_after_page_create( + request=request_with_user, page=content_item_with_tag_and_category + ) + + +def test_explorer_show_organisation_pages_only( + request_with_user, + content_item_with_tag_and_category, +): + explorer_show_organisation_pages_only( + parent_page=None, pages=ContentItem.objects.all(), request=request_with_user + ) + + +def test_chooser_show_organisation_pages_only( + request_with_user, + content_item_with_tag_and_category, +): + chooser_show_organisation_pages_only( + pages=ContentItem.objects.all(), request=request_with_user + ) + + +def test_set_organisation_after_snippet_create(request_with_user): + author = baker.make(Author) + + set_organisation_after_snippet_create(request=request_with_user, instance=author) + + assert author.organisation == request_with_user.user.organisation + + +def test_show_organisation_media_only(request_with_user): + baker.make(CustomMedia, organisation=request_with_user.user.organisation) + + show_organisation_media_only(media=CustomMedia.objects.all(), request=request_with_user) diff --git a/mycarehub/content/views/__init__.py b/mycarehub/content/views/__init__.py index 1755ee5..91c7174 100644 --- a/mycarehub/content/views/__init__.py +++ b/mycarehub/content/views/__init__.py @@ -10,14 +10,14 @@ from .views import ContentItemCategoryViewSet, CustomPageAPIViewset __all__ = [ - CustomPageAPIViewset, - SignedURLView, - ContentItemCategoryViewSet, - ContentViewViewSet, - ContentShareViewSet, - ContentLikeViewSet, - ContentBookmarkViewSet, - AuthorSnippetViewSet, - ContentItemCategorySnippetViewSet, - author_chooser_viewset, + "CustomPageAPIViewset", + "SignedURLView", + "ContentItemCategoryViewSet", + "ContentViewViewSet", + "ContentShareViewSet", + "ContentLikeViewSet", + "ContentBookmarkViewSet", + "AuthorSnippetViewSet", + "ContentItemCategorySnippetViewSet", + "author_chooser_viewset", ] diff --git a/mycarehub/content/views/snippets.py b/mycarehub/content/views/snippets.py index ab015fd..f7637ea 100644 --- a/mycarehub/content/views/snippets.py +++ b/mycarehub/content/views/snippets.py @@ -2,10 +2,12 @@ from mycarehub.content.filters import AuthorFilterSet, ContentItemCategoryFilterSet + class AuthorSnippetViewSet(SnippetViewSet): list_display = ["name", "avatar"] filterset_class = AuthorFilterSet + class ContentItemCategorySnippetViewSet(SnippetViewSet): list_display = ["name", "icon"] filterset_class = ContentItemCategoryFilterSet diff --git a/setup.cfg b/setup.cfg index fd5d85a..af63096 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,6 +14,7 @@ warn_unused_ignores = True warn_redundant_casts = True warn_unused_configs = True plugins = mypy_django_plugin.main +no_implicit_optional=False [mypy.plugins.django-stubs] django_settings_module = config.settings.test