diff --git a/integration/tests/posit/connect/conftest.py b/integration/tests/posit/connect/conftest.py index f0ad6eef..095e7413 100644 --- a/integration/tests/posit/connect/conftest.py +++ b/integration/tests/posit/connect/conftest.py @@ -25,10 +25,43 @@ "with-connect", ] +# The 2026.04+ Connect images moved to a new base image that contains one +# versioned Python installation but does not enable it in the default config. +_CONNECT_PYTHON_VERSIONS = { + "2026.04": "3.14.4", + "2026.05": "3.14.5", + "2026.06": "3.14.6", + "2026.07": "3.14.6", + "2026.08": "3.14.7", +} + # Posit Connect license file to mount into each container (gitignored). _LICENSE = os.environ.get("LICENSE", "./license.lic") +def _connect_env(connect_version: str) -> list[str]: + """Return Connect configuration overrides for the current image family.""" + release = ".".join(connect_version.split(".")[:2]) + python_version = _CONNECT_PYTHON_VERSIONS.get(release) + if python_version is None: + return [] + + return [ + "--env", + "CONNECT_PYTHON_ENABLED=true", + "--env", + f"CONNECT_PYTHON_EXECUTABLE=/opt/python/{python_version}/bin/python", + "--env", + "CONNECT_PYTHON_VERSIONMATCHING=nearest", + "--env", + "CONNECT_PYTHON_ENVIRONMENTMANAGEMENT=true", + "--env", + "CONNECT_SERVER_ALLOWRUNTIMECACHEMANAGEMENT=true", + "--env", + "CONNECT_METRICS_INSTRUMENTATION=true", + ] + + def _free_port() -> int: with socket.socket() as s: s.bind(("", 0)) @@ -55,6 +88,7 @@ def fresh_connect(): "--license", _LICENSE, "--quiet", + *_connect_env(version), ], capture_output=True, text=True, diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index dcfa5f64..7b8fe98a 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -29,10 +29,10 @@ def test_get(self): assert key in item assert item[key] == self.content[key] if CONNECT_VERSION >= version.parse("2024.06.0"): - # get() always includes owner, tags, and vanity_url. Owner data is always present in - # all content, tags and vanity_url are only present if explicitly set in the content. + # Owner data is always present. Newer Connect versions include an empty tags field, + # while older versions omit it when no tags are set. assert "owner" in item - assert "tags" not in item + assert item.get("tags", []) == [] assert "vanity_url" not in item def test_find(self): @@ -63,17 +63,18 @@ def test_content_item_owner_from_include(self): def test_restart(self): # create content content = self.client.content.create(name="example-flask-minimal") - # create bundle - path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") - path = (Path(__file__).parent / path).resolve() - bundle = content.bundles.create(str(path)) - # deploy bundle - task = bundle.deploy() - task.wait_for() - # restart - content.restart() - # delete content - content.delete() + try: + # create bundle + path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") + path = (Path(__file__).parent / path).resolve() + bundle = content.bundles.create(str(path)) + # deploy bundle + task = bundle.deploy() + task.wait_for() + # restart + content.restart() + finally: + content.delete() @pytest.mark.skipif( CONNECT_VERSION <= version.parse("2023.01.1"), @@ -102,32 +103,33 @@ def test_render(self): def test_get_lockfile(self): # create content content = self.client.content.create(name="example-flask-lockfile-test") - # create bundle with Python requirements - path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") - path = (Path(__file__).parent / path).resolve() - bundle = content.bundles.create(str(path)) - # deploy bundle - task = bundle.deploy() - task.wait_for() - # get lockfile - lockfile = content.get_lockfile() - # verify lockfile metadata - assert lockfile.generated_by is not None - assert isinstance(lockfile.generated_by, str) - assert len(lockfile.generated_by) > 0 - # verify python version was parsed - assert lockfile.python_version is not None - assert isinstance(lockfile.python_version, str) - assert len(lockfile.python_version) > 0 - # verify lockfile content - assert lockfile.text is not None - assert isinstance(lockfile.text, str) - assert len(lockfile.text) > 0 - # lockfile should contain package information - # The flask bundle has Flask as a dependency - assert "flask" in lockfile.text.lower() or "Flask" in lockfile.text - # delete content - content.delete() + try: + # create bundle with Python requirements + path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") + path = (Path(__file__).parent / path).resolve() + bundle = content.bundles.create(str(path)) + # deploy bundle + task = bundle.deploy() + task.wait_for() + # get lockfile + lockfile = content.get_lockfile() + # verify lockfile metadata + assert lockfile.generated_by is not None + assert isinstance(lockfile.generated_by, str) + assert len(lockfile.generated_by) > 0 + # verify python version was parsed + assert lockfile.python_version is not None + assert isinstance(lockfile.python_version, str) + assert len(lockfile.python_version) > 0 + # verify lockfile content + assert lockfile.text is not None + assert isinstance(lockfile.text, str) + assert len(lockfile.text) > 0 + # lockfile should contain package information + # The flask bundle has Flask as a dependency + assert "flask" in lockfile.text.lower() or "Flask" in lockfile.text + finally: + content.delete() @pytest.mark.skipif( CONNECT_VERSION < version.parse("2025.12.0"), @@ -140,21 +142,21 @@ def test_get_lockfile_version_check(self): # Since we skip this test on older versions, we just verify # that the method exists and is callable on supported versions content = self.client.content.create(name="example-version-check") - path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") - path = (Path(__file__).parent / path).resolve() - bundle = content.bundles.create(str(path)) - task = bundle.deploy() - task.wait_for() - - # Verify the method exists and is callable - assert hasattr(content, "get_lockfile") - assert callable(content.get_lockfile) - - # Call it to ensure no version errors on supported versions - lockfile = content.get_lockfile() - assert lockfile.generated_by is not None - assert lockfile.python_version is not None - assert lockfile.text is not None - - # delete content - content.delete() + try: + path = Path("../../../resources/connect/bundles/example-flask-minimal/bundle.tar.gz") + path = (Path(__file__).parent / path).resolve() + bundle = content.bundles.create(str(path)) + task = bundle.deploy() + task.wait_for() + + # Verify the method exists and is callable + assert hasattr(content, "get_lockfile") + assert callable(content.get_lockfile) + + # Call it to ensure no version errors on supported versions + lockfile = content.get_lockfile() + assert lockfile.generated_by is not None + assert lockfile.python_version is not None + assert lockfile.text is not None + finally: + content.delete() diff --git a/integration/tests/posit/connect/test_groups.py b/integration/tests/posit/connect/test_groups.py index 4ecb992d..b18c0028 100644 --- a/integration/tests/posit/connect/test_groups.py +++ b/integration/tests/posit/connect/test_groups.py @@ -19,7 +19,13 @@ def test_get(self): assert self.client.groups.get(self.group["guid"]) def test_find(self): - assert self.client.groups.find() == [self.group] + groups = self.client.groups.find() + assert len(groups) == 1 + for key, value in self.group.items(): + assert groups[0][key] == value def test_find_one(self): - assert self.client.groups.find_one() == self.group + group = self.client.groups.find_one() + assert group is not None + for key, value in self.group.items(): + assert group[key] == value diff --git a/integration/tests/posit/connect/test_packages.py b/integration/tests/posit/connect/test_packages.py index 1d56c420..f218c11b 100644 --- a/integration/tests/posit/connect/test_packages.py +++ b/integration/tests/posit/connect/test_packages.py @@ -28,8 +28,8 @@ def teardown_class(cls): cls.content.delete() def test(self): - assert self.client.packages - assert self.content.packages + assert list(self.client.packages) + assert list(self.content.packages) def test_find_by(self): package = self.client.packages.find_by(name="flask") diff --git a/integration/tests/posit/connect/test_users.py b/integration/tests/posit/connect/test_users.py index efb3c72c..58c13d9b 100644 --- a/integration/tests/posit/connect/test_users.py +++ b/integration/tests/posit/connect/test_users.py @@ -69,8 +69,10 @@ def test_user_group_interactions(self): # `Group.members.find()` group_users = test_group.members.find() assert len(group_users) == 2 - assert group_users[0]["guid"] == self.bill["guid"] - assert group_users[1]["guid"] == self.cole["guid"] + assert {user["guid"] for user in group_users} == { + self.bill["guid"], + self.cole["guid"], + } # `User.group.find()` bill_groups = self.bill.groups.find()