Skip to content

Commit f71c5e3

Browse files
committed
starting with pytest
1 parent 1df26fb commit f71c5e3

File tree

15 files changed

+255
-97
lines changed

15 files changed

+255
-97
lines changed

.github/workflows/frontend.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 0 additions & 44 deletions
This file was deleted.

.github/workflows/tests.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Test django-filer
2+
3+
on:
4+
push:
5+
branches:
6+
- finder
7+
paths-ignore:
8+
- '**.md'
9+
- '**.rst'
10+
- '/docs/**'
11+
pull_request:
12+
branches:
13+
- develop
14+
paths-ignore:
15+
- '**.md'
16+
- '**.rst'
17+
- '/docs/**'
18+
19+
jobs:
20+
build:
21+
runs-on: ubuntu-latest
22+
strategy:
23+
matrix:
24+
python-version: ["3.11", "3.12"]
25+
django-version: ["5.2.*"]
26+
node-version: ["18.x"]
27+
28+
steps:
29+
- uses: actions/checkout@v2
30+
- name: Use Node.js ${{ matrix.node-version }}
31+
uses: actions/setup-node@v2
32+
with:
33+
node-version: ${{ matrix.node-version }}
34+
- name: Set up Python ${{ matrix.python-version }}
35+
uses: actions/setup-python@v2
36+
with:
37+
python-version: ${{ matrix.python-version }}
38+
- name: Install dependencies
39+
run: |
40+
npm install --include=dev
41+
python -m pip install --upgrade pip
42+
python -m pip install https://github.com/django/django/archive/refs/heads/main.zip
43+
python -m pip install django-cte django-entangled ffmpeg-python pillow reportlab svglib
44+
python -m pip install beautifulsoup4 coverage Faker lxml pytest pytest-django pytest-cov
45+
- name: Build Client
46+
run: |
47+
npm run compilescss
48+
npm run esbuild
49+
- name: Test with pytest
50+
run: |
51+
python -m pytest -v demoapp/unittests

client/scss/finder-admin.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,4 +695,3 @@ ul.messagelist {
695695
}
696696

697697
@import 'node_modules/react-image-crop/src/ReactCrop.scss';
698-
@import 'node_modules/react-h5-audio-player/src/styles.scss';

demoapp/pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
DJANGO_SETTINGS_MODULE = demoapp.settings
3+
django_find_project = false
4+
addopts = --tb=native

demoapp/settings.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
'django.contrib.messages',
3535
'django.contrib.sites',
3636
'django.contrib.staticfiles',
37-
'easy_thumbnails',
3837
'filer',
3938
'finder',
4039
'finder.contrib.archive',
@@ -75,24 +74,25 @@
7574
WSGI_APPLICATION = 'wsgi.application'
7675

7776

78-
# Database
79-
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
80-
81-
DATABASES = {
82-
'default': {
83-
'ENGINE': 'django.db.backends.sqlite3',
84-
'NAME': BASE_DIR / 'workdir/db.sqlite3',
85-
},
86-
'default_': {
87-
'ENGINE': 'django.db.backends.postgresql',
88-
'NAME': 'finder',
89-
'USER': 'finder',
90-
'PASSWORD': '',
91-
'HOST': 'localhost',
92-
'PORT': 5432,
93-
# 'CONN_MAX_AGE': 900,
94-
},
95-
}
77+
if os.getenv('USE_POSTGRES', False) in ['1', 'True', 'true']:
78+
DATABASES = {
79+
'default': {
80+
'ENGINE': 'django.db.backends.postgresql',
81+
'NAME': 'finder',
82+
'USER': 'finder',
83+
'PASSWORD': '',
84+
'HOST': 'localhost',
85+
'PORT': 5432,
86+
# 'CONN_MAX_AGE': 900,
87+
},
88+
}
89+
else:
90+
DATABASES = {
91+
'default': {
92+
'ENGINE': 'django.db.backends.sqlite3',
93+
'NAME': BASE_DIR / 'workdir/db.sqlite3',
94+
},
95+
}
9696

9797

9898
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

demoapp/unittests/__init__.py

Whitespace-only changes.

