summaryrefslogtreecommitdiffstats
path: root/tests/test_vdkr_registry.py
blob: 9073444d2fbfaa3d9e36ba761c2780d221f32f46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# 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 <url> pull <image>
- Persistent config: vconfig registry <url> + pull <image>
- 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 <subcommand> 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