Skip to content

Commit b7b2f36

Browse files
authored
Fix frontend dist shadowing in integration tests
* Update tests to account for UI menu updates * Reuse pyenv-builder cache in OIDC integration test
1 parent e28198a commit b7b2f36

File tree

7 files changed

+122
-21
lines changed

7 files changed

+122
-21
lines changed

.github/workflows/oidc-integration-test.yml

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,44 @@
22
name: "OIDC Integration Test"
33
on:
44
workflow_dispatch:
5+
env:
6+
UBUNTU_VERSION: "24.04"
7+
PYTHON_VERSION: "3.10"
58
jobs:
9+
cache-pyenv-builder:
10+
name: "Cache layers of the pyenv-builder stage"
11+
runs-on: "ubuntu-24.04"
12+
steps:
13+
- name: "Check out repository"
14+
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
15+
- name: "Set up Docker Buildx"
16+
uses: "docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f" # v3.12.0
17+
with:
18+
driver-opts: |
19+
network=host
20+
- name: "Generate cache key from requirements"
21+
id: cache_key
22+
run: |
23+
echo "requirements_hash=${{ hashFiles('requirements*.txt', 'requirements*.in', 'pyproject.toml') }}" >> $GITHUB_OUTPUT
24+
- name: "Build base layers"
25+
uses: "docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8" # v6.19.2
26+
with:
27+
context: .
28+
file: ./hack/Dockerfile
29+
target: pyenv-builder
30+
build-args: |
31+
UBUNTU_VERSION=${{ env.UBUNTU_VERSION }}
32+
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
33+
BUILDKIT_INLINE_CACHE=1
34+
push: false
35+
cache-from: |
36+
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
37+
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }}
38+
cache-to: |
39+
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
40+
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }},mode=max
641
test:
42+
needs: [cache-pyenv-builder]
743
name: "Test"
844
runs-on: "ubuntu-24.04"
945
steps:
@@ -17,8 +53,38 @@ jobs:
1753
id: group_id
1854
run: |
1955
echo "group_id=$(id -g)" >> $GITHUB_OUTPUT
56+
- name: "Generate cache key from requirements"
57+
id: cache_key
58+
run: |
59+
echo "requirements_hash=${{ hashFiles('requirements*.txt', 'requirements*.in', 'pyproject.toml') }}" >> $GITHUB_OUTPUT
2060
- name: "Set up buildx"
2161
uses: "docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f" # v3.12.0
62+
with:
63+
driver-opts: |
64+
network=host
65+
- name: "Build integration test image"
66+
uses: "docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8" # v6.19.2
67+
with:
68+
context: .
69+
file: ./hack/Dockerfile
70+
target: archivematica-dashboard-integration-tests
71+
build-args: |
72+
USER_ID=${{ steps.user_id.outputs.user_id }}
73+
GROUP_ID=${{ steps.group_id.outputs.group_id }}
74+
UBUNTU_VERSION=${{ env.UBUNTU_VERSION }}
75+
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
76+
BUILDKIT_INLINE_CACHE=1
77+
tags: archivematica-dashboard-integration-tests:latest
78+
push: false
79+
load: true
80+
cache-from: |
81+
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
82+
type=gha,scope=archivematica-oidc-integration-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
83+
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }}
84+
cache-to: |
85+
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
86+
type=gha,scope=archivematica-oidc-integration-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
87+
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }},mode=max
2288
- name: "Run tests"
2389
run: |
2490
./run.sh
@@ -27,8 +93,9 @@ jobs:
2793
env:
2894
USER_ID: ${{ steps.user_id.outputs.user_id }}
2995
GROUP_ID: ${{ steps.group_id.outputs.group_id }}
30-
UBUNTU_VERSION: "24.04"
31-
PYTHON_VERSION: "3.10"
96+
UBUNTU_VERSION: ${{ env.UBUNTU_VERSION }}
97+
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
98+
SKIP_DOCKER_BUILD: 1
3299
COMPOSE_DOCKER_CLI_BUILD: 1
33100
DOCKER_BUILDKIT: 1
34101
PYTEST_ADDOPTS: -vv

hack/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ FROM base AS archivematica-tests
283283
FROM base AS archivematica-dashboard-integration-tests
284284

285285
USER root
286+
ARG USER_ID
287+
ARG GROUP_ID
286288