demoapp/unittests/conftest.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import os
2+
import pytest
3+
4+
from playwright.sync_api import sync_playwright
5+
6+
from django.conf import settings
7+
from django.contrib.admin.sites import site as admin_site
8+
from django.core.management import call_command
9+
from django.urls import reverse
10+
11+
from finder.models.folder import FolderModel
12+
from finder.models.realm import RealmModel
13+
14+
from .utils import create_random_image
15+
16+
os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', 'true')
17+
18+
19+
@pytest.fixture(autouse=True, scope='session')
20+
def create_assets():
21+
os.makedirs(settings.BASE_DIR / 'workdir/assets', exist_ok=True)
22+
for counter in range(10):
23+
image = create_random_image()
24+
image.save(settings.BASE_DIR / 'workdir/assets' / f'image_{counter:01d}.png')
25+
26+
27+
@pytest.fixture(scope='session')
28+
def django_db_setup(django_db_blocker):
29+
database_file = settings.BASE_DIR / 'workdir/test_db.sqlite3'
30+
settings.DATABASES['default']['NAME'] = database_file
31+
with django_db_blocker.unblock():
32+
call_command('migrate', verbosity=0)
33+
yield
34+
os.remove(database_file)
35+
36+
37+
@pytest.fixture
38+
def realm(admin_client):
39+
if realm := RealmModel.objects.first():
40+
return realm
41+
response = admin_client.get(reverse('admin:finder_foldermodel_changelist'))
42+
assert response.status_code == 302
43+
realm = RealmModel.objects.first()
44+
assert realm is not None
45+
redirected = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id})
46+
assert response.url == redirected
47+
assert realm.root_folder.is_folder is True
48+
assert realm.root_folder.is_trash is False
49+
assert realm.root_folder.owner == response.wsgi_request.user
50+
assert realm.root_folder.name == '__root__'
51+
assert realm.root_folder.parent is None
52+
assert realm.root_folder.is_root
53+
assert realm.trash_folders.count() == 0
54+
return realm
55+
56+
57+
class Connector:
58+
def __init__(self, live_server):
59+
print(f"\nStarting end-to-end test server at {live_server}\n")
60+
self.live_server = live_server
61+
62+
def __enter__(self):
63+
def print_args(msg):
64+
if msg.type in ['info', 'debug']:
65+
return
66+
for arg in msg.args:
67+
print(arg.json_value())
68+
69+
self.playwright = sync_playwright().start()
70+
self.browser = self.playwright.chromium.launch()
71+
return self
72+
73+
def __exit__(self, exc_type, exc_val, exc_tb):
74+
self.browser.close()
75+
self.playwright.stop()
76+
77+
78+
@pytest.fixture(scope='session')
79+
def connector(live_server):
80+
with Connector(live_server) as connector:
81+
yield connector
82+
83+
84+
@pytest.fixture
85+
def locale():
86+
return 'en-US'
87+
88+
89+
@pytest.fixture
90+
def language():
91+
return 'en'
92+
93+
94+
def print_args(msg):
95+
"""
96+
Print messages from the browser console.
97+
"""
98+
for arg in msg.args:
99+
print(arg.json_value())
100+
101+
102+
@pytest.fixture()
103+
def page(connector, viewname, locale, language):
104+
context = connector.browser.new_context(locale=locale)
105+
context.add_cookies([{'name': 'django_language', 'value': language, 'domain': 'localhost', 'path': '/'}])
106+
page = context.new_page()
107+
# page.on('console', print_args)
108+
page.goto(connector.live_server.url + reverse(viewname))
109+
# django_formset = page.locator('django-formset:defined')
110+
# django_formset.wait_for()
111+
return page
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import json
2+
import pytest
3+
4+
from bs4 import BeautifulSoup
5+
6+
from django.urls import reverse
7+
8+
9+
@pytest.mark.django_db
10+
def test_access_root_folder(realm, admin_client):
11+
admin_url = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id})
12+
request = admin_client.get(admin_url)
13+
assert request.status_code == 200
14+
soup = BeautifulSoup(request.content, 'html.parser')
15+
assert soup.title.string == "Root | Change Folder | Django site admin"
16+
script_element = soup.find(id='finder-settings')
17+
assert script_element.name == 'script'
18+
finder_settings = json.loads(script_element.string)
19+
finder_settings.pop('csrf_token')
20+
finder_settings.pop('favorite_folders')
21+
finder_settings.pop('menu_extensions')
22+
assert finder_settings == {
23+
'name': '__root__',
24+
'is_folder': True,
25+
'folder_id': str(realm.root_folder.id),
26+
'parent_id': None,
27+
'parent_url': None,
28+
'is_root': True,
29+
'is_trash': False,
30+
'folder_url': admin_url,
31+
'base_url': reverse('admin:finder_foldermodel_changelist'),
32+
'ancestors': [str(realm.root_folder.id)],
33+
}

demoapp/unittests/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import colorsys
2+
from faker import Faker
3+
import random
4+
from PIL import Image, ImageDraw
5+
from typing import NewType
6+
7+
8+
ColorRGBA = NewType('ColorRGBA', tuple[int, int, int, int])
9+
10+
11+
def random_color() -> ColorRGBA:
12+
return *(random.randint(0, 255) for _ in range(3)), 255
13+
14+
15+
def rotate_hue(rgb: ColorRGBA, degrees: float) -> ColorRGBA:
16+
h, l, s = colorsys.rgb_to_hls(rgb[0], rgb[1], rgb[2])
17+
h = (h + degrees / 360.0) % 1.0
18+
l = 255.0 - l
19+
return *map(lambda c: int(c), colorsys.hls_to_rgb(h, l, s)), rgb[3]
20+
21+
22+
def create_random_image() -> Image:
23+
faker = Faker()
24+
background_color = random_color()
25+
image = Image.new('RGB', (100, 100), color=background_color)
26+
drawing = ImageDraw.Draw(image)
27+
foreground_color = rotate_hue(background_color, 180)
28+
drawing.text((5, 40), faker.text(20), fill=foreground_color)
29+
return image

0 commit comments

Comments
 (0)