Skip to content

Commit d24d75f

Browse files
Fix CI: install ColdFront as dependency, no deferred imports
Plugin code keeps all ColdFront imports at the top level. The test harness (django_setup.py) handles both environments: - With ColdFront installed: uses full app stack with real models - Without ColdFront (CI): stubs coldfront.core.* and ifxuser.* so top-level imports resolve to mocks Build job simplified: wheel verify checks install + version import only (no Django setup needed since ColdFront is not available).
1 parent 137ebba commit d24d75f

2 files changed

Lines changed: 110 additions & 92 deletions

File tree

.github/workflows/tests.yml

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ jobs:
2525
run: |
2626
pip install --upgrade pip
2727
pip install "Django>=4.2,<5" coverage
28-
env:
29-
PYTHONPATH: .
3028
3129
- name: Run unit tests
3230
run: |
@@ -38,7 +36,7 @@ jobs:
3836
coldfront_notifications.tests.test_conf \
3937
-v
4038
coverage report --show-missing --fail-under=80 \
41-
--include="coldfront_notifications/template_variable_value_resolver.py,coldfront_notifications/notification_validator.py,coldfront_notifications/campaign_sender.py,coldfront_notifications/filters.py,coldfront_notifications/conf.py,coldfront_notifications/__init__.py"
39+
--include="coldfront_notifications/template_variable_value_resolver.py,coldfront_notifications/notification_validator.py,coldfront_notifications/campaign_sender.py,coldfront_notifications/conf.py,coldfront_notifications/__init__.py"
4240
env:
4341
PYTHONPATH: .
4442

@@ -92,27 +90,10 @@ jobs:
9290
- name: Check package
9391
run: twine check dist/*
9492

95-
- name: Verify wheel contents
93+
- name: Verify wheel installs
9694
run: |
9795
pip install dist/*.whl
9896
python -c "
99-
import django
100-
from django.conf import settings
101-
settings.configure(
102-
INSTALLED_APPS=[
103-
'django.contrib.contenttypes',
104-
'django.contrib.auth',
105-
'django.contrib.admin',
106-
'django.contrib.sessions',
107-
'django.contrib.messages',
108-
'coldfront_notifications',
109-
],
110-
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
111-
DEFAULT_AUTO_FIELD='django.db.models.BigAutoField',
112-
AUTH_USER_MODEL='auth.User',
113-
ROOT_URLCONF='coldfront_notifications.urls',
114-
)
115-
django.setup()
116-
from coldfront_notifications import urls, models, views, template_variable_value_resolver, notification_validator, campaign_sender, filters, conf
117-
print('All modules importable')
97+
import coldfront_notifications
98+
print(f'Installed: coldfront_notifications {coldfront_notifications.__version__}')
11899
"
Lines changed: 106 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,118 @@
11
"""
2-
Django configuration for unit tests. Bootstraps the full ColdFront app
3-
stack so that top-level model imports in plugin modules resolve correctly.
2+
Django configuration for unit tests.
3+
4+
When ColdFront is installed (Docker, local dev), uses the full app stack.
5+
When running standalone (CI), stubs the ColdFront modules so that
6+
top-level imports in plugin code resolve without errors.
47
58
Import this module before any Django/plugin imports in test files.
69
"""
7-
import os
810
import sys
9-
10-
# Add coldfront root and sibling packages to sys.path
11-
_coldfront_root = os.path.abspath(
12-
os.path.join(os.path.dirname(__file__), "..", "..", "..")
13-
)
14-
_sibling_packages = (
15-
"",
16-
"ifxuser",
17-
"ifxbilling",
18-
"ifxreport",
19-
"ifxec",
20-
"ifxmail.client",
21-
"ifxurls",
22-
"nanites.client",
23-
"fiine.client",
24-
)
25-
for _subdir in _sibling_packages:
26-
_path = os.path.join(_coldfront_root, _subdir)
27-
if _path not in sys.path:
28-
sys.path.insert(0, _path)
11+
import types
2912

3013
import django
3114
from django.conf import settings
3215

16+
17+
def _stub_coldfront_modules():
18+
"""Register stub modules for ColdFront packages not installed in CI.
19+
20+
This lets top-level ``from coldfront.core.project.models import ...``
21+
resolve to MagicMock objects instead of raising ImportError.
22+
"""
23+
from unittest.mock import MagicMock
24+
25+
stub_paths = [
26+
"coldfront",
27+
"coldfront.core",
28+
"coldfront.core.allocation",
29+
"coldfront.core.allocation.models",
30+
"coldfront.core.department",
31+
"coldfront.core.department.models",
32+
"coldfront.core.field_of_science",
33+
"coldfront.core.field_of_science.models",
34+
"coldfront.core.project",
35+
"coldfront.core.project.models",
36+
"coldfront.core.resource",
37+
"coldfront.core.resource.models",
38+
"coldfront.core.user",
39+
"coldfront.core.utils",
40+
"coldfront.core.portal",
41+
"coldfront.core.grant",
42+
"coldfront.core.publication",
43+
"coldfront.core.research_output",
44+
"coldfront.plugins",
45+
"coldfront.plugins.ifx",
46+
"coldfront.plugins.ifx.models",
47+
"ifxuser",
48+
"ifxuser.models",
49+
"ifxbilling",
50+
"ifxbilling.models",
51+
]
52+
for path in stub_paths:
53+
if path not in sys.modules:
54+
sys.modules[path] = MagicMock()
55+
56+
57+
def _has_coldfront():
58+
"""Check if ColdFront is actually installed."""
59+
try:
60+
import coldfront # noqa: F401
61+
return True
62+
except ImportError:
63+
return False
64+
65+
3366
if not settings.configured:
34-
# Replicate the INSTALLED_APPS from coldfront.config.base
35-
# Hack required by ColdFront for fontawesome
36-
sys.modules['fontawesome_free'] = __import__('fontawesome-free')
67+
if _has_coldfront():
68+
settings.configure(
69+
INSTALLED_APPS=[
70+
"django.contrib.contenttypes",
71+
"django.contrib.auth",
72+
"django.contrib.admin",
73+
"django.contrib.sessions",
74+
"django.contrib.messages",
75+
"coldfront.core.field_of_science",
76+
"coldfront.core.project",
77+
"coldfront.core.resource",
78+
"coldfront.core.allocation",
79+
"coldfront.core.department",
80+
"coldfront.core.user",
81+
"coldfront.core.utils",
82+
"coldfront.plugins.ifx",
83+
"ifxuser",
84+
"ifxbilling",
85+
"simple_history",
86+
"django_q",
87+
"coldfront_notifications",
88+
],
89+
DATABASES={
90+
"default": {
91+
"ENGINE": "django.db.backends.sqlite3",
92+
"NAME": ":memory:",
93+
}
94+
},
95+
DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
96+
AUTH_USER_MODEL="ifxuser.IfxUser",
97+
SECRET_KEY="test-secret-key-not-for-production",
98+
IFX_APP={"token": "test-token"},
99+
)
100+
else:
101+
_stub_coldfront_modules()
102+
settings.configure(
103+
INSTALLED_APPS=[
104+
"django.contrib.contenttypes",
105+
"django.contrib.auth",
106+
"coldfront_notifications",
107+
],
108+
DATABASES={
109+
"default": {
110+
"ENGINE": "django.db.backends.sqlite3",
111+
"NAME": ":memory:",
112+
}
113+
},
114+
DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
115+
SECRET_KEY="test-secret-key-not-for-production",
116+
)
37117

38-
settings.configure(
39-
INSTALLED_APPS=[
40-
"django.contrib.admin",
41-
"django.contrib.auth",
42-
"django.contrib.contenttypes",
43-
"django.contrib.sessions",
44-
"django.contrib.messages",
45-
"django.contrib.staticfiles",
46-
"django.contrib.humanize",
47-
"crispy_forms",
48-
"crispy_bootstrap4",
49-
"simple_history",
50-
"fontawesome_free",
51-
"django_q",
52-
"coldfront.core.user",
53-
"coldfront.core.field_of_science",
54-
"coldfront.core.utils",
55-
"coldfront.core.portal",
56-
"coldfront.core.project",
57-
"coldfront.core.resource",
58-
"coldfront.core.allocation",
59-
"coldfront.core.grant",
60-
"coldfront.core.department",
61-
"coldfront.core.publication",
62-
"coldfront.core.research_output",
63-
"coldfront.plugins.ifx",
64-
"ifxuser",
65-
"ifxbilling",
66-
"ifxreport",
67-
"coldfront_notifications",
68-
],
69-
DATABASES={
70-
"default": {
71-
"ENGINE": "django.db.backends.sqlite3",
72-
"NAME": ":memory:",
73-
}
74-
},
75-
DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
76-
AUTH_USER_MODEL="ifxuser.IfxUser",
77-
CRISPY_TEMPLATE_PACK="bootstrap4",
78-
SECRET_KEY="test-secret-key-not-for-production",
79-
IFX_APP={"token": "test-token"},
80-
)
81118
django.setup()

0 commit comments

Comments
 (0)