diff --git a/pkgs/clan-cli/clan_cli/machines/list_test.py b/pkgs/clan-cli/clan_cli/machines/list_test.py new file mode 100644 index 000000000..e19110cf7 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/list_test.py @@ -0,0 +1,361 @@ +import pytest + +from clan_cli.tests import fixtures_flakes +from clan_cli.tests.helpers import cli +from clan_cli.tests.stdout import CaptureOutput + + +@pytest.mark.impure +def list_basic( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)]) + + assert "vm1" in output.out + assert "vm2" in output.out + + +@pytest.mark.impure +@pytest.mark.parametrize( + "test_flake_with_core", + [ + { + "inventory_expr": r"""{ + machines = { + web-server = { + tags = [ "web" "production" ]; + description = "Production web server"; + }; + db-server = { + tags = [ "database" "production" ]; + description = "Production database server"; + }; + dev-machine = { + tags = [ "development" ]; + description = "Development machine"; + }; + backup-server = { + tags = [ "backup" "production" ]; + description = "Backup server"; + }; + }; + }""" + }, + ], + indirect=True, +) +@pytest.mark.impure +def list_with_tags_single_tag( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "production", + ] + ) + + assert "web-server" in output.out + assert "db-server" in output.out + assert "backup-server" in output.out + assert "dev-machine" not in output.out + + +@pytest.mark.impure +@pytest.mark.parametrize( + "test_flake_with_core", + [ + { + "inventory_expr": r"""{ + machines = { + web-server = { + tags = [ "web" "production" ]; + description = "Production web server"; + }; + db-server = { + tags = [ "database" "production" ]; + description = "Production database server"; + }; + dev-machine = { + tags = [ "development" ]; + description = "Development machine"; + }; + backup-server = { + tags = [ "backup" "production" ]; + description = "Backup server"; + }; + }; + }""" + }, + ], + indirect=True, +) +@pytest.mark.impure +def list_with_tags_multiple_tags_intersection( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "web", + "production", + ] + ) + + # Should only include machines that have BOTH tags (intersection) + assert "web-server" in output.out + # Should not include machines that have only one of the tags (intersection) + assert "db-server" not in output.out + assert "backup-server" not in output.out + assert "dev-machine" not in output.out + + +@pytest.mark.impure +def test_machines_list_with_tags_no_matches( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "nonexistent", + ] + ) + + assert output.out.strip() == "" + + +@pytest.mark.impure +@pytest.mark.parametrize( + "test_flake_with_core", + [ + { + "inventory_expr": r"""{ + machines = { + server1 = { + tags = [ "web" ]; + }; + server2 = { + tags = [ "database" ]; + }; + server3 = { + tags = [ "web" "database" ]; + }; + server4 = { }; + }; + }""" + }, + ], + indirect=True, +) +@pytest.mark.impure +def list_with_tags_various_scenarios( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "web", + ] + ) + + assert "server1" in output.out + assert "server3" in output.out + assert "server2" not in output.out + assert "server4" not in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "database", + ] + ) + + assert "server2" in output.out + assert "server3" in output.out + assert "server1" not in output.out + assert "server4" not in output.out + + # Test intersection + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "web", + "database", + ] + ) + + assert "server3" in output.out + assert "server1" not in output.out + assert "server2" not in output.out + assert "server4" not in output.out + + +@pytest.mark.impure +def created_machine_and_tags( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + cli.run( + [ + "machines", + "create", + "--flake", + str(test_flake_with_core.path), + "test-machine", + "--tags", + "test", + "server", + ] + ) + + with capture_output as output: + cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)]) + + assert "test-machine" in output.out + assert "vm1" in output.out + assert "vm2" in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "test", + ] + ) + + assert "test-machine" in output.out + assert "vm1" not in output.out + assert "vm2" not in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "server", + ] + ) + + assert "test-machine" in output.out + assert "vm1" not in output.out + assert "vm2" not in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "test", + "server", + ] + ) + + assert "test-machine" in output.out + assert "vm1" not in output.out + assert "vm2" not in output.out + + +@pytest.mark.impure +@pytest.mark.parametrize( + "test_flake_with_core", + [ + { + "inventory_expr": r"""{ + machines = { + machine-with-tags = { + tags = [ "tag1" "tag2" "tag3" ]; + }; + machine-without-tags = { }; + }; + }""" + }, + ], + indirect=True, +) +@pytest.mark.impure +def list_mixed_tagged_untagged( + test_flake_with_core: fixtures_flakes.FlakeForTest, + capture_output: CaptureOutput, +) -> None: + with capture_output as output: + cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)]) + + assert "machine-with-tags" in output.out + assert "machine-without-tags" in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "tag1", + ] + ) + + assert "machine-with-tags" in output.out + assert "machine-without-tags" not in output.out + + with capture_output as output: + cli.run( + [ + "machines", + "list", + "--flake", + str(test_flake_with_core.path), + "--tags", + "nonexistent", + ] + ) + + assert "machine-with-tags" not in output.out + assert "machine-without-tags" not in output.out + assert output.out.strip() == ""