1131 lines
41 KiB
Python
1131 lines
41 KiB
Python
# Copyright (C) 2022 Cetmix OÜ
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from unittest.mock import patch
|
|
|
|
from psycopg2 import IntegrityError
|
|
|
|
from odoo import _, fields
|
|
from odoo.exceptions import AccessError, ValidationError
|
|
from odoo.tests.common import Form
|
|
from odoo.tools.misc import mute_logger
|
|
|
|
from .common import TestTowerCommon
|
|
|
|
|
|
class TestTowerVariable(TestTowerCommon):
|
|
"""Testing variables and variable values."""
|
|
|
|
def check_variable_values(self, vals, server_ids=None):
|
|
"""Check if variable values are correctly stored in db
|
|
|
|
Args:
|
|
vals (List of tuples): format ("variable_id", "value")
|
|
server_id (cx.tower.server()): Servers those variables belong to.
|
|
"""
|
|
if server_ids:
|
|
variable_records = server_ids.variable_value_ids
|
|
else:
|
|
variable_records = self.VariableValue.search([("is_global", "=", True)])
|
|
len_vals = len(vals)
|
|
|
|
# Ensure correct number of records
|
|
self.assertEqual(
|
|
len(variable_records), len_vals, msg="Must be %s records" % str(len_vals)
|
|
)
|
|
|
|
# Check variable values
|
|
for val in vals:
|
|
variable_line = variable_records.filtered(
|
|
lambda v, val=val: v.variable_id.id == val[0]
|
|
)
|
|
self.assertEqual(
|
|
len(variable_line), 1, msg="Must be a single variable line"
|
|
)
|
|
expected_value = val[1] or False
|
|
self.assertEqual(
|
|
variable_line.value_char,
|
|
expected_value,
|
|
msg="Variable value does not match provided one",
|
|
)
|
|
|
|
def test_variable_values(self):
|
|
"""Test common variable operations"""
|
|
|
|
# -- 1 --
|
|
# Server specific variables
|
|
|
|
# Add two variables
|
|
with Form(self.server_test_1) as f:
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_dir
|
|
line.value_char = "/opt/odoo"
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_url
|
|
line.value_char = "example.com"
|
|
f.save()
|
|
|
|
vals = [
|
|
(self.variable_url.id, "example.com"),
|
|
(self.variable_dir.id, "/opt/odoo"),
|
|
]
|
|
self.check_variable_values(vals=vals, server_ids=self.server_test_1)
|
|
|
|
# Add another variable and edit the existing one
|
|
with Form(self.server_test_1) as f:
|
|
with f.variable_value_ids.edit(1) as line:
|
|
line.value_char = "meme.example.com"
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_version
|
|
line.value_char = "10.0"
|
|
f.save()
|
|
|
|
vals = [
|
|
(self.variable_url.id, "meme.example.com"),
|
|
(self.variable_dir.id, "/opt/odoo"),
|
|
(self.variable_version.id, "10.0"),
|
|
]
|
|
self.check_variable_values(vals=vals, server_ids=self.server_test_1)
|
|
|
|
# Delete two variables, add a new one
|
|
with Form(self.server_test_1) as f:
|
|
f.variable_value_ids.remove(index=0)
|
|
f.variable_value_ids.remove(index=0)
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_os
|
|
line.value_char = "Debian"
|
|
|
|
# Add an empty variable value
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_url
|
|
f.save()
|
|
|
|
vals = [
|
|
(self.variable_os.id, "Debian"),
|
|
(self.variable_version.id, "10.0"),
|
|
(self.variable_url.id, False),
|
|
]
|
|
self.check_variable_values(vals=vals, server_ids=self.server_test_1)
|
|
|
|
# Test 'get_variable_values' function
|
|
res = self.server_test_1.get_variable_values(
|
|
["test_dir", "test_os", "test_url", "test_version"]
|
|
)
|
|
self.assertEqual(len(res), 1, "Must be a single record key in the result")
|
|
|
|
res_vars = res.get(self.server_test_1.id)
|
|
var_dir = res_vars["test_dir"]
|
|
var_os = res_vars["test_os"]
|
|
var_url = res_vars["test_url"]
|
|
var_version = res_vars["test_version"]
|
|
|
|
self.assertIsNone(var_dir, msg="Variable 'dir' must be None")
|
|
self.assertFalse(var_url, msg="Variable 'url' must be False")
|
|
self.assertEqual(var_os, "Debian", msg="Variable 'os' must be 'Debian'")
|
|
self.assertEqual(var_version, "10.0", msg="Variable 'version' must be '10.0'")
|
|
|
|
# -- 2 --
|
|
# Test global variable values
|
|
|
|
# Create a global value for the 'dir' variable
|
|
self.VariableValue.create(
|
|
{"variable_id": self.variable_dir.id, "value_char": "/global/dir"}
|
|
)
|
|
res = self.server_test_1.get_variable_values(
|
|
["test_dir", "test_os", "test_url", "test_version"]
|
|
)
|
|
self.assertEqual(len(res), 1, "Must be a single record key in the result")
|
|
|
|
res_vars = res.get(self.server_test_1.id)
|
|
var_dir = res_vars["test_dir"]
|
|
var_os = res_vars["test_os"]
|
|
var_url = res_vars["test_url"]
|
|
var_version = res_vars["test_version"]
|
|
|
|
self.assertEqual(
|
|
var_dir, "/global/dir", msg="Variable 'dir' must be equal to '/global/dir'"
|
|
)
|
|
self.assertFalse(var_url, msg="Variable 'url' must be False")
|
|
self.assertEqual(var_os, "Debian", msg="Variable 'os' must be 'Debian'")
|
|
self.assertEqual(var_version, "10.0", msg="Variable 'version' must be '10.0'")
|
|
|
|
# Now save a local value for the variable
|
|
with Form(self.server_test_1) as f:
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_dir
|
|
line.value_char = "/opt/odoo"
|
|
f.save()
|
|
|
|
# Check
|
|
res = self.server_test_1.get_variable_values(
|
|
["test_dir", "test_os", "test_url", "test_version"]
|
|
)
|
|
self.assertEqual(len(res), 1, "Must be a single record key in the result")
|
|
|
|
res_vars = res.get(self.server_test_1.id)
|
|
var_dir = res_vars["test_dir"]
|
|
var_os = res_vars["test_os"]
|
|
var_url = res_vars["test_url"]
|
|
var_version = res_vars["test_version"]
|
|
|
|
self.assertEqual(
|
|
var_dir, "/opt/odoo", msg="Variable 'dir' must be equal to '/opt/odoo'"
|
|
)
|
|
self.assertFalse(var_url, msg="Variable 'url' must be False")
|
|
self.assertEqual(var_os, "Debian", msg="Variable 'os' must be 'Debian'")
|
|
self.assertEqual(var_version, "10.0", msg="Variable 'version' must be '10.0'")
|
|
|
|
def test_variables_in_variable_values(self):
|
|
"""Test variables in variable values
|
|
eg
|
|
home: /home
|
|
user: bob
|
|
home_dir: {{ home }}/{{ user }} --> /home/bob
|
|
"""
|
|
|
|
# Add local variables
|
|
with Form(self.server_test_1) as f:
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_dir
|
|
line.value_char = "/web"
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_path
|
|
line.value_char = "{{ test_dir }}/{{ test_version }}"
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_url
|
|
line.value_char = "{{ test_path_ }}/example.com"
|
|
f.save()
|
|
|
|
# Create a global value for the 'Version' variable
|
|
self.VariableValue.create(
|
|
{"variable_id": self.variable_version.id, "value_char": "10.0"}
|
|
)
|
|
|
|
# Check values
|
|
res = self.server_test_1.get_variable_values(
|
|
["test_dir", "test_url", "test_version"]
|
|
)
|
|
self.assertEqual(len(res), 1, "Must be a single record key in the result")
|
|
|
|
res_vars = res.get(self.server_test_1.id)
|
|
var_dir = res_vars["test_dir"]
|
|
var_url = res_vars["test_url"]
|
|
var_version = res_vars["test_version"]
|
|
|
|
self.assertEqual(var_dir, "/web", msg="Variable 'dir' must be '/web'")
|
|
self.assertEqual(
|
|
var_url,
|
|
"/web/10.0/example.com",
|
|
msg="Variable 'url' must be '/web/10.0/example.com'",
|
|
)
|
|
self.assertEqual(var_version, "10.0", msg="Variable 'version' must be '10.0'")
|
|
|
|
def test_variable_values_unlink(self):
|
|
"""Ensure variable values are deleted properly
|
|
- Create a new server
|
|
- Add 2 variable values
|
|
- Delete server
|
|
- Ensure variable values are deleted
|
|
"""
|
|
|
|
def get_value_count(variable):
|
|
"""helper function to count variable value records
|
|
Arg: (cx.tower.variable) variable rec
|
|
Returns: (int) record count
|
|
"""
|
|
return self.VariableValue.search_count([("variable_id", "=", variable.id)])
|
|
|
|
# Get variable values count before adding variables to server
|
|
count_dir_before = get_value_count(self.variable_dir)
|
|
count_url_before = get_value_count(self.variable_url)
|
|
|
|
# Create new server
|
|
server_test_var = self.Server.create(
|
|
{
|
|
"name": "Test Var",
|
|
"os_id": self.os_debian_10.id,
|
|
"ip_v4_address": "localhost",
|
|
"ssh_username": "bob",
|
|
"ssh_password": "pass",
|
|
}
|
|
)
|
|
|
|
# Add two variables to server
|
|
with Form(server_test_var) as f:
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_dir
|
|
line.value_char = "/opt/odoo"
|
|
with f.variable_value_ids.new() as line:
|
|
line.variable_id = self.variable_url
|
|
line.value_char = "example.com"
|
|
f.save()
|
|
|
|
# Number of values should be incremented
|
|
self.assertEqual(
|
|
get_value_count(self.variable_dir),
|
|
count_dir_before + 1,
|
|
msg="Value count must be incremented!",
|
|
)
|
|
self.assertEqual(
|
|
get_value_count(self.variable_url),
|
|
count_url_before + 1,
|
|
msg="Value count must be incremented!",
|
|
)
|
|
|
|
# Delete the server
|
|
server_test_var.unlink()
|
|
self.assertEqual(
|
|
get_value_count(self.variable_dir),
|
|
count_dir_before,
|
|
msg="Value count must be same as before server creation!",
|
|
)
|
|
self.assertEqual(
|
|
get_value_count(self.variable_url),
|
|
count_url_before,
|
|
msg="Value count must be same as before server creation!",
|
|
)
|
|
|
|
def test_variable_value_toggle_global(self):
|
|
"""Test what happens when variable value 'global' setting is togged"""
|
|
|
|
variable_meme = self.Variable.create({"name": "meme"})
|
|
variable_value_pepe = self.VariableValue.create(
|
|
{"variable_id": variable_meme.id, "value_char": "Pepe"}
|
|
)
|
|
|
|
self.assertEqual(
|
|
variable_value_pepe.is_global, True, msg="Value 'Pepe' must be global"
|
|
)
|
|
|
|
# Test `_check_is_global` function
|
|
self.assertEqual(
|
|
variable_value_pepe._check_is_global(),
|
|
True,
|
|
msg="Value 'Pepe' must be global",
|
|
)
|
|
|
|
# Try to create another global value for the same variable
|
|
with self.assertRaises(ValidationError) as err:
|
|
self.VariableValue.create(
|
|
{"variable_id": variable_meme.id, "value_char": "Doge"}
|
|
)
|
|
|
|
# We check the message in order to ensure that
|
|
# exception was raised by the correct event.
|
|
self.assertEqual(
|
|
err.exception.args[0],
|
|
_("Only one global value can be defined for variable 'meme'"),
|
|
msg="Error message doesn't match. Check if you have modified it in code:"
|
|
"models/cx_tower_server.py",
|
|
)
|
|
|
|
# Try to disable 'global' for a global variable explicitly
|
|
with self.assertRaises(ValidationError) as err:
|
|
variable_value_pepe.is_global = False
|
|
|
|
# We check the message in order to ensure that
|
|
# exception was raised by the correct event.
|
|
self.assertEqual(
|
|
err.exception.args[0],
|
|
_(
|
|
"Cannot change 'global' status for "
|
|
"'meme' with value 'Pepe'."
|
|
"\nTry to assigns it to a record instead."
|
|
),
|
|
msg="Error message doesn't match. Check if you have modified it in code:"
|
|
"models/cx_tower_server.py",
|
|
)
|
|
|
|
def test_system_variable_server_type_values(self):
|
|
"""Test system variables of `server` type"""
|
|
|
|
# Modify server record for testing
|
|
self.server_test_1.ip_v6_address = "suchmuchipv6"
|
|
self.server_test_1.url = "meme.example.com"
|
|
self.server_test_1.partner_id = (
|
|
self.env["res.partner"].create({"name": "Pepe Frog"}).id
|
|
)
|
|
|
|
# Create new command with system variables
|
|
command = self.Command.create(
|
|
{
|
|
"name": "Super System Command",
|
|
"code": "echo {{ tower.server.name }} "
|
|
"{{ tower.server.username}} "
|
|
"{{ tower.server.partner_name }} "
|
|
"{{ tower.server.ipv4 }} "
|
|
"{{ tower.server.ipv6 }} "
|
|
"{{ tower.server.url }} ",
|
|
}
|
|
)
|
|
|
|
# Get variables
|
|
variables = command.get_variables().get(str(command.id))
|
|
# Get variable values
|
|
variable_values = self.server_test_1.get_variable_values(variables).get(
|
|
self.server_test_1.id
|
|
)
|
|
|
|
# Check values
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["name"],
|
|
self.server_test_1.name,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["reference"],
|
|
self.server_test_1.reference,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["username"],
|
|
self.server_test_1.ssh_username,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["username"],
|
|
self.server_test_1.ssh_username,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["partner_name"],
|
|
self.server_test_1.partner_id.name,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["ipv4"],
|
|
self.server_test_1.ip_v4_address,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["ipv6"],
|
|
self.server_test_1.ip_v6_address,
|
|
"System variable doesn't match server property",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["server"]["url"],
|
|
self.server_test_1.url,
|
|
"System variable doesn't match server property",
|
|
)
|
|
|
|
@patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_variable_mixin.fields.Datetime.now",
|
|
return_value=fields.Datetime.now(),
|
|
)
|
|
@patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_variable_mixin.fields.Date.today",
|
|
return_value=fields.Date.today(),
|
|
)
|
|
@patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_variable_mixin.uuid.uuid4",
|
|
return_value="suchmuchuuid4",
|
|
)
|
|
def test_system_variable_tools_type_values(self, mock_uuid4, mock_today, mock_now):
|
|
"""Test system variables of `tools` type"""
|
|
|
|
# Create new command with system variables
|
|
command = self.Command.create(
|
|
{"name": "Super System Command", "code": "echo {{ tower.tools.uuid}}"}
|
|
)
|
|
|
|
# Get variables
|
|
variables = command.get_variables().get(str(command.id))
|
|
# Get variable values
|
|
variable_values = self.server_test_1.get_variable_values(variables).get(
|
|
self.server_test_1.id
|
|
)
|
|
|
|
# Check values
|
|
self.assertEqual(
|
|
variable_values["tower"]["tools"]["uuid"],
|
|
mock_uuid4.return_value,
|
|
"System variable doesn't match result provided by tools",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["tools"]["today"],
|
|
str(mock_today.return_value),
|
|
"System variable doesn't match result provided by tools",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["tools"]["now"],
|
|
str(mock_now.return_value),
|
|
"System variable doesn't match result provided by tools",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["tools"]["today_underscore"],
|
|
str(mock_today.return_value)
|
|
.replace("-", "_")
|
|
.replace(" ", "_")
|
|
.replace(":", "_")
|
|
.replace(".", "_")
|
|
.replace("/", "_"),
|
|
"System variable doesn't match result provided by tools",
|
|
)
|
|
self.assertEqual(
|
|
variable_values["tower"]["tools"]["now_underscore"],
|
|
str(mock_now.return_value)
|
|
.replace("-", "_")
|
|
.replace(":", "_")
|
|
.replace(" ", "_")
|
|
.replace(".", "_")
|
|
.replace("/", "_"),
|
|
"System variable doesn't match result provided by tools",
|
|
)
|
|
|
|
def test_make_value_pythonic(self):
|
|
"""Test making variable values 'pythonic`"""
|
|
|
|
# Number
|
|
value = 12.34
|
|
expected_value = '"12.34"'
|
|
result_value = self.Command._make_value_pythonic(value)
|
|
|
|
self.assertEqual(
|
|
expected_value, result_value, "Result value doesn't match expected"
|
|
)
|
|
|
|
# Text
|
|
value = "Doge much like"
|
|
expected_value = '"Doge much like"'
|
|
result_value = self.Command._make_value_pythonic(value)
|
|
|
|
self.assertEqual(
|
|
expected_value, result_value, "Result value doesn't match expected"
|
|
)
|
|
|
|
# Boolean
|
|
value = True
|
|
expected_value = True
|
|
result_value = self.Command._make_value_pythonic(value)
|
|
|
|
self.assertEqual(
|
|
expected_value, result_value, "Result value doesn't match expected"
|
|
)
|
|
|
|
# None
|
|
value = None
|
|
expected_value = None
|
|
result_value = self.Command._make_value_pythonic(value)
|
|
|
|
self.assertEqual(
|
|
expected_value, result_value, "Result value doesn't match expected"
|
|
)
|
|
|
|
# Dict
|
|
value = {"doge": {"likes": "memes", "much": 200}}
|
|
expected_value = {"doge": {"likes": '"memes"', "much": '"200"'}}
|
|
result_value = self.Command._make_value_pythonic(value)
|
|
|
|
self.assertEqual(
|
|
expected_value, result_value, "Result value doesn't match expected"
|
|
)
|
|
|
|
def test_get_by_variable_reference(self):
|
|
"""Test getting variable values by variable reference"""
|
|
|
|
variable_meme = self.Variable.create(
|
|
{"name": "Meme Variable", "reference": "meme_variable"}
|
|
)
|
|
global_value = self.VariableValue.create(
|
|
{"variable_id": variable_meme.id, "value_char": "Memes Globalvs"}
|
|
)
|
|
|
|
# -- 1 -- Get value for Server with no server value defined
|
|
server_result = self.VariableValue.get_by_variable_reference(
|
|
variable_meme.reference, server_id=self.server_test_1.id
|
|
)
|
|
self.assertIsNone(server_result.get("server"))
|
|
self.assertIsNone(server_result.get("server_template"))
|
|
self.assertEqual(server_result.get("global"), global_value.value_char)
|
|
|
|
# -- 2 -- Add server value and try again
|
|
server_value = self.VariableValue.create(
|
|
{
|
|
"variable_id": variable_meme.id,
|
|
"value_char": "Memes Servervs",
|
|
"server_id": self.server_test_1.id,
|
|
}
|
|
)
|
|
server_result = self.VariableValue.get_by_variable_reference(
|
|
variable_meme.reference, server_id=self.server_test_1.id
|
|
)
|
|
self.assertEqual(server_result.get("server"), server_value.value_char)
|
|
self.assertEqual(server_result.get("global"), global_value.value_char)
|
|
self.assertIsNone(server_result.get("server_template"))
|
|
|
|
# -- 3 -- Do not fetch global value now
|
|
server_result = self.VariableValue.get_by_variable_reference(
|
|
variable_meme.reference, server_id=self.server_test_1.id, check_global=False
|
|
)
|
|
self.assertIsNone(server_result.get("global"))
|
|
self.assertEqual(server_result.get("server"), server_value.value_char)
|
|
self.assertIsNone(server_result.get("server_template"))
|
|
|
|
# -- 4 -- Check server template value
|
|
server_template_value = self.VariableValue.create(
|
|
{
|
|
"variable_id": variable_meme.id,
|
|
"value_char": "Memes Servervs Templatvs",
|
|
"server_template_id": self.server_template_sample.id,
|
|
}
|
|
)
|
|
server_result = self.VariableValue.get_by_variable_reference(
|
|
variable_meme.reference, server_template_id=self.server_template_sample.id
|
|
)
|
|
self.assertEqual(server_result.get("global"), global_value.value_char)
|
|
self.assertIsNone(server_result.get("server"))
|
|
self.assertEqual(
|
|
server_result.get("server_template"), server_template_value.value_char
|
|
)
|
|
|
|
def test_single_assignment(self):
|
|
"""Test that a variable can only be assigned to one model at a time."""
|
|
# Create a variable value assigned to the server
|
|
variable_value = self.env["cx.tower.variable.value"].create(
|
|
{
|
|
"variable_id": self.variable_os.id,
|
|
"value_char": "Branch = Main",
|
|
"server_id": self.server_test_1.id,
|
|
}
|
|
)
|
|
|
|
# Try to assign the same variable value to
|
|
# server template and expect a ValidationError
|
|
with self.assertRaises(ValidationError):
|
|
variable_value.write({"server_template_id": self.server_template_sample.id})
|
|
|
|
# Try to assign the same variable value to
|
|
# plan line action and expect a ValidationError
|
|
with self.assertRaises(ValidationError):
|
|
variable_value.write({"plan_line_action_id": self.plan_line_1_action_1.id})
|
|
|
|
def test_unique_assignment(self):
|
|
"""Test that the same variable value cannot be
|
|
assigned multiple times to the same record.
|
|
"""
|
|
|
|
# Create a variable
|
|
variable = self.env["cx.tower.variable"].create(
|
|
{"name": "Environment Type", "note": "The environment type for the server."}
|
|
)
|
|
|
|
# Create a server
|
|
server = self.env["cx.tower.server"].create(
|
|
{
|
|
"name": "Test Server",
|
|
"ip_v4_address": "127.0.0.1",
|
|
"ssh_username": "testuser",
|
|
"ssh_password": "testpassword",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create a variable value for the server
|
|
self.env["cx.tower.variable.value"].create(
|
|
{
|
|
"variable_id": variable.id,
|
|
"value_char": "Production",
|
|
"server_id": server.id,
|
|
}
|
|
)
|
|
|
|
# Try to create a second variable value with the same variable and server
|
|
with mute_logger("odoo.sql_db"), self.assertRaises(
|
|
IntegrityError,
|
|
msg="A variable value cannot be assigned multiple times to the same server",
|
|
):
|
|
self.env["cx.tower.variable.value"].create(
|
|
{
|
|
"variable_id": variable.id,
|
|
"value_char": "Production",
|
|
"server_id": server.id,
|
|
}
|
|
)
|
|
|
|
def test_value_access_level_consistency(self):
|
|
"""Test that variable value access level cannot be lower
|
|
than variable access level."""
|
|
|
|
# Create test servers
|
|
server_2 = self.Server.create(
|
|
{
|
|
"name": "Test Server 2",
|
|
"ip_v4_address": "localhost",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"os_id": self.os_debian_10.id,
|
|
}
|
|
)
|
|
|
|
server_3 = self.Server.create(
|
|
{
|
|
"name": "Test Server 3",
|
|
"ip_v4_address": "localhost",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"os_id": self.os_debian_10.id,
|
|
}
|
|
)
|
|
|
|
# Create a variable with access level "2"
|
|
variable_restricted = self.Variable.create(
|
|
{
|
|
"name": "restricted_variable",
|
|
"access_level": "2",
|
|
}
|
|
)
|
|
|
|
# Should succeed: value with same access level as variable
|
|
try:
|
|
self.VariableValue.create(
|
|
{
|
|
"variable_id": variable_restricted.id,
|
|
"value_char": "test_value1",
|
|
"access_level": "2",
|
|
"is_global": True,
|
|
}
|
|
)
|
|
except ValidationError:
|
|
self.fail("Should allow creating value with same access level as variable")
|
|
|
|
# Should succeed: value with higher access level than variable
|
|
try:
|
|
self.VariableValue.create(
|
|
{
|
|
"variable_id": variable_restricted.id,
|
|
"value_char": "test_value2",
|
|
"access_level": "3",
|
|
"server_id": server_2.id,
|
|
}
|
|
)
|
|
except ValidationError:
|
|
self.fail(
|
|
"Should allow creating value with higher access level than variable"
|
|
)
|
|
|
|
# Should fail: value with lower access level than variable
|
|
with self.assertRaises(
|
|
ValidationError,
|
|
msg="Should not allow creating value with lower access level than variable",
|
|
):
|
|
self.VariableValue.create(
|
|
{
|
|
"variable_id": variable_restricted.id,
|
|
"value_char": "test_value3",
|
|
"access_level": "1",
|
|
"server_id": server_3.id,
|
|
}
|
|
)
|
|
|
|
# Test updating existing value's access level
|
|
value = self.VariableValue.create(
|
|
{
|
|
"variable_id": self.variable_dir.id, # Using a different variable
|
|
"value_char": "test_value4",
|
|
"access_level": "2",
|
|
"server_id": server_3.id,
|
|
}
|
|
)
|
|
|
|
# Should fail: updating to lower access level than variable
|
|
with self.assertRaises(
|
|
ValidationError,
|
|
msg="Should not allow updating value to lower access level than variable",
|
|
):
|
|
value.write({"access_level": "1"})
|
|
|
|
# Should succeed: updating to higher access level than variable
|
|
try:
|
|
value.write({"access_level": "3"})
|
|
except ValidationError:
|
|
self.fail(
|
|
"Should allow updating value to higher access level than variable"
|
|
)
|
|
|
|
def test_variable_access_rights(self):
|
|
"""Test access rights for variables based on access levels and user roles."""
|
|
|
|
# Create variables with different access levels
|
|
variable_level_1 = self.Variable.create(
|
|
{
|
|
"name": "Level 1 Variable",
|
|
"access_level": "1",
|
|
}
|
|
)
|
|
|
|
variable_level_2 = self.Variable.create(
|
|
{
|
|
"name": "Level 2 Variable",
|
|
"access_level": "2",
|
|
}
|
|
)
|
|
|
|
variable_level_3 = self.Variable.create(
|
|
{
|
|
"name": "Level 3 Variable",
|
|
"access_level": "3",
|
|
}
|
|
)
|
|
manager2 = self.Users.create(
|
|
{
|
|
"name": "Manager 2",
|
|
"login": "manager2@example.com",
|
|
"groups_id": [(4, self.group_manager.id)],
|
|
}
|
|
)
|
|
|
|
# Test User Access
|
|
# ---------------
|
|
# Should see level 1 variables
|
|
records = self.Variable.with_user(self.user).search(
|
|
[
|
|
(
|
|
"id",
|
|
"in",
|
|
[variable_level_1.id, variable_level_2.id, variable_level_3.id],
|
|
)
|
|
]
|
|
)
|
|
self.assertEqual(len(records), 1, "User should only see level 1 variables")
|
|
self.assertEqual(
|
|
records.id, variable_level_1.id, "User should only see level 1 variables"
|
|
)
|
|
|
|
# Test Manager Access
|
|
# -----------------
|
|
# Should see level 1 and 2 variables
|
|
records = self.Variable.with_user(self.manager).search(
|
|
[
|
|
(
|
|
"id",
|
|
"in",
|
|
[variable_level_1.id, variable_level_2.id, variable_level_3.id],
|
|
)
|
|
]
|
|
)
|
|
self.assertEqual(len(records), 2, "Manager should see level 1 and 2 variables")
|
|
self.assertIn(
|
|
variable_level_1.id, records.ids, "Manager should see level 1 variables"
|
|
)
|
|
self.assertIn(
|
|
variable_level_2.id, records.ids, "Manager should see level 2 variables"
|
|
)
|
|
|
|
# Test Manager Write Access
|
|
# -----------------------
|
|
# Create a variable as manager
|
|
manager_variable = self.Variable.with_user(self.manager).create(
|
|
{
|
|
"name": "Manager Created Variable",
|
|
"access_level": "2",
|
|
}
|
|
)
|
|
|
|
# Manager should be able to modify their own variable
|
|
try:
|
|
manager_variable.with_user(self.manager).write({"name": "Updated Name"})
|
|
except AccessError:
|
|
self.fail("Manager should be able to modify their own variables")
|
|
|
|
# Manager should not be able to modify another manager's variable
|
|
manager2_variable = self.Variable.with_user(manager2).create(
|
|
{
|
|
"name": "Other Manager Variable",
|
|
"access_level": "2",
|
|
}
|
|
)
|
|
|
|
with self.assertRaises(AccessError):
|
|
manager2_variable.with_user(self.manager).write({"name": "Try Update"})
|
|
|
|
# Manager should not be able to create level 3 variable
|
|
with self.assertRaises(AccessError):
|
|
self.Variable.with_user(self.manager).create(
|
|
{
|
|
"name": "Try Level 3",
|
|
"access_level": "3",
|
|
}
|
|
)
|
|
|
|
# Test Root Access
|
|
# --------------
|
|
# Root should see all variables
|
|
records = self.Variable.with_user(self.root).search(
|
|
[
|
|
(
|
|
"id",
|
|
"in",
|
|
[variable_level_1.id, variable_level_2.id, variable_level_3.id],
|
|
)
|
|
]
|
|
)
|
|
self.assertEqual(len(records), 3, "Root should see all variables")
|
|
|
|
# Root should be able to create any level variable
|
|
try:
|
|
self.Variable.with_user(self.root).create(
|
|
{
|
|
"name": "Root Level 3",
|
|
"access_level": "3",
|
|
}
|
|
)
|
|
except AccessError:
|
|
self.fail("Root should be able to create any level variable")
|
|
|
|
# Root should be able to modify any variable
|
|
try:
|
|
variable_level_3.with_user(self.root).write({"name": "Updated by Root"})
|
|
except AccessError:
|
|
self.fail("Root should be able to modify any variable")
|
|
|
|
def test_validate_value(self):
|
|
"""Test variable value validation"""
|
|
# Create variable with validation pattern
|
|
variable_with_pattern = self.Variable.create(
|
|
{
|
|
"name": "Test Pattern",
|
|
"validation_pattern": "^[a-z0-9]+$",
|
|
"validation_message": "Only lowercase letters and numbers allowed",
|
|
}
|
|
)
|
|
|
|
# Test valid values
|
|
valid_value = "abc123"
|
|
is_valid, message = variable_with_pattern._validate_value(valid_value)
|
|
self.assertTrue(is_valid, "Value should be valid")
|
|
self.assertIsNone(message, "No message should be returned for valid value")
|
|
|
|
# Test invalid values
|
|
invalid_value = "ABC123!"
|
|
is_valid, message = variable_with_pattern._validate_value(invalid_value)
|
|
self.assertFalse(is_valid, "Value should be invalid")
|
|
self.assertEqual(
|
|
message,
|
|
f"Variable: {variable_with_pattern.name}, Value: {invalid_value}\n"
|
|
"Only lowercase letters and numbers allowed",
|
|
"Invalid value message doesn't match",
|
|
)
|
|
|
|
# Test empty value
|
|
is_valid, message = variable_with_pattern._validate_value(None)
|
|
self.assertTrue(is_valid, "Empty value should be valid")
|
|
self.assertIsNone(message, "No message should be returned for empty value")
|
|
|
|
# Test variable without pattern
|
|
variable_no_pattern = self.Variable.create(
|
|
{
|
|
"name": "No Pattern",
|
|
}
|
|
)
|
|
test_value = "Any Value!"
|
|
is_valid, message = variable_no_pattern._validate_value(test_value)
|
|
self.assertTrue(is_valid, "Value should be valid when no pattern is set")
|
|
self.assertIsNone(
|
|
message, "No message should be returned when no pattern is set"
|
|
)
|
|
|
|
# Test default validation message
|
|
variable_default_message = self.Variable.create(
|
|
{
|
|
"name": "Default Message",
|
|
"validation_pattern": "^[a-z]+$",
|
|
}
|
|
)
|
|
invalid_value = "123"
|
|
is_valid, message = variable_default_message._validate_value(invalid_value)
|
|
self.assertFalse(is_valid, "Value should be invalid")
|
|
self.assertEqual(
|
|
message,
|
|
f"Variable: {variable_default_message.name}, Value: {invalid_value}\n"
|
|
f"{variable_default_message.DEFAULT_VALIDATION_MESSAGE}",
|
|
"Default validation message doesn't match",
|
|
)
|
|
|
|
|
|
class TestVariableReferenceRename(TestTowerCommon):
|
|
"""Ensure variable rename updates all Jinja references using shared fixtures."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.ref_old = cls.variable_version.reference
|
|
cls.ref_new = "software_version"
|
|
|
|
cls.command = cls.Command.create(
|
|
{
|
|
"name": "Show version (test)",
|
|
"code": f"echo {{ {{ {cls.ref_old} }} }}",
|
|
"variable_ids": [(6, 0, [cls.variable_version.id])],
|
|
}
|
|
)
|
|
|
|
cls.file = cls.File.create(
|
|
{
|
|
"name": "test_version.txt",
|
|
"server_dir": "/tmp",
|
|
"code": f"{{ {{ {cls.ref_old} }} }}",
|
|
"variable_ids": [(6, 0, [cls.variable_version.id])],
|
|
}
|
|
)
|
|
|
|
def _rename(self):
|
|
"""Rename variable and invalidate caches for records under test."""
|
|
self.variable_version.write({"reference": self.ref_new})
|
|
self.command.invalidate_recordset()
|
|
self.file.invalidate_recordset()
|
|
|
|
def test_false_references_are_ignored(self):
|
|
"""Ignore malformed or non-Jinja references."""
|
|
cmd_plain = self.Command.create(
|
|
{
|
|
"name": "Plain",
|
|
"code": "print(test_version)",
|
|
"variable_ids": [(6, 0, [self.variable_version.id])],
|
|
}
|
|
)
|
|
cmd_bad = self.Command.create(
|
|
{
|
|
"name": "BadBrackets",
|
|
"code": "{test_version}",
|
|
"variable_ids": [(6, 0, [self.variable_version.id])],
|
|
}
|
|
)
|
|
|
|
self._rename()
|
|
cmd_plain.invalidate_recordset()
|
|
cmd_bad.invalidate_recordset()
|
|
|
|
self.assertEqual(cmd_plain.code, "print(test_version)")
|
|
self.assertEqual(cmd_bad.code, "{test_version}")
|
|
|
|
def test_multiple_occurrences_replace_all(self):
|
|
"""Replace all valid Jinja references in one field."""
|
|
code = "A: {{ test_version }}, B: {{ test_version }}, C-end"
|
|
cmd_multi = self.Command.create(
|
|
{
|
|
"name": "Multi",
|
|
"code": code,
|
|
"variable_ids": [(6, 0, [self.variable_version.id])],
|
|
}
|
|
)
|
|
|
|
self._rename()
|
|
cmd_multi.invalidate_recordset()
|
|
actual_ref = self.variable_version.reference
|
|
expected = f"A: {{{{ {actual_ref} }}}}, " f"B: {{{{ {actual_ref} }}}}, C-end"
|
|
self.assertEqual(cmd_multi.code, expected)
|
|
|
|
def test_template_files_updated(self):
|
|
"""Propagate rename in template and generated file."""
|
|
tpl = self.env["cx.tower.file.template"].create(
|
|
{
|
|
"name": "TmpTpl",
|
|
"file_name": "tpl.txt",
|
|
"server_dir": "/tmp",
|
|
"code": "{{ test_version }}",
|
|
"variable_ids": [(6, 0, [self.variable_version.id])],
|
|
}
|
|
)
|
|
tpl_file = self.File.create(
|
|
{
|
|
"name": "from_tpl.txt",
|
|
"server_dir": "/tmp",
|
|
"template_id": tpl.id,
|
|
"code": "{{ test_version }}",
|
|
}
|
|
)
|
|
|
|
self._rename()
|
|
tpl.invalidate_recordset()
|
|
tpl_file.invalidate_recordset()
|
|
|
|
actual_ref = self.variable_version.reference
|
|
expected = f"{{{{ {actual_ref} }}}}"
|
|
self.assertEqual(tpl.code, expected)
|
|
self.assertEqual(tpl_file.code, expected)
|
|
|
|
def test_value_and_plan_line_update(self):
|
|
"""Update value_char and plan line condition."""
|
|
|
|
def patched_mapping(_):
|
|
return {
|
|
"cx.tower.command": ["code", "path"],
|
|
"cx.tower.file": ["code", "server_dir", "name"],
|
|
"cx.tower.file.template": ["code", "server_dir", "file_name"],
|
|
"cx.tower.variable.value": ["value_char"],
|
|
"cx.tower.plan.line": ["condition"],
|
|
}
|
|
|
|
with patch.object(
|
|
type(self.variable_version),
|
|
"_get_propagation_field_mapping",
|
|
patched_mapping,
|
|
):
|
|
val = self.env["cx.tower.variable.value"].create(
|
|
{
|
|
"variable_id": self.variable_version.id,
|
|
"value_char": "hello {{ test_version }} world",
|
|
}
|
|
)
|
|
|
|
pl = self.plan_line_1
|
|
pl.write(
|
|
{
|
|
"variable_ids": [(6, 0, [self.variable_version.id])],
|
|
"condition": "if {{ test_version }} then",
|
|
}
|
|
)
|
|
|
|
self.assertIn(self.variable_version.id, pl.variable_ids.ids)
|
|
|
|
self._rename()
|
|
val.invalidate_recordset()
|
|
pl.invalidate_recordset()
|
|
|
|
actual_ref = self.variable_version.reference
|
|
expected_val = f"hello {{{{ {actual_ref} }}}} world"
|
|
self.assertEqual(val.value_char, expected_val)
|
|
expected_cond = f"if {{{{ {actual_ref} }}}} then"
|
|
self.assertEqual(pl.condition, expected_cond)
|
|
|
|
def test_variable_reference_update(self):
|
|
"""Test variable reference update cascades to dependent models"""
|
|
# 1. Add a variable value to variable_os
|
|
variable_value = self.VariableValue.create(
|
|
{
|
|
"variable_id": self.variable_os.id,
|
|
"value_char": "Ubuntu 20.04",
|
|
"server_id": self.server_test_1.id,
|
|
}
|
|
)
|
|
|
|
# Store original references for comparison
|
|
original_variable_reference = self.variable_os.reference
|
|
original_variable_value_reference = variable_value.reference
|
|
|
|
# 2. Change the reference for variable_os to "awesome_variable"
|
|
self.variable_os.write({"reference": "awesome_variable"})
|
|
|
|
# 3. Verify that references are updated for dependent models
|
|
# Invalidate models to refresh all references
|
|
self.env["cx.tower.variable"].invalidate_model(["reference"])
|
|
self.env["cx.tower.variable.value"].invalidate_model(["reference"])
|
|
|
|
# Check that variable reference was updated
|
|
self.assertEqual(self.variable_os.reference, "awesome_variable")
|
|
self.assertNotEqual(self.variable_os.reference, original_variable_reference)
|
|
|
|
# Check that variable value reference was updated
|
|
# to include the new variable reference
|
|
self.assertIn("awesome_variable", variable_value.reference)
|
|
self.assertNotEqual(variable_value.reference, original_variable_value_reference)
|
|
|
|
# Verify the reference pattern for variable value follows the expected format:
|
|
# <variable_reference>_<model_generic_reference>_<linked_model_generic_reference>_<linked_record_reference> # noqa: E501
|
|
expected_variable_pattern = (
|
|
f"{self.variable_os.reference}_variable_value_server_"
|
|
f"{self.server_test_1.reference}"
|
|
)
|
|
self.assertEqual(variable_value.reference, expected_variable_pattern)
|