From af40bbad899e0f29a1b826417a3da3cac203cab4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Mar 2026 13:50:34 +0000 Subject: [PATCH 01/15] feat(connect): add system checks test (closes #74) Add a BDD test that triggers the Connect system diagnostics, verifies the report is returned, and downloads the report artifact. New methods on ConnectClient: - list_server_checks(): GET /v1/server_checks - run_server_check(): POST /v1/server_checks - get_server_check_report(check_id): GET /v1/server_checks/{id}/download https://claude.ai/code/session_014qbzfzFuPSQE66nsZzUffW --- src/vip/clients/connect.py | 20 +++++++++ tests/connect/test_system_checks.feature | 12 +++++ tests/connect/test_system_checks.py | 45 +++++++++++++++++++ validation_docs/demo-connect-system-checks.md | 40 +++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 tests/connect/test_system_checks.feature create mode 100644 tests/connect/test_system_checks.py create mode 100644 validation_docs/demo-connect-system-checks.md diff --git a/src/vip/clients/connect.py b/src/vip/clients/connect.py index aac2cfa..0ca321b 100644 --- a/src/vip/clients/connect.py +++ b/src/vip/clients/connect.py @@ -237,6 +237,26 @@ def quarto_versions(self) -> list[str]: return [i.get("version", i.get("path", "")) for i in installations] return [] + # -- System checks ------------------------------------------------------ + + def list_server_checks(self) -> list[dict[str, Any]]: + """Return a list of all server check reports.""" + resp = self._client.get("/v1/server_checks") + resp.raise_for_status() + return resp.json().get("results", []) + + def run_server_check(self) -> dict[str, Any]: + """Trigger a new server check run and return the report object.""" + resp = self._client.post("/v1/server_checks") + resp.raise_for_status() + return resp.json() + + def get_server_check_report(self, check_id: str) -> bytes: + """Download the server check report as bytes.""" + resp = self._client.get(f"/v1/server_checks/{check_id}/download") + resp.raise_for_status() + return resp.content + # -- Email -------------------------------------------------------------- def send_test_email(self, to: str) -> dict[str, Any]: diff --git a/tests/connect/test_system_checks.feature b/tests/connect/test_system_checks.feature new file mode 100644 index 0000000..a30cece --- /dev/null +++ b/tests/connect/test_system_checks.feature @@ -0,0 +1,12 @@ +@connect +Feature: Connect system checks + As a Posit Team administrator + I want to run the Connect system diagnostics + So that I can verify the Connect installation is healthy + + Scenario: Connect system checks can be run and the report downloaded + Given Connect is accessible at the configured URL + And a valid API key is configured + When I trigger a new system check run via the Connect API + Then the system check report is returned + And I can download the system check report artifact diff --git a/tests/connect/test_system_checks.py b/tests/connect/test_system_checks.py new file mode 100644 index 0000000..36dfffb --- /dev/null +++ b/tests/connect/test_system_checks.py @@ -0,0 +1,45 @@ +"""Step definitions for Connect system checks tests.""" + +from __future__ import annotations + +from pytest_bdd import given, scenario, then, when + + +@scenario( + "test_system_checks.feature", + "Connect system checks can be run and the report downloaded", +) +def test_connect_system_checks(): + pass + + +# --------------------------------------------------------------------------- +# Steps +# --------------------------------------------------------------------------- + + +@given("a valid API key is configured") +def api_key_configured(vip_config): + assert vip_config.connect.api_key, ( + "VIP_CONNECT_API_KEY is not set. Set it in vip.toml or as an environment variable." + ) + + +@when("I trigger a new system check run via the Connect API", target_fixture="server_check") +def trigger_system_check(connect_client): + return connect_client.run_server_check() + + +@then("the system check report is returned") +def check_report_returned(server_check): + assert server_check is not None, "System check API returned no result" + assert "id" in server_check, ( + f"System check response missing 'id' field. Got: {list(server_check.keys())}" + ) + + +@then("I can download the system check report artifact") +def download_report_artifact(connect_client, server_check): + check_id = server_check["id"] + report_bytes = connect_client.get_server_check_report(check_id) + assert report_bytes, f"System check report for id={check_id!r} was empty" diff --git a/validation_docs/demo-connect-system-checks.md b/validation_docs/demo-connect-system-checks.md new file mode 100644 index 0000000..f017ca3 --- /dev/null +++ b/validation_docs/demo-connect-system-checks.md @@ -0,0 +1,40 @@ +# feat: Add Connect system checks test (issue #74) + +*2026-03-23T13:48:44Z by Showboat 0.6.1* + + +Added a Connect system checks test that triggers the built-in Connect server diagnostics and downloads the resulting report artifact, implementing posit-dev/vip#74. + +Changes: +- Added list_server_checks(), run_server_check(), and get_server_check_report(check_id) to ConnectClient +- Added tests/connect/test_system_checks.feature with @connect-tagged BDD scenario +- Added tests/connect/test_system_checks.py with step definitions + +The test triggers a new system check run (POST /v1/server_checks), verifies the report contains an 'id' field, then downloads the artifact (GET /v1/server_checks/{id}/download). + +```bash +uv run ruff check src/ tests/ selftests/ examples/ && uv run ruff format --check src/ tests/ selftests/ examples/ && echo 'Lint/format: OK' +``` + +```output +All checks passed! +90 files already formatted +Lint/format: OK +``` + +```bash +uv run pytest selftests/ -q 2>&1 | grep -oE '^[0-9]+ passed, [0-9]+ warnings' +``` + +```output +95 passed, 2 warnings +``` + +```bash +uv run pytest tests/connect/test_system_checks.py --collect-only -q 2>&1 | grep -v 'UserWarning\|vip_cfg\|plugin.py' | grep -v 'in [0-9]' +``` + +```output +tests/connect/test_system_checks.py::test_connect_system_checks + +``` From afed95516157db8487024d600f4b495281497a63 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Mar 2026 17:00:43 +0000 Subject: [PATCH 02/15] feat(connect): integrate system checks report into VIP output - Test step saves downloaded artifact to report/connect_system_checks.html alongside results.json (uses --vip-report path for placement) - report/index.qmd: new "Connect System Checks" section embeds the artifact in a sandboxed srcdoc iframe; shows placeholder when absent - example-report.yml: adds test_system_checks.py to CI smoke run so the section is populated in every PR preview report https://claude.ai/code/session_014qbzfzFuPSQE66nsZzUffW --- .github/workflows/example-report.yml | 3 ++- report/index.qmd | 23 +++++++++++++++++++ tests/connect/test_system_checks.py | 11 ++++++++- validation_docs/demo-connect-system-checks.md | 18 ++++++++------- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index e7feadc..1ba6ab0 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -132,8 +132,9 @@ jobs: tests/prerequisites/test_components.py \ tests/prerequisites/test_expected_failure.py \ tests/connect/test_auth.py \ + tests/connect/test_system_checks.py \ tests/package_manager/test_repos.py \ - -v -k "reachable or api or mirror or repo_exists or expected_failure" \ + -v -k "reachable or api or mirror or repo_exists or expected_failure or system_checks" \ --vip-config=vip.toml \ || true diff --git a/report/index.qmd b/report/index.qmd index 0862422..26ffb32 100644 --- a/report/index.qmd +++ b/report/index.qmd @@ -451,3 +451,26 @@ document.querySelectorAll('.vip-copy-btn').forEach(function(btn) { if failures: display(Markdown(f"_`failures.json` is written by `pytest` alongside `results.json`._")) ``` + +## Connect System Checks + +```{python} +#| echo: false + +checks_path = Path("connect_system_checks.html") +if not checks_path.exists(): + display(Markdown( + "_No Connect system checks report found. " + "Run `tests/connect/test_system_checks.py` against a configured Connect instance._" + )) +else: + content = checks_path.read_text(encoding="utf-8", errors="replace") + # Escape double quotes for use in the srcdoc attribute + srcdoc = content.replace("&", "&").replace('"', """) + display(HTML( + '" + )) +``` diff --git a/tests/connect/test_system_checks.py b/tests/connect/test_system_checks.py index 36dfffb..85452e2 100644 --- a/tests/connect/test_system_checks.py +++ b/tests/connect/test_system_checks.py @@ -2,6 +2,8 @@ from __future__ import annotations +from pathlib import Path + from pytest_bdd import given, scenario, then, when @@ -39,7 +41,14 @@ def check_report_returned(server_check): @then("I can download the system check report artifact") -def download_report_artifact(connect_client, server_check): +def download_report_artifact(connect_client, server_check, pytestconfig): check_id = server_check["id"] report_bytes = connect_client.get_server_check_report(check_id) assert report_bytes, f"System check report for id={check_id!r} was empty" + + # Persist alongside results.json so the Quarto report can embed it. + vip_report = pytestconfig.getoption("--vip-report") + if vip_report: + artifact_path = Path(vip_report).parent / "connect_system_checks.html" + artifact_path.parent.mkdir(parents=True, exist_ok=True) + artifact_path.write_bytes(report_bytes) diff --git a/validation_docs/demo-connect-system-checks.md b/validation_docs/demo-connect-system-checks.md index f017ca3..c91b6d0 100644 --- a/validation_docs/demo-connect-system-checks.md +++ b/validation_docs/demo-connect-system-checks.md @@ -1,16 +1,16 @@ # feat: Add Connect system checks test (issue #74) -*2026-03-23T13:48:44Z by Showboat 0.6.1* - +*2026-03-23T16:59:59Z by Showboat 0.6.1* + -Added a Connect system checks test that triggers the built-in Connect server diagnostics and downloads the resulting report artifact, implementing posit-dev/vip#74. +Implements posit-dev/vip#74: adds a Connect system checks BDD test that triggers the built-in diagnostics, downloads the artifact, and embeds it in the VIP Quarto report. Changes: -- Added list_server_checks(), run_server_check(), and get_server_check_report(check_id) to ConnectClient -- Added tests/connect/test_system_checks.feature with @connect-tagged BDD scenario -- Added tests/connect/test_system_checks.py with step definitions - -The test triggers a new system check run (POST /v1/server_checks), verifies the report contains an 'id' field, then downloads the artifact (GET /v1/server_checks/{id}/download). +- ConnectClient: list_server_checks(), run_server_check(), get_server_check_report() +- tests/connect/test_system_checks.feature: @connect BDD scenario +- tests/connect/test_system_checks.py: step defs; saves artifact to report/connect_system_checks.html +- report/index.qmd: new 'Connect System Checks' section embeds the artifact via srcdoc iframe +- .github/workflows/example-report.yml: adds test_system_checks.py to CI smoke run ```bash uv run ruff check src/ tests/ selftests/ examples/ && uv run ruff format --check src/ tests/ selftests/ examples/ && echo 'Lint/format: OK' @@ -38,3 +38,5 @@ uv run pytest tests/connect/test_system_checks.py --collect-only -q 2>&1 | grep tests/connect/test_system_checks.py::test_connect_system_checks ``` + +The index.qmd 'Connect System Checks' section renders when report/connect_system_checks.html exists (saved by the test step). When no system checks were run, it shows a placeholder message. The CI example-report.yml now includes test_system_checks.py in the smoke run so the section is populated in every PR preview. From ffd14a4f5ca87d3bb2e8e5f21c16265ff30587aa Mon Sep 17 00:00:00 2001 From: Ian Flores Siaca Date: Tue, 24 Mar 2026 11:09:13 -0700 Subject: [PATCH 03/15] feat: add git-backed publishing test for Connect (#99) --- src/vip/clients/connect.py | 60 +++++++++++++++++++++++ tests/connect/test_content_deploy.feature | 10 ++++ tests/connect/test_content_deploy.py | 47 ++++++++++++++++-- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/vip/clients/connect.py b/src/vip/clients/connect.py index aac2cfa..81f4570 100644 --- a/src/vip/clients/connect.py +++ b/src/vip/clients/connect.py @@ -237,6 +237,66 @@ def quarto_versions(self) -> list[str]: return [i.get("version", i.get("path", "")) for i in installations] return [] + # -- Git-backed publishing ---------------------------------------------- + + def set_repository( + self, + guid: str, + repo_url: str, + branch: str = "main", + directory: str = ".", + ) -> dict[str, Any]: + """Link a content item to a git repository via PUT.""" + payload: dict[str, Any] = { + "repository": repo_url, + "branch": branch, + "directory": directory, + "polling": False, + } + resp = self._client.put(f"/v1/content/{guid}/repository", json=payload) + resp.raise_for_status() + return resp.json() + + def deploy_from_repository(self, guid: str) -> dict[str, Any]: + """Trigger a deployment from the linked git repository.""" + resp = self._client.post(f"/v1/content/{guid}/deploy", json={}) + resp.raise_for_status() + return resp.json() + + # -- Content fetching (authenticated, redirect-safe) ----------------------- + + def fetch_content(self, url: str, *, timeout: float = 30.0) -> httpx.Response: + """Fetch a content URL with API-key auth, following only same-origin redirects. + + This avoids leaking the API key to external domains if Connect + redirects to a CDN or OAuth provider. + """ + from urllib.parse import urlparse + + origin = urlparse(self.base_url) + max_redirects = 10 + resp = httpx.get( + url, + headers={"Authorization": self._client.headers["Authorization"]}, + follow_redirects=False, + timeout=timeout, + ) + for _ in range(max_redirects): + if not resp.is_redirect: + break + location = resp.headers.get("location", "") + target = urlparse(location) + # Only follow redirects to the same origin. + if target.hostname and target.hostname != origin.hostname: + break + resp = httpx.get( + location, + headers={"Authorization": self._client.headers["Authorization"]}, + follow_redirects=False, + timeout=timeout, + ) + return resp + # -- Email -------------------------------------------------------------- def send_test_email(self, to: str) -> dict[str, Any]: diff --git a/tests/connect/test_content_deploy.feature b/tests/connect/test_content_deploy.feature index 12e6322..1a74965 100644 --- a/tests/connect/test_content_deploy.feature +++ b/tests/connect/test_content_deploy.feature @@ -66,3 +66,13 @@ Feature: Connect content deployment Then the content is accessible via HTTP And the content renders expected output And I clean up the test content + + Scenario: Deploy and execute a git-backed Quarto document + Given Connect is accessible at the configured URL + When I create a VIP test content item named "vip-gitbacked-test" + And I link the content item to a public test git repository + And I trigger a git-backed deployment + And I wait for the deployment to complete + Then the content is accessible via HTTP + And the content renders expected output + And I clean up the test content diff --git a/tests/connect/test_content_deploy.py b/tests/connect/test_content_deploy.py index 3b6920c..3dab513 100644 --- a/tests/connect/test_content_deploy.py +++ b/tests/connect/test_content_deploy.py @@ -16,6 +16,12 @@ from tests.connect.conftest import _make_tar_gz +_GIT_REPO_URL = "https://github.com/posit-dev/connect-extensions" +# Using main branch — this is a Posit-maintained repo with stable examples. +# Connect's git integration requires a branch name (not a commit SHA). +_GIT_BRANCH = "main" +_GIT_DIRECTORY = "extensions/quarto-document" + @scenario("test_content_deploy.feature", "Deploy and execute a Quarto document") def test_deploy_quarto(): @@ -52,6 +58,11 @@ def test_deploy_fastapi(): pass +@scenario("test_content_deploy.feature", "Deploy and execute a git-backed Quarto document") +def test_deploy_gitbacked(): + pass + + # --------------------------------------------------------------------------- # Shared state for the current scenario # --------------------------------------------------------------------------- @@ -281,6 +292,7 @@ def _get_bundle(name: str, connect_client) -> dict[str, str]: "vip-rmarkdown-test", "vip-jupyter-test", "vip-fastapi-test", + "vip-gitbacked-test", ] @@ -291,6 +303,7 @@ def _get_bundle(name: str, connect_client) -> dict[str, str]: @when('I create a VIP test content item named "vip-rmarkdown-test"', target_fixture="deploy_state") @when('I create a VIP test content item named "vip-jupyter-test"', target_fixture="deploy_state") @when('I create a VIP test content item named "vip-fastapi-test"', target_fixture="deploy_state") +@when('I create a VIP test content item named "vip-gitbacked-test"', target_fixture="deploy_state") def create_content(connect_client, request): # Extract content name by matching the content type keyword (e.g., "plumber") # from the bundle name against the test function name (e.g., "test_deploy_plumber"). @@ -325,6 +338,29 @@ def upload_and_deploy(connect_client, deploy_state): deploy_state["task_id"] = result["task_id"] +@when("I link the content item to a public test git repository") +def link_git_repository(connect_client, deploy_state): + quarto_versions = connect_client.quarto_versions() + if not quarto_versions: + pytest.skip("No Quarto on Connect — cannot deploy git-backed Quarto document") + # Check that the remote repository is reachable before attempting to link. + try: + resp = httpx.head(_GIT_REPO_URL, follow_redirects=True, timeout=10) + if resp.status_code >= 400: + pytest.skip(f"Git repository not reachable (HTTP {resp.status_code}): {_GIT_REPO_URL}") + except httpx.TransportError as exc: + pytest.skip(f"Git repository not reachable: {exc}") + connect_client.set_repository( + deploy_state["guid"], _GIT_REPO_URL, branch=_GIT_BRANCH, directory=_GIT_DIRECTORY + ) + + +@when("I trigger a git-backed deployment") +def trigger_git_deploy(connect_client, deploy_state): + result = connect_client.deploy_from_repository(deploy_state["guid"]) + deploy_state["task_id"] = result["task_id"] + + @when("I wait for the deployment to complete") def wait_for_deploy(connect_client, deploy_state, vip_config): task_id = deploy_state["task_id"] @@ -347,7 +383,7 @@ def content_accessible(connect_client, deploy_state): content = connect_client.get_content(deploy_state["guid"]) url = content.get("content_url", "") if url: - resp = httpx.get(url, follow_redirects=True, timeout=30) + resp = connect_client.fetch_content(url) assert resp.status_code < 400, f"Content returned HTTP {resp.status_code}" @@ -393,6 +429,11 @@ def content_accessible(connect_client, deploy_state): "key": "message", "value": "VIP fastapi OK", }, + # posit-dev/connect-extensions quarto-document renders an HTML page. + "vip-gitbacked-test": { + "type": "html", + "markers": ["Quarto Document", "Penguins"], + }, } @@ -410,7 +451,7 @@ def content_renders_expected_output(connect_client, deploy_state): if expected["type"] == "json": # Plumber: append the route path and verify JSON response. - resp = httpx.get(url.rstrip("/") + "/", follow_redirects=True, timeout=30) + resp = connect_client.fetch_content(url.rstrip("/") + "/") assert resp.status_code < 400, f"Plumber API returned HTTP {resp.status_code}" try: body = resp.json() @@ -425,7 +466,7 @@ def content_renders_expected_output(connect_client, deploy_state): else: # HTML content types: check that the page contains expected marker strings. - resp = httpx.get(url, follow_redirects=True, timeout=30) + resp = connect_client.fetch_content(url) assert resp.status_code < 400, f"Content page returned HTTP {resp.status_code}" body_lower = resp.text.lower() for marker in expected["markers"]: From 19c43809647528b9058dd5e62c5390624324cb7a Mon Sep 17 00:00:00 2001 From: semantic-release Date: Tue, 24 Mar 2026 18:09:30 +0000 Subject: [PATCH 04/15] chore(release): 0.12.0 --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/vip/__init__.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4633c..ef5689e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # CHANGELOG +## v0.12.0 (2026-03-24) + +### Features + +- Add git-backed publishing test for Connect ([#99](https://github.com/posit-dev/vip/pull/99), + [`ffd14a4`](https://github.com/posit-dev/vip/commit/ffd14a4f5ca87d3bb2e8e5f21c16265ff30587aa)) + + ## v0.11.1 (2026-03-23) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 7c9a2aa..d454c7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vip" -version = "0.11.1" +version = "0.12.0" description = "Verified Installation of Posit - An extensible test suite for validating Posit Team deployments" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/vip/__init__.py b/src/vip/__init__.py index d604c8f..a31e7d2 100644 --- a/src/vip/__init__.py +++ b/src/vip/__init__.py @@ -1,3 +1,3 @@ """VIP - Verified Installation of Posit.""" -__version__ = "0.11.1" +__version__ = "0.12.0" From dc322002abbf49279e1a7f1a7e5ca1125260ebd4 Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Tue, 24 Mar 2026 19:09:14 -0400 Subject: [PATCH 05/15] fix(ci): rename package to posit-vip to match PyPI project (#102) --- .github/workflows/ci.yml | 9 ++- .github/workflows/publish.yml | 2 +- pyproject.toml | 2 +- uv.lock | 144 +++++++++++++++++----------------- 4 files changed, 82 insertions(+), 75 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1983cb6..486bfbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,14 @@ jobs: run: uv sync --all-extras - name: Audit dependencies - run: uv run pip-audit --skip-editable + run: | + # CVE-2026-4539: ReDoS in pygments' AdlLexer (archetype.py), CVSS 3.3. + # Requires local access; no network exposure. VIP never invokes the ADL lexer. + # Pygments cannot be removed — it is a hard dependency of pytest, rich, ipython, + # and the full Jupyter stack. No patched version exists upstream yet (reported at + # https://github.com/pygments/pygments/issues/3058 but unaddressed as of 2026-03-24). + # Re-evaluate when a fix is released. + uv run pip-audit --skip-editable --ignore-vuln CVE-2026-4539 selftest: name: Selftests (${{ matrix.python-version }}) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9a9ce3d..c8e2399 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/vip + url: https://pypi.org/p/posit-vip permissions: id-token: write steps: diff --git a/pyproject.toml b/pyproject.toml index d454c7d..a1fa632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "vip" +name = "posit-vip" version = "0.12.0" description = "Verified Installation of Posit - An extensible test suite for validating Posit Team deployments" readme = "README.md" diff --git a/uv.lock b/uv.lock index 18c49f0..8db259d 100644 --- a/uv.lock +++ b/uv.lock @@ -2182,6 +2182,78 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "posit-vip" +version = "0.12.0" +source = { editable = "." } +dependencies = [ + { name = "brand-yml" }, + { name = "htmltools" }, + { name = "httpx" }, + { name = "playwright" }, + { name = "pytest" }, + { name = "pytest-bdd" }, + { name = "pytest-playwright" }, + { name = "pytest-xdist" }, + { name = "shiny", extra = ["theme"] }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] + +[package.optional-dependencies] +cluster = [ + { name = "azure-identity" }, + { name = "azure-mgmt-containerservice" }, + { name = "boto3" }, + { name = "pyjwt" }, + { name = "pyyaml" }, +] +dev = [ + { name = "mypy" }, + { name = "pip-audit" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "types-pyyaml" }, +] +report = [ + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "tornado" }, +] + +[package.metadata] +requires-dist = [ + { name = "azure-identity", marker = "extra == 'cluster'", specifier = ">=1.12" }, + { name = "azure-mgmt-containerservice", marker = "extra == 'cluster'", specifier = ">=20.0" }, + { name = "boto3", marker = "extra == 'cluster'", specifier = ">=1.26" }, + { name = "brand-yml" }, + { name = "htmltools" }, + { name = "httpx", specifier = ">=0.27,<1" }, + { name = "ipykernel", marker = "extra == 'report'" }, + { name = "jinja2", marker = "extra == 'report'", specifier = ">=3.1" }, + { name = "jupyter", marker = "extra == 'report'" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" }, + { name = "nbclient", marker = "extra == 'report'", specifier = ">=0.8" }, + { name = "nbformat", marker = "extra == 'report'", specifier = ">=5.7" }, + { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.7" }, + { name = "playwright", specifier = ">=1.40" }, + { name = "pyjwt", marker = "extra == 'cluster'", specifier = ">=2.12.0" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-bdd", specifier = ">=7.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0" }, + { name = "pytest-playwright", specifier = ">=0.5" }, + { name = "pytest-xdist", specifier = ">=3.0" }, + { name = "pyyaml", marker = "extra == 'cluster'", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.0,<0.16" }, + { name = "shiny", extras = ["theme"], specifier = ">=1.0" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, + { name = "tornado", marker = "extra == 'report'", specifier = ">=6.5.5" }, + { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0" }, +] +provides-extras = ["report", "cluster", "dev"] + [[package]] name = "prometheus-client" version = "0.24.1" @@ -3336,78 +3408,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] -[[package]] -name = "vip" -version = "0.11.0" -source = { editable = "." } -dependencies = [ - { name = "brand-yml" }, - { name = "htmltools" }, - { name = "httpx" }, - { name = "playwright" }, - { name = "pytest" }, - { name = "pytest-bdd" }, - { name = "pytest-playwright" }, - { name = "pytest-xdist" }, - { name = "shiny", extra = ["theme"] }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] - -[package.optional-dependencies] -cluster = [ - { name = "azure-identity" }, - { name = "azure-mgmt-containerservice" }, - { name = "boto3" }, - { name = "pyjwt" }, - { name = "pyyaml" }, -] -dev = [ - { name = "mypy" }, - { name = "pip-audit" }, - { name = "pytest-cov" }, - { name = "ruff" }, - { name = "types-pyyaml" }, -] -report = [ - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter" }, - { name = "nbclient" }, - { name = "nbformat" }, - { name = "tornado" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", marker = "extra == 'cluster'", specifier = ">=1.12" }, - { name = "azure-mgmt-containerservice", marker = "extra == 'cluster'", specifier = ">=20.0" }, - { name = "boto3", marker = "extra == 'cluster'", specifier = ">=1.26" }, - { name = "brand-yml" }, - { name = "htmltools" }, - { name = "httpx", specifier = ">=0.27,<1" }, - { name = "ipykernel", marker = "extra == 'report'" }, - { name = "jinja2", marker = "extra == 'report'", specifier = ">=3.1" }, - { name = "jupyter", marker = "extra == 'report'" }, - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" }, - { name = "nbclient", marker = "extra == 'report'", specifier = ">=0.8" }, - { name = "nbformat", marker = "extra == 'report'", specifier = ">=5.7" }, - { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.7" }, - { name = "playwright", specifier = ">=1.40" }, - { name = "pyjwt", marker = "extra == 'cluster'", specifier = ">=2.12.0" }, - { name = "pytest", specifier = ">=8.0" }, - { name = "pytest-bdd", specifier = ">=7.0" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0" }, - { name = "pytest-playwright", specifier = ">=0.5" }, - { name = "pytest-xdist", specifier = ">=3.0" }, - { name = "pyyaml", marker = "extra == 'cluster'", specifier = ">=6.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.0,<0.16" }, - { name = "shiny", extras = ["theme"], specifier = ">=1.0" }, - { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, - { name = "tornado", marker = "extra == 'report'", specifier = ">=6.5.5" }, - { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0" }, -] -provides-extras = ["report", "cluster", "dev"] - [[package]] name = "watchfiles" version = "1.1.1" From 09b3f26b37dd750ea498dda040dc72d5717f1b12 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Tue, 24 Mar 2026 23:09:31 +0000 Subject: [PATCH 06/15] chore(release): 0.12.1 --- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- src/vip/__init__.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5689e..61c79b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # CHANGELOG +## v0.12.1 (2026-03-24) + +### Bug Fixes + +- **ci**: Rename package to posit-vip to match PyPI project + ([#102](https://github.com/posit-dev/vip/pull/102), + [`dc32200`](https://github.com/posit-dev/vip/commit/dc322002abbf49279e1a7f1a7e5ca1125260ebd4)) + + ## v0.12.0 (2026-03-24) ### Features diff --git a/pyproject.toml b/pyproject.toml index a1fa632..c4699c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "posit-vip" -version = "0.12.0" +version = "0.12.1" description = "Verified Installation of Posit - An extensible test suite for validating Posit Team deployments" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/vip/__init__.py b/src/vip/__init__.py index a31e7d2..4f817dd 100644 --- a/src/vip/__init__.py +++ b/src/vip/__init__.py @@ -1,3 +1,3 @@ """VIP - Verified Installation of Posit.""" -__version__ = "0.12.0" +__version__ = "0.12.1" From c5b576f74f43a653ed12ad6fa12f6b2e1139731c Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Tue, 24 Mar 2026 19:19:29 -0400 Subject: [PATCH 07/15] docs: update install instructions to use PyPI (#104) --- README.md | 7 ++++--- website/src/pages/getting-started.astro | 2 +- website/src/pages/index.astro | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index de7c389..0f90f80 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ that can be published to a Connect server. ## Quick start ```bash -git clone https://github.com/posit-dev/vip.git -cd vip -uv sync && uv run playwright install chromium +uv venv +source .venv/bin/activate +uv pip install posit-vip +playwright install chromium cp vip.toml.example vip.toml # edit with your deployment details uv run pytest # run all tests diff --git a/website/src/pages/getting-started.astro b/website/src/pages/getting-started.astro index 14b3180..a68c8f8 100644 --- a/website/src/pages/getting-started.astro +++ b/website/src/pages/getting-started.astro @@ -34,7 +34,7 @@ import Footer from "../components/Footer.astro";

Install VIP, then point it at your server:

uv venv
 source .venv/bin/activate
-uv pip install "vip @ git+https://github.com/posit-dev/vip.git"
+uv pip install posit-vip
 playwright install chromium

Using RStudio or Positron?

diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 673295b..c51d5b7 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -89,7 +89,7 @@ const categories = [ 1

Install

-

uv pip install "vip @ git+https://github.com/posit-dev/vip.git"

+

uv pip install posit-vip

From d3a118bbc39aa736636b457abe022a0e2322d5a6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:17:39 +0000 Subject: [PATCH 08/15] chore(deps): bump requests from 2.32.5 to 2.33.0 (#114) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: statik <983+statik@users.noreply.github.com> --- pyproject.toml | 1 + uv.lock | 10 +++-- .../demo-update-requests-dependency.md | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 validation_docs/demo-update-requests-dependency.md diff --git a/pyproject.toml b/pyproject.toml index c4699c9..bdadf40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "brand-yml", "htmltools", "tomli>=2.0;python_version<'3.11'", + "requests>=2.33.0", # transitive via pytest-playwright; pinned to track updates ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 8db259d..ce9f539 100644 --- a/uv.lock +++ b/uv.lock @@ -2184,7 +2184,7 @@ wheels = [ [[package]] name = "posit-vip" -version = "0.12.0" +version = "0.12.1" source = { editable = "." } dependencies = [ { name = "brand-yml" }, @@ -2195,6 +2195,7 @@ dependencies = [ { name = "pytest-bdd" }, { name = "pytest-playwright" }, { name = "pytest-xdist" }, + { name = "requests" }, { name = "shiny", extra = ["theme"] }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] @@ -2246,6 +2247,7 @@ requires-dist = [ { name = "pytest-playwright", specifier = ">=0.5" }, { name = "pytest-xdist", specifier = ">=3.0" }, { name = "pyyaml", marker = "extra == 'cluster'", specifier = ">=6.0" }, + { name = "requests", specifier = ">=2.33.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.0,<0.16" }, { name = "shiny", extras = ["theme"], specifier = ">=1.0" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, @@ -2842,7 +2844,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2850,9 +2852,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] [[package]] diff --git a/validation_docs/demo-update-requests-dependency.md b/validation_docs/demo-update-requests-dependency.md new file mode 100644 index 0000000..5190fd1 --- /dev/null +++ b/validation_docs/demo-update-requests-dependency.md @@ -0,0 +1,42 @@ +# Update requests dependency to 2.33.0 + +*2026-03-25T23:01:46Z by Showboat 0.6.1* + + +Updated the requests dependency from 2.32.5 to 2.33.0. Added requests>=2.33.0 as a direct dependency constraint in pyproject.toml (transitive via pytest-playwright) and regenerated the lockfile. + +```bash +grep 'requests' pyproject.toml +``` + +```output + "requests>=2.33.0", # transitive via pytest-playwright; pinned to track updates +``` + +```bash +grep -A3 '^name = "requests"' uv.lock | head -4 +``` + +```output +name = "requests" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ +``` + +```bash +uv run pytest selftests/ -q --no-header 2>&1 | grep -E 'passed|failed' +``` + +```output +95 passed, 2 warnings in 0.76s +``` + +```bash +uv run ruff check src/ tests/ selftests/ examples/ && uv run ruff format --check src/ tests/ selftests/ examples/ +``` + +```output +All checks passed! +89 files already formatted +``` From 90ca94f4f61586d6e36ef998041d5c5db97b6d97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:04:54 -0400 Subject: [PATCH 09/15] chore(deps): bump the python-dependencies group with 4 updates (#111) --- uv.lock | 100 ++++++++++++++++++-------------------------------------- 1 file changed, 31 insertions(+), 69 deletions(-) diff --git a/uv.lock b/uv.lock index ce9f539..43b2cd9 100644 --- a/uv.lock +++ b/uv.lock @@ -158,7 +158,7 @@ wheels = [ [[package]] name = "azure-identity" -version = "1.25.2" +version = "1.25.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -167,23 +167,23 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/3a/439a32a5e23e45f6a91f0405949dc66cfe6834aba15a430aebfc063a81e7/azure_identity-1.25.2.tar.gz", hash = "sha256:030dbaa720266c796221c6cdbd1999b408c079032c919fef725fcc348a540fe9", size = 284709, upload-time = "2026-02-11T01:55:42.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304, upload-time = "2026-03-13T01:12:20.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/77/f658c76f9e9a52c784bd836aaca6fd5b9aae176f1f53273e758a2bcda695/azure_identity-1.25.2-py3-none-any.whl", hash = "sha256:1b40060553d01a72ba0d708b9a46d0f61f56312e215d8896d836653ffdc6753d", size = 191423, upload-time = "2026-02-11T01:55:44.245Z" }, + { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138, upload-time = "2026-03-13T01:12:22.951Z" }, ] [[package]] name = "azure-mgmt-containerservice" -version = "40.2.0" +version = "41.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-mgmt-core" }, - { name = "msrest" }, + { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/507e5abdb984ce84849f12e37a6a5c31927bdf2d466952af05bb81d36dda/azure_mgmt_containerservice-40.2.0.tar.gz", hash = "sha256:acd55cae95b768efeb0377d83dea07d610c434eec0c089e02935ff31f0e3e07d", size = 206403, upload-time = "2025-11-24T03:36:46.442Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d5/77c8345ba10e72d02e4abae8ce50ecba417fcffcc4c3bbb226134bc5b69a/azure_mgmt_containerservice-41.0.0.tar.gz", hash = "sha256:9830d0a42730609c97a133a913e2caffbd163d4d3ff315afc3a76728222a3f61", size = 205609, upload-time = "2026-03-17T07:26:15.377Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/f0/c9407fc48457723f70c1a6a3d995f0a1f3c0de1f7400fe2484773ed19fdc/azure_mgmt_containerservice-40.2.0-py3-none-any.whl", hash = "sha256:a73d9720dd0ae29257dd6dcf39942b9274f15b588ffe25f4564aa77798eb4703", size = 226549, upload-time = "2025-11-24T03:36:47.891Z" }, + { url = "https://files.pythonhosted.org/packages/36/ed/7954777dcbea94816af61a282e7ae340af489d3871a889392a5756f08aad/azure_mgmt_containerservice-41.0.0-py3-none-any.whl", hash = "sha256:000d95c2f8b248e56113c82f8d2908b82477e9e3d74294fc4219db2f760c8108", size = 185350, upload-time = "2026-03-17T07:26:16.894Z" }, ] [[package]] @@ -248,16 +248,16 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.65" +version = "1.42.71" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/c9/8ff8a901cf62374f1289cf36391f855e1702c70f545c28d1b57608a84ff2/boto3-1.42.65.tar.gz", hash = "sha256:c740af6bdaebcc1a00f3827a5729050bf6fc820ee148bf7d06f28db11c80e2a1", size = 112805, upload-time = "2026-03-10T19:44:58.255Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/39/774ff22347856ebbe9da350045ad5851aa0524ee6e4832fdc98b27981801/boto3-1.42.71.tar.gz", hash = "sha256:500edd2699a3f479053bbfb407b06c231d1ff1e574f7c90d269d605a6a1f8160", size = 112773, upload-time = "2026-03-18T19:44:37.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/bb/ace5921655df51e3c9b787b3f0bd6aa25548e5cf1dabae02e53fa88f2d98/boto3-1.42.65-py3-none-any.whl", hash = "sha256:cc7f2e0aec6c68ee5b10232cf3e01326acf6100bc785a770385b61a0474b31f4", size = 140556, upload-time = "2026-03-10T19:44:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b6/b0b93090cfc3fdbdb21a0b18961508678a2b36a42e2b3a90994ac34e102c/boto3-1.42.71-py3-none-any.whl", hash = "sha256:a89fae01c4bc948671e99440ddc0f10bc73cc72d83218656057f730df0898eab", size = 140553, upload-time = "2026-03-18T19:44:35.317Z" }, ] [[package]] @@ -1740,22 +1740,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, ] -[[package]] -name = "msrest" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "certifi" }, - { name = "isodate" }, - { name = "requests" }, - { name = "requests-oauthlib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332, upload-time = "2022-06-13T22:41:25.111Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384, upload-time = "2022-06-13T22:41:22.42Z" }, -] - [[package]] name = "mypy" version = "1.19.1" @@ -1912,15 +1896,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, ] -[[package]] -name = "oauthlib" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, -] - [[package]] name = "orjson" version = "3.11.7" @@ -2857,19 +2832,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, -] - [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -3049,27 +3011,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, - { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, - { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, - { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, ] [[package]] From 653be07797f88c97c491525c46b696ecc1e0833d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:05:30 -0400 Subject: [PATCH 10/15] chore(deps): bump the actions-dependencies group with 4 updates (#112) --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/connect-smoke.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/example-report.yml | 2 +- .github/workflows/packagemanager-smoke.yml | 2 +- .github/workflows/publish.yml | 6 +++--- .github/workflows/website-preview.yml | 6 +++--- .github/workflows/website.yml | 2 +- .github/workflows/workbench-smoke.yml | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 486bfbd..2244da5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true @@ -46,7 +46,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true @@ -72,7 +72,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true @@ -104,7 +104,7 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true - uses: extractions/setup-just@v3 diff --git a/.github/workflows/connect-smoke.yml b/.github/workflows/connect-smoke.yml index 16e7e66..dcb4203 100644 --- a/.github/workflows/connect-smoke.yml +++ b/.github/workflows/connect-smoke.yml @@ -99,7 +99,7 @@ jobs: } # Set up Python and install VIP - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 5acd22d..c018ef2 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -25,6 +25,6 @@ jobs: with: version: v0.48.1 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 - name: Install showboat run: uv tool install showboat diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index e7feadc..392c8e4 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -91,7 +91,7 @@ jobs: echo "Resolved Package Manager version: ${VERSION}" echo "resolved=${VERSION}" >> "$GITHUB_OUTPUT" - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true diff --git a/.github/workflows/packagemanager-smoke.yml b/.github/workflows/packagemanager-smoke.yml index 83bb714..44e445b 100644 --- a/.github/workflows/packagemanager-smoke.yml +++ b/.github/workflows/packagemanager-smoke.yml @@ -139,7 +139,7 @@ jobs: } # Set up Python and install VIP - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 if: steps.license.outputs.available == 'true' with: enable-cache: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c8e2399..d3435b7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,14 +12,14 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true - name: Build wheel and sdist run: uv build - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: dist path: dist/ @@ -34,7 +34,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: dist path: dist/ diff --git a/.github/workflows/website-preview.yml b/.github/workflows/website-preview.yml index b49db77..11fb93a 100644 --- a/.github/workflows/website-preview.yml +++ b/.github/workflows/website-preview.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true @@ -63,7 +63,7 @@ jobs: umbrella-dir: pr-preview-site comment: false - - uses: marocchino/sticky-pull-request-comment@v2 + - uses: marocchino/sticky-pull-request-comment@v3 env: WEBSITE_URL: https://posit-dev.github.io/vip/pr-preview-site/pr-${{ github.event.pull_request.number }}/ REPORT_URL: https://posit-dev.github.io/vip/pr-preview/pr-${{ github.event.pull_request.number }}/ @@ -93,7 +93,7 @@ jobs: comment: false action: remove - - uses: marocchino/sticky-pull-request-comment@v2 + - uses: marocchino/sticky-pull-request-comment@v3 with: header: preview-links delete: true diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 55c8a67..2822a84 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -33,7 +33,7 @@ jobs: with: persist-credentials: false - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true diff --git a/.github/workflows/workbench-smoke.yml b/.github/workflows/workbench-smoke.yml index 7066992..a0e6823 100644 --- a/.github/workflows/workbench-smoke.yml +++ b/.github/workflows/workbench-smoke.yml @@ -124,7 +124,7 @@ jobs: } # Set up Python and install VIP - - uses: astral-sh/setup-uv@v5 + - uses: astral-sh/setup-uv@v7 with: enable-cache: true From bf232662ede07d481f4e658d9682ef3679907ce1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:43:33 -0400 Subject: [PATCH 11/15] fix: include tests/ directory in the package wheel (#110) --- .claude/agents/test-architect.md | 10 ++--- .github/workflows/ci.yml | 6 +-- .github/workflows/connect-smoke.yml | 10 ++--- .github/workflows/example-report.yml | 8 ++-- .github/workflows/packagemanager-smoke.yml | 6 +-- .github/workflows/website.yml | 2 +- .github/workflows/workbench-smoke.yml | 4 +- AGENTS.md | 30 +++++++------- IMPLEMENTATION_GUIDE.md | 18 ++++----- docs/development.md | 8 ++-- plans/connect-ci-testing.md | 4 +- plans/workbench-ci-testing.md | 16 ++++---- pyproject.toml | 6 +-- src/vip/app/app.py | 10 ++--- {tests => src/vip_tests}/__init__.py | 0 {tests => src/vip_tests}/conftest.py | 0 {tests => src/vip_tests}/connect/__init__.py | 0 {tests => src/vip_tests}/connect/conftest.py | 0 .../vip_tests}/connect/plumber_manifest.json | 0 .../vip_tests}/connect/shiny_manifest.json | 0 .../vip_tests}/connect/test_auth.feature | 0 {tests => src/vip_tests}/connect/test_auth.py | 0 .../connect/test_content_deploy.feature | 0 .../vip_tests}/connect/test_content_deploy.py | 2 +- .../connect/test_data_sources.feature | 0 .../vip_tests}/connect/test_data_sources.py | 2 +- .../vip_tests}/connect/test_email.feature | 0 .../vip_tests}/connect/test_email.py | 0 .../vip_tests}/connect/test_packages.feature | 0 .../vip_tests}/connect/test_packages.py | 2 +- .../connect/test_runtime_versions.feature | 0 .../connect/test_runtime_versions.py | 0 .../vip_tests}/connect/test_users.feature | 0 .../vip_tests}/connect/test_users.py | 0 .../vip_tests}/cross_product/__init__.py | 0 .../vip_tests}/cross_product/conftest.py | 0 .../cross_product/test_integration.feature | 0 .../cross_product/test_integration.py | 2 +- .../cross_product/test_monitoring.feature | 0 .../cross_product/test_monitoring.py | 0 .../cross_product/test_resources.feature | 0 .../cross_product/test_resources.py | 0 .../vip_tests}/cross_product/test_ssl.feature | 0 .../vip_tests}/cross_product/test_ssl.py | 0 {tests => src/vip_tests}/helpers.py | 0 .../vip_tests}/package_manager/__init__.py | 0 .../vip_tests}/package_manager/conftest.py | 0 .../test_private_repos.feature | 0 .../package_manager/test_private_repos.py | 0 .../package_manager/test_repos.feature | 0 .../vip_tests}/package_manager/test_repos.py | 0 .../vip_tests}/performance/__init__.py | 0 .../vip_tests}/performance/conftest.py | 0 .../performance/test_concurrency.feature | 0 .../performance/test_concurrency.py | 0 .../performance/test_login_load_times.feature | 0 .../performance/test_login_load_times.py | 0 .../test_package_install_speed.feature | 0 .../performance/test_package_install_speed.py | 0 .../performance/test_resource_usage.feature | 0 .../performance/test_resource_usage.py | 0 .../vip_tests}/prerequisites/__init__.py | 0 .../vip_tests}/prerequisites/conftest.py | 0 .../test_auth_configured.feature | 0 .../prerequisites/test_auth_configured.py | 0 .../prerequisites/test_components.feature | 0 .../prerequisites/test_components.py | 0 .../test_expected_failure.feature | 0 .../prerequisites/test_expected_failure.py | 0 .../prerequisites/test_versions.feature | 0 .../vip_tests}/prerequisites/test_versions.py | 0 {tests => src/vip_tests}/security/__init__.py | 0 {tests => src/vip_tests}/security/conftest.py | 0 .../security/test_auth_policy.feature | 0 .../vip_tests}/security/test_auth_policy.py | 0 .../security/test_error_handling.feature | 0 .../security/test_error_handling.py | 0 .../vip_tests}/security/test_https.feature | 0 .../vip_tests}/security/test_https.py | 0 .../vip_tests}/security/test_secrets.feature | 0 .../vip_tests}/security/test_secrets.py | 0 {tests => src/vip_tests}/troubleshooting.toml | 0 .../vip_tests}/workbench/__init__.py | 0 .../vip_tests}/workbench/conftest.py | 2 +- .../vip_tests}/workbench/pages/__init__.py | 0 .../workbench/pages/console_pane.py | 0 .../vip_tests}/workbench/pages/homepage.py | 0 .../vip_tests}/workbench/pages/ide_base.py | 0 .../workbench/pages/jupyterlab_session.py | 0 .../vip_tests}/workbench/pages/login.py | 0 .../workbench/pages/positron_session.py | 0 .../workbench/pages/rstudio_session.py | 0 .../workbench/pages/vscode_session.py | 0 .../vip_tests}/workbench/test_auth.feature | 0 .../vip_tests}/workbench/test_auth.py | 4 +- .../workbench/test_data_sources.feature | 0 .../vip_tests}/workbench/test_data_sources.py | 4 +- .../workbench/test_ide_launch.feature | 0 .../vip_tests}/workbench/test_ide_launch.py | 4 +- .../workbench/test_packages.feature | 0 .../vip_tests}/workbench/test_packages.py | 4 +- .../workbench/test_sessions.feature | 0 .../vip_tests}/workbench/test_sessions.py | 4 +- validation_docs/demo-fix-package-build.md | 31 ++++++++++++++ validation_docs/demo-option-b-vip-tests.md | 40 +++++++++++++++++++ 105 files changed, 155 insertions(+), 84 deletions(-) rename {tests => src/vip_tests}/__init__.py (100%) rename {tests => src/vip_tests}/conftest.py (100%) rename {tests => src/vip_tests}/connect/__init__.py (100%) rename {tests => src/vip_tests}/connect/conftest.py (100%) rename {tests => src/vip_tests}/connect/plumber_manifest.json (100%) rename {tests => src/vip_tests}/connect/shiny_manifest.json (100%) rename {tests => src/vip_tests}/connect/test_auth.feature (100%) rename {tests => src/vip_tests}/connect/test_auth.py (100%) rename {tests => src/vip_tests}/connect/test_content_deploy.feature (100%) rename {tests => src/vip_tests}/connect/test_content_deploy.py (99%) rename {tests => src/vip_tests}/connect/test_data_sources.feature (100%) rename {tests => src/vip_tests}/connect/test_data_sources.py (93%) rename {tests => src/vip_tests}/connect/test_email.feature (100%) rename {tests => src/vip_tests}/connect/test_email.py (100%) rename {tests => src/vip_tests}/connect/test_packages.feature (100%) rename {tests => src/vip_tests}/connect/test_packages.py (98%) rename {tests => src/vip_tests}/connect/test_runtime_versions.feature (100%) rename {tests => src/vip_tests}/connect/test_runtime_versions.py (100%) rename {tests => src/vip_tests}/connect/test_users.feature (100%) rename {tests => src/vip_tests}/connect/test_users.py (100%) rename {tests => src/vip_tests}/cross_product/__init__.py (100%) rename {tests => src/vip_tests}/cross_product/conftest.py (100%) rename {tests => src/vip_tests}/cross_product/test_integration.feature (100%) rename {tests => src/vip_tests}/cross_product/test_integration.py (98%) rename {tests => src/vip_tests}/cross_product/test_monitoring.feature (100%) rename {tests => src/vip_tests}/cross_product/test_monitoring.py (100%) rename {tests => src/vip_tests}/cross_product/test_resources.feature (100%) rename {tests => src/vip_tests}/cross_product/test_resources.py (100%) rename {tests => src/vip_tests}/cross_product/test_ssl.feature (100%) rename {tests => src/vip_tests}/cross_product/test_ssl.py (100%) rename {tests => src/vip_tests}/helpers.py (100%) rename {tests => src/vip_tests}/package_manager/__init__.py (100%) rename {tests => src/vip_tests}/package_manager/conftest.py (100%) rename {tests => src/vip_tests}/package_manager/test_private_repos.feature (100%) rename {tests => src/vip_tests}/package_manager/test_private_repos.py (100%) rename {tests => src/vip_tests}/package_manager/test_repos.feature (100%) rename {tests => src/vip_tests}/package_manager/test_repos.py (100%) rename {tests => src/vip_tests}/performance/__init__.py (100%) rename {tests => src/vip_tests}/performance/conftest.py (100%) rename {tests => src/vip_tests}/performance/test_concurrency.feature (100%) rename {tests => src/vip_tests}/performance/test_concurrency.py (100%) rename {tests => src/vip_tests}/performance/test_login_load_times.feature (100%) rename {tests => src/vip_tests}/performance/test_login_load_times.py (100%) rename {tests => src/vip_tests}/performance/test_package_install_speed.feature (100%) rename {tests => src/vip_tests}/performance/test_package_install_speed.py (100%) rename {tests => src/vip_tests}/performance/test_resource_usage.feature (100%) rename {tests => src/vip_tests}/performance/test_resource_usage.py (100%) rename {tests => src/vip_tests}/prerequisites/__init__.py (100%) rename {tests => src/vip_tests}/prerequisites/conftest.py (100%) rename {tests => src/vip_tests}/prerequisites/test_auth_configured.feature (100%) rename {tests => src/vip_tests}/prerequisites/test_auth_configured.py (100%) rename {tests => src/vip_tests}/prerequisites/test_components.feature (100%) rename {tests => src/vip_tests}/prerequisites/test_components.py (100%) rename {tests => src/vip_tests}/prerequisites/test_expected_failure.feature (100%) rename {tests => src/vip_tests}/prerequisites/test_expected_failure.py (100%) rename {tests => src/vip_tests}/prerequisites/test_versions.feature (100%) rename {tests => src/vip_tests}/prerequisites/test_versions.py (100%) rename {tests => src/vip_tests}/security/__init__.py (100%) rename {tests => src/vip_tests}/security/conftest.py (100%) rename {tests => src/vip_tests}/security/test_auth_policy.feature (100%) rename {tests => src/vip_tests}/security/test_auth_policy.py (100%) rename {tests => src/vip_tests}/security/test_error_handling.feature (100%) rename {tests => src/vip_tests}/security/test_error_handling.py (100%) rename {tests => src/vip_tests}/security/test_https.feature (100%) rename {tests => src/vip_tests}/security/test_https.py (100%) rename {tests => src/vip_tests}/security/test_secrets.feature (100%) rename {tests => src/vip_tests}/security/test_secrets.py (100%) rename {tests => src/vip_tests}/troubleshooting.toml (100%) rename {tests => src/vip_tests}/workbench/__init__.py (100%) rename {tests => src/vip_tests}/workbench/conftest.py (99%) rename {tests => src/vip_tests}/workbench/pages/__init__.py (100%) rename {tests => src/vip_tests}/workbench/pages/console_pane.py (100%) rename {tests => src/vip_tests}/workbench/pages/homepage.py (100%) rename {tests => src/vip_tests}/workbench/pages/ide_base.py (100%) rename {tests => src/vip_tests}/workbench/pages/jupyterlab_session.py (100%) rename {tests => src/vip_tests}/workbench/pages/login.py (100%) rename {tests => src/vip_tests}/workbench/pages/positron_session.py (100%) rename {tests => src/vip_tests}/workbench/pages/rstudio_session.py (100%) rename {tests => src/vip_tests}/workbench/pages/vscode_session.py (100%) rename {tests => src/vip_tests}/workbench/test_auth.feature (100%) rename {tests => src/vip_tests}/workbench/test_auth.py (95%) rename {tests => src/vip_tests}/workbench/test_data_sources.feature (100%) rename {tests => src/vip_tests}/workbench/test_data_sources.py (98%) rename {tests => src/vip_tests}/workbench/test_ide_launch.feature (100%) rename {tests => src/vip_tests}/workbench/test_ide_launch.py (99%) rename {tests => src/vip_tests}/workbench/test_packages.feature (100%) rename {tests => src/vip_tests}/workbench/test_packages.py (98%) rename {tests => src/vip_tests}/workbench/test_sessions.feature (100%) rename {tests => src/vip_tests}/workbench/test_sessions.py (98%) create mode 100644 validation_docs/demo-fix-package-build.md create mode 100644 validation_docs/demo-option-b-vip-tests.md diff --git a/.claude/agents/test-architect.md b/.claude/agents/test-architect.md index 3bdd6de..e8cb555 100644 --- a/.claude/agents/test-architect.md +++ b/.claude/agents/test-architect.md @@ -23,8 +23,8 @@ Work through each layer top-down: - Write scenarios in business language -- no URLs, status codes, or selectors - Always include a product tag: `@connect`, `@workbench`, or `@package_manager` -- Place in the correct category directory under `tests/` -- Reuse existing Given steps from `tests/conftest.py` for common guards +- Place in the correct category directory under `src/vip_tests/` +- Reuse existing Given steps from `src/vip_tests/conftest.py` for common guards ### 2. Step Definitions (Layer 2) @@ -32,7 +32,7 @@ Work through each layer top-down: - Use `@scenario("file.feature", "Scenario name")` to link scenarios - Use `target_fixture` to pass state between steps - Keep steps under ~10 lines; push logic to the client layer -- Reuse existing steps; check `tests/conftest.py` and sibling test files +- Reuse existing steps; check `src/vip_tests/conftest.py` and sibling test files ### 3. Driver Port (Layer 3) @@ -64,9 +64,9 @@ When reviewing test code, verify: - Tests must be non-destructive. Tag created content with `_vip_test` and clean up. - Use `pytest.skip("reason")` in Given steps when preconditions aren't met -- don't use assertions, which produce confusing failures instead of clean skips. -- Fixtures are defined in `tests/conftest.py` (session-scoped) and available everywhere. +- Fixtures are defined in `src/vip_tests/conftest.py` (session-scoped) and available everywhere. - Available clients: `connect_client`, `workbench_client`, `pm_client` (all session-scoped, `None` if unconfigured). -- Selftests in `selftests/` verify framework behavior; product tests in `tests/` verify deployments. +- Selftests in `selftests/` verify framework behavior; product tests in `src/vip_tests/` verify deployments. ## Anti-Patterns to Flag diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2244da5..394e8e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,13 +16,13 @@ jobs: name: Ruff lint with: version: "0.15.0" - args: check src/ tests/ selftests/ examples/ + args: check src/ selftests/ examples/ - uses: astral-sh/ruff-action@v3 name: Ruff format with: version: "0.15.0" - args: format --check src/ tests/ selftests/ examples/ + args: format --check src/ selftests/ examples/ typecheck: name: Type Check @@ -86,7 +86,7 @@ jobs: run: uv run pytest selftests/ -v --junitxml=selftest-results.xml --cov=src/vip --cov-report=term-missing - name: Collect VIP tests (dry run) - run: uv run pytest tests/ --collect-only --quiet + run: uv run pytest src/vip_tests/ --collect-only --quiet - name: Upload test results if: always() diff --git a/.github/workflows/connect-smoke.yml b/.github/workflows/connect-smoke.yml index dcb4203..80b904e 100644 --- a/.github/workflows/connect-smoke.yml +++ b/.github/workflows/connect-smoke.yml @@ -131,11 +131,11 @@ jobs: - name: Run Connect smoke tests run: | uv run pytest \ - tests/prerequisites/test_components.py \ - tests/connect/test_auth.py \ - tests/connect/test_users.py \ - tests/connect/test_runtime_versions.py \ - tests/security/test_error_handling.py \ + src/vip_tests/prerequisites/test_components.py \ + src/vip_tests/connect/test_auth.py \ + src/vip_tests/connect/test_users.py \ + src/vip_tests/connect/test_runtime_versions.py \ + src/vip_tests/security/test_error_handling.py \ -v \ -k "not connect_login_ui" \ --vip-config=vip.toml \ diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index 392c8e4..046e260 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -129,10 +129,10 @@ jobs: VIP_ENABLE_EXPECTED_FAILURE_DEMO: "1" run: | uv run pytest \ - tests/prerequisites/test_components.py \ - tests/prerequisites/test_expected_failure.py \ - tests/connect/test_auth.py \ - tests/package_manager/test_repos.py \ + src/vip_tests/prerequisites/test_components.py \ + src/vip_tests/prerequisites/test_expected_failure.py \ + src/vip_tests/connect/test_auth.py \ + src/vip_tests/package_manager/test_repos.py \ -v -k "reachable or api or mirror or repo_exists or expected_failure" \ --vip-config=vip.toml \ || true diff --git a/.github/workflows/packagemanager-smoke.yml b/.github/workflows/packagemanager-smoke.yml index 44e445b..c9d0330 100644 --- a/.github/workflows/packagemanager-smoke.yml +++ b/.github/workflows/packagemanager-smoke.yml @@ -172,9 +172,9 @@ jobs: if: steps.license.outputs.available == 'true' run: | uv run pytest \ - "tests/prerequisites/test_components.py::test_product_server_is_reachable[Package Manager]" \ - tests/prerequisites/test_versions.py \ - tests/package_manager/test_repos.py \ + "src/vip_tests/prerequisites/test_components.py::test_product_server_is_reachable[Package Manager]" \ + src/vip_tests/prerequisites/test_versions.py \ + src/vip_tests/package_manager/test_repos.py \ -v \ --vip-config=vip.toml \ --junitxml=smoke-results.xml diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 2822a84..7a171c2 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -5,7 +5,7 @@ on: branches: [main] paths: - "website/**" - - "tests/**" + - "src/vip_tests/**" - "report/**" - "src/vip/**" - "scripts/generate-test-catalog.py" diff --git a/.github/workflows/workbench-smoke.yml b/.github/workflows/workbench-smoke.yml index a0e6823..c9d9ec1 100644 --- a/.github/workflows/workbench-smoke.yml +++ b/.github/workflows/workbench-smoke.yml @@ -162,8 +162,8 @@ jobs: - name: Run Workbench smoke tests run: | uv run pytest \ - tests/prerequisites/test_components.py \ - tests/workbench/test_auth.py \ + src/vip_tests/prerequisites/test_components.py \ + src/vip_tests/workbench/test_auth.py \ -v -k "workbench" \ --vip-config=vip.toml \ --junitxml=smoke-results.xml diff --git a/AGENTS.md b/AGENTS.md index 3e01b7b..159a910 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,8 +20,8 @@ Use `uv run` to execute all commands (pytest, ruff, quarto). Do not use bare `py Ruff is the linter and formatter. CI enforces both. Always run checks before committing: ``` bash -uv run ruff check src/ tests/ selftests/ examples/ -uv run ruff format --check src/ tests/ selftests/ examples/ +uv run ruff check src/ src/vip_tests/ selftests/ examples/ +uv run ruff format --check src/ src/vip_tests/ selftests/ examples/ ``` Or with just: @@ -30,7 +30,7 @@ Or with just: just check ``` -Ruff rules: `E`, `F`, `I`, `UP`. Line length is 100. All Python directories (`src/`, `tests/`, `selftests/`, `examples/`) must pass. CI pins ruff to version 0.15.0 -- do not change the version without updating `.github/workflows/ci.yml`. +Ruff rules: `E`, `F`, `I`, `UP`. Line length is 100. All Python directories (`src/`, `src/vip_tests/`, `selftests/`, `examples/`) must pass. CI pins ruff to version 0.15.0 -- do not change the version without updating `.github/workflows/ci.yml`. Auto-fix before committing: @@ -52,18 +52,18 @@ uv run pytest selftests/ -v Run selftests after any change to `src/vip/`. If you add new config fields, plugin hooks, or reporting features, add corresponding selftests. Plugin integration tests use the `pytester` fixture (subprocess isolation). -### Product tests (`tests/`) +### Product tests (`src/vip_tests/`) BDD tests that run against real Posit Team deployments. These are organized by category: ``` -tests/prerequisites/ # Server reachability, auth -tests/package_manager/ # CRAN/PyPI mirrors, repos -tests/connect/ # Auth, deploy, data sources, packages, email -tests/workbench/ # Auth, IDE launch, sessions, packages -tests/cross_product/ # SSL, monitoring, system resources -tests/performance/ # Load times, concurrency -tests/security/ # HTTPS, auth policy, secrets +src/vip_tests/prerequisites/ # Server reachability, auth +src/vip_tests/package_manager/ # CRAN/PyPI mirrors, repos +src/vip_tests/connect/ # Auth, deploy, data sources, packages, email +src/vip_tests/workbench/ # Auth, IDE launch, sessions, packages +src/vip_tests/cross_product/ # SSL, monitoring, system resources +src/vip_tests/performance/ # Load times, concurrency +src/vip_tests/security/ # HTTPS, auth policy, secrets ``` Product tests cannot run in CI (no products available). They are collected with `--collect-only` as a dry run in CI. @@ -75,7 +75,7 @@ Every test is a pair of files: 1. **`.feature` file** -- Gherkin scenarios with a product marker tag 2. **`.py` file** -- Step definitions using `pytest_bdd` -Example feature file (`tests/connect/test_auth.feature`): +Example feature file (`src/vip_tests/connect/test_auth.feature`): ``` gherkin @connect @@ -86,7 +86,7 @@ Feature: Connect authentication Then I see the Connect dashboard ``` -Example step file (`tests/connect/test_auth.py`): +Example step file (`src/vip_tests/connect/test_auth.py`): ``` python from pytest_bdd import scenario, given, when, then @@ -145,13 +145,13 @@ Key principles: | `src/vip/verify/site.py` | PTD Site CR parsing, vip.toml generation | | `src/vip/verify/credentials.py` | Keycloak + interactive credential provisioning | | `src/vip/verify/job.py` | K8s Job creation, log streaming, cleanup | -| `tests/conftest.py` | Root fixtures: clients, auth, runtimes, data sources | +| `src/vip_tests/conftest.py` | Root fixtures: clients, auth, runtimes, data sources | | `report/index.qmd` | Quarto summary page | | `report/details.qmd` | Quarto detailed results page | ## Fixtures available in product tests -These are defined in `tests/conftest.py` and available to all tests: +These are defined in `src/vip_tests/conftest.py` and available to all tests: - `vip_config` -- the full `VIPConfig` object - `connect_client` / `workbench_client` / `pm_client` -- httpx API clients (or `None` if not configured) diff --git a/IMPLEMENTATION_GUIDE.md b/IMPLEMENTATION_GUIDE.md index 49a9d4b..2f1d1f4 100644 --- a/IMPLEMENTATION_GUIDE.md +++ b/IMPLEMENTATION_GUIDE.md @@ -18,7 +18,7 @@ vip/ │ ├── workbench.py # Health, server info, sessions │ └── packagemanager.py # Repos, CRAN/PyPI availability │ -├── tests/ # The VIP test suite (runs against real products) +├── src/vip_tests/ # The VIP test suite (runs against real products) │ ├── conftest.py # Root fixtures: clients, auth, runtimes, data sources │ ├── prerequisites/ # Server reachability, auth credentials present │ ├── package_manager/ # CRAN/PyPI mirrors, repo listing, private repos @@ -38,7 +38,7 @@ vip/ │ ├── index.qmd # Summary: pass/fail counts, category breakdown, failures │ └── details.qmd # Per-test listing with outcome and duration │ -├── examples/custom_tests/ # Extension example for customer-specific tests +├── examples/custom_src/vip_tests/ # Extension example for customer-specific tests ├── .github/workflows/ │ ├── ci.yml # Lint + format + selftests (Python 3.10 & 3.12) │ └── preview.yml # Render Quarto report and publish PR preview to gh-pages @@ -114,15 +114,15 @@ environment. This will immediately surface: ```bash cp vip.toml.example vip.toml # Fill in real URLs, set env vars for secrets -pytest tests/ -v 2>&1 | tee first-run.log +pytest src/vip_tests/ -v 2>&1 | tee first-run.log ``` Start with prerequisites, then work outward: ```bash -pytest tests/prerequisites/ -v -pytest tests/connect/test_auth.py -v -pytest tests/connect/ -v +pytest src/vip_tests/prerequisites/ -v +pytest src/vip_tests/connect/test_auth.py -v +pytest src/vip_tests/connect/ -v # ... ``` @@ -147,7 +147,7 @@ When fixing selectors: Two tests are stubs that need real implementations: -**`tests/workbench/test_packages.py` - R repos.conf check** +**`src/vip_tests/workbench/test_packages.py` - R repos.conf check** The current step `check_r_repos` returns the Workbench URL without verifying anything. To implement it: @@ -159,7 +159,7 @@ anything. To implement it: - Option C (session): Start a session, run `getOption("repos")` in the R console, and verify the output contains the expected URL. -**`tests/security/test_secrets.py` - plaintext detection** +**`src/vip_tests/security/test_secrets.py` - plaintext detection** The current placeholder list (`{"...", "your-api-key", "changeme", ""}`) works for basic cases. To harden it: @@ -288,7 +288,7 @@ pairs. A `conftest.py` in the directory can define customer-specific fixtures. All VIP fixtures (`vip_config`, `connect_client`, etc.) are available automatically. -See `examples/custom_tests/` for a working example. +See `examples/custom_src/vip_tests/` for a working example. ### 9. Improve the Quarto report diff --git a/docs/development.md b/docs/development.md index 8f325ef..9e8b296 100644 --- a/docs/development.md +++ b/docs/development.md @@ -32,10 +32,10 @@ just format # ruff format Without just, run ruff directly: ```bash -uv run ruff check src/ tests/ # lint -uv run ruff format --check src/ tests/ # format check -uv run ruff check --fix src/ tests/ # auto-fix lint -uv run ruff format src/ tests/ # reformat +uv run ruff check src/ src/vip_tests/ # lint +uv run ruff format --check src/ src/vip_tests/ # format check +uv run ruff check --fix src/ src/vip_tests/ # auto-fix lint +uv run ruff format src/ src/vip_tests/ # reformat ``` ## Type checking diff --git a/plans/connect-ci-testing.md b/plans/connect-ci-testing.md index 7ffd05a..7c394f3 100644 --- a/plans/connect-ci-testing.md +++ b/plans/connect-ci-testing.md @@ -168,8 +168,8 @@ jobs: - name: Run Connect smoke tests run: | uv run pytest \ - tests/prerequisites/test_components.py \ - tests/connect/test_auth.py \ + src/vip_tests/prerequisites/test_components.py \ + src/vip_tests/connect/test_auth.py \ -v -k "reachable or api" \ --vip-config=vip.toml env: diff --git a/plans/workbench-ci-testing.md b/plans/workbench-ci-testing.md index 6960084..88b07dc 100644 --- a/plans/workbench-ci-testing.md +++ b/plans/workbench-ci-testing.md @@ -85,7 +85,7 @@ RESPONSE=$(curl -s -b /tmp/wb_cookies.txt http://localhost:8787/api/server-info) VERSION=$(echo "${RESPONSE}" | jq -r '.version // empty') # Run tests -pytest tests/prerequisites/test_components.py tests/workbench/test_auth.py ... +pytest src/vip_tests/prerequisites/test_components.py src/vip_tests/workbench/test_auth.py ... # Cleanup docker stop workbench && docker rm workbench || true @@ -96,21 +96,21 @@ docker stop workbench && docker rm workbench || true | Test file | Feasibility | Notes | |---|---|---| | `prerequisites/test_components` | ✅ Yes | Health-check only — no auth required | -| `workbench/test_auth` | ✅ Yes | Password form login; uses page objects from `tests/workbench/pages/` | +| `workbench/test_auth` | ✅ Yes | Password form login; uses page objects from `src/vip_tests/workbench/pages/` | | `workbench/test_ide_launch` | ❌ No | Requires R/Python; not in minimal image | | `workbench/test_packages` | ❌ No | Requires R runtime | | `workbench/test_data_sources` | ❌ No | Requires external databases | > **Note:** `test_runtime_versions` and `test_sessions` were removed from the > workbench test suite (commit `f45d242` on main). Runtime and session fixtures -> now live in `tests/workbench/conftest.py` and are consumed by `test_ide_launch`. +> now live in `src/vip_tests/workbench/conftest.py` and are consumed by `test_ide_launch`. ### Recommended initial test scope 1. **`prerequisites/test_components`** — Workbench health check (no credentials) 2. **`workbench/test_auth`** — Web UI login with the PAM test user -`test_auth` now uses the page-object selectors in `tests/workbench/pages/` +`test_auth` now uses the page-object selectors in `src/vip_tests/workbench/pages/` (e.g. `#posit-logo`, `#current-user`, `button:text-is('New Session')`) — the same selectors used by rstudio-pro's own end-to-end suite. It also verifies that `#current-user` text matches `auth.username` (`rstudio` in the Docker @@ -247,8 +247,8 @@ jobs: - name: Run Workbench smoke tests run: | uv run pytest \ - tests/prerequisites/test_components.py \ - tests/workbench/test_auth.py \ + src/vip_tests/prerequisites/test_components.py \ + src/vip_tests/workbench/test_auth.py \ -v -k "workbench" \ --vip-config=vip.toml \ --junitxml=smoke-results.xml @@ -336,7 +336,7 @@ Once Phase 1 is stable: 1. **Add a version matrix** for additional stable releases. 2. **Add `workbench/test_ide_launch`** using a Workbench image that ships R/Python runtimes, or build a custom image on top of the official one. - The `wb_start_session` fixture in `tests/workbench/conftest.py` already + The `wb_start_session` fixture in `src/vip_tests/workbench/conftest.py` already provides the session-launch helper. 3. **Add `workbench/test_packages`** once R runtime is available in the image. @@ -368,7 +368,7 @@ Once Phase 1 is stable: a private registry. 3. **UI selector accuracy**: The Playwright selectors in `test_auth.py` use the - page-object classes in `tests/workbench/pages/` — mirroring the rstudio-pro + page-object classes in `src/vip_tests/workbench/pages/` — mirroring the rstudio-pro e2e selectors (`#posit-logo`, `#current-user`, etc.). They should be accurate for the 2026.01 image but may need adjustment for older releases. diff --git a/pyproject.toml b/pyproject.toml index bdadf40..c27e3db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,10 +54,10 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/vip"] +packages = ["src/vip", "src/vip_tests"] [tool.pytest.ini_options] -testpaths = ["tests"] +testpaths = ["src/vip_tests"] addopts = "--dist loadgroup" filterwarnings = [ # gherkin-official 29.0.0 passes maxsplit positionally to re.split; @@ -98,7 +98,7 @@ ignore_missing_imports = true [tool.ruff] line-length = 100 -src = ["src", "tests", "selftests", "examples"] +src = ["src", "selftests", "examples"] [tool.ruff.lint] select = ["E", "F", "I", "UP"] diff --git a/src/vip/app/app.py b/src/vip/app/app.py index 7224384..cb1a951 100644 --- a/src/vip/app/app.py +++ b/src/vip/app/app.py @@ -33,10 +33,10 @@ def _find_project_root() -> Path: - """Walk up from cwd looking for a directory containing both `tests/` and `pyproject.toml`.""" + """Walk up from cwd looking for a directory with both `src/vip_tests/` and `pyproject.toml`.""" candidate = Path.cwd() for _ in range(10): - if (candidate / "tests").is_dir() and (candidate / "pyproject.toml").is_file(): + if (candidate / "src" / "vip_tests").is_dir() and (candidate / "pyproject.toml").is_file(): return candidate parent = candidate.parent if parent == candidate: @@ -55,7 +55,7 @@ def _find_project_root() -> Path: def _load_category_features() -> dict[str, list[dict]]: """Return {category_key: [parsed_feature, ...]} for all .feature files.""" - tests_dir = PROJECT_ROOT / "tests" + tests_dir = PROJECT_ROOT / "src" / "vip_tests" result: dict[str, list[dict]] = {} for key in CATEGORIES: cat_dir = tests_dir / key @@ -360,7 +360,7 @@ def _build_command() -> tuple[list[str], str | None]: "-m", "pytest", f"--rootdir={PROJECT_ROOT}", - str(PROJECT_ROOT / "tests"), + str(PROJECT_ROOT / "src" / "vip_tests"), ] temp_config = None @@ -613,7 +613,7 @@ def report_view(): from vip.reporting import load_results, load_troubleshooting data = load_results(report_path) - troubleshooting_path = PROJECT_ROOT / "tests" / "troubleshooting.toml" + troubleshooting_path = PROJECT_ROOT / "src" / "vip_tests" / "troubleshooting.toml" hints = load_troubleshooting(troubleshooting_path) html = render_report_html( diff --git a/tests/__init__.py b/src/vip_tests/__init__.py similarity index 100% rename from tests/__init__.py rename to src/vip_tests/__init__.py diff --git a/tests/conftest.py b/src/vip_tests/conftest.py similarity index 100% rename from tests/conftest.py rename to src/vip_tests/conftest.py diff --git a/tests/connect/__init__.py b/src/vip_tests/connect/__init__.py similarity index 100% rename from tests/connect/__init__.py rename to src/vip_tests/connect/__init__.py diff --git a/tests/connect/conftest.py b/src/vip_tests/connect/conftest.py similarity index 100% rename from tests/connect/conftest.py rename to src/vip_tests/connect/conftest.py diff --git a/tests/connect/plumber_manifest.json b/src/vip_tests/connect/plumber_manifest.json similarity index 100% rename from tests/connect/plumber_manifest.json rename to src/vip_tests/connect/plumber_manifest.json diff --git a/tests/connect/shiny_manifest.json b/src/vip_tests/connect/shiny_manifest.json similarity index 100% rename from tests/connect/shiny_manifest.json rename to src/vip_tests/connect/shiny_manifest.json diff --git a/tests/connect/test_auth.feature b/src/vip_tests/connect/test_auth.feature similarity index 100% rename from tests/connect/test_auth.feature rename to src/vip_tests/connect/test_auth.feature diff --git a/tests/connect/test_auth.py b/src/vip_tests/connect/test_auth.py similarity index 100% rename from tests/connect/test_auth.py rename to src/vip_tests/connect/test_auth.py diff --git a/tests/connect/test_content_deploy.feature b/src/vip_tests/connect/test_content_deploy.feature similarity index 100% rename from tests/connect/test_content_deploy.feature rename to src/vip_tests/connect/test_content_deploy.feature diff --git a/tests/connect/test_content_deploy.py b/src/vip_tests/connect/test_content_deploy.py similarity index 99% rename from tests/connect/test_content_deploy.py rename to src/vip_tests/connect/test_content_deploy.py index 3dab513..9a5d299 100644 --- a/tests/connect/test_content_deploy.py +++ b/src/vip_tests/connect/test_content_deploy.py @@ -14,7 +14,7 @@ import pytest from pytest_bdd import scenario, then, when -from tests.connect.conftest import _make_tar_gz +from vip_tests.connect.conftest import _make_tar_gz _GIT_REPO_URL = "https://github.com/posit-dev/connect-extensions" # Using main branch — this is a Posit-maintained repo with stable examples. diff --git a/tests/connect/test_data_sources.feature b/src/vip_tests/connect/test_data_sources.feature similarity index 100% rename from tests/connect/test_data_sources.feature rename to src/vip_tests/connect/test_data_sources.feature diff --git a/tests/connect/test_data_sources.py b/src/vip_tests/connect/test_data_sources.py similarity index 93% rename from tests/connect/test_data_sources.py rename to src/vip_tests/connect/test_data_sources.py index f05a276..c4feca8 100644 --- a/tests/connect/test_data_sources.py +++ b/src/vip_tests/connect/test_data_sources.py @@ -5,7 +5,7 @@ import pytest from pytest_bdd import given, scenario, then, when -from tests.helpers import check_data_source_connectivity +from vip_tests.helpers import check_data_source_connectivity @scenario("test_data_sources.feature", "External data sources are reachable from Connect") diff --git a/tests/connect/test_email.feature b/src/vip_tests/connect/test_email.feature similarity index 100% rename from tests/connect/test_email.feature rename to src/vip_tests/connect/test_email.feature diff --git a/tests/connect/test_email.py b/src/vip_tests/connect/test_email.py similarity index 100% rename from tests/connect/test_email.py rename to src/vip_tests/connect/test_email.py diff --git a/tests/connect/test_packages.feature b/src/vip_tests/connect/test_packages.feature similarity index 100% rename from tests/connect/test_packages.feature rename to src/vip_tests/connect/test_packages.feature diff --git a/tests/connect/test_packages.py b/src/vip_tests/connect/test_packages.py similarity index 98% rename from tests/connect/test_packages.py rename to src/vip_tests/connect/test_packages.py index 4228be7..77b66af 100644 --- a/tests/connect/test_packages.py +++ b/src/vip_tests/connect/test_packages.py @@ -7,7 +7,7 @@ import pytest from pytest_bdd import given, scenario, then, when -from tests.connect.conftest import _make_tar_gz +from vip_tests.connect.conftest import _make_tar_gz @scenario("test_packages.feature", "Connect is configured to use the expected package repository") diff --git a/tests/connect/test_runtime_versions.feature b/src/vip_tests/connect/test_runtime_versions.feature similarity index 100% rename from tests/connect/test_runtime_versions.feature rename to src/vip_tests/connect/test_runtime_versions.feature diff --git a/tests/connect/test_runtime_versions.py b/src/vip_tests/connect/test_runtime_versions.py similarity index 100% rename from tests/connect/test_runtime_versions.py rename to src/vip_tests/connect/test_runtime_versions.py diff --git a/tests/connect/test_users.feature b/src/vip_tests/connect/test_users.feature similarity index 100% rename from tests/connect/test_users.feature rename to src/vip_tests/connect/test_users.feature diff --git a/tests/connect/test_users.py b/src/vip_tests/connect/test_users.py similarity index 100% rename from tests/connect/test_users.py rename to src/vip_tests/connect/test_users.py diff --git a/tests/cross_product/__init__.py b/src/vip_tests/cross_product/__init__.py similarity index 100% rename from tests/cross_product/__init__.py rename to src/vip_tests/cross_product/__init__.py diff --git a/tests/cross_product/conftest.py b/src/vip_tests/cross_product/conftest.py similarity index 100% rename from tests/cross_product/conftest.py rename to src/vip_tests/cross_product/conftest.py diff --git a/tests/cross_product/test_integration.feature b/src/vip_tests/cross_product/test_integration.feature similarity index 100% rename from tests/cross_product/test_integration.feature rename to src/vip_tests/cross_product/test_integration.feature diff --git a/tests/cross_product/test_integration.py b/src/vip_tests/cross_product/test_integration.py similarity index 98% rename from tests/cross_product/test_integration.py rename to src/vip_tests/cross_product/test_integration.py index 6abf1cd..bfc8cf2 100644 --- a/tests/cross_product/test_integration.py +++ b/src/vip_tests/cross_product/test_integration.py @@ -7,7 +7,7 @@ import pytest from pytest_bdd import scenario, then, when -from tests.connect.conftest import _make_tar_gz +from vip_tests.connect.conftest import _make_tar_gz @scenario( diff --git a/tests/cross_product/test_monitoring.feature b/src/vip_tests/cross_product/test_monitoring.feature similarity index 100% rename from tests/cross_product/test_monitoring.feature rename to src/vip_tests/cross_product/test_monitoring.feature diff --git a/tests/cross_product/test_monitoring.py b/src/vip_tests/cross_product/test_monitoring.py similarity index 100% rename from tests/cross_product/test_monitoring.py rename to src/vip_tests/cross_product/test_monitoring.py diff --git a/tests/cross_product/test_resources.feature b/src/vip_tests/cross_product/test_resources.feature similarity index 100% rename from tests/cross_product/test_resources.feature rename to src/vip_tests/cross_product/test_resources.feature diff --git a/tests/cross_product/test_resources.py b/src/vip_tests/cross_product/test_resources.py similarity index 100% rename from tests/cross_product/test_resources.py rename to src/vip_tests/cross_product/test_resources.py diff --git a/tests/cross_product/test_ssl.feature b/src/vip_tests/cross_product/test_ssl.feature similarity index 100% rename from tests/cross_product/test_ssl.feature rename to src/vip_tests/cross_product/test_ssl.feature diff --git a/tests/cross_product/test_ssl.py b/src/vip_tests/cross_product/test_ssl.py similarity index 100% rename from tests/cross_product/test_ssl.py rename to src/vip_tests/cross_product/test_ssl.py diff --git a/tests/helpers.py b/src/vip_tests/helpers.py similarity index 100% rename from tests/helpers.py rename to src/vip_tests/helpers.py diff --git a/tests/package_manager/__init__.py b/src/vip_tests/package_manager/__init__.py similarity index 100% rename from tests/package_manager/__init__.py rename to src/vip_tests/package_manager/__init__.py diff --git a/tests/package_manager/conftest.py b/src/vip_tests/package_manager/conftest.py similarity index 100% rename from tests/package_manager/conftest.py rename to src/vip_tests/package_manager/conftest.py diff --git a/tests/package_manager/test_private_repos.feature b/src/vip_tests/package_manager/test_private_repos.feature similarity index 100% rename from tests/package_manager/test_private_repos.feature rename to src/vip_tests/package_manager/test_private_repos.feature diff --git a/tests/package_manager/test_private_repos.py b/src/vip_tests/package_manager/test_private_repos.py similarity index 100% rename from tests/package_manager/test_private_repos.py rename to src/vip_tests/package_manager/test_private_repos.py diff --git a/tests/package_manager/test_repos.feature b/src/vip_tests/package_manager/test_repos.feature similarity index 100% rename from tests/package_manager/test_repos.feature rename to src/vip_tests/package_manager/test_repos.feature diff --git a/tests/package_manager/test_repos.py b/src/vip_tests/package_manager/test_repos.py similarity index 100% rename from tests/package_manager/test_repos.py rename to src/vip_tests/package_manager/test_repos.py diff --git a/tests/performance/__init__.py b/src/vip_tests/performance/__init__.py similarity index 100% rename from tests/performance/__init__.py rename to src/vip_tests/performance/__init__.py diff --git a/tests/performance/conftest.py b/src/vip_tests/performance/conftest.py similarity index 100% rename from tests/performance/conftest.py rename to src/vip_tests/performance/conftest.py diff --git a/tests/performance/test_concurrency.feature b/src/vip_tests/performance/test_concurrency.feature similarity index 100% rename from tests/performance/test_concurrency.feature rename to src/vip_tests/performance/test_concurrency.feature diff --git a/tests/performance/test_concurrency.py b/src/vip_tests/performance/test_concurrency.py similarity index 100% rename from tests/performance/test_concurrency.py rename to src/vip_tests/performance/test_concurrency.py diff --git a/tests/performance/test_login_load_times.feature b/src/vip_tests/performance/test_login_load_times.feature similarity index 100% rename from tests/performance/test_login_load_times.feature rename to src/vip_tests/performance/test_login_load_times.feature diff --git a/tests/performance/test_login_load_times.py b/src/vip_tests/performance/test_login_load_times.py similarity index 100% rename from tests/performance/test_login_load_times.py rename to src/vip_tests/performance/test_login_load_times.py diff --git a/tests/performance/test_package_install_speed.feature b/src/vip_tests/performance/test_package_install_speed.feature similarity index 100% rename from tests/performance/test_package_install_speed.feature rename to src/vip_tests/performance/test_package_install_speed.feature diff --git a/tests/performance/test_package_install_speed.py b/src/vip_tests/performance/test_package_install_speed.py similarity index 100% rename from tests/performance/test_package_install_speed.py rename to src/vip_tests/performance/test_package_install_speed.py diff --git a/tests/performance/test_resource_usage.feature b/src/vip_tests/performance/test_resource_usage.feature similarity index 100% rename from tests/performance/test_resource_usage.feature rename to src/vip_tests/performance/test_resource_usage.feature diff --git a/tests/performance/test_resource_usage.py b/src/vip_tests/performance/test_resource_usage.py similarity index 100% rename from tests/performance/test_resource_usage.py rename to src/vip_tests/performance/test_resource_usage.py diff --git a/tests/prerequisites/__init__.py b/src/vip_tests/prerequisites/__init__.py similarity index 100% rename from tests/prerequisites/__init__.py rename to src/vip_tests/prerequisites/__init__.py diff --git a/tests/prerequisites/conftest.py b/src/vip_tests/prerequisites/conftest.py similarity index 100% rename from tests/prerequisites/conftest.py rename to src/vip_tests/prerequisites/conftest.py diff --git a/tests/prerequisites/test_auth_configured.feature b/src/vip_tests/prerequisites/test_auth_configured.feature similarity index 100% rename from tests/prerequisites/test_auth_configured.feature rename to src/vip_tests/prerequisites/test_auth_configured.feature diff --git a/tests/prerequisites/test_auth_configured.py b/src/vip_tests/prerequisites/test_auth_configured.py similarity index 100% rename from tests/prerequisites/test_auth_configured.py rename to src/vip_tests/prerequisites/test_auth_configured.py diff --git a/tests/prerequisites/test_components.feature b/src/vip_tests/prerequisites/test_components.feature similarity index 100% rename from tests/prerequisites/test_components.feature rename to src/vip_tests/prerequisites/test_components.feature diff --git a/tests/prerequisites/test_components.py b/src/vip_tests/prerequisites/test_components.py similarity index 100% rename from tests/prerequisites/test_components.py rename to src/vip_tests/prerequisites/test_components.py diff --git a/tests/prerequisites/test_expected_failure.feature b/src/vip_tests/prerequisites/test_expected_failure.feature similarity index 100% rename from tests/prerequisites/test_expected_failure.feature rename to src/vip_tests/prerequisites/test_expected_failure.feature diff --git a/tests/prerequisites/test_expected_failure.py b/src/vip_tests/prerequisites/test_expected_failure.py similarity index 100% rename from tests/prerequisites/test_expected_failure.py rename to src/vip_tests/prerequisites/test_expected_failure.py diff --git a/tests/prerequisites/test_versions.feature b/src/vip_tests/prerequisites/test_versions.feature similarity index 100% rename from tests/prerequisites/test_versions.feature rename to src/vip_tests/prerequisites/test_versions.feature diff --git a/tests/prerequisites/test_versions.py b/src/vip_tests/prerequisites/test_versions.py similarity index 100% rename from tests/prerequisites/test_versions.py rename to src/vip_tests/prerequisites/test_versions.py diff --git a/tests/security/__init__.py b/src/vip_tests/security/__init__.py similarity index 100% rename from tests/security/__init__.py rename to src/vip_tests/security/__init__.py diff --git a/tests/security/conftest.py b/src/vip_tests/security/conftest.py similarity index 100% rename from tests/security/conftest.py rename to src/vip_tests/security/conftest.py diff --git a/tests/security/test_auth_policy.feature b/src/vip_tests/security/test_auth_policy.feature similarity index 100% rename from tests/security/test_auth_policy.feature rename to src/vip_tests/security/test_auth_policy.feature diff --git a/tests/security/test_auth_policy.py b/src/vip_tests/security/test_auth_policy.py similarity index 100% rename from tests/security/test_auth_policy.py rename to src/vip_tests/security/test_auth_policy.py diff --git a/tests/security/test_error_handling.feature b/src/vip_tests/security/test_error_handling.feature similarity index 100% rename from tests/security/test_error_handling.feature rename to src/vip_tests/security/test_error_handling.feature diff --git a/tests/security/test_error_handling.py b/src/vip_tests/security/test_error_handling.py similarity index 100% rename from tests/security/test_error_handling.py rename to src/vip_tests/security/test_error_handling.py diff --git a/tests/security/test_https.feature b/src/vip_tests/security/test_https.feature similarity index 100% rename from tests/security/test_https.feature rename to src/vip_tests/security/test_https.feature diff --git a/tests/security/test_https.py b/src/vip_tests/security/test_https.py similarity index 100% rename from tests/security/test_https.py rename to src/vip_tests/security/test_https.py diff --git a/tests/security/test_secrets.feature b/src/vip_tests/security/test_secrets.feature similarity index 100% rename from tests/security/test_secrets.feature rename to src/vip_tests/security/test_secrets.feature diff --git a/tests/security/test_secrets.py b/src/vip_tests/security/test_secrets.py similarity index 100% rename from tests/security/test_secrets.py rename to src/vip_tests/security/test_secrets.py diff --git a/tests/troubleshooting.toml b/src/vip_tests/troubleshooting.toml similarity index 100% rename from tests/troubleshooting.toml rename to src/vip_tests/troubleshooting.toml diff --git a/tests/workbench/__init__.py b/src/vip_tests/workbench/__init__.py similarity index 100% rename from tests/workbench/__init__.py rename to src/vip_tests/workbench/__init__.py diff --git a/tests/workbench/conftest.py b/src/vip_tests/workbench/conftest.py similarity index 99% rename from tests/workbench/conftest.py rename to src/vip_tests/workbench/conftest.py index 852f6a1..44a425a 100644 --- a/tests/workbench/conftest.py +++ b/src/vip_tests/workbench/conftest.py @@ -11,7 +11,7 @@ import pytest from playwright.sync_api import Page, expect -from tests.workbench.pages import Homepage, LoginPage +from vip_tests.workbench.pages import Homepage, LoginPage pytestmark = pytest.mark.workbench diff --git a/tests/workbench/pages/__init__.py b/src/vip_tests/workbench/pages/__init__.py similarity index 100% rename from tests/workbench/pages/__init__.py rename to src/vip_tests/workbench/pages/__init__.py diff --git a/tests/workbench/pages/console_pane.py b/src/vip_tests/workbench/pages/console_pane.py similarity index 100% rename from tests/workbench/pages/console_pane.py rename to src/vip_tests/workbench/pages/console_pane.py diff --git a/tests/workbench/pages/homepage.py b/src/vip_tests/workbench/pages/homepage.py similarity index 100% rename from tests/workbench/pages/homepage.py rename to src/vip_tests/workbench/pages/homepage.py diff --git a/tests/workbench/pages/ide_base.py b/src/vip_tests/workbench/pages/ide_base.py similarity index 100% rename from tests/workbench/pages/ide_base.py rename to src/vip_tests/workbench/pages/ide_base.py diff --git a/tests/workbench/pages/jupyterlab_session.py b/src/vip_tests/workbench/pages/jupyterlab_session.py similarity index 100% rename from tests/workbench/pages/jupyterlab_session.py rename to src/vip_tests/workbench/pages/jupyterlab_session.py diff --git a/tests/workbench/pages/login.py b/src/vip_tests/workbench/pages/login.py similarity index 100% rename from tests/workbench/pages/login.py rename to src/vip_tests/workbench/pages/login.py diff --git a/tests/workbench/pages/positron_session.py b/src/vip_tests/workbench/pages/positron_session.py similarity index 100% rename from tests/workbench/pages/positron_session.py rename to src/vip_tests/workbench/pages/positron_session.py diff --git a/tests/workbench/pages/rstudio_session.py b/src/vip_tests/workbench/pages/rstudio_session.py similarity index 100% rename from tests/workbench/pages/rstudio_session.py rename to src/vip_tests/workbench/pages/rstudio_session.py diff --git a/tests/workbench/pages/vscode_session.py b/src/vip_tests/workbench/pages/vscode_session.py similarity index 100% rename from tests/workbench/pages/vscode_session.py rename to src/vip_tests/workbench/pages/vscode_session.py diff --git a/tests/workbench/test_auth.feature b/src/vip_tests/workbench/test_auth.feature similarity index 100% rename from tests/workbench/test_auth.feature rename to src/vip_tests/workbench/test_auth.feature diff --git a/tests/workbench/test_auth.py b/src/vip_tests/workbench/test_auth.py similarity index 95% rename from tests/workbench/test_auth.py rename to src/vip_tests/workbench/test_auth.py index 4b4beef..7ce82e6 100644 --- a/tests/workbench/test_auth.py +++ b/src/vip_tests/workbench/test_auth.py @@ -6,12 +6,12 @@ from playwright.sync_api import Page, expect from pytest_bdd import given, scenario, then, when -from tests.workbench.conftest import ( +from vip_tests.workbench.conftest import ( TIMEOUT_DIALOG, assert_homepage_loaded, workbench_login, ) -from tests.workbench.pages import Homepage +from vip_tests.workbench.pages import Homepage @scenario("test_auth.feature", "User can log in to Workbench via the web UI") diff --git a/tests/workbench/test_data_sources.feature b/src/vip_tests/workbench/test_data_sources.feature similarity index 100% rename from tests/workbench/test_data_sources.feature rename to src/vip_tests/workbench/test_data_sources.feature diff --git a/tests/workbench/test_data_sources.py b/src/vip_tests/workbench/test_data_sources.py similarity index 98% rename from tests/workbench/test_data_sources.py rename to src/vip_tests/workbench/test_data_sources.py index 48ed000..4dcf020 100644 --- a/tests/workbench/test_data_sources.py +++ b/src/vip_tests/workbench/test_data_sources.py @@ -9,7 +9,7 @@ from playwright.sync_api import Page, expect from pytest_bdd import given, scenario, then, when -from tests.workbench.conftest import ( +from vip_tests.workbench.conftest import ( TIMEOUT_DIALOG, TIMEOUT_IDE_LOAD, TIMEOUT_PAGE_LOAD, @@ -18,7 +18,7 @@ assert_homepage_loaded, workbench_login, ) -from tests.workbench.pages import ( +from vip_tests.workbench.pages import ( ConsolePaneSelectors, Homepage, NewSessionDialog, diff --git a/tests/workbench/test_ide_launch.feature b/src/vip_tests/workbench/test_ide_launch.feature similarity index 100% rename from tests/workbench/test_ide_launch.feature rename to src/vip_tests/workbench/test_ide_launch.feature diff --git a/tests/workbench/test_ide_launch.py b/src/vip_tests/workbench/test_ide_launch.py similarity index 99% rename from tests/workbench/test_ide_launch.py rename to src/vip_tests/workbench/test_ide_launch.py index 83e78fe..6ebfc38 100644 --- a/tests/workbench/test_ide_launch.py +++ b/src/vip_tests/workbench/test_ide_launch.py @@ -13,7 +13,7 @@ from playwright.sync_api import Page, expect from pytest_bdd import given, scenario, then, when -from tests.workbench.conftest import ( +from vip_tests.workbench.conftest import ( TIMEOUT_CLEANUP, TIMEOUT_CODE_EXEC, TIMEOUT_DIALOG, @@ -24,7 +24,7 @@ assert_homepage_loaded, workbench_login, ) -from tests.workbench.pages import ( +from vip_tests.workbench.pages import ( ConsolePaneSelectors, Homepage, JupyterLabSession, diff --git a/tests/workbench/test_packages.feature b/src/vip_tests/workbench/test_packages.feature similarity index 100% rename from tests/workbench/test_packages.feature rename to src/vip_tests/workbench/test_packages.feature diff --git a/tests/workbench/test_packages.py b/src/vip_tests/workbench/test_packages.py similarity index 98% rename from tests/workbench/test_packages.py rename to src/vip_tests/workbench/test_packages.py index 3f2fe7a..1de935c 100644 --- a/tests/workbench/test_packages.py +++ b/src/vip_tests/workbench/test_packages.py @@ -10,7 +10,7 @@ from playwright.sync_api import Page, expect from pytest_bdd import given, scenario, then, when -from tests.workbench.conftest import ( +from vip_tests.workbench.conftest import ( TIMEOUT_DIALOG, TIMEOUT_IDE_LOAD, TIMEOUT_PAGE_LOAD, @@ -19,7 +19,7 @@ assert_homepage_loaded, workbench_login, ) -from tests.workbench.pages import ( +from vip_tests.workbench.pages import ( ConsolePaneSelectors, Homepage, NewSessionDialog, diff --git a/tests/workbench/test_sessions.feature b/src/vip_tests/workbench/test_sessions.feature similarity index 100% rename from tests/workbench/test_sessions.feature rename to src/vip_tests/workbench/test_sessions.feature diff --git a/tests/workbench/test_sessions.py b/src/vip_tests/workbench/test_sessions.py similarity index 98% rename from tests/workbench/test_sessions.py rename to src/vip_tests/workbench/test_sessions.py index d75434d..07d8049 100644 --- a/tests/workbench/test_sessions.py +++ b/src/vip_tests/workbench/test_sessions.py @@ -13,7 +13,7 @@ from playwright.sync_api import Page, expect from pytest_bdd import given, scenario, then, when -from tests.workbench.conftest import ( +from vip_tests.workbench.conftest import ( TIMEOUT_CLEANUP, TIMEOUT_DIALOG, TIMEOUT_PAGE_LOAD, @@ -22,7 +22,7 @@ assert_homepage_loaded, workbench_login, ) -from tests.workbench.pages import Homepage, NewSessionDialog +from vip_tests.workbench.pages import Homepage, NewSessionDialog # Get filename for session naming _FILENAME = Path(__file__).name diff --git a/validation_docs/demo-fix-package-build.md b/validation_docs/demo-fix-package-build.md new file mode 100644 index 0000000..0c87f7a --- /dev/null +++ b/validation_docs/demo-fix-package-build.md @@ -0,0 +1,31 @@ +# fix: include tests/ directory in the package wheel + +*2026-03-25T19:02:37Z by Showboat 0.6.1* + + +The pyproject.toml only listed src/vip in the hatch wheel packages, so the tests/ directory was missing from the built wheel. Added tests to the packages list so all BDD test files are included when the package is installed. + +```bash +uv build --wheel 2>&1 | tail -3 +``` + +```output +Building wheel... +Successfully built dist/posit_vip-0.12.1-py3-none-any.whl +``` + +```bash +python -m zipfile -l dist/posit_vip-0.12.1-py3-none-any.whl | grep '^tests' | wc -l +``` + +```output +89 +``` + +```bash +uv run pytest selftests/ 2>&1 | grep -E "passed|failed|error" | grep -oE "[0-9]+ passed" +``` + +```output +95 passed +``` diff --git a/validation_docs/demo-option-b-vip-tests.md b/validation_docs/demo-option-b-vip-tests.md new file mode 100644 index 0000000..1bb499d --- /dev/null +++ b/validation_docs/demo-option-b-vip-tests.md @@ -0,0 +1,40 @@ +# refactor: move tests/ to src/vip_tests/ (Option B) + +*2026-03-26T00:13:13Z by Showboat 0.6.1* + + +Moved product tests from top-level tests/ to src/vip_tests/ to eliminate namespace collision and pytest import ambiguity when the package is installed. Updated all from tests. imports, pyproject.toml (hatch packages + testpaths), ruff src paths, CI workflows, and documentation. + +```bash +uv run pytest src/vip_tests/ --collect-only --quiet 2>&1 | grep 'tests collected' +``` + +```output +76 tests collected in 0.09s +``` + +```bash +uv run pytest src/vip_tests/ --collect-only --quiet 2>&1 | grep 'tests collected' | sed 's/ in [0-9.]*s//' +``` + +```output +76 tests collected +``` + +```bash +uv run pytest selftests/ 2>&1 | grep -E '^=.*passed' | sed 's/ in [0-9.]*s//' +``` + +```output +======================== 95 passed, 2 warnings ======================== +``` + +```bash +uv run ruff check src/ selftests/ examples/ && uv run ruff format --check src/ selftests/ examples/ && echo 'All checks passed' +``` + +```output +All checks passed! +89 files already formatted +All checks passed +``` From 7b19e8c8fac660eceb5e4d22eae51a907e37acd7 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Thu, 26 Mar 2026 00:43:49 +0000 Subject: [PATCH 12/15] chore(release): 0.12.2 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/vip/__init__.py | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c79b3..aadfad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,36 @@ # CHANGELOG +## v0.12.2 (2026-03-26) + +### Bug Fixes + +- Include tests/ directory in the package wheel ([#110](https://github.com/posit-dev/vip/pull/110), + [`bf23266`](https://github.com/posit-dev/vip/commit/bf232662ede07d481f4e658d9682ef3679907ce1)) + +### Chores + +- **deps**: Bump requests from 2.32.5 to 2.33.0 ([#114](https://github.com/posit-dev/vip/pull/114), + [`d3a118b`](https://github.com/posit-dev/vip/commit/d3a118bbc39aa736636b457abe022a0e2322d5a6)) + +Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> + +Co-authored-by: statik <983+statik@users.noreply.github.com> + +- **deps**: Bump the actions-dependencies group with 4 updates + ([#112](https://github.com/posit-dev/vip/pull/112), + [`653be07`](https://github.com/posit-dev/vip/commit/653be07797f88c97c491525c46b696ecc1e0833d)) + +- **deps**: Bump the python-dependencies group with 4 updates + ([#111](https://github.com/posit-dev/vip/pull/111), + [`90ca94f`](https://github.com/posit-dev/vip/commit/90ca94f4f61586d6e36ef998041d5c5db97b6d97)) + +### Documentation + +- Update install instructions to use PyPI ([#104](https://github.com/posit-dev/vip/pull/104), + [`c5b576f`](https://github.com/posit-dev/vip/commit/c5b576f74f43a653ed12ad6fa12f6b2e1139731c)) + + ## v0.12.1 (2026-03-24) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index c27e3db..125a1da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "posit-vip" -version = "0.12.1" +version = "0.12.2" description = "Verified Installation of Posit - An extensible test suite for validating Posit Team deployments" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/vip/__init__.py b/src/vip/__init__.py index 4f817dd..146fded 100644 --- a/src/vip/__init__.py +++ b/src/vip/__init__.py @@ -1,3 +1,3 @@ """VIP - Verified Installation of Posit.""" -__version__ = "0.12.1" +__version__ = "0.12.2" From 7852bd1692611f2e047ea5eda2a48edba7db6742 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Mar 2026 13:50:34 +0000 Subject: [PATCH 13/15] feat(connect): add system checks test (closes #74) Add a BDD test that triggers the Connect system diagnostics, verifies the report is returned, and downloads the report artifact. New methods on ConnectClient: - list_server_checks(): GET /v1/server_checks - run_server_check(): POST /v1/server_checks - get_server_check_report(check_id): GET /v1/server_checks/{id}/download https://claude.ai/code/session_014qbzfzFuPSQE66nsZzUffW --- src/vip/clients/connect.py | 20 ++++++++++ src/vip_tests/connect/conftest.py | 7 ++++ src/vip_tests/connect/test_auth.py | 9 +---- .../connect/test_system_checks.feature | 12 ++++++ src/vip_tests/connect/test_system_checks.py | 38 ++++++++++++++++++ validation_docs/demo-connect-system-checks.md | 40 +++++++++++++++++++ 6 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 src/vip_tests/connect/test_system_checks.feature create mode 100644 src/vip_tests/connect/test_system_checks.py create mode 100644 validation_docs/demo-connect-system-checks.md diff --git a/src/vip/clients/connect.py b/src/vip/clients/connect.py index 81f4570..1ccd36c 100644 --- a/src/vip/clients/connect.py +++ b/src/vip/clients/connect.py @@ -297,6 +297,26 @@ def fetch_content(self, url: str, *, timeout: float = 30.0) -> httpx.Response: ) return resp + # -- System checks ------------------------------------------------------ + + def list_server_checks(self) -> list[dict[str, Any]]: + """Return a list of server check reports.""" + resp = self._client.get("/v1/server_checks") + resp.raise_for_status() + return resp.json().get("results", []) + + def run_server_check(self) -> dict[str, Any]: + """Trigger a new server check run and return the report object.""" + resp = self._client.post("/v1/server_checks") + resp.raise_for_status() + return resp.json() + + def get_server_check_report(self, check_id: str | int) -> bytes: + """Download the server check report as bytes.""" + resp = self._client.get(f"/v1/server_checks/{check_id}/download") + resp.raise_for_status() + return resp.content + # -- Email -------------------------------------------------------------- def send_test_email(self, to: str) -> dict[str, Any]: diff --git a/src/vip_tests/connect/conftest.py b/src/vip_tests/connect/conftest.py index 17e867f..d470f98 100644 --- a/src/vip_tests/connect/conftest.py +++ b/src/vip_tests/connect/conftest.py @@ -18,6 +18,13 @@ def connect_accessible(connect_client): assert status < 400, f"Connect returned HTTP {status}" +@given("a valid API key is configured") +def api_key_configured(vip_config): + assert vip_config.connect.api_key, ( + "VIP_CONNECT_API_KEY is not set. Set it in vip.toml or as an environment variable." + ) + + def _make_tar_gz(files: dict[str, str]) -> bytes: """Create an in-memory tar.gz archive from a dict of {filename: content}.""" buf = io.BytesIO() diff --git a/src/vip_tests/connect/test_auth.py b/src/vip_tests/connect/test_auth.py index a11d020..8209d08 100644 --- a/src/vip_tests/connect/test_auth.py +++ b/src/vip_tests/connect/test_auth.py @@ -3,7 +3,7 @@ from __future__ import annotations import pytest -from pytest_bdd import given, scenario, then, when +from pytest_bdd import scenario, then, when @scenario("test_auth.feature", "User can log in via the web UI") @@ -21,13 +21,6 @@ def test_connect_login_api(): # --------------------------------------------------------------------------- -@given("a valid API key is configured") -def api_key_configured(vip_config): - assert vip_config.connect.api_key, ( - "VIP_CONNECT_API_KEY is not set. Set it in vip.toml or as an environment variable." - ) - - @when("a user navigates to the Connect login page") def navigate_to_login(page, connect_url): page.goto(f"{connect_url}/__login__") diff --git a/src/vip_tests/connect/test_system_checks.feature b/src/vip_tests/connect/test_system_checks.feature new file mode 100644 index 0000000..a30cece --- /dev/null +++ b/src/vip_tests/connect/test_system_checks.feature @@ -0,0 +1,12 @@ +@connect +Feature: Connect system checks + As a Posit Team administrator + I want to run the Connect system diagnostics + So that I can verify the Connect installation is healthy + + Scenario: Connect system checks can be run and the report downloaded + Given Connect is accessible at the configured URL + And a valid API key is configured + When I trigger a new system check run via the Connect API + Then the system check report is returned + And I can download the system check report artifact diff --git a/src/vip_tests/connect/test_system_checks.py b/src/vip_tests/connect/test_system_checks.py new file mode 100644 index 0000000..966957e --- /dev/null +++ b/src/vip_tests/connect/test_system_checks.py @@ -0,0 +1,38 @@ +"""Step definitions for Connect system checks tests.""" + +from __future__ import annotations + +from pytest_bdd import scenario, then, when + + +@scenario( + "test_system_checks.feature", + "Connect system checks can be run and the report downloaded", +) +def test_connect_system_checks(): + pass + + +# --------------------------------------------------------------------------- +# Steps +# --------------------------------------------------------------------------- + + +@when("I trigger a new system check run via the Connect API", target_fixture="server_check") +def trigger_system_check(connect_client): + return connect_client.run_server_check() + + +@then("the system check report is returned") +def check_report_returned(server_check): + assert server_check is not None, "System check API returned no result" + assert "id" in server_check, ( + f"System check response missing 'id' field. Got: {list(server_check.keys())}" + ) + + +@then("I can download the system check report artifact") +def download_report_artifact(connect_client, server_check): + check_id = server_check["id"] + report_bytes = connect_client.get_server_check_report(check_id) + assert report_bytes, f"System check report for id={check_id!r} was empty" diff --git a/validation_docs/demo-connect-system-checks.md b/validation_docs/demo-connect-system-checks.md new file mode 100644 index 0000000..f017ca3 --- /dev/null +++ b/validation_docs/demo-connect-system-checks.md @@ -0,0 +1,40 @@ +# feat: Add Connect system checks test (issue #74) + +*2026-03-23T13:48:44Z by Showboat 0.6.1* + + +Added a Connect system checks test that triggers the built-in Connect server diagnostics and downloads the resulting report artifact, implementing posit-dev/vip#74. + +Changes: +- Added list_server_checks(), run_server_check(), and get_server_check_report(check_id) to ConnectClient +- Added tests/connect/test_system_checks.feature with @connect-tagged BDD scenario +- Added tests/connect/test_system_checks.py with step definitions + +The test triggers a new system check run (POST /v1/server_checks), verifies the report contains an 'id' field, then downloads the artifact (GET /v1/server_checks/{id}/download). + +```bash +uv run ruff check src/ tests/ selftests/ examples/ && uv run ruff format --check src/ tests/ selftests/ examples/ && echo 'Lint/format: OK' +``` + +```output +All checks passed! +90 files already formatted +Lint/format: OK +``` + +```bash +uv run pytest selftests/ -q 2>&1 | grep -oE '^[0-9]+ passed, [0-9]+ warnings' +``` + +```output +95 passed, 2 warnings +``` + +```bash +uv run pytest tests/connect/test_system_checks.py --collect-only -q 2>&1 | grep -v 'UserWarning\|vip_cfg\|plugin.py' | grep -v 'in [0-9]' +``` + +```output +tests/connect/test_system_checks.py::test_connect_system_checks + +``` From dde6e8ee99b0ea0c0b4895e91f1318f805b1e37f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Mar 2026 17:00:43 +0000 Subject: [PATCH 14/15] feat(connect): integrate system checks report into VIP output - Test step saves downloaded artifact to report/connect_system_checks.html alongside results.json (uses --vip-report path for placement) - report/index.qmd: new "Connect System Checks" section embeds the artifact in a sandboxed srcdoc iframe; shows placeholder when absent - example-report.yml: adds test_system_checks.py to CI smoke run so the section is populated in every PR preview report https://claude.ai/code/session_014qbzfzFuPSQE66nsZzUffW --- .github/workflows/example-report.yml | 3 ++- report/index.qmd | 23 +++++++++++++++++++ src/vip_tests/connect/test_system_checks.py | 11 ++++++++- validation_docs/demo-connect-system-checks.md | 18 ++++++++------- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index 046e260..af05074 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -132,8 +132,9 @@ jobs: src/vip_tests/prerequisites/test_components.py \ src/vip_tests/prerequisites/test_expected_failure.py \ src/vip_tests/connect/test_auth.py \ + src/vip_tests/connect/test_system_checks.py \ src/vip_tests/package_manager/test_repos.py \ - -v -k "reachable or api or mirror or repo_exists or expected_failure" \ + -v -k "reachable or api or mirror or repo_exists or expected_failure or system_checks" \ --vip-config=vip.toml \ || true diff --git a/report/index.qmd b/report/index.qmd index 0862422..26ffb32 100644 --- a/report/index.qmd +++ b/report/index.qmd @@ -451,3 +451,26 @@ document.querySelectorAll('.vip-copy-btn').forEach(function(btn) { if failures: display(Markdown(f"_`failures.json` is written by `pytest` alongside `results.json`._")) ``` + +## Connect System Checks + +```{python} +#| echo: false + +checks_path = Path("connect_system_checks.html") +if not checks_path.exists(): + display(Markdown( + "_No Connect system checks report found. " + "Run `tests/connect/test_system_checks.py` against a configured Connect instance._" + )) +else: + content = checks_path.read_text(encoding="utf-8", errors="replace") + # Escape double quotes for use in the srcdoc attribute + srcdoc = content.replace("&", "&").replace('"', """) + display(HTML( + '" + )) +``` diff --git a/src/vip_tests/connect/test_system_checks.py b/src/vip_tests/connect/test_system_checks.py index 966957e..28092e3 100644 --- a/src/vip_tests/connect/test_system_checks.py +++ b/src/vip_tests/connect/test_system_checks.py @@ -2,6 +2,8 @@ from __future__ import annotations +from pathlib import Path + from pytest_bdd import scenario, then, when @@ -32,7 +34,14 @@ def check_report_returned(server_check): @then("I can download the system check report artifact") -def download_report_artifact(connect_client, server_check): +def download_report_artifact(connect_client, server_check, pytestconfig): check_id = server_check["id"] report_bytes = connect_client.get_server_check_report(check_id) assert report_bytes, f"System check report for id={check_id!r} was empty" + + # Persist alongside results.json so the Quarto report can embed it. + vip_report = pytestconfig.getoption("--vip-report") + if vip_report: + artifact_path = Path(vip_report).parent / "connect_system_checks.html" + artifact_path.parent.mkdir(parents=True, exist_ok=True) + artifact_path.write_bytes(report_bytes) diff --git a/validation_docs/demo-connect-system-checks.md b/validation_docs/demo-connect-system-checks.md index f017ca3..c91b6d0 100644 --- a/validation_docs/demo-connect-system-checks.md +++ b/validation_docs/demo-connect-system-checks.md @@ -1,16 +1,16 @@ # feat: Add Connect system checks test (issue #74) -*2026-03-23T13:48:44Z by Showboat 0.6.1* - +*2026-03-23T16:59:59Z by Showboat 0.6.1* + -Added a Connect system checks test that triggers the built-in Connect server diagnostics and downloads the resulting report artifact, implementing posit-dev/vip#74. +Implements posit-dev/vip#74: adds a Connect system checks BDD test that triggers the built-in diagnostics, downloads the artifact, and embeds it in the VIP Quarto report. Changes: -- Added list_server_checks(), run_server_check(), and get_server_check_report(check_id) to ConnectClient -- Added tests/connect/test_system_checks.feature with @connect-tagged BDD scenario -- Added tests/connect/test_system_checks.py with step definitions - -The test triggers a new system check run (POST /v1/server_checks), verifies the report contains an 'id' field, then downloads the artifact (GET /v1/server_checks/{id}/download). +- ConnectClient: list_server_checks(), run_server_check(), get_server_check_report() +- tests/connect/test_system_checks.feature: @connect BDD scenario +- tests/connect/test_system_checks.py: step defs; saves artifact to report/connect_system_checks.html +- report/index.qmd: new 'Connect System Checks' section embeds the artifact via srcdoc iframe +- .github/workflows/example-report.yml: adds test_system_checks.py to CI smoke run ```bash uv run ruff check src/ tests/ selftests/ examples/ && uv run ruff format --check src/ tests/ selftests/ examples/ && echo 'Lint/format: OK' @@ -38,3 +38,5 @@ uv run pytest tests/connect/test_system_checks.py --collect-only -q 2>&1 | grep tests/connect/test_system_checks.py::test_connect_system_checks ``` + +The index.qmd 'Connect System Checks' section renders when report/connect_system_checks.html exists (saved by the test step). When no system checks were run, it shows a placeholder message. The CI example-report.yml now includes test_system_checks.py in the smoke run so the section is populated in every PR preview. From 29171293b2ba991afff99ec6fedc4d246d26e2f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:49:27 +0000 Subject: [PATCH 15/15] fix(connect): rebase on main, fix duplicate step, apply review feedback Co-authored-by: statik <983+statik@users.noreply.github.com> Agent-Logs-Url: https://github.com/posit-dev/vip/sessions/d1df95d0-8c42-4372-9577-6321b8eaac12 --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 43b2cd9..2d97611 100644 --- a/uv.lock +++ b/uv.lock @@ -2159,7 +2159,7 @@ wheels = [ [[package]] name = "posit-vip" -version = "0.12.1" +version = "0.12.2" source = { editable = "." } dependencies = [ { name = "brand-yml" },