Skip to content

Commit

Permalink
starting with pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
jrief committed Jan 7, 2025
1 parent 1df26fb commit a7bb29a
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 74 deletions.
44 changes: 0 additions & 44 deletions .github/workflows/test.yml

This file was deleted.

51 changes: 51 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test django-filer

on:
push:
branches:
- finder
paths-ignore:
- '**.md'
- '**.rst'
- '/docs/**'
pull_request:
branches:
- develop
paths-ignore:
- '**.md'
- '**.rst'
- '/docs/**'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
django-version: ["5.2.*"]
node-version: ["18.x"]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
npm install --include=dev
python -m pip install --upgrade pip
python -m pip install https://github.com/django/django/archive/refs/heads/main.zip
python -m pip install django-cte django-entangled ffmpeg-python pillow reportlab svglib
python -m pip install beautifulsoup4 coverage Faker lxml pytest pytest-django pytest-cov
- name: Build Client
run: |
npm run compilescss
npm run esbuild
- name: Test with pytest
run: |
python -m pytest -v demoapp/unittests
1 change: 0 additions & 1 deletion client/scss/finder-admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -695,4 +695,3 @@ ul.messagelist {
}

@import 'node_modules/react-image-crop/src/ReactCrop.scss';
@import 'node_modules/react-h5-audio-player/src/styles.scss';
4 changes: 4 additions & 0 deletions demoapp/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
DJANGO_SETTINGS_MODULE = demoapp.settings
django_find_project = false
addopts = --tb=native
37 changes: 19 additions & 18 deletions demoapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,25 @@
WSGI_APPLICATION = 'wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'workdir/db.sqlite3',
},
'default_': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'finder',
'USER': 'finder',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': 5432,
# 'CONN_MAX_AGE': 900,
},
}
if os.getenv('USE_POSTGRES', False) in ['1', 'True', 'true']:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'finder',
'USER': 'finder',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': 5432,
# 'CONN_MAX_AGE': 900,
},
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'workdir/db.sqlite3',
},
}


DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Expand Down
Empty file added demoapp/unittests/__init__.py
Empty file.
111 changes: 111 additions & 0 deletions demoapp/unittests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import os
import pytest

from playwright.sync_api import sync_playwright

from django.conf import settings
from django.contrib.admin.sites import site as admin_site

Check failure on line 7 in demoapp/unittests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8

'django.contrib.admin.sites.site as admin_site' imported but unused
from django.core.management import call_command
from django.urls import reverse

from finder.models.folder import FolderModel

Check failure on line 11 in demoapp/unittests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8

'finder.models.folder.FolderModel' imported but unused
from finder.models.realm import RealmModel

from .utils import create_random_image

os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', 'true')


@pytest.fixture(autouse=True, scope='session')
def create_assets():
os.makedirs(settings.BASE_DIR / 'workdir/assets', exist_ok=True)
for counter in range(10):
image = create_random_image()
image.save(settings.BASE_DIR / 'workdir/assets' / f'image_{counter:01d}.png')


@pytest.fixture(scope='session')
def django_db_setup(django_db_blocker):
database_file = settings.BASE_DIR / 'workdir/test_db.sqlite3'
settings.DATABASES['default']['NAME'] = database_file
with django_db_blocker.unblock():
call_command('migrate', verbosity=0)
yield
os.remove(database_file)


@pytest.fixture
def realm(admin_client):
if realm := RealmModel.objects.first():
return realm
response = admin_client.get(reverse('admin:finder_foldermodel_changelist'))
assert response.status_code == 302
realm = RealmModel.objects.first()
assert realm is not None
redirected = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id})
assert response.url == redirected
assert realm.root_folder.is_folder is True
assert realm.root_folder.is_trash is False
assert realm.root_folder.owner == response.wsgi_request.user
assert realm.root_folder.name == '__root__'
assert realm.root_folder.parent is None
assert realm.root_folder.is_root
assert realm.trash_folders.count() == 0
return realm


class Connector:
def __init__(self, live_server):
print(f"\nStarting end-to-end test server at {live_server}\n")
self.live_server = live_server

def __enter__(self):
def print_args(msg):
if msg.type in ['info', 'debug']:
return
for arg in msg.args:
print(arg.json_value())

self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.browser.close()
self.playwright.stop()


@pytest.fixture(scope='session')
def connector(live_server):
with Connector(live_server) as connector:
yield connector


@pytest.fixture
def locale():
return 'en-US'


@pytest.fixture
def language():
return 'en'


def print_args(msg):
"""
Print messages from the browser console.
"""
for arg in msg.args:
print(arg.json_value())


@pytest.fixture()
def page(connector, viewname, locale, language):
context = connector.browser.new_context(locale=locale)
context.add_cookies([{'name': 'django_language', 'value': language, 'domain': 'localhost', 'path': '/'}])
page = context.new_page()
# page.on('console', print_args)
page.goto(connector.live_server.url + reverse(viewname))
# django_formset = page.locator('django-formset:defined')
# django_formset.wait_for()
return page
33 changes: 33 additions & 0 deletions demoapp/unittests/test_folder_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json
import pytest

from bs4 import BeautifulSoup

from django.urls import reverse


@pytest.mark.django_db
def test_access_root_folder(realm, admin_client):
admin_url = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id})
request = admin_client.get(admin_url)
assert request.status_code == 200
soup = BeautifulSoup(request.content, 'html.parser')
assert soup.title.string == "Root | Change Folder | Django site admin"
script_element = soup.find(id='finder-settings')
assert script_element.name == 'script'
finder_settings = json.loads(script_element.string)
finder_settings.pop('csrf_token')
finder_settings.pop('favorite_folders')
finder_settings.pop('menu_extensions')
assert finder_settings == {
'name': '__root__',
'is_folder': True,
'folder_id': str(realm.root_folder.id),
'parent_id': None,
'parent_url': None,
'is_root': True,
'is_trash': False,
'folder_url': admin_url,
'base_url': reverse('admin:finder_foldermodel_changelist'),
'ancestors': [str(realm.root_folder.id)],
}
29 changes: 29 additions & 0 deletions demoapp/unittests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import colorsys
from faker import Faker
import random
from PIL import Image, ImageDraw
from typing import NewType


ColorRGBA = NewType('ColorRGBA', tuple[int, int, int, int])


def random_color() -> ColorRGBA:
return *(random.randint(0, 255) for _ in range(3)), 255


def rotate_hue(rgb: ColorRGBA, degrees: float) -> ColorRGBA:
h, l, s = colorsys.rgb_to_hls(rgb[0], rgb[1], rgb[2])
h = (h + degrees / 360.0) % 1.0
l = 255.0 - l

Check failure on line 18 in demoapp/unittests/utils.py

View workflow job for this annotation

GitHub Actions / flake8

ambiguous variable name 'l'
return *map(lambda c: int(c), colorsys.hls_to_rgb(h, l, s)), rgb[3]


def create_random_image() -> Image:
faker = Faker()
background_color = random_color()
image = Image.new('RGB', (100, 100), color=background_color)
drawing = ImageDraw.Draw(image)
foreground_color = rotate_hue(background_color, 180)
drawing.text((5, 40), faker.text(20), fill=foreground_color)
return image
8 changes: 0 additions & 8 deletions finder/admin/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@
class FolderAdmin(InodeAdmin):
form_template = 'finder/admin/change_folder_form.html'
_model_admin_cache = {}
_legends = {
'name': _("Name"),
'owner_name': _("Owner"),
'details': _("Details"),
'created_at': _("Created at"),
'mime_type': _("Mime type"),
}

@property
def media(self):
Expand Down Expand Up @@ -146,7 +139,6 @@ def get_editor_settings(self, request, inode):
settings.update(
base_url=reverse('admin:finder_foldermodel_changelist', current_app=self.admin_site.name),
ancestors=ancestor_ids,
legends=self._legends,
menu_extensions=self.get_menu_extension_settings(request),
)
return settings
Expand Down
4 changes: 2 additions & 2 deletions finder/models/inode.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ class InodeManagerMixin:

def filter_unified(self, **lookup):
"""
Returns a unified QuerySet of all folders and files with fields from all involved models inheriting
from InodeModel. The QuerySet is filtered by the given lookup parameters.
Returns a unified QuerySet of all folders and files with fields from all involved models
inheriting from InodeModel. The QuerySet is filtered by the given lookup parameters.
Entries are represented as dictionaries rather than model instances.
"""
from .file import FileModel
Expand Down
5 changes: 5 additions & 0 deletions finder/models/realm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@


class RealmModel(models.Model):
"""
The RealmModel is the top-level container for each tennant. This usually is associated with a
Django Admin Site.
Each RealmModel has one root folder and a trash folder per user.
"""
site = models.ForeignKey(
Site,
on_delete=models.CASCADE,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"downshift": "^9.0.8",
"esbuild": "^0.19.12",
"esbuild-plugin-svgr": "^2.1.0",
"react-h5-audio-player": "^3.9.3",
"react-image-crop": "^11.0.7",
"react-intersection-observer": "^9.13.1",
"react-player": "^2.16.0",
Expand Down

0 comments on commit a7bb29a

Please sign in to comment.