From e70a8f75a306d31203c139ca25934d47b2c4a998 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Thu, 5 Mar 2026 13:05:27 +0100 Subject: [PATCH 1/8] chore: no sync for backend/unit tests (eleminates useless downloads of libs from dev group) --- .github/workflows/grimp.yml | 2 +- .github/workflows/mypy.yml | 2 +- .github/workflows/ruff.yml | 2 +- .github/workflows/stack-tests.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/grimp.yml b/.github/workflows/grimp.yml index 0d0fc584..87f399ea 100644 --- a/.github/workflows/grimp.yml +++ b/.github/workflows/grimp.yml @@ -29,4 +29,4 @@ jobs: - name: Run grimp orphan module check run: | cd backend - uv run python scripts/check_orphan_modules.py + uv run --no-sync python scripts/check_orphan_modules.py diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 35820dc5..b72bc733 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -31,4 +31,4 @@ jobs: SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }} run: | cd backend - uv run mypy --config-file pyproject.toml --strict . + uv run --no-sync mypy --config-file pyproject.toml --strict . diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index c81bfec0..2470a40a 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -29,4 +29,4 @@ jobs: - name: Run ruff run: | cd backend - uv run ruff check . --config pyproject.toml + uv run --no-sync ruff check . --config pyproject.toml diff --git a/.github/workflows/stack-tests.yml b/.github/workflows/stack-tests.yml index eee041c3..ccc42c7e 100644 --- a/.github/workflows/stack-tests.yml +++ b/.github/workflows/stack-tests.yml @@ -58,7 +58,7 @@ jobs: timeout-minutes: 5 run: | cd backend - uv run pytest tests/unit -v -rs \ + uv run --no-sync pytest tests/unit -v -rs \ --durations=0 \ --cov=app \ --cov-report=xml --cov-report=term From fabd8c9d02c59df2caefae076ab560bf138c7964 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Thu, 5 Mar 2026 13:38:35 +0100 Subject: [PATCH 2/8] chore: e2e-ready with parallel actions via crictl --- .github/actions/e2e-boot/action.yml | 24 ++++++++++++ .github/actions/e2e-ready/action.yml | 56 ++++++++++++++-------------- .github/workflows/stack-tests.yml | 13 +++++-- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/.github/actions/e2e-boot/action.yml b/.github/actions/e2e-boot/action.yml index e6d49b1a..1cd395b0 100644 --- a/.github/actions/e2e-boot/action.yml +++ b/.github/actions/e2e-boot/action.yml @@ -41,3 +41,27 @@ runs: echo "$K3S_INSTALL_SHA256 /tmp/k3s-install.sh" | sha256sum -c - chmod +x /tmp/k3s-install.sh INSTALL_K3S_VERSION="$K3S_VERSION" INSTALL_K3S_EXEC="--disable=traefik --bind-address 0.0.0.0 --tls-san host.docker.internal" /tmp/k3s-install.sh + + - name: Pre-pull executor images and Kueue manifest (background) + shell: bash + run: | + KUEUE_VERSION="${KUEUE_VERSION:-v0.16.1}" + + nohup bash -c ' + curl -fsSL -o /tmp/kueue-manifests.yaml \ + "https://github.com/kubernetes-sigs/kueue/releases/download/'"$KUEUE_VERSION"'/manifests.yaml" + echo $? > /tmp/kueue-download.exit + ' > /tmp/kueue-download.log 2>&1 & + echo $! > /tmp/kueue-download.pid + + nohup bash -c ' + sudo k3s crictl pull docker.io/library/python:3.11-slim + echo $? > /tmp/crictl-python.exit + ' > /tmp/crictl-python.log 2>&1 & + echo $! > /tmp/crictl-python.pid + + nohup bash -c ' + sudo k3s crictl pull docker.io/library/busybox:1.36 + echo $? > /tmp/crictl-busybox.exit + ' > /tmp/crictl-busybox.log 2>&1 & + echo $! > /tmp/crictl-busybox.pid diff --git a/.github/actions/e2e-ready/action.yml b/.github/actions/e2e-ready/action.yml index 3d2361a6..96634190 100644 --- a/.github/actions/e2e-ready/action.yml +++ b/.github/actions/e2e-ready/action.yml @@ -9,6 +9,10 @@ inputs: description: 'Also wait for frontend health check (default: false)' required: false default: 'false' + install-kueue: + description: 'Install Kueue CRDs for executor pods (default: true)' + required: false + default: 'true' runs: using: 'composite' @@ -26,18 +30,26 @@ runs: /home/runner/.kube/config > backend/kubeconfig.yaml chmod 644 backend/kubeconfig.yaml - - name: Install Kueue + - name: Install Kueue and wait for executor image pulls + if: inputs.install-kueue == 'true' shell: bash run: | + set -e + KUEUE_VERSION="${KUEUE_VERSION:-v0.16.1}" KUEUE_MANIFEST_SHA256="${KUEUE_MANIFEST_SHA256:-3201a66ff731be440ecfcf3c0fa5979d001b834f68389208fe7ee18017fbcfe8}" KUEUE_MANIFEST="/tmp/kueue-manifests.yaml" - curl -fsSL -o "$KUEUE_MANIFEST" "https://github.com/kubernetes-sigs/kueue/releases/download/${KUEUE_VERSION}/manifests.yaml" + + tail --pid="$(cat /tmp/kueue-download.pid)" -f /dev/null 2>/dev/null || true + cat /tmp/kueue-download.log 2>/dev/null || true + [ "$(cat /tmp/kueue-download.exit)" = "0" ] + echo "${KUEUE_MANIFEST_SHA256} ${KUEUE_MANIFEST}" | sha256sum -c - kubectl apply --server-side -f "$KUEUE_MANIFEST" rm -f "$KUEUE_MANIFEST" kubectl wait --for=condition=Available --timeout=120s \ deployment/kueue-controller-manager -n kueue-system + kubectl apply --server-side -f - <<'EOF' apiVersion: kueue.x-k8s.io/v1beta1 kind: ResourceFlavor @@ -69,34 +81,23 @@ runs: clusterQueue: executor-queue EOF - - name: Use test environment config - shell: bash - run: cp backend/config.test.toml backend/config.toml + tail --pid="$(cat /tmp/crictl-python.pid)" -f /dev/null 2>/dev/null || true + cat /tmp/crictl-python.log 2>/dev/null || true + [ "$(cat /tmp/crictl-python.exit)" = "0" ] - - name: Pre-pull test runtime images into K3s - shell: bash - run: | - sudo k3s crictl pull docker.io/library/python:3.11-slim - sudo k3s crictl pull docker.io/library/busybox:1.36 + tail --pid="$(cat /tmp/crictl-busybox.pid)" -f /dev/null 2>/dev/null || true + cat /tmp/crictl-busybox.log 2>/dev/null || true + [ "$(cat /tmp/crictl-busybox.exit)" = "0" ] - - name: Wait for image pull and infra + - name: Prepare config, wait for infra shell: bash run: | - if [ -f /tmp/infra-pull.pid ]; then - PID=$(cat /tmp/infra-pull.pid) - if kill -0 "$PID" 2>/dev/null; then - echo "Waiting for image pull + infra startup..." - tail --pid="$PID" -f /dev/null 2>/dev/null || true - fi - fi + set -e + cp backend/config.test.toml backend/config.toml + + tail --pid="$(cat /tmp/infra-pull.pid)" -f /dev/null 2>/dev/null || true cat /tmp/infra-pull.log 2>/dev/null || true - if [ -f /tmp/infra-pull.exit ]; then - EXIT_CODE=$(cat /tmp/infra-pull.exit) - if [ "$EXIT_CODE" != "0" ]; then - echo "::error::Background image pull / infra pre-warm failed (exit $EXIT_CODE)" - exit 1 - fi - fi + [ "$(cat /tmp/infra-pull.exit)" = "0" ] - name: Start stack shell: bash @@ -110,11 +111,10 @@ runs: WAIT_FOR_FRONTEND: ${{ inputs.wait-for-frontend }} run: | echo "Waiting for backend health..." - timeout 120 bash -c 'until curl -ksf https://localhost/api/v1/health/live 2>/dev/null; do sleep 2; done' + timeout 120 bash -c 'until curl -ksf https://localhost/api/v1/health/live 2>/dev/null; do sleep 1; done' echo "Backend ready" if [ "$WAIT_FOR_FRONTEND" = "true" ]; then echo "Waiting for frontend health..." - timeout 60 bash -c 'until curl -ksf https://localhost:5001 2>/dev/null; do sleep 2; done' + timeout 60 bash -c 'until curl -ksf https://localhost:5001 2>/dev/null; do sleep 1; done' echo "Frontend ready" fi - diff --git a/.github/workflows/stack-tests.yml b/.github/workflows/stack-tests.yml index ccc42c7e..de9cef02 100644 --- a/.github/workflows/stack-tests.yml +++ b/.github/workflows/stack-tests.yml @@ -236,12 +236,14 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/e2e-boot + - name: Boot E2E environment + uses: ./.github/actions/e2e-boot with: image-tag: ${{ needs.build-images.outputs.sha-tag }} github-token: ${{ secrets.GITHUB_TOKEN }} - - uses: ./.github/actions/e2e-ready + - name: Finalize k3s & start stack + uses: ./.github/actions/e2e-ready with: image-tag: ${{ needs.build-images.outputs.sha-tag }} @@ -305,7 +307,8 @@ jobs: - uses: actions/checkout@v6 # Phase 1: kick off image pull + infra + k3s in background - - uses: ./.github/actions/e2e-boot + - name: Boot E2E environment + uses: ./.github/actions/e2e-boot with: image-tag: ${{ needs.build-images.outputs.sha-tag }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -339,10 +342,12 @@ jobs: run: npx playwright install chromium # Phase 3: finalize k3s + start stack (k3s has been booting since e2e-boot) - - uses: ./.github/actions/e2e-ready + - name: Finalize k3s & start stack + uses: ./.github/actions/e2e-ready with: image-tag: ${{ needs.build-images.outputs.sha-tag }} wait-for-frontend: 'true' + install-kueue: 'false' - name: Run Playwright tests timeout-minutes: 10 From dc7623d6738721273b21edd048748f27b7882a07 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Thu, 5 Mar 2026 13:42:58 +0100 Subject: [PATCH 3/8] chore: mypy test group deps added --- .github/workflows/mypy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index b72bc733..30bd9f71 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -24,7 +24,7 @@ jobs: run: | cd backend uv python install 3.12 - uv sync --frozen --group lint --no-dev + uv sync --frozen --group lint --group test --no-dev - name: Run mypy env: From d20bbf4092db342fb96a2965dc12fab5a1736f85 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Thu, 5 Mar 2026 14:50:33 +0100 Subject: [PATCH 4/8] chore: frontend/unit update --- frontend/package-lock.json | 27 +++++-------------- frontend/package.json | 1 + frontend/src/__tests__/test-utils.ts | 16 +++++++++++ frontend/src/components/Pagination.svelte | 8 +++--- .../components/__tests__/Pagination.test.ts | 6 ++--- .../admin/AutoRefreshControl.svelte | 23 ++++++---------- .../src/routes/admin/AdminExecutions.svelte | 8 +++--- frontend/src/routes/admin/AdminSagas.svelte | 7 ++--- frontend/src/routes/admin/AdminUsers.svelte | 5 ++-- .../admin/__tests__/AdminEvents.test.ts | 3 ++- .../admin/__tests__/AdminExecutions.test.ts | 12 ++++----- .../routes/admin/__tests__/AdminSagas.test.ts | 10 +++---- .../routes/admin/__tests__/AdminUsers.test.ts | 10 +++---- frontend/vitest.config.ts | 12 ++++----- frontend/vitest.setup.ts | 26 +++++++++++++++++- 15 files changed, 97 insertions(+), 77 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 381e22bd..85756696 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -59,6 +59,7 @@ "eslint-plugin-svelte": "^3.15.0", "express": "^5.2.1", "globals": "^17.3.0", + "happy-dom": "^20.8.3", "http-proxy": "^1.18.1", "jsdom": "^28.1.0", "monocart-reporter": "^2.10.0", @@ -2550,8 +2551,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2571,17 +2570,13 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "@types/node": "*" } @@ -5299,12 +5294,10 @@ "dev": true }, "node_modules/happy-dom": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.7.0.tgz", - "integrity": "sha512-hR/uLYQdngTyEfxnOoa+e6KTcfBFyc1hgFj/Cc144A5JJUuHFYqIEBDcD4FeGqUeKLRZqJ9eN9u7/GDjYEgS1g==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.3.tgz", + "integrity": "sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", @@ -5322,8 +5315,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=0.12" }, @@ -5336,8 +5327,6 @@ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=12" } @@ -5347,8 +5336,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -9773,9 +9760,7 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/unicorn-magic": { "version": "0.4.0", diff --git a/frontend/package.json b/frontend/package.json index 88cd8215..875b8e94 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -71,6 +71,7 @@ "eslint-plugin-svelte": "^3.15.0", "express": "^5.2.1", "globals": "^17.3.0", + "happy-dom": "^20.8.3", "http-proxy": "^1.18.1", "jsdom": "^28.1.0", "monocart-reporter": "^2.10.0", diff --git a/frontend/src/__tests__/test-utils.ts b/frontend/src/__tests__/test-utils.ts index ae049433..910776ca 100644 --- a/frontend/src/__tests__/test-utils.ts +++ b/frontend/src/__tests__/test-utils.ts @@ -352,3 +352,19 @@ export const createMockUsers = (count: number): UserResponse[] => is_active: i % 3 !== 0, }), ); + +/** + * Selects an option in a onPageSizeChange?.(Number(e.currentTarget.value))} + bind:value={pageSize} + onchange={() => onPageSizeChange?.()} aria-label="Items per page" > {#each pageSizeOptions as size} diff --git a/frontend/src/components/__tests__/Pagination.test.ts b/frontend/src/components/__tests__/Pagination.test.ts index 59c16df6..cea97923 100644 --- a/frontend/src/components/__tests__/Pagination.test.ts +++ b/frontend/src/components/__tests__/Pagination.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/svelte'; -import { user } from '$test/test-utils'; +import { user, selectOption } from '$test/test-utils'; import Pagination from '$components/Pagination.svelte'; const defaultProps = { @@ -85,8 +85,8 @@ describe('Pagination', () => { it('fires onPageSizeChange on select change', async () => { const onPageSizeChange = vi.fn(); renderPagination({ onPageSizeChange } as Record); - await user.selectOptions(screen.getByRole('combobox'), '25'); - expect(onPageSizeChange).toHaveBeenCalledWith(25); + selectOption(screen.getByRole('combobox'), '25'); + expect(onPageSizeChange).toHaveBeenCalled(); }); }); diff --git a/frontend/src/components/admin/AutoRefreshControl.svelte b/frontend/src/components/admin/AutoRefreshControl.svelte index 2cff796d..31e7c2de 100644 --- a/frontend/src/components/admin/AutoRefreshControl.svelte +++ b/frontend/src/components/admin/AutoRefreshControl.svelte @@ -31,18 +31,6 @@ onEnabledChange, onRateChange, }: Props = $props(); - - function handleEnabledChange(e: Event): void { - const target = e.target as HTMLInputElement; - enabled = target.checked; - onEnabledChange?.(enabled); - } - - function handleRateChange(e: Event): void { - const target = e.target as HTMLSelectElement; - rate = parseInt(target.value, 10); - onRateChange?.(rate); - }
@@ -50,8 +38,8 @@