mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-29 15:06:26 +02:00
Restructured CLI logic
This commit is contained in:
0
tests/unit/cli/create/__init__.py
Normal file
0
tests/unit/cli/create/__init__.py
Normal file
102
tests/unit/cli/create/test_credentials.py
Normal file
102
tests/unit/cli/create/test_credentials.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
# Ensure cli module is importable
|
||||
dir_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '../../../cli')
|
||||
)
|
||||
sys.path.insert(0, dir_path)
|
||||
|
||||
# Import functions and classes to test
|
||||
from cli.create.credentials import ask_for_confirmation, main
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
import subprocess
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
class TestCreateCredentials(unittest.TestCase):
|
||||
def test_ask_for_confirmation_yes(self):
|
||||
with mock.patch('builtins.input', return_value='y'):
|
||||
self.assertTrue(ask_for_confirmation('test_key'))
|
||||
|
||||
def test_ask_for_confirmation_no(self):
|
||||
with mock.patch('builtins.input', return_value='n'):
|
||||
self.assertFalse(ask_for_confirmation('test_key'))
|
||||
|
||||
def test_vault_encrypt_string_success(self):
|
||||
handler = VaultHandler('dummy_pw_file')
|
||||
# Mock subprocess.run to simulate successful vault encryption
|
||||
fake_output = 'Encrypted data'
|
||||
completed = subprocess.CompletedProcess(
|
||||
args=['ansible-vault'], returncode=0, stdout=fake_output, stderr=''
|
||||
)
|
||||
with mock.patch('subprocess.run', return_value=completed) as proc_run:
|
||||
result = handler.encrypt_string('plain_val', 'name')
|
||||
proc_run.assert_called_once()
|
||||
self.assertEqual(result, fake_output)
|
||||
|
||||
def test_vault_encrypt_string_failure(self):
|
||||
handler = VaultHandler('dummy_pw_file')
|
||||
# Mock subprocess.run to simulate failure
|
||||
completed = subprocess.CompletedProcess(
|
||||
args=['ansible-vault'], returncode=1, stdout='', stderr='error')
|
||||
with mock.patch('subprocess.run', return_value=completed):
|
||||
with self.assertRaises(RuntimeError):
|
||||
handler.encrypt_string('plain_val', 'name')
|
||||
|
||||
def test_main_overrides_and_file_writing(self):
|
||||
# Setup temporary files for role-path vars and inventory
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
role_path = os.path.join(tmpdir, 'role')
|
||||
os.makedirs(os.path.join(role_path, 'config'))
|
||||
os.makedirs(os.path.join(role_path, 'schema'))
|
||||
os.makedirs(os.path.join(role_path, 'vars'))
|
||||
# Create vars/main.yml with application_id
|
||||
main_vars = {'application_id': 'app_test'}
|
||||
with open(os.path.join(role_path, 'vars', 'main.yml'), 'w') as f:
|
||||
yaml.dump(main_vars, f)
|
||||
# Create config/main.yml with features disabled
|
||||
config = {'features': {'central_database': False}}
|
||||
with open(os.path.join(role_path, "config" , "main.yml"), 'w') as f:
|
||||
yaml.dump(config, f)
|
||||
# Create schema.yml defining plain credential
|
||||
schema = {'credentials': {'api_key': {'description': 'API key', 'algorithm': 'plain', 'validation': {}}}}
|
||||
with open(os.path.join(role_path, 'schema', 'main.yml'), 'w') as f:
|
||||
yaml.dump(schema, f)
|
||||
# Prepare inventory file
|
||||
inventory_file = os.path.join(tmpdir, 'inventory.yml')
|
||||
with open(inventory_file, 'w') as f:
|
||||
yaml.dump({}, f)
|
||||
vault_pw_file = os.path.join(tmpdir, 'pw.txt')
|
||||
with open(vault_pw_file, 'w') as f:
|
||||
f.write('pw')
|
||||
|
||||
# Simulate ansible-vault encrypt_string output for api_key
|
||||
fake_snippet = "!vault |\n $ANSIBLE_VAULT;1.1;AES256\n ENCRYPTEDVALUE"
|
||||
completed = subprocess.CompletedProcess(
|
||||
args=['ansible-vault'], returncode=0, stdout=fake_snippet, stderr=''
|
||||
)
|
||||
with mock.patch('subprocess.run', return_value=completed):
|
||||
# Run main with override for credentials.api_key and force to skip prompt
|
||||
sys.argv = [
|
||||
'create/credentials.py',
|
||||
'--role-path', role_path,
|
||||
'--inventory-file', inventory_file,
|
||||
'--vault-password-file', vault_pw_file,
|
||||
'--set', 'credentials.api_key=SECRET',
|
||||
'--force'
|
||||
]
|
||||
# Should complete without error
|
||||
main()
|
||||
# Verify inventory file updated with vaulted api_key
|
||||
data = yaml.safe_load(open(inventory_file))
|
||||
creds = data['applications']['app_test']['credentials']
|
||||
self.assertIn('api_key', creds)
|
||||
# VaultScalar serializes to a vault block, safe_load returns a string containing the vault header
|
||||
self.assertIsInstance(creds['api_key'], str)
|
||||
self.assertTrue(creds['api_key'].lstrip().startswith('$ANSIBLE_VAULT'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
137
tests/unit/cli/create/test_role.py
Normal file
137
tests/unit/cli/create/test_role.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import unittest
|
||||
import ipaddress
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
# Import functions to test; adjust path as needed
|
||||
from cli.create.role import (
|
||||
get_next_network,
|
||||
get_next_port,
|
||||
load_yaml_with_comments,
|
||||
dump_yaml_with_comments,
|
||||
render_templates
|
||||
)
|
||||
|
||||
class TestCreateDockerRoleCLI(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Temporary directory for YAML files and templates
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
self.ports_file = os.path.join(self.tmpdir, '09_ports.yml')
|
||||
self.networks_file = os.path.join(self.tmpdir, '10_networks.yml')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
||||
def test_get_next_network_multiple(self):
|
||||
networks = {
|
||||
'defaults_networks': {
|
||||
'local': {
|
||||
'app1': {'subnet': '192.168.0.0/28'},
|
||||
'app2': {'subnet': '192.168.0.16/28'},
|
||||
}
|
||||
}
|
||||
}
|
||||
next_net = get_next_network(networks, 28)
|
||||
self.assertEqual(next_net, ipaddress.ip_network('192.168.0.32/28'))
|
||||
|
||||
def test_get_next_network_single(self):
|
||||
networks = {
|
||||
'defaults_networks': {
|
||||
'local': {
|
||||
'only': {'subnet': '10.0.0.0/24'},
|
||||
}
|
||||
}
|
||||
}
|
||||
next_net = get_next_network(networks, 24)
|
||||
self.assertEqual(next_net, ipaddress.ip_network('10.0.1.0/24'))
|
||||
|
||||
def test_get_next_port_existing(self):
|
||||
ports = {
|
||||
'ports': {
|
||||
'localhost': {
|
||||
'http': {'app1': '8000', 'app2': '8001'}
|
||||
}
|
||||
}
|
||||
}
|
||||
next_port = get_next_port(ports, 'http')
|
||||
self.assertEqual(next_port, 8002)
|
||||
|
||||
def test_get_next_port_empty(self):
|
||||
ports = {
|
||||
'ports': {
|
||||
'localhost': {
|
||||
'ldap': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
next_port = get_next_port(ports, 'ldap')
|
||||
self.assertEqual(next_port, 1)
|
||||
|
||||
def test_yaml_load_dump_preserves_comments(self):
|
||||
# Initialize ruamel.yaml
|
||||
yaml = YAML()
|
||||
yaml.preserve_quotes = True
|
||||
# Create sample file with comment
|
||||
content = (
|
||||
'# Ports file\n'
|
||||
'ports:\n'
|
||||
' localhost:\n'
|
||||
' http:\n'
|
||||
' app1: 8000 # existing port\n'
|
||||
)
|
||||
with open(self.ports_file, 'w') as f:
|
||||
f.write(content)
|
||||
# Load and modify
|
||||
data = load_yaml_with_comments(self.ports_file)
|
||||
data['ports']['localhost']['http']['app2'] = 8001
|
||||
dump_yaml_with_comments(data, self.ports_file)
|
||||
# Check comment and new entry
|
||||
text = open(self.ports_file).read()
|
||||
self.assertIn('# existing port', text)
|
||||
self.assertIn('app2: 8001', text)
|
||||
|
||||
def test_render_creates_and_overwrites_skips_merges(self):
|
||||
# Prepare source and dest directories
|
||||
src = os.path.join(self.tmpdir, 'src')
|
||||
dst = os.path.join(self.tmpdir, 'dst')
|
||||
os.makedirs(src)
|
||||
os.makedirs(dst)
|
||||
# Template file
|
||||
tpl = os.path.join(src, 'file.txt.j2')
|
||||
with open(tpl, 'w') as f:
|
||||
f.write('Line1\nLine2')
|
||||
out_file = os.path.join(dst, 'file.txt')
|
||||
# Test create
|
||||
render_templates(src, dst, {})
|
||||
with open(out_file) as f:
|
||||
self.assertEqual(f.read(), 'Line1\nLine2')
|
||||
# Test overwrite
|
||||
with open(out_file, 'w') as f:
|
||||
f.write('Old')
|
||||
import builtins
|
||||
original_input = builtins.input
|
||||
builtins.input = lambda _: '1'
|
||||
render_templates(src, dst, {})
|
||||
with open(out_file) as f:
|
||||
self.assertEqual(f.read(), 'Line1\nLine2')
|
||||
# Test skip
|
||||
with open(out_file, 'w') as f:
|
||||
f.write('Old')
|
||||
builtins.input = lambda _: '2'
|
||||
render_templates(src, dst, {})
|
||||
with open(out_file) as f:
|
||||
self.assertEqual(f.read(), 'Old')
|
||||
# Test merge
|
||||
with open(out_file, 'w') as f:
|
||||
f.write('Line1\n')
|
||||
builtins.input = lambda _: '3'
|
||||
render_templates(src, dst, {})
|
||||
content = open(out_file).read().splitlines()
|
||||
self.assertIn('Line1', content)
|
||||
self.assertIn('Line2', content)
|
||||
builtins.input = original_input
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Reference in New Issue
Block a user