287289
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
288290
--mount=type=cache,target=/var/lib/apt,sharing=locked \
@@ -295,6 +297,9 @@ RUN set -ex \
295297
&& mkdir -p /var/archivematica/.cache/ms-playwright \
296298
&& python3 -m playwright install firefox
297299

300+
# Copy frontend assets out of where /src is bind-mounted during tests.
301+
COPY --chown=${USER_ID}:${GROUP_ID} --from=archivematica-dashboard-vue-builder --link /src/src/archivematica/dashboard/vue/dist /opt/am-dashboard-dist
302+
298303
# -----------------------------------------------------------------------------
299304

300305
FROM ${TARGET}

src/archivematica/dashboard/settings/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,11 @@ def _get_settings_from_file(path):
385385
# Examples: "http://foo.com/static/admin/", "/static/admin/".
386386
ADMIN_MEDIA_PREFIX = "/media/"
387387

388+
# Allow tests to serve frontend assets from a path not shadowed by bind mounts over /src.
389+
FRONTEND_DIST_DIR = os.environ.get(
390+
"FRONTEND_DIST_DIR", os.path.join(BASE_PATH, "vue", "dist")
391+
)
392+
388393
# Additional locations of static files
389394
STATICFILES_DIRS = (
390395
# Put strings here, like "/home/html/static" or "C:/www/django/static".
@@ -394,7 +399,7 @@ def _get_settings_from_file(path):
394399
("css", os.path.join(BASE_PATH, "media", "css")),
395400
("images", os.path.join(BASE_PATH, "media", "images")),
396401
("vendor", os.path.join(BASE_PATH, "media", "vendor")),
397-
("vue", os.path.join(BASE_PATH, "vue", "dist")),
402+
("vue", FRONTEND_DIST_DIR),
398403
)
399404

400405
# List of finder classes that know how to find static files in

tests/integration/docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ services:
2222
start_period: 15s
2323

2424
archivematica-dashboard:
25+
image: "archivematica-dashboard-integration-tests:latest"
2526
build:
2627
context: "../../"
2728
dockerfile: "hack/Dockerfile"
@@ -85,6 +86,8 @@ services:
8586
OIDC_ACCESS_ATTRIBUTE_MAP_SECONDARY: >
8687
{"given_name": "first_name", "family_name": "last_name", "realm_access": "realm_access"}
8788
OIDC_RP_SIGN_ALGO: "RS256"
89+
# /src is bind-mounted, so point Django staticfiles to the image-owned dist copy.
90+
FRONTEND_DIST_DIR: "/opt/am-dashboard-dist"
8891
volumes:
8992
- "../../:/src"
9093
depends_on:

tests/integration/run.sh

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44

55
cd ${__dir}
66

7-
docker compose build archivematica-dashboard
7+
if [ -z "${SKIP_DOCKER_BUILD}" ]; then
8+
docker compose build archivematica-dashboard
89

9-
status=$?
10+
status=$?
1011

11-
if [ $status -ne 0 ]; then
12-
exit $status
12+
if [ $status -ne 0 ]; then
13+
exit $status
14+
fi
1315
fi
1416

1517
docker compose run --rm archivematica-dashboard

tests/integration/test_auth.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
from django.contrib.auth.models import AbstractUser
66
from django.urls import reverse
77
from playwright.sync_api import Page
8+
from playwright.sync_api import expect
89
from pytest_django.live_server_helper import LiveServer
910

1011
if "RUN_INTEGRATION_TESTS" not in os.environ:
1112
pytest.skip("Skipping integration tests", allow_module_level=True)
1213

1314

15+
def click_logout_from_user_menu(page: Page) -> None:
16+
user_menu = page.locator("li.user.dropdown")
17+
expect(user_menu).to_be_visible()
18+
user_menu.evaluate("node => node.classList.add('open')")
19+
page.get_by_role("button", name="Log out").click()
20+
21+
1422
@pytest.mark.django_db
1523
def test_logout_link_logs_out_user(
1624
page: Page, live_server: LiveServer, dashboard_uuid: uuid.UUID, user: AbstractUser
1725
) -> None:
1826
page.goto(live_server.url)
27+
1928
assert page.url == f"{live_server.url}{reverse('accounts:login')}"
2029

2130
page.get_by_label("Username").fill("foobar")
@@ -24,7 +33,6 @@ def test_logout_link_logs_out_user(
2433

2534
assert page.url == f"{live_server.url}/transfer/"
2635

27-
page.get_by_text("foobar").click()
28-
page.get_by_role("button", name="Log out").click()
36+
click_logout_from_user_menu(page)
2937

3038
assert page.url == f"{live_server.url}{reverse('accounts:login')}"

tests/integration/test_oidc_auth.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,30 @@
55
from django.contrib.auth.models import User
66
from django.urls import reverse
77
from playwright.sync_api import Page
8+
from playwright.sync_api import expect
89
from pytest_django.fixtures import SettingsWrapper
910
from pytest_django.live_server_helper import LiveServer
1011

1112
if "RUN_INTEGRATION_TESTS" not in os.environ:
1213
pytest.skip("Skipping integration tests", allow_module_level=True)
1314

1415

16+
def open_user_menu(page: Page) -> None:
17+
user_menu = page.locator("li.user.dropdown")
18+
expect(user_menu).to_be_visible()
19+
user_menu.evaluate("node => node.classList.add('open')")
20+
21+
22+
def click_profile_from_user_menu(page: Page) -> None:
23+
open_user_menu(page)
24+
page.get_by_role("link", name="Your profile").click()
25+
26+
27+
def click_logout_from_user_menu(page: Page) -> None:
28+
open_user_menu(page)
29+
page.get_by_role("button", name="Log out").click()
30+
31+
1532
@pytest.mark.django_db
1633
def test_oidc_backend_creates_local_user(
1734
page: Page,
@@ -27,8 +44,7 @@ def test_oidc_backend_creates_local_user(
2744
page.get_by_role("button", name="Sign In").click()
2845

2946
assert page.url == f"{live_server.url}/transfer/"
30-
page.get_by_text("demo@example.com").click()
31-
page.get_by_role("link", name="Your profile").click()
47+
click_profile_from_user_menu(page)
3248

3349
assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
3450
assert [
@@ -66,8 +82,7 @@ def test_local_authentication_backend_authenticates_existing_user(
6682

6783
assert page.url == f"{live_server.url}/transfer/"
6884

69-
page.get_by_text("foobar").click()
70-
page.get_by_role("link", name="Your profile").click()
85+
click_profile_from_user_menu(page)
7186

7287
assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
7388
assert [
@@ -147,8 +162,7 @@ def test_setting_request_parameter_in_local_login_url_redirects_to_secondary_pro
147162
page.get_by_role("button", name="Sign In").click()
148163

149164
assert page.url == f"{live_server.url}/transfer/"
150-
page.get_by_text("supportadmin@example.com").click()
151-
page.get_by_role("link", name="Your profile").click()
165+
click_profile_from_user_menu(page)
152166

153167
assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
154168

@@ -172,8 +186,7 @@ def test_setting_request_parameter_in_local_login_url_redirects_to_secondary_pro
172186

173187
assert page.url == f"{live_server.url}/transfer/"
174188

175-
page.get_by_text("supportdefault@example.com").click()
176-
page.get_by_role("link", name="Your profile").click()
189+
click_profile_from_user_menu(page)
177190

178191
assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
179192
assert [
@@ -211,8 +224,7 @@ def test_logging_out_logs_out_user_from_secondary_provider_admin_role(
211224
assert page.url == f"{live_server.url}/transfer/"
212225

213226
# Logging out redirects the user to the login url.
214-
page.get_by_text("supportadmin@example.com").click()
215-
page.get_by_role("button", name="Log out").click()
227+
click_logout_from_user_menu(page)
216228
assert page.url == f"{live_server.url}{reverse('accounts:login')}"
217229

218230
# Logging in through the OIDC provider requires to authenticate again.
@@ -244,8 +256,7 @@ def test_logging_out_logs_out_user_from_secondary_provider_default_role(
244256
assert page.url == f"{live_server.url}/transfer/"
245257

246258
# Logging out redirects the user to the login url.
247-
page.get_by_text("supportdefault@example.com").click()
248-
page.get_by_role("button", name="Log out").click()
259+
click_logout_from_user_menu(page)
249260
assert page.url == f"{live_server.url}{reverse('accounts:login')}"
250261

251262
# Logging in through the OIDC provider requires to authenticate again.

0 commit comments

Comments
 (0)