Skip to content

Conversation

@timonrieger
Copy link
Owner

@timonrieger timonrieger commented Jan 11, 2026

@coderabbitai review

Summary by CodeRabbit

  • New Features

    • CI now runs a gated end-to-end job; release workflow depends on it and release concurrency no longer cancels in-progress runs.
  • Tests

    • Added a comprehensive e2e test suite for uploads, downloads, playback, archives, and profile images.
    • Added local and CI e2e tasks and pytest e2e marker with asyncio mode.
  • Bug Fixes

    • Improved cross-platform file timestamp handling.
  • Chores

    • Removed an early external-spec network sanity check from client generation.
  • Dev Dependencies

    • Updated image-handling dev dependency (pillow).

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end test support and CI wiring: new GitHub Actions e2e-tests job, CI mise config, mise task entries, e2e test modules and data generators, a generator script preflight removal, a small platform fix for file birthtime retrieval, and pytest/dev dependency/test marker updates.

Changes

Cohort / File(s) Summary
CI/CD Workflow
\.github/workflows/cd.yml``
Adds e2e-tests job (checkout, install mise with --env ci, determine IMMICH-VERSION, clone Immich, GHCR auth, docker-compose up Immich e2e env, wait for readiness via container logs, set IMMICH_URL/IMMICH_API_URL, run mise run ci:test:e2e, print logs on failure, teardown). Updates release job needs to include e2e-tests and sets concurrency.cancel-in-progress: false.
Task Definitions
\mise.toml``
Adds [tasks."test:e2e"] and [tasks."ci:test:e2e"]; adjusts test:unit and test:unit:cov to target tests/unit.
CI Tooling Override
\mise.ci.toml``
Adds CI tool pin for uv = "0.9.21" used by CI installs.
Test Configuration
\pyproject.toml``
Adds dev dependency pillow>=12.1.0,<13.0.0; sets asyncio_mode = "auto" and a pytest e2e marker.
End-to-End Tests
\tests/e2e/client/test_wrappers.py`, `tests/e2e/client/generators.py``
Adds comprehensive e2e tests for client wrappers (signup/login, API key creation, upload/download, thumbnails, video playback, archive export, profile image) and generators producing minimal JPEG/MP4 bytes for uploads.
Generator Script
\bin/generate.py``
Removes an early network sanity check / try-except that fetched the OpenAPI spec; generator now proceeds without explicit preflight verification.
Platform fix
\immich/_internal/upload.py``
Passes path as string to statx for birthtime (btime) on Unix/Linux; retains st_ctime fallback on Windows.

Sequence Diagram(s)

sequenceDiagram
  actor Developer
  participant GitHubActions as GitHub Actions
  participant Repo as Repository
  participant Mise as Mise (uv)
  participant ImmichRepo as Immich Git Repo
  participant Docker as Docker Compose
  participant Pytest as pytest (e2e)

  Developer ->> GitHubActions: push / PR (or label run-e2e)
  GitHubActions ->> Repo: checkout repository
  GitHubActions ->> Mise: install tooling (mise + --env ci), read IMMICH-VERSION
  Mise ->> ImmichRepo: clone specified Immich version
  Mise ->> Docker: docker-compose up (start Immich e2e environment)
  GitHubActions ->> Docker: poll container logs until server ready
  GitHubActions ->> Pytest: run `mise run ci:test:e2e` against IMMICH_URL/IMMICH_API_URL
  alt tests fail
    Docker ->> GitHubActions: collect & print container logs
  end
  GitHubActions ->> Docker: docker-compose down (always)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped into the CI night,
Containers hummed and tests took flight,
Pixels marched in tiny rows,
Keys and uploads in gentle throes,
A carrot cheer for passing light!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title Check ✅ Passed Title check skipped as CodeRabbit has written the PR title.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot changed the title @coderabbitai test: add end-to-end testing infrastructure Jan 11, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @tests/e2e/client/test_wrappers.py:
- Around line 85-87: The unit test job is running E2E tests (e.g.,
test_assets_upload marked with @pytest.mark.e2e) because mise.toml's test:unit
task runs "uv run pytest tests/" without excluding e2e; update mise.toml to
change the test:unit pytest command to exclude e2e-marked tests (for example add
"-m 'not e2e'" or equivalent pytest args) and likewise update the coverage task
to run pytest with the same exclusion so coverage only covers unit tests.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 720f859 and a4997f9.

📒 Files selected for processing (10)
  • .github/workflows/cd.yml
  • bin/generate.py
  • mise.toml
  • pyproject.toml
  • tests/e2e/client/__init__.py
  • tests/e2e/client/test_wrappers.py
  • tests/unit/test_async_client.py
  • tests/unit/test_async_custom_session.py
  • tests/unit/test_download.py
  • tests/unit/test_upload.py
💤 Files with no reviewable changes (1)
  • bin/generate.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-07T00:43:43.563Z
Learnt from: CR
Repo: timonrieger/immich-python-client PR: 0
File: .cursor/rules/dev-tools.mdc:0-0
Timestamp: 2026-01-07T00:43:43.563Z
Learning: Use the mise tasks to run commands on the project. See the mise.toml file for the available tasks.

Applied to files:

  • mise.toml
🧬 Code graph analysis (1)
tests/e2e/client/test_wrappers.py (4)
immich/sdk.py (1)
  • AsyncClient (72-365)
immich/client_wrapper/assets_api_wrapped.py (1)
  • upload (167-260)
immich/client_wrapper/download_api_wrapped.py (1)
  • download_archive_to_file (17-110)
immich/client_wrapper/users_api_wrapped.py (1)
  • get_profile_image_to_file (12-51)
🪛 GitHub Actions: Continuous Delivery
tests/e2e/client/test_wrappers.py

[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_assets_upload setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.


[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_assets_download_asset_to_file setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.


[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_assets_view_asset_to_file setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.


[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_assets_play_asset_video_to_file setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.


[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_download_archive_to_file setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.


[error] 38-38: Cannot connect to API at 127.0.0.1:2285 during test_users_get_profile_image_to_file setup (sign_up_admin). The API server may not be running or is unreachable. Command: uv run pytest tests/.

🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

29-29: Unused function argument: tmp_path

(ARG001)


39-39: Possible hardcoded password assigned to argument: "password"

(S106)


44-44: Possible hardcoded password assigned to argument: "password"

(S106)


87-87: Unused function argument: tmp_path

(ARG001)

🔇 Additional comments (7)
pyproject.toml (1)

87-90: LGTM!

The pytest configuration properly enables async mode and defines the e2e marker with a helpful deselection hint. This aligns well with the new e2e test suite structure.

mise.toml (1)

21-24: LGTM!

The new e2e tasks follow the established patterns (test:unit/ci:test:unit) and correctly chain the install step before running tests in CI. Based on learnings, this aligns with the project's task-based workflow.

Also applies to: 87-92

tests/e2e/client/test_wrappers.py (3)

28-66: Well-structured fixture with proper resource cleanup.

The fixture correctly handles client lifecycle with try/finally ensuring setup_client is closed even on exceptions, and the yielded client is closed after test completion. The hardcoded credentials are acceptable here since they're used against an ephemeral Docker-based test server.


103-124: LGTM!

The test correctly verifies the download functionality by comparing the downloaded bytes against the original test image. Good use of the arrange-act-assert pattern.


173-197: LGTM!

Good coverage of the archive download functionality. The test properly uses DownloadInfoDto to request an archive and validates both existence and file extension.

.github/workflows/cd.yml (2)

49-108: Well-structured e2e job with proper setup/teardown.

Good practices observed:

  • Timeout-based readiness polling with informative progress output
  • Docker logs printed on failure for debugging
  • Unconditional teardown with if: always() ensures cleanup regardless of test outcome

110-112: E2E tests not gating the release.

The release job depends only on [lint, test] but not e2e-tests. If e2e tests should be required before releasing, add the dependency:

♻️ Optional: Add e2e-tests as release prerequisite
   release:
     runs-on: ubuntu-latest
-    needs: [lint, test]
+    needs: [lint, test, e2e-tests]
     if: github.ref == 'refs/heads/main' && github.event_name == 'push'

If e2e tests are intentionally informational-only (non-blocking), this is fine as-is—please confirm the intended behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/cd.yml (1)

65-73: Consider adding error handling for missing IMMICH-VERSION file.

If the IMMICH-VERSION file doesn't exist or is empty, the workflow will fail with an unclear error during the git clone step. Adding validation would improve debuggability.

🛠️ Suggested improvement
       - name: Read Immich version
         id: immich-version
-        run: echo "version=$(cat IMMICH-VERSION | tr -d '[:space:]')" >> $GITHUB_OUTPUT
+        run: |
+          if [ ! -f IMMICH-VERSION ]; then
+            echo "::error::IMMICH-VERSION file not found"
+            exit 1
+          fi
+          version=$(cat IMMICH-VERSION | tr -d '[:space:]')
+          if [ -z "$version" ]; then
+            echo "::error::IMMICH-VERSION file is empty"
+            exit 1
+          fi
+          echo "version=$version" >> $GITHUB_OUTPUT
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4997f9 and 5a4612c.

📒 Files selected for processing (2)
  • .github/workflows/cd.yml
  • mise.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-07T00:43:43.563Z
Learnt from: CR
Repo: timonrieger/immich-python-client PR: 0
File: .cursor/rules/dev-tools.mdc:0-0
Timestamp: 2026-01-07T00:43:43.563Z
Learning: Use the mise tasks to run commands on the project. See the mise.toml file for the available tasks.

Applied to files:

  • mise.toml
🔇 Additional comments (7)
.github/workflows/cd.yml (4)

49-63: LGTM on job setup and tooling installation.

The job structure with read-only permissions and pinned action versions follows the existing patterns in the workflow.


75-92: Server readiness polling looks solid.

The timeout mechanism with incremental sleep and progress logging is well-implemented. Showing docker logs on timeout failure aids debugging.

One minor consideration: the curl -f only checks HTTP success, but if the endpoint returns a non-JSON response or unexpected content, tests could still fail unexpectedly. This is likely fine given you control the Immich version being tested.


94-108: Teardown and failure handling are well-structured.

Using if: always() for teardown and if: failure() for logs ensures proper cleanup and debugging capability.


112-112: Verify: e2e-tests job not included in release dependencies.

The release job depends on [lint, test] but not e2e-tests. This means failing e2e tests won't block releases. Please confirm this is intentional—if e2e failures should gate releases, add e2e-tests to the needs array.

🔧 If e2e tests should gate releases
   release:
     runs-on: ubuntu-latest
-    needs: [lint, test]
+    needs: [lint, test, e2e-tests]
mise.toml (3)

17-23: Clean separation of unit and e2e test paths.

The updated test:unit path and new test:e2e task follow consistent naming conventions and prevent accidental cross-execution of test suites.


25-27: Coverage task correctly updated to match unit test path.

Ensures coverage reporting only runs against unit tests, which is appropriate since e2e tests typically shouldn't contribute to unit coverage metrics.


87-92: CI e2e task follows established patterns.

The ci:test:e2e task mirrors the structure of ci:test:unit, maintaining consistency across the task definitions. Based on learnings, using mise tasks is the standard approach for this project.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/cd.yml:
- Around line 114-116: The release job's needs array currently lists only lint
and test, so E2E failures won't block releases; update the release job
definition (the release job's "needs" array) to include the e2e-tests job name
(add "e2e-tests" to the needs list) so the release job depends on successful
completion of E2E tests.
🧹 Nitpick comments (2)
tests/e2e/client/test_wrappers.py (2)

28-68: Remove unused parameter and improve client cleanup.

  1. The tmp_path parameter is unused and should be removed.
  2. If the test raises an exception, await client.close() (line 66) will be skipped, causing a resource leak. Consider wrapping the client in its own try/finally block.
♻️ Suggested fix
 @pytest.fixture
-async def client_with_api_key(tmp_path: Path):
+async def client_with_api_key():
     """Set up admin user, create API key, and return authenticated client."""
     base_url = os.environ.get("IMMICH_API_URL", "http://127.0.0.1:2285/api")

     # Create unauthenticated client for setup
     setup_client = AsyncClient(base_url=base_url)

     try:
         # Sign up admin
         await setup_client.authentication.sign_up_admin(
             SignUpDto(
                 email="admin@immich.cloud", name="Immich Admin", password="password"
             )
         )

         # Login to get access token
         login_response = await setup_client.authentication.login(
             LoginCredentialDto(email="admin@immich.cloud", password="password")
         )

         # Mark admin as onboarded
         await setup_client.system_metadata.update_admin_onboarding(
             AdminOnboardingUpdateDto(is_onboarded=True),
             _headers={"Authorization": f"Bearer {login_response.access_token}"},
         )

         # Create API key with all permissions
         api_key_response = await setup_client.api_keys.create_api_key(
             APIKeyCreateDto(name="e2e", permissions=[Permission.ALL]),
             _headers={"Authorization": f"Bearer {login_response.access_token}"},
         )

         # Create authenticated client with API key
         client = AsyncClient(base_url=base_url, api_key=api_key_response.secret)

-        yield client
-
-        await client.close()
+        try:
+            yield client
+        finally:
+            await client.close()
     finally:
         await setup_client.close()

87-104: Remove unused tmp_path parameter.

The tmp_path parameter is not used in this test function.

♻️ Suggested fix
 @pytest.mark.asyncio
 @pytest.mark.e2e
 async def test_assets_upload(
-    client_with_api_key: AsyncClient, test_image: Path, test_video: Path, tmp_path: Path
+    client_with_api_key: AsyncClient, test_image: Path, test_video: Path
 ):
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a4612c and 7981362.

📒 Files selected for processing (3)
  • .github/workflows/cd.yml
  • bin/generate.py
  • tests/e2e/client/test_wrappers.py
💤 Files with no reviewable changes (1)
  • bin/generate.py
🧰 Additional context used
🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

29-29: Unused function argument: tmp_path

(ARG001)


40-40: Possible hardcoded password assigned to argument: "password"

(S106)


46-46: Possible hardcoded password assigned to argument: "password"

(S106)


90-90: Unused function argument: tmp_path

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (8)
tests/e2e/client/test_wrappers.py (7)

1-25: LGTM!

Imports are appropriate for the E2E test module, and the minimal base64-encoded test assets provide self-contained test data without external dependencies.


71-84: LGTM!

Clean implementation of test asset fixtures using tmp_path for automatic cleanup.


107-128: LGTM!

Good test coverage: uploads an asset, downloads it, and verifies the content matches the original.


131-151: LGTM!

Appropriate assertions for thumbnail download—verifying existence without content comparison since thumbnails are server-generated transformations.


154-174: LGTM!

Video playback/download test correctly verifies the output file exists.


177-201: LGTM!

Archive download test correctly verifies the output is a zip file.


204-225: LGTM!

Complete workflow test for profile image upload and download.

.github/workflows/cd.yml (1)

49-112: Well-structured E2E test job.

Good implementation with proper health checking, timeout handling, log capture on failure, and guaranteed teardown with if: always().

timonrieger and others added 4 commits January 12, 2026 10:47
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@timonrieger timonrieger added the run-e2e Set this label when expensive e2e tests should run on a PR. label Jan 12, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/e2e/client/test_wrappers.py (2)

28-69: Remove unused tmp_path parameter and consider more robust client cleanup.

The tmp_path parameter on line 29 is unused. Additionally, if an exception occurs after client is created but before yield, the client won't be properly closed.

Suggested improvement
 @pytest.fixture
-async def client_with_api_key(tmp_path: Path):
+async def client_with_api_key():
     """Set up admin user, create API key, and return authenticated client."""
     base_url = os.environ.get("IMMICH_API_URL", "http://127.0.0.1:2285/api")

     # Create unauthenticated client for setup
     setup_client = AsyncClient(base_url=base_url)
+    client = None

     try:
         # Sign up admin
         await setup_client.authentication.sign_up_admin(
             SignUpDto(
                 email="admin@immich.cloud", name="Immich Admin", password="password"
             )
         )

         # Login to get access token
         login_response = await setup_client.authentication.login(
             LoginCredentialDto(email="admin@immich.cloud", password="password")
         )

         # Mark admin as onboarded
         await setup_client.system_metadata.update_admin_onboarding(
             # NOTE: type ignore likely a ty issue
             AdminOnboardingUpdateDto(is_onboarded=True),  # type: ignore[missing-argument]
             _headers={"Authorization": f"Bearer {login_response.access_token}"},
         )

         # Create API key with all permissions
         api_key_response = await setup_client.api_keys.create_api_key(
             APIKeyCreateDto(name="e2e", permissions=[Permission.ALL]),
             _headers={"Authorization": f"Bearer {login_response.access_token}"},
         )

         # Create authenticated client with API key
         client = AsyncClient(base_url=base_url, api_key=api_key_response.secret)

         yield client
-
-        await client.close()
     finally:
+        if client is not None:
+            await client.close()
         await setup_client.close()

88-106: Remove unused tmp_path parameter.

The tmp_path parameter is not used in this test function.

Suggested fix
 @pytest.mark.asyncio
 @pytest.mark.e2e
 async def test_assets_upload(
-    client_with_api_key: AsyncClient, test_image: Path, test_video: Path, tmp_path: Path
+    client_with_api_key: AsyncClient, test_image: Path, test_video: Path
 ):
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7981362 and a52a47d.

📒 Files selected for processing (2)
  • .github/workflows/cd.yml
  • tests/e2e/client/test_wrappers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/cd.yml
🧰 Additional context used
🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

29-29: Unused function argument: tmp_path

(ARG001)


40-40: Possible hardcoded password assigned to argument: "password"

(S106)


46-46: Possible hardcoded password assigned to argument: "password"

(S106)


91-91: Unused function argument: tmp_path

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (5)
tests/e2e/client/test_wrappers.py (5)

108-129: LGTM!

Good test structure with thorough content verification - comparing downloaded bytes against the original ensures download integrity.


132-152: LGTM!

Thumbnail tests appropriately verify existence rather than exact content, since the server may process and resize the image.


155-175: LGTM!

Video stream test appropriately verifies existence, as the server may transcode the video.


178-202: LGTM!

Archive download test validates the expected output correctly.


205-226: LGTM!

Profile image test correctly exercises the upload and download workflow.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/cd.yml:
- Line 51: The if condition using github.ref, github.event_name and
contains(...) has ambiguous operator precedence; update the expression to add
explicit parentheses so the intent is clear: group (github.ref ==
'refs/heads/main' && github.event_name == 'push') as one branch OR
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-e2e')) as the other
branch, ensuring the label check only applies to pull_request events.
🧹 Nitpick comments (1)
.github/workflows/cd.yml (1)

80-102: Consider checking if docker-compose process is still alive during polling.

If docker compose up fails early (e.g., image pull error, port conflict), the script will wait the full 60 seconds before timing out. Adding a process check would fail faster:

Proposed improvement
           docker compose up --build --renew-anon-volumes --force-recreate --remove-orphans > /tmp/docker-compose.log 2>&1 &
           COMPOSE_PID=$!
           echo "Waiting for Immich server to be ready..."
           timeout=60
           elapsed=0
           while ! grep -q "Immich Microservices is running" /tmp/docker-compose.log 2>/dev/null; do
+            if ! kill -0 $COMPOSE_PID 2>/dev/null; then
+              echo "Docker compose process exited unexpectedly"
+              cat /tmp/docker-compose.log
+              exit 1
+            fi
             if [ $elapsed -ge $timeout ]; then
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a52a47d and d27aca2.

📒 Files selected for processing (1)
  • .github/workflows/cd.yml
🔇 Additional comments (4)
.github/workflows/cd.yml (4)

55-64: LGTM!

Checkout and mise setup are consistent with other jobs in the workflow.


66-74: LGTM!

Good use of shallow clone to minimize checkout time. Reading the version from a dedicated file keeps version management clean.


104-118: LGTM!

Good practices: conditional log output on failure for debugging, and if: always() ensures cleanup runs regardless of test outcome. The -v flag properly removes volumes to prevent state leakage between runs.


122-122: Verify behavior when e2e-tests is skipped.

When e2e-tests is skipped (PRs without run-e2e label), the release job will also be skipped due to the needs dependency. This appears intentional since releases only happen on main pushes where e2e-tests will run, but worth confirming this matches expected behavior.

…am e2e setup, improving readiness check and simplifying logs handling.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/cd.yml:
- Around line 80-87: The readiness check in the "Start Immich server" step is
racy because the command uses docker compose logs -f --no-color --tail=0 | grep
-m 1 "Immich Microservices is running", which misses messages emitted before
logs start; fix by either removing --tail=0 so existing log history is included,
or replace the timeout grep line with a polling loop that checks a stable
readiness endpoint (e.g., curl http://localhost:<port>/health or the Immich
service health path) with retries and sleeps until successful; update the
timeout 60s bash -lc line accordingly to implement the chosen approach.
🧹 Nitpick comments (1)
.github/workflows/cd.yml (1)

49-54: Consider adding explicit parentheses for clarity.

While GitHub Actions expression precedence (&& > ||) makes this evaluate correctly, the intent is clearer with explicit grouping:

if: (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e'))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d27aca2 and f09dc78.

📒 Files selected for processing (1)
  • .github/workflows/cd.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (2)
.github/workflows/cd.yml (2)

89-103: Good failure handling and teardown practices.

The conditional log output on failure and guaranteed teardown with if: always() are well implemented.


105-111: Dependency chain looks correct.

The e2e-tests job's if condition covers main pushes, which aligns with the release job's own condition. This ensures e2e tests run before releases while not blocking unrelated PR workflows.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/cd.yml (1)

51-51: Add parentheses to clarify operator precedence.

The condition works correctly due to && having higher precedence than ||, but explicit parentheses improve readability and prevent future maintenance errors.

🔧 Suggested improvement
-    if: github.ref == 'refs/heads/main' && github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')
+    if: (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e'))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f09dc78 and 1d307e2.

📒 Files selected for processing (1)
  • .github/workflows/cd.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (5)
.github/workflows/cd.yml (5)

66-74: LGTM!

The version reading and shallow clone approach is efficient for CI. The tr -d '[:space:]' ensures clean version strings.


76-78: LGTM!

Token passed via --password-stdin is the correct approach to avoid exposing credentials in process lists.


89-103: LGTM!

Good CI patterns: environment variables for test configuration, conditional logging on failure, and always() for guaranteed cleanup.


105-111: LGTM!

The dependency chain is correct. When e2e-tests is skipped (PR without label), GitHub Actions treats it as successful for the needs constraint, and the release job's own if condition correctly prevents it from running on PRs anyway.


80-87: The values used in this workflow (container name immich-e2e-server, timeout of 60 seconds, and log message "Immich Microservices is running") are directly from Immich's official e2e setup configuration, not arbitrary hardcoded choices. The upstream docker-compose.ts uses the same 60-second timeout and identical log message check, so this implementation faithfully follows Immich's pattern rather than introducing additional brittleness.

Likely an incorrect or invalid review comment.

skip-ci

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
.github/workflows/cd.yml (2)

66-68: Consider adding validation for the version file.

If IMMICH-VERSION is missing or empty, the git clone step will fail with an unclear error. Consider adding validation for earlier failure with a clear message.

💡 Optional improvement
       - name: Read Immich version
         id: immich-version
-        run: echo "version=$(cat IMMICH-VERSION | tr -d '[:space:]')" >> $GITHUB_OUTPUT
+        run: |
+          if [ ! -f IMMICH-VERSION ]; then
+            echo "::error::IMMICH-VERSION file not found"
+            exit 1
+          fi
+          version=$(cat IMMICH-VERSION | tr -d '[:space:]')
+          if [ -z "$version" ]; then
+            echo "::error::IMMICH-VERSION is empty"
+            exit 1
+          fi
+          echo "version=$version" >> $GITHUB_OUTPUT

80-87: Readiness check relies on exact log message.

The wait loop searches for "Immich Microservices is running" which couples this workflow to Immich's internal log format. If upstream changes the message, this will time out. Consider adding a comment noting this dependency, or use an HTTP health check endpoint if available.

The 60-second timeout should be sufficient for container startup when images are pre-pulled, but may be tight if images need to be downloaded.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d307e2 and aad7979.

📒 Files selected for processing (1)
  • .github/workflows/cd.yml
🔇 Additional comments (4)
.github/workflows/cd.yml (4)

49-54: LGTM!

The job conditional correctly gates expensive e2e tests to main branch pushes or PRs explicitly labeled with run-e2e. Permissions are appropriately scoped to read-only.


76-78: LGTM!

The GHCR login correctly uses --password-stdin to avoid exposing the token in the process list, following security best practices.


89-103: LGTM!

The e2e test execution, failure logging, and teardown are well-structured:

  • Environment variables correctly configure the test target
  • if: failure() captures logs only when needed for debugging
  • if: always() ensures proper cleanup regardless of test outcome

105-108: LGTM!

The dependency chain correctly ensures e2e tests pass before releasing. The conditional logic aligns well: on main branch pushes, both e2e-tests and release conditions are satisfied, so releases will be gated by e2e test results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @.github/workflows/cd.yml:
- Around line 49-79: The e2e-tests job overrides permissions to only "contents:
read" which removes GHCR access used by the "Login to GitHub Container Registry"
step; update the job's permissions block in the e2e-tests job to include
"packages: read" alongside "contents: read" so the `${{ github.token }}` can
pull images from ghcr.io (keep the permissions keys under the e2e-tests job and
ensure the Login to GitHub Container Registry step remains unchanged).
- Around line 66-75: Validate and/or provide a fallback for IMMICH_VERSION
before using it in the git clone step: read the output from step immich-version
(steps.immich-version.outputs.version), ensure it's non-empty and corresponds to
an existing ref (e.g., use git ls-remote to check tags/branches for that ref)
and if the ref is missing or invalid, set IMMICH_VERSION to a safe default like
"main" (or normalize a missing "v" prefix to the expected form) so the git clone
--branch $IMMICH_VERSION command cannot fail catastrophically.

In @tests/e2e/client/generators.py:
- Around line 42-72: _create_minimal_mp4 currently base64-decodes the template
on every call and mutates hard-coded offsets [100,200,300,400], which is
fragile; instead decode the base64 once at module import into a MODULE_TEMPLATE
bytes object and in _create_minimal_mp4 copy that template before mutating,
locate a safe payload region by scanning the copied bytes for the b"mdat" box
(or if not found, append a small harmless free/uuid box) and apply seed-based
mutations inside that payload region using seed.to_bytes(...) so offsets are
computed relative to the located payload start rather than fixed positions;
update references to _create_minimal_mp4 and _new_mp4_factory accordingly so
uniqueness is preserved without repeated decoding or brittle fixed offsets.

In @tests/e2e/client/test_wrappers.py:
- Around line 123-149: The test function test_assets_upload has an unused
parameter tmp_path causing a Ruff ARG001; remove tmp_path from the async def
signature so the parameters list becomes (client_with_api_key: AsyncClient,
test_image: Path, test_video: Path, asset_cleanup: dict), and run tests/linter
to ensure no other references to tmp_path exist in this file.
- Around line 25-71: The client_with_api_key fixture uses hardcoded admin
credentials and a brittle error-body check; make the admin email and password
configurable via environment variables (e.g., read ADMIN_EMAIL and
ADMIN_PASSWORD with safe defaults) and replace the fragile substring check in
the BadRequestException handler by normalizing e.body to a string (e.g.,
str(e.body)) or, if available, checking a structured error code field; update
references in the fixture (SignUpDto, BadRequestException, e.body) accordingly
so sign_up_admin and the exception handling remain robust and configurable.
🧹 Nitpick comments (4)
pyproject.toml (1)

49-55: Pillow pin may break on newer Python (e.g., 3.14) depending on wheel support—please verify.

Given CI runs Python 3.14, double-check that pillow>=10,<11 has wheels for your runners; otherwise this will fall back to source builds (slow/fail) or fail outright.

Proposed tweak (if you hit wheel/build issues)
-    "pillow>=10.0.0,<11.0.0",
+    "pillow>=10.0.0,<12.0.0",
.github/workflows/cd.yml (1)

80-91: Readiness check is log-string + 60s timeout → likely flaky on cold runners.

Consider increasing the timeout and/or using a real health endpoint (or docker compose ps + HTTP probe) so transient log timing doesn’t fail the job.

tests/e2e/client/generators.py (1)

12-40: JPEG generator: global mutable iterator is fine for single-worker runs, but can race under parallel test execution.

If you ever run e2e with xdist (or any parallelism), this shared global _jpeg_factory can interleave and re-emit duplicates. Safer pattern: make the generator instance fixture-scoped (per test/session) or make make_random_image(seed: int) pure.

tests/e2e/client/test_wrappers.py (1)

57-71: API key is created but never deleted (local runs will accumulate keys).

CI is probably ephemeral, but for local/dev repeat runs this pollutes the instance. If create_api_key returns an ID, store it and delete it in teardown.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aad7979 and 0cb460d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/workflows/cd.yml
  • pyproject.toml
  • tests/e2e/client/generators.py
  • tests/e2e/client/test_wrappers.py
🧰 Additional context used
🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

38-38: Possible hardcoded password assigned to argument: "password"

(S106)


47-47: Possible hardcoded password assigned to argument: "password"

(S106)


112-113: try-except-pass detected, consider logging the exception

(S110)


112-112: Do not catch blind exception: Exception

(BLE001)


119-120: try-except-pass detected, consider logging the exception

(S110)


119-119: Do not catch blind exception: Exception

(BLE001)


129-129: Unused function argument: tmp_path

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (1)
pyproject.toml (1)

86-91: The asyncio_mode = "auto" configuration is fully supported by the project's pytest-asyncio version constraint (>=1.2.0), which exceeds the minimum version (0.17) where this mode was introduced. No version-related warnings or incompatibilities should occur.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/e2e/client/test_wrappers.py (2)

99-120: Consider logging cleanup errors for debugging flaky tests.

Silent except Exception: pass blocks can hide issues when tests fail intermittently. While best-effort cleanup is appropriate, logging at debug level helps diagnose problems.

♻️ Suggested improvement
+import logging
+
+logger = logging.getLogger(__name__)
+
 @pytest.fixture(autouse=True)
 async def cleanup_assets_teardown(
     client_with_api_key: AsyncClient, asset_cleanup: dict
 ):
     """Autouse fixture to clean up uploaded assets after each test."""
     yield
     # Teardown: Clean up all uploaded assets
     asset_ids = asset_cleanup.get("asset_ids", [])
     if asset_ids:
         try:
             await client_with_api_key.assets.delete_assets(
                 AssetBulkDeleteDto(ids=asset_ids, force=True)
             )
-        except Exception:
-            pass  # Ignore cleanup errors
+        except Exception:
+            logger.debug("Failed to cleanup assets: %s", asset_ids, exc_info=True)
 
     # Teardown: Clean up profile image if uploaded
     if asset_cleanup.get("profile_image", False):
         try:
             await client_with_api_key.users.delete_profile_image()
-        except Exception:
-            pass  # Ignore cleanup errors
+        except Exception:
+            logger.debug("Failed to cleanup profile image", exc_info=True)

123-148: Remove unused tmp_path parameter.

The tmp_path fixture is declared but not used in this test.

♻️ Suggested fix
 @pytest.mark.asyncio
 @pytest.mark.e2e
 async def test_assets_upload(
     client_with_api_key: AsyncClient,
     test_image: Path,
     test_video: Path,
-    tmp_path: Path,
     asset_cleanup: dict,
 ):
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cb460d and 1ce7761.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • pyproject.toml
  • tests/e2e/client/generators.py
  • tests/e2e/client/test_wrappers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (1)
tests/e2e/client/test_wrappers.py (7)
immich/sdk.py (1)
  • AsyncClient (72-365)
immich/client/exceptions.py (1)
  • BadRequestException (182-183)
tests/e2e/client/generators.py (2)
  • make_random_image (30-38)
  • make_random_video (78-80)
immich/client/api/api_keys_api.py (1)
  • create_api_key (42-103)
immich/client/api/assets_api.py (1)
  • delete_assets (1063-1124)
immich/client_wrapper/assets_api_wrapped.py (1)
  • upload (167-260)
immich/client_wrapper/users_api_wrapped.py (1)
  • get_profile_image_to_file (12-51)
🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

38-38: Possible hardcoded password assigned to argument: "password"

(S106)


47-47: Possible hardcoded password assigned to argument: "password"

(S106)


112-113: try-except-pass detected, consider logging the exception

(S110)


112-112: Do not catch blind exception: Exception

(BLE001)


119-120: try-except-pass detected, consider logging the exception

(S110)


119-119: Do not catch blind exception: Exception

(BLE001)


129-129: Unused function argument: tmp_path

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (12)
tests/e2e/client/generators.py (5)

11-16: LGTM!

Clean implementation for generating minimal test JPEGs.


19-24: LGTM!

The step of 16 provides 4,096 unique images, which is ample for E2E testing.


27-38: LGTM!

The reset logic correctly handles factory exhaustion. Note that global state means tests sharing this module must run sequentially (not with pytest-xdist parallel workers) to avoid race conditions, which is typically fine for E2E tests.


41-64: LGTM!

The seed-based mutation approach correctly generates unique MP4 files. Python's arbitrary-precision integers handle large seed values safely, and the % 256 ensures valid byte outputs.


67-80: LGTM!

The infinite generator with seed wrapping provides sufficient uniqueness for E2E tests.

tests/e2e/client/test_wrappers.py (7)

25-70: LGTM!

The fixture handles both initial setup and subsequent runs gracefully. The hardcoded credentials are acceptable for E2E test fixtures running against ephemeral test servers.

Minor consideration: API keys accumulate across test runs since they're not cleaned up. This is typically fine for short-lived E2E environments but worth noting if tests run against a persistent server.


73-86: LGTM!

Simple, clean fixtures that leverage the generators module.


151-176: LGTM!

Good round-trip test verifying upload and download content integrity.


179-203: LGTM!

Correctly tests thumbnail download. Checking only existence (not content) is appropriate since thumbnails are server-generated.


206-230: LGTM!

Correctly tests video stream download functionality.


233-261: LGTM!

Good coverage for the archive download functionality.


264-292: LGTM!

Good coverage for user profile image upload and download. The tuple format ("profile.jpg", img_bytes) correctly handles content type detection.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/cd.yml (1)

89-99: Consider increasing timeout for slower CI environments.

The 60-second timeout may be tight on slower CI runners or when pulling images for the first time. The documentation comments referencing the source of the log message are excellent for maintainability.

💡 Optional: Increase timeout for reliability
-          timeout 60s bash -lc 'until docker logs immich-e2e-server 2>&1 | grep -q "Immich Microservices is running"; do sleep 1; done'
+          timeout 120s bash -lc 'until docker logs immich-e2e-server 2>&1 | grep -q "Immich Microservices is running"; do sleep 1; done'
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce7761 and 5da9ee4.

📒 Files selected for processing (2)
  • .github/workflows/cd.yml
  • immich/_internal/upload.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (7)
immich/_internal/upload.py (1)

260-260: LGTM!

Using os.fspath(path) correctly converts the Path object to a string before passing it to statx(). This is the standard approach for ensuring compatibility with libraries that expect filesystem path strings rather than Path objects.

.github/workflows/cd.yml (6)

44-49: LGTM!

Good documentation explaining why mise_toml is used over install_args for matrix Python version override. The inline configuration approach ensures the matrix version becomes the active interpreter.


75-83: LGTM!

Good approach: reading the version from a tracked file ensures consistency, and the shallow clone minimizes checkout time. The git clone will fail naturally if the specified tag doesn't exist, providing implicit validation.


85-87: LGTM!

Correctly pipes the token to docker login to avoid command-line exposure. Standard pattern for GHCR authentication in GitHub Actions.


101-115: LGTM!

Good job structure: environment variables are correctly set for the Immich e2e server, failure logs are captured for debugging, and the if: always() teardown ensures Docker resources are cleaned up even on failure or cancellation.


119-119: LGTM!

The dependency chain is correctly configured. When e2e-tests is skipped due to its conditional, GitHub Actions treats it as successful, so the release job will still run on main branch pushes (where both jobs' conditions are satisfied).


54-73: Python 3.14 is stable and available as of January 2026.

The job correctly gates e2e tests to main pushes or PRs with the run-e2e label, and uses minimal permissions. Python 3.14.0 was released on October 7, 2025, and is widely available in pyenv and similar package managers. Pinning to Python 3.14 is appropriate and poses no stability concerns.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/cd.yml:
- Around line 64-65: The "Install mise" GitHub Actions step currently using
jdx/mise-action@146a281... needs to pass the --env ci flag so mise loads
mise.ci.toml and uses the pinned uv version; update the "Install mise" step (the
step named "Install mise" that invokes jdx/mise-action) to include the action
input for arguments (e.g., add a with/args entry supplying "--env ci" or
equivalent per the action’s docs) so the CI environment file is loaded
consistently with the "test" job.

In @mise.ci.toml:
- Around line 1-3: Update the uv tool version defined in the tools table from
"0.9.21" to a current patched release (preferably "0.9.24", or "0.9.23" if you
prefer the slightly older stable patch) in mise.ci.toml; locate the tools table
entry with the key uv and change its string value accordingly to pick up the
security/stability fixes.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5da9ee4 and 599d56d.

📒 Files selected for processing (2)
  • .github/workflows/cd.yml
  • mise.ci.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (4)
.github/workflows/cd.yml (4)

44-47: LGTM! Clear workaround with helpful documentation.

The comment explains the rationale well, and the approach cleanly separates constant tools (in mise.ci.toml) from dynamic Python version management.


81-97: Well-structured e2e setup with proper readiness check and cleanup.

Good practices observed:

  • Timeout-based readiness check with clear log message pattern
  • Helpful inline comment referencing the source of the expected log message
  • Environment variables properly configured for test execution

99-107: LGTM! Proper failure diagnostics and guaranteed cleanup.

Using if: failure() for logs and if: always() for teardown ensures good debuggability while guaranteeing resource cleanup.


111-111: This concern does not apply to the current configuration.

The release job is already gated to main branch pushes via its own if condition on line 112. Since e2e-tests is configured to run on all main branch pushes (the first part of its conditional on line 54), it will never be skipped when release attempts to execute. Therefore, the dependency on e2e-tests in the needs array does not create a blocking issue with conditional skipping.

…mplate, enhancing uniqueness through seed-based mutations in the payload region.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/e2e/client/generators.py (1)

31-39: Consider clarifying the naming/docstring.

The function name and docstring use "random" but the generator is deterministic and sequential. For clarity, consider renaming to make_unique_image() or updating the docstring to say "unique" rather than "random unique" to accurately reflect the deterministic cycling behavior.

tests/e2e/client/test_wrappers.py (1)

89-96: Consider using TypedDict for stronger typing.

The current union type dict[str, list[UUID] | bool] works but loses type safety when accessing keys. A TypedDict would provide better IDE support and type checking:

from typing import TypedDict

class CleanupData(TypedDict):
    asset_ids: list[UUID]
    profile_image: bool

This is optional since the current implementation is functional.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 599d56d and 4eb9443.

📒 Files selected for processing (2)
  • tests/e2e/client/generators.py
  • tests/e2e/client/test_wrappers.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: timonrieger
Repo: timonrieger/immich-python-client PR: 28
File: .github/workflows/cd.yml:64-65
Timestamp: 2026-01-12T20:50:31.491Z
Learning: In the `.github/workflows/cd.yml` workflow, the `--env ci` flag for mise should only be used when overriding specific tool versions (like in the `test` job where Python version is overridden via matrix), not universally across all jobs. The `e2e-tests` job doesn't need this flag since it uses the default `mise.toml` configuration without overrides.
📚 Learning: 2026-01-12T20:50:31.491Z
Learnt from: timonrieger
Repo: timonrieger/immich-python-client PR: 28
File: .github/workflows/cd.yml:64-65
Timestamp: 2026-01-12T20:50:31.491Z
Learning: In the `.github/workflows/cd.yml` workflow, the `--env ci` flag for mise should only be used when overriding specific tool versions (like in the `test` job where Python version is overridden via matrix), not universally across all jobs. The `e2e-tests` job doesn't need this flag since it uses the default `mise.toml` configuration without overrides.

Applied to files:

  • tests/e2e/client/test_wrappers.py
🧬 Code graph analysis (1)
tests/e2e/client/test_wrappers.py (8)
immich/sdk.py (1)
  • AsyncClient (72-365)
immich/client/exceptions.py (1)
  • BadRequestException (182-183)
immich/client/models/asset_bulk_delete_dto.py (1)
  • AssetBulkDeleteDto (26-84)
tests/e2e/client/generators.py (2)
  • make_random_image (31-39)
  • make_random_video (85-87)
immich/client/api/assets_api.py (1)
  • delete_assets (1063-1124)
immich/client/api/users_api.py (2)
  • delete_profile_image (304-361)
  • get_my_user (1190-1247)
immich/client_wrapper/download_api_wrapped.py (1)
  • download_archive_to_file (17-110)
immich/client_wrapper/users_api_wrapped.py (1)
  • get_profile_image_to_file (12-51)
🪛 Ruff (0.14.10)
tests/e2e/client/test_wrappers.py

38-38: Possible hardcoded password assigned to argument: "password"

(S106)


47-47: Possible hardcoded password assigned to argument: "password"

(S106)


112-113: try-except-pass detected, consider logging the exception

(S110)


112-112: Do not catch blind exception: Exception

(BLE001)


119-120: try-except-pass detected, consider logging the exception

(S110)


119-119: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e-tests
🔇 Additional comments (15)
tests/e2e/client/generators.py (5)

12-17: LGTM!

Clean and efficient implementation for generating minimal test JPEGs.


20-28: LGTM!

The stepped RGB cycling provides 4096 unique images, which should be ample for E2E test coverage.


42-71: LGTM!

The MP4 mutation strategy is well-designed:

  • Safely copies the template before mutating
  • Correctly locates the mdat payload region
  • Provides a fallback with a harmless free box if mdat is absent
  • Bounds checking prevents buffer overflows

74-79: LGTM!

Infinite generator with 2³² unique seed values ensures the factory never needs reinitialization during test runs.


85-87: LGTM!

Straightforward delegation to the infinite factory.

tests/e2e/client/test_wrappers.py (10)

1-22: LGTM!

Clean import organization with all necessary dependencies for the E2E test suite.


25-70: LGTM!

Well-structured fixture with:

  • Idempotent admin setup (gracefully handles "already has an admin")
  • Proper resource cleanup with try/finally
  • Environment variable support for base URL

The hardcoded credentials flagged by static analysis (S106) are acceptable for E2E tests running against isolated test environments.


73-86: LGTM!

Clean fixtures leveraging pytest's tmp_path for automatic file cleanup.


99-120: LGTM!

The autouse fixture ensures reliable test isolation with automatic cleanup. The broad exception handling (flagged by S110/BLE001) is appropriate here—cleanup failures shouldn't cause test failures, and debugging cleanup issues is secondary to the actual test outcomes.

If you'd like to improve debuggability, consider logging cleanup failures at DEBUG level, but this is optional.


123-147: LGTM!

Good test coverage for the upload workflow with:

  • Concurrent upload testing (concurrency=2)
  • Comprehensive assertions on stats and results
  • Proper asset tracking for cleanup

150-175: LGTM!

Excellent round-trip verification—the byte-level comparison at line 175 ensures data integrity through the upload/download cycle.


178-202: LGTM!

Correctly tests the thumbnail download path. Since thumbnails are server-generated, existence/file-type verification is the appropriate level of assertion here.


205-229: LGTM!

Properly tests the video playback download path. Similar to thumbnails, server-side processing means existence verification is the correct assertion level.


232-260: LGTM!

Good coverage of the archive download functionality with appropriate assertions for ZIP file creation.


263-291: LGTM!

Complete test of the profile image workflow with proper cleanup tracking. The tuple format ("profile.jpg", img_bytes) at line 280 correctly ensures content-type detection.

@timonrieger timonrieger merged commit 2ab0716 into main Jan 12, 2026
10 checks passed
@timonrieger timonrieger deleted the e2e-tests branch January 12, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-e2e Set this label when expensive e2e tests should run on a PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants