# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield # # SPDX-License-Identifier: MIT """ Tests for vdkr registry functionality. These tests verify vdkr registry configuration and pulling from registries: - CLI override: --registry pull - Persistent config: vconfig registry + pull - Config reset: vconfig registry --reset - Image compound commands: image ls/rm/pull/inspect/tag Note: Tests that require a running registry use mock endpoints or skip if no registry is available. For full integration testing, start the registry first: $TOPDIR/container-registry/container-registry.sh start Run with: pytest tests/test_vdkr_registry.py -v --vdkr-dir /tmp/vcontainer Run specific test class: pytest tests/test_vdkr_registry.py::TestVconfigRegistry -v """ import pytest import json import subprocess class TestVconfigRegistry: """Test vconfig registry configuration commands.""" def test_vconfig_registry_show_empty(self, vdkr): """Test showing registry config when not set.""" # Reset first to ensure clean state vdkr.run("vconfig", "registry", "--reset", check=False) result = vdkr.run("vconfig", "registry") assert result.returncode == 0 # Should show empty or "not set" message output = result.stdout.strip() assert output == "" or "not set" in output.lower() or "registry:" in output.lower() def test_vconfig_registry_set(self, vdkr): """Test setting registry configuration.""" test_registry = "10.0.2.2:5000/test" result = vdkr.run("vconfig", "registry", test_registry) assert result.returncode == 0 # Verify it was set result = vdkr.run("vconfig", "registry") assert result.returncode == 0 assert test_registry in result.stdout def test_vconfig_registry_reset(self, vdkr): """Test resetting registry configuration.""" # Set a value first vdkr.run("vconfig", "registry", "10.0.2.2:5000/test") # Reset it result = vdkr.run("vconfig", "registry", "--reset") assert result.returncode == 0 # Verify it was reset result = vdkr.run("vconfig", "registry") assert result.returncode == 0 # Should be empty after reset output = result.stdout.strip() assert "10.0.2.2:5000/test" not in output def test_vconfig_show_all_includes_registry(self, vdkr): """Test that vconfig (no args) shows registry in output.""" result = vdkr.run("vconfig") assert result.returncode == 0 # Should list registry as one of the config keys assert "registry" in result.stdout.lower() class TestImageCompoundCommands: """Test vdkr image compound commands. These translate docker image to the appropriate docker command: - image ls → images - image rm → rmi - image pull → pull (with registry transform) - image inspect → inspect - image tag → tag - image prune → image prune - image history → history Note: These tests reset any registry config to ensure commands work with unqualified image names. Registry-specific tests are in TestRegistryTransform. """ @pytest.fixture(autouse=True) def setup_memres(self, memres_session): """Ensure memres is running and registry config is reset.""" self.vdkr = memres_session self.vdkr.ensure_memres() # Reset any baked-in or configured registry to test basic commands self.vdkr.run("vconfig", "registry", "--reset", check=False) def get_alpine_info(self): """Get alpine image info (ref and ID). Returns tuple of (full_ref, image_id) or (None, None) if not found. """ # Use raw 'images' to see what's actually stored images_result = self.vdkr.run("images", check=False) if images_result.returncode != 0: return None, None # Parse the images output to find alpine for line in images_result.stdout.splitlines(): if "alpine" in line.lower() and "REPOSITORY" not in line: # Parse: REPOSITORY TAG IMAGE_ID CREATED SIZE parts = line.split() if len(parts) >= 3: repo = parts[0] tag = parts[1] image_id = parts[2] return f"{repo}:{tag}", image_id return None, None def test_image_ls(self): """Test 'image ls' command.""" result = self.vdkr.run("image", "ls") assert result.returncode == 0 # Should have header line assert "REPOSITORY" in result.stdout or "IMAGE" in result.stdout def ensure_alpine_and_get_info(self): """Ensure alpine is present and return (ref, image_id).""" # First check if alpine is already present alpine_ref, alpine_id = self.get_alpine_info() if alpine_ref: return alpine_ref, alpine_id # Try to pull alpine result = self.vdkr.run("pull", "alpine:latest", timeout=300, check=False) if result.returncode != 0: return None, None # Get the info again return self.get_alpine_info() @pytest.mark.network def test_image_ls_with_filter(self): """Test 'image ls' with filter. After the transform fix, 'images alpine' should show alpine directly without registry prefix transformation. """ alpine_ref, alpine_id = self.ensure_alpine_and_get_info() if not alpine_ref: pytest.skip("Could not get alpine image (network issue?)") # Get the simple name for filtering (e.g., "alpine" from "alpine:latest") simple_name = alpine_ref.split("/")[-1].split(":")[0] # "alpine" # Filter should work with simple name after transform fix result = self.vdkr.run("images", simple_name) assert result.returncode == 0 # Should show the alpine image (check for image ID to be robust) if alpine_id not in result.stdout and "alpine" not in result.stdout: # If filter doesn't work (old vdkr), at least verify full list works full_result = self.vdkr.run("images") assert alpine_id in full_result.stdout, f"Alpine ({alpine_id}) not in images" pytest.skip("Filter transform not fixed yet - vdkr rebuild needed") @pytest.mark.network def test_image_pull(self): """Test 'image pull' command.""" # Remove if exists self.vdkr.run("image", "rm", "-f", "busybox:latest", check=False) # Pull via image command result = self.vdkr.run("image", "pull", "busybox:latest", timeout=300, check=False) if result.returncode != 0: pytest.skip(f"Could not pull busybox (network issue?): {result.stderr}") # Verify it appears in images images = self.vdkr.run("image", "ls") assert "busybox" in images.stdout @pytest.mark.network def test_image_inspect(self): """Test 'image inspect' command.""" alpine_ref, alpine_id = self.ensure_alpine_and_get_info() if not alpine_id: pytest.skip("Could not get alpine image (network issue?)") # Use image ID to avoid registry transform result = self.vdkr.run("image", "inspect", alpine_id) assert result.returncode == 0 # Should be valid JSON data = json.loads(result.stdout) assert isinstance(data, list) assert len(data) > 0 @pytest.mark.network def test_image_history(self): """Test 'image history' command.""" alpine_ref, alpine_id = self.ensure_alpine_and_get_info() if not alpine_id: pytest.skip("Could not get alpine image (network issue?)") # Use image ID to avoid registry transform result = self.vdkr.run("image", "history", alpine_id) assert result.returncode == 0 # Should show history with IMAGE or CREATED columns assert "IMAGE" in result.stdout or "CREATED" in result.stdout @pytest.mark.network def test_image_tag(self): """Test 'image tag' command.""" alpine_ref, alpine_id = self.ensure_alpine_and_get_info() if not alpine_id: pytest.skip("Could not get alpine image (network issue?)") # Use image ID as source to avoid registry transform result = self.vdkr.run("image", "tag", alpine_id, "my-test-alpine:v1") assert result.returncode == 0 # Verify the new tag exists images = self.vdkr.run("image", "ls") assert "my-test-alpine" in images.stdout # Clean up using image ID of the new tag self.vdkr.run("rmi", "my-test-alpine:v1", check=False) @pytest.mark.network def test_image_rm(self): """Test 'image rm' command.""" alpine_ref, alpine_id = self.ensure_alpine_and_get_info() if not alpine_id: pytest.skip("Could not get alpine image (network issue?)") # Tag using image ID to create a removable image result = self.vdkr.run("tag", alpine_id, "test-rm:latest", check=False) if result.returncode != 0: pytest.skip(f"Could not tag image: {result.stderr}") # Remove it using rmi (not image rm) to avoid transform result = self.vdkr.run("rmi", "test-rm:latest", check=False) # Verify it succeeded or at least didn't crash assert result.returncode == 0 or "No such image" in result.stdout def test_image_prune(self): """Test 'image prune' command.""" # Prune dangling images (-f to skip confirmation) result = self.vdkr.run("image", "prune", "-f") assert result.returncode == 0 def test_image_requires_subcommand(self): """Test that 'image' without subcommand shows error.""" result = self.vdkr.run("image", check=False) assert result.returncode != 0 assert "subcommand" in result.stderr.lower() or "requires" in result.stderr.lower() class TestRegistryCLIOverride: """Test --registry CLI flag for one-off registry usage. Note: These tests require a running registry. They skip if no registry is available. """ @pytest.fixture def registry_url(self, request): """Get registry URL from command line or environment, or skip.""" url = request.config.getoption("--registry-url", default=None) if url is None: import os url = os.environ.get("TEST_REGISTRY_URL") if url is None: pytest.skip("No registry URL provided (use --registry-url or TEST_REGISTRY_URL)") return url @pytest.mark.network def test_registry_flag_pull(self, memres_session, registry_url): """Test pulling with --registry flag.""" vdkr = memres_session vdkr.ensure_memres() # Pull with explicit registry result = vdkr.run("--registry", registry_url, "pull", "alpine", timeout=300, check=False) # May succeed or fail depending on whether image exists in registry # Just verify the command is accepted assert "unknown flag" not in result.stderr.lower() @pytest.mark.network def test_registry_flag_run(self, memres_session, registry_url): """Test run with --registry flag.""" vdkr = memres_session vdkr.ensure_memres() # Try to run with explicit registry result = vdkr.run("--registry", registry_url, "run", "--rm", "alpine", "echo", "hello", timeout=300, check=False) # Just verify the flag is accepted assert "unknown flag" not in result.stderr.lower() class TestRegistryPersistentConfig: """Test persistent registry configuration with vconfig.""" @pytest.fixture(autouse=True) def reset_registry_config(self, vdkr): """Reset registry config before and after each test.""" vdkr.run("vconfig", "registry", "--reset", check=False) yield vdkr.run("vconfig", "registry", "--reset", check=False) def test_config_persists_across_commands(self, vdkr): """Test that registry config persists across multiple vdkr invocations.""" test_registry = "10.0.2.2:5000/persistent-test" # Set registry vdkr.run("vconfig", "registry", test_registry) # Verify in new command result = vdkr.run("vconfig", "registry") assert test_registry in result.stdout # Verify in vconfig all result = vdkr.run("vconfig") assert test_registry in result.stdout def test_pull_uses_config(self, memres_session): """Test that pull command uses configured registry. Note: This test verifies the configuration is passed to the VM. It may fail if the registry doesn't have the image, but we can check the error message to verify the registry was used. """ vdkr = memres_session vdkr.ensure_memres() # Set a fake registry fake_registry = "10.0.2.2:9999/fake" vdkr.run("vconfig", "registry", fake_registry) # Try to pull - should fail because registry doesn't exist # but error should reference the fake registry result = vdkr.run("pull", "nonexistent-image", timeout=60, check=False) # The important thing is that it tried to use our registry # (connection refused or similar error indicates it tried) assert result.returncode != 0 # Error should indicate connection issue to our fake registry class TestInsecureRegistry: """Test --insecure-registry flag.""" def test_insecure_registry_flag_accepted(self, vdkr): """Test that --insecure-registry flag is accepted.""" vdkr.ensure_memres() # Just verify the flag is recognized result = vdkr.run("--insecure-registry", "10.0.2.2:5000", "images", check=False) assert "unknown flag" not in result.stderr.lower() assert "unrecognized" not in result.stderr.lower() def test_multiple_insecure_registries(self, vdkr): """Test multiple --insecure-registry flags.""" vdkr.ensure_memres() result = vdkr.run( "--insecure-registry", "10.0.2.2:5000", "--insecure-registry", "10.0.2.2:5001", "images", check=False ) assert "unknown flag" not in result.stderr.lower() class TestRegistryTransform: """Test image name transformation with registry prefix. When a default registry is configured, unqualified image names should be transformed to include the registry prefix. """ @pytest.fixture(autouse=True) def reset_registry_config(self, vdkr): """Reset registry config before and after each test.""" vdkr.run("vconfig", "registry", "--reset", check=False) yield vdkr.run("vconfig", "registry", "--reset", check=False) def test_qualified_names_not_transformed(self, memres_session): """Test that fully qualified image names are not transformed.""" vdkr = memres_session vdkr.ensure_memres() # Set a registry vdkr.run("vconfig", "registry", "10.0.2.2:5000/test") # Pull fully qualified image - should use docker.io, not our registry result = vdkr.run("pull", "docker.io/library/alpine:latest", timeout=300, check=False) # If successful, alpine from docker.io should be present # If failed, should NOT be a connection error to 10.0.2.2 if result.returncode != 0: # Should not have tried our fake registry assert "10.0.2.2:5000/test" not in result.stderr # Note: Registry options (--registry-url) are defined in conftest.py