Files
devx/tests/unit/test_utils_vault.py
T
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 3f223d5657 test: add unicode preservation and round-trip tests for save_vault_yaml
Add tests verifying allow_unicode=True behavior:
- test_save_preserves_unicode: non-ASCII content (Cyrillic, German
  umlaut, Chinese) is written as UTF-8, not \uXXXX escapes
- test_save_unicode_round_trip: saved unicode data loads back correctly

Also add CHANGELOG entry under [Unreleased] > Fixed.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 17:27:20 +02:00

154 lines
5.2 KiB
Python

"""Unit tests for devx.utils.vault."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
from devx.utils.vault import (
decrypt_file,
encrypt_file,
is_encrypted,
load_vault_yaml,
save_vault_yaml,
)
class TestIsEncrypted:
def test_encrypted_file(self, tmp_path: Path) -> None:
f = tmp_path / "secret.yml"
f.write_text("$ANSIBLE_VAULT;1.1;AES256\n9382928...\n")
assert is_encrypted(f) is True
def test_plain_file(self, tmp_path: Path) -> None:
f = tmp_path / "plain.yml"
f.write_text("key: value\n")
assert is_encrypted(f) is False
class TestLoadVaultYaml:
def test_plain_yaml_no_vault_pass(self, tmp_path: Path) -> None:
f = tmp_path / "data.yml"
f.write_text("key: value\nlist:\n - a\n - b\n")
data = load_vault_yaml(f)
assert data == {"key": "value", "list": ["a", "b"]}
def test_empty_file(self, tmp_path: Path) -> None:
f = tmp_path / "empty.yml"
f.write_text("")
data = load_vault_yaml(f)
assert data == {}
def test_vault_pass_not_exists(self, tmp_path: Path) -> None:
f = tmp_path / "data.yml"
f.write_text("key: value\n")
data = load_vault_yaml(f, vault_pass=tmp_path / "nonexistent")
assert data == {"key": "value"}
@patch("devx.utils.vault.subprocess.run")
def test_encrypted_file_success(self, mock_run: MagicMock, tmp_path: Path) -> None:
f = tmp_path / "secret.yml"
f.write_text("$ANSIBLE_VAULT\n...")
vp = tmp_path / "vault-password"
vp.write_text("secret")
mock_run.return_value = MagicMock(returncode=0, stdout="key: decrypted\n", stderr="")
data = load_vault_yaml(f, vault_pass=vp)
assert data == {"key": "decrypted"}
@patch("devx.utils.vault.subprocess.run")
def test_not_vault_encrypted_fallback(self, mock_run: MagicMock, tmp_path: Path) -> None:
f = tmp_path / "plain.yml"
f.write_text("key: value\n")
vp = tmp_path / "vault-password"
vp.write_text("secret")
mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="is not vault encrypted")
data = load_vault_yaml(f, vault_pass=vp)
assert data == {"key": "value"}
class TestSaveVaultYaml:
def test_save_plain(self, tmp_path: Path) -> None:
f = tmp_path / "output.yml"
save_vault_yaml(f, {"key": "value"})
content = f.read_text()
assert "key: value" in content
def test_save_preserves_unicode(self, tmp_path: Path) -> None:
"""allow_unicode=True writes UTF-8 instead of \\uXXXX escapes."""
f = tmp_path / "output.yml"
data = {"name": "Тест", "city": "München", "role": "管理员"}
save_vault_yaml(f, data)
content = f.read_text()
assert "Тест" in content
assert "München" in content
assert "管理员" in content
assert "\\u" not in content
def test_save_unicode_round_trip(self, tmp_path: Path) -> None:
"""Saved unicode data loads back correctly."""
f = tmp_path / "output.yml"
data = {"name": "Тест", "city": "München"}
save_vault_yaml(f, data)
loaded = load_vault_yaml(f)
assert loaded == data
def test_save_with_vault_pass_not_exists(self, tmp_path: Path) -> None:
f = tmp_path / "output.yml"
vp = tmp_path / "nonexistent"
save_vault_yaml(f, {"key": "value"}, vault_pass=vp)
# Should save as plain YAML
content = f.read_text()
assert "key: value" in content
assert "$ANSIBLE_VAULT" not in content
@patch("devx.utils.vault.subprocess.run")
def test_save_and_encrypt(self, mock_run: MagicMock, tmp_path: Path) -> None:
f = tmp_path / "output.yml"
vp = tmp_path / "vault-password"
vp.write_text("secret")
save_vault_yaml(f, {"key": "value"}, vault_pass=vp)
# File should be written
assert f.exists()
# ansible-vault encrypt should be called
mock_run.assert_called_once()
cmd = mock_run.call_args[0][0]
assert "ansible-vault" in cmd
assert "encrypt" in cmd
class TestEncryptFile:
@patch("devx.utils.vault.subprocess.run")
def test_calls_ansible_vault(self, mock_run: MagicMock, tmp_path: Path) -> None:
f = tmp_path / "file.yml"
f.write_text("key: value")
vp = tmp_path / "vault-password"
vp.write_text("secret")
encrypt_file(f, vp)
mock_run.assert_called_once()
cmd = mock_run.call_args[0][0]
assert "ansible-vault" in cmd
assert "encrypt" in cmd
assert str(f) in cmd
assert str(vp) in cmd
class TestDecryptFile:
@patch("devx.utils.vault.subprocess.run")
def test_calls_ansible_vault(self, mock_run: MagicMock, tmp_path: Path) -> None:
f = tmp_path / "file.yml"
f.write_text("$ANSIBLE_VAULT\n...")
vp = tmp_path / "vault-password"
vp.write_text("secret")
decrypt_file(f, vp)
mock_run.assert_called_once()
cmd = mock_run.call_args[0][0]
assert "ansible-vault" in cmd
assert "decrypt" in cmd
assert str(f) in cmd
assert str(vp) in cmd