1774 lines
65 KiB
Python
1774 lines
65 KiB
Python
# Copyright (C) 2025 Cetmix OÜ
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from unittest.mock import patch
|
|
|
|
from odoo.exceptions import ValidationError
|
|
|
|
from .common_jets import TestTowerJetsCommon
|
|
|
|
|
|
class TestTowerJetTemplateInstall(TestTowerJetsCommon):
|
|
"""
|
|
Test the cx.tower.jet.template.install model methods
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
# Create additional servers for testing
|
|
cls.server_test_2 = cls.Server.create(
|
|
{
|
|
"name": "Test Server 2",
|
|
"reference": "test_server_2",
|
|
"ip_v4_address": "192.168.1.102",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
"os_id": cls.os_debian_10.id,
|
|
}
|
|
)
|
|
cls.server_test_3 = cls.Server.create(
|
|
{
|
|
"name": "Test Server 3",
|
|
"reference": "test_server_3",
|
|
"ip_v4_address": "192.168.1.103",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
"os_id": cls.os_debian_10.id,
|
|
}
|
|
)
|
|
|
|
def test_uninstall_creates_install_record(self):
|
|
"""Test that uninstall creates a new install record with correct data"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create a dummy record to satisfy ensure_one()
|
|
# Note: This is a workaround for ensure_one() in @api.model method
|
|
dummy_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
}
|
|
)
|
|
|
|
# Call uninstall on the dummy record
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
) as mock_process:
|
|
install_record = dummy_record.uninstall(server, template)
|
|
|
|
# Verify install record was created
|
|
self.assertTrue(install_record, "Should return an install record")
|
|
self.assertEqual(
|
|
install_record.jet_template_id,
|
|
template,
|
|
"Install record should reference the template",
|
|
)
|
|
self.assertEqual(
|
|
install_record.server_id,
|
|
server,
|
|
"Install record should reference the server",
|
|
)
|
|
self.assertEqual(
|
|
install_record.action,
|
|
"uninstall",
|
|
"Install record action should be 'uninstall'",
|
|
)
|
|
self.assertEqual(
|
|
install_record.state,
|
|
"processing",
|
|
"Install record state should be 'processing'",
|
|
)
|
|
|
|
# Verify line_ids contains only the template (no dependencies)
|
|
self.assertEqual(
|
|
len(install_record.line_ids),
|
|
1,
|
|
"Should have exactly one line for uninstall",
|
|
)
|
|
line = install_record.line_ids[0]
|
|
self.assertEqual(
|
|
line.jet_template_id,
|
|
template,
|
|
"Line should reference the template",
|
|
)
|
|
self.assertEqual(line.order, 0, "Line order should be 0")
|
|
|
|
# Verify _process_install was called
|
|
mock_process.assert_called_once()
|
|
|
|
def test_uninstall_creates_notification(self):
|
|
"""Test that uninstall sends a notification to the user"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create a dummy record to satisfy ensure_one()
|
|
dummy_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
}
|
|
)
|
|
|
|
# Mock notify_info to verify it's called
|
|
with patch.object(self.env.user.__class__, "notify_info") as mock_notify, patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
):
|
|
dummy_record.uninstall(server, template)
|
|
|
|
# Verify notify_info was called
|
|
self.assertEqual(mock_notify.call_count, 1, "Should call notify_info once")
|
|
|
|
# Verify notification parameters
|
|
call_args = mock_notify.call_args
|
|
self.assertIn("message", call_args.kwargs, "Should have message")
|
|
self.assertIn("title", call_args.kwargs, "Should have title")
|
|
self.assertEqual(
|
|
call_args.kwargs["title"],
|
|
template.name,
|
|
"Notification title should be template name",
|
|
)
|
|
self.assertEqual(
|
|
call_args.kwargs["sticky"],
|
|
False,
|
|
"Notification should not be sticky",
|
|
)
|
|
self.assertIn("action", call_args.kwargs, "Should have action")
|
|
|
|
def test_uninstall_different_template(self):
|
|
"""Test uninstall with a different template"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_odoo
|
|
|
|
# Create a dummy record to satisfy ensure_one()
|
|
dummy_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
}
|
|
)
|
|
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
):
|
|
install_record = dummy_record.uninstall(server, template)
|
|
|
|
self.assertEqual(
|
|
install_record.jet_template_id,
|
|
template,
|
|
"Should uninstall the specified template",
|
|
)
|
|
self.assertEqual(
|
|
install_record.server_id,
|
|
server,
|
|
"Should uninstall on the specified server",
|
|
)
|
|
|
|
def test_uninstall_different_server(self):
|
|
"""Test uninstall with a different server"""
|
|
server = self.server_test_2
|
|
template = self.jet_template_test
|
|
|
|
# Create a dummy record to satisfy ensure_one()
|
|
dummy_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": self.server_test_1.id,
|
|
"action": "install",
|
|
}
|
|
)
|
|
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
):
|
|
install_record = dummy_record.uninstall(server, template)
|
|
|
|
self.assertEqual(
|
|
install_record.server_id,
|
|
server,
|
|
"Should uninstall on the specified server",
|
|
)
|
|
|
|
def test_uninstall_removes_template_from_server_ids(self):
|
|
"""Test that successful uninstallation removes template from server_ids"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# First, add template to server_ids to simulate installed state
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
self.assertIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be in server_ids before uninstall",
|
|
)
|
|
|
|
# Create uninstall record
|
|
uninstall_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "uninstall",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Process uninstallation (without flight plan - direct completion)
|
|
# This simulates the case where there's no flight plan
|
|
uninstall_record.line_ids[0].write({"state": "to_process"})
|
|
uninstall_record.with_context(cetmix_tower_no_commit=True)._process_install()
|
|
|
|
# Verify template was removed from server_ids
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertNotIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be removed from server_ids after successful uninstall",
|
|
)
|
|
|
|
def test_uninstall_does_not_remove_template_on_failure(self):
|
|
"""Test that template is not removed from server_ids if uninstallation fails"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# First, add template to server_ids to simulate installed state
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
self.assertIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be in server_ids before uninstall",
|
|
)
|
|
|
|
# Create uninstall record with a line
|
|
uninstall_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "uninstall",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
uninstall_record.write({"current_line_id": uninstall_record.line_ids[0].id})
|
|
|
|
# Simulate flight plan finishing with failure (exit code != 0)
|
|
uninstall_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(1)
|
|
|
|
# Verify template is still in server_ids (not removed on failure)
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should remain in server_ids after uninstall failure",
|
|
)
|
|
|
|
# ======================
|
|
# Tests for _flight_plan_finished
|
|
# ======================
|
|
|
|
def test_flight_plan_finished_success_install_adds_template_to_server_ids(self):
|
|
"""Test that successful install flight plan adds template to server_ids"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Ensure template is not in server_ids initially
|
|
template.write({"server_ids": [(5, 0, 0)]})
|
|
self.assertNotIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should not be in server_ids before install",
|
|
)
|
|
|
|
# Create install record with a line
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
current_line = install_record.line_ids[0]
|
|
install_record.write({"current_line_id": current_line.id})
|
|
|
|
# Simulate flight plan finishing successfully (exit code 0)
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
) as mock_process:
|
|
install_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(0)
|
|
|
|
# Verify template was added to server_ids
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be added to server_ids after install success",
|
|
)
|
|
|
|
# Verify current line was marked as done (check before clearing)
|
|
current_line.invalidate_recordset(["state"])
|
|
self.assertEqual(
|
|
current_line.state,
|
|
"done",
|
|
"Current line should be marked as done",
|
|
)
|
|
|
|
# Verify current_line_id was cleared
|
|
install_record.invalidate_recordset(["current_line_id"])
|
|
self.assertFalse(
|
|
install_record.current_line_id,
|
|
"current_line_id should be cleared after success",
|
|
)
|
|
|
|
# Verify _process_install was called to continue processing
|
|
mock_process.assert_called_once()
|
|
|
|
def test_flight_plan_finished_success_uninstall_removes_template_from_server_ids(
|
|
self,
|
|
):
|
|
"""
|
|
Test that successful uninstall flight plan
|
|
removes template from server_ids
|
|
"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Add template to server_ids to simulate installed state
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
self.assertIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be in server_ids before uninstall",
|
|
)
|
|
|
|
# Create uninstall record with a line
|
|
uninstall_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "uninstall",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
current_line = uninstall_record.line_ids[0]
|
|
uninstall_record.write({"current_line_id": current_line.id})
|
|
|
|
# Simulate flight plan finishing successfully (exit code 0)
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall._process_install"
|
|
) as mock_process:
|
|
uninstall_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(0)
|
|
|
|
# Verify template was removed from server_ids
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertNotIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should be removed from server_ids after uninstall success",
|
|
)
|
|
|
|
# Verify current line was marked as done (check before clearing)
|
|
current_line.invalidate_recordset(["state"])
|
|
self.assertEqual(
|
|
current_line.state,
|
|
"done",
|
|
"Current line should be marked as done",
|
|
)
|
|
|
|
# Verify current_line_id was cleared
|
|
uninstall_record.invalidate_recordset(["current_line_id"])
|
|
self.assertFalse(
|
|
uninstall_record.current_line_id,
|
|
"current_line_id should be cleared after success",
|
|
)
|
|
|
|
# Verify _process_install was called to continue processing
|
|
mock_process.assert_called_once()
|
|
|
|
def test_flight_plan_finished_failure_marks_line_as_failed(self):
|
|
"""Test that failed flight plan marks current line as failed"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create install record with a line
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
current_line = install_record.line_ids[0]
|
|
install_record.write({"current_line_id": current_line.id})
|
|
|
|
# Simulate flight plan finishing with failure (exit code != 0)
|
|
install_record.with_context(cetmix_tower_no_commit=True)._flight_plan_finished(
|
|
1
|
|
)
|
|
|
|
# Verify current line was marked as failed
|
|
self.assertEqual(
|
|
current_line.state,
|
|
"failed",
|
|
"Current line should be marked as failed",
|
|
)
|
|
|
|
# Verify install record state was set to failed
|
|
self.assertEqual(
|
|
install_record.state,
|
|
"failed",
|
|
"Install record state should be 'failed'",
|
|
)
|
|
|
|
# Verify date_done was set
|
|
self.assertTrue(
|
|
install_record.date_done,
|
|
"date_done should be set on failure",
|
|
)
|
|
|
|
# Verify current_line_id was cleared
|
|
self.assertFalse(
|
|
install_record.current_line_id,
|
|
"current_line_id should be cleared after failure",
|
|
)
|
|
|
|
def test_flight_plan_finished_failure_marks_all_to_process_lines_as_failed(self):
|
|
"""Test that failed flight plan marks all 'to_process' lines as failed"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create install record with multiple lines
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [
|
|
(0, 0, {"jet_template_id": template.id, "order": 0}),
|
|
(0, 0, {"jet_template_id": template.id, "order": 1}),
|
|
(0, 0, {"jet_template_id": template.id, "order": 2}),
|
|
],
|
|
}
|
|
)
|
|
|
|
# Set first line as current and mark others as to_process
|
|
current_line = install_record.line_ids[0]
|
|
other_lines = install_record.line_ids[1:]
|
|
install_record.write({"current_line_id": current_line.id})
|
|
other_lines.write({"state": "to_process"})
|
|
|
|
# Simulate flight plan finishing with failure
|
|
install_record.with_context(cetmix_tower_no_commit=True)._flight_plan_finished(
|
|
1
|
|
)
|
|
|
|
# Verify all 'to_process' lines were marked as failed
|
|
for line in other_lines:
|
|
self.assertEqual(
|
|
line.state,
|
|
"failed",
|
|
"All 'to_process' lines should be marked as failed",
|
|
)
|
|
|
|
def test_flight_plan_finished_failure_sends_notification(self):
|
|
"""Test that failed flight plan sends error notification when enabled"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Enable error notifications
|
|
self.env["ir.config_parameter"].sudo().set_param(
|
|
"cetmix_tower_server.notification_type_error", "sticky"
|
|
)
|
|
|
|
# Create install record with a line
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
install_record.write({"current_line_id": install_record.line_ids[0].id})
|
|
|
|
# Mock notify_danger to verify it's called
|
|
with patch.object(self.env.user.__class__, "notify_danger") as mock_notify:
|
|
install_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(1)
|
|
|
|
# Verify notify_danger was called
|
|
self.assertEqual(
|
|
mock_notify.call_count, 1, "Should call notify_danger once"
|
|
)
|
|
|
|
# Verify notification parameters
|
|
call_args = mock_notify.call_args
|
|
self.assertIn("message", call_args.kwargs, "Should have message")
|
|
self.assertIn("title", call_args.kwargs, "Should have title")
|
|
self.assertEqual(
|
|
call_args.kwargs["title"],
|
|
template.name,
|
|
"Notification title should be template name",
|
|
)
|
|
self.assertEqual(
|
|
call_args.kwargs["sticky"],
|
|
True,
|
|
"Notification should be sticky when configured",
|
|
)
|
|
self.assertIn("action", call_args.kwargs, "Should have action")
|
|
|
|
def test_flight_plan_finished_no_notification_when_disabled(self):
|
|
"""Test that failed flight plan doesn't send notification when disabled"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Disable error notifications
|
|
self.env["ir.config_parameter"].sudo().set_param(
|
|
"cetmix_tower_server.notification_type_error", False
|
|
)
|
|
|
|
# Create install record with a line
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id to simulate flight plan execution
|
|
install_record.write({"current_line_id": install_record.line_ids[0].id})
|
|
|
|
# Mock notify_danger to verify it's NOT called
|
|
with patch.object(self.env.user.__class__, "notify_danger") as mock_notify:
|
|
install_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(1)
|
|
|
|
# Verify notify_danger was NOT called
|
|
mock_notify.assert_not_called()
|
|
|
|
def test_flight_plan_finished_no_current_line_id_returns_early(self):
|
|
"""Test that _flight_plan_finished returns early if no current_line_id"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create install record without current_line_id
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "processing",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Ensure current_line_id is False
|
|
self.assertFalse(install_record.current_line_id)
|
|
|
|
# Mock logger to verify warning is logged
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install._logger.warning"
|
|
) as mock_warning:
|
|
install_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(0)
|
|
|
|
# Verify warning was logged
|
|
mock_warning.assert_called_once()
|
|
|
|
# Verify template was not modified (early return)
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertNotIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should not be modified when no current_line_id",
|
|
)
|
|
|
|
def test_flight_plan_finished_wrong_state_returns_early(self):
|
|
"""Test that _flight_plan_finished returns early if state is not 'processing'"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Create install record in 'done' state
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
"action": "install",
|
|
"state": "done",
|
|
"line_ids": [(0, 0, {"jet_template_id": template.id, "order": 0})],
|
|
}
|
|
)
|
|
|
|
# Set current_line_id
|
|
install_record.write({"current_line_id": install_record.line_ids[0].id})
|
|
|
|
# Mock logger to verify warning is logged
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install._logger.warning"
|
|
) as mock_warning:
|
|
install_record.with_context(
|
|
cetmix_tower_no_commit=True
|
|
)._flight_plan_finished(0)
|
|
|
|
# Verify warning was logged
|
|
mock_warning.assert_called_once()
|
|
|
|
# Verify template was not modified (early return)
|
|
template.invalidate_recordset(["server_ids"])
|
|
self.assertNotIn(
|
|
server.id,
|
|
template.server_ids.ids,
|
|
"Template should not be modified when state is not 'processing'",
|
|
)
|
|
|
|
# ======================
|
|
# Tests for _is_installation_needed (from JetTemplate model)
|
|
# ======================
|
|
|
|
def test_is_installation_needed_server_already_installed(self):
|
|
"""Test _is_installation_needed when server is already installed"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Add server to template's installed servers
|
|
self.jet_template_test.server_ids = [(4, server.id)]
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertFalse(result, "Should return False when server is already installed")
|
|
|
|
def test_is_installation_needed_installation_in_progress_processing(self):
|
|
"""Test _is_installation_needed when installation is in processing state"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create an installation record in processing state
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertFalse(
|
|
result, "Should return False when installation is in processing state"
|
|
)
|
|
|
|
def test_is_installation_needed_installation_in_progress_to_process(self):
|
|
"""Test _is_installation_needed when installation is in to_process state"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create an installation record in to_process state
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "to_process",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertFalse(
|
|
result, "Should return False when installation is in to_process state"
|
|
)
|
|
|
|
def test_is_installation_needed_installation_completed(self):
|
|
"""Test _is_installation_needed when installation is completed"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create an installation record in installed state
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertTrue(
|
|
result,
|
|
"Should return True when installation is completed (not in progress)",
|
|
)
|
|
|
|
def test_is_installation_needed_installation_failed(self):
|
|
"""Test _is_installation_needed when installation failed"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create an installation record in failed state
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "failed",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "failed",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertTrue(result, "Should return True when installation failed")
|
|
|
|
def test_is_installation_needed_multiple_installations(self):
|
|
"""Test _is_installation_needed with multiple installation records"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create multiple installation records
|
|
install_record1 = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
install_record2 = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Create install lines
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record1.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record2.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertFalse(
|
|
result, "Should return False when any installation is in progress"
|
|
)
|
|
|
|
def test_is_installation_needed_different_servers(self):
|
|
"""Test _is_installation_needed with different servers"""
|
|
# pylint: disable=protected-access
|
|
# Create two servers
|
|
server1 = self.Server.create(
|
|
{
|
|
"name": "Test Server 1",
|
|
"reference": "test_server_1",
|
|
"ip_v4_address": "192.168.1.101",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
server2 = self.Server.create(
|
|
{
|
|
"name": "Test Server 2",
|
|
"reference": "test_server_2",
|
|
"ip_v4_address": "192.168.1.102",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Add server1 to template's installed servers
|
|
self.jet_template_test.server_ids = [(4, server1.id)]
|
|
|
|
# Create installation record for server2
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server2.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Check server1 (already installed)
|
|
result1 = self.jet_template_test._is_installation_needed(server1)
|
|
self.assertFalse(result1, "Should return False for server1 (already installed)")
|
|
|
|
# Check server2 (installation in progress)
|
|
result2 = self.jet_template_test._is_installation_needed(server2)
|
|
self.assertFalse(
|
|
result2, "Should return False for server2 (installation in progress)"
|
|
)
|
|
|
|
def test_is_installation_needed_no_installations(self):
|
|
"""Test _is_installation_needed when no installation records exist"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertTrue(result, "Should return True when no installation records exist")
|
|
|
|
def test_is_installation_needed_mixed_states(self):
|
|
"""Test _is_installation_needed with mixed installation states"""
|
|
# pylint: disable=protected-access
|
|
# Create a server
|
|
server = self.Server.create(
|
|
{
|
|
"name": "Test Server",
|
|
"reference": "test_server",
|
|
"ip_v4_address": "192.168.1.100",
|
|
"ssh_username": "admin",
|
|
"ssh_password": "password",
|
|
"ssh_auth_mode": "p",
|
|
}
|
|
)
|
|
|
|
# Create installation records with different states
|
|
install_record1 = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
install_record2 = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "failed",
|
|
}
|
|
)
|
|
|
|
# Create install lines
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record1.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "done",
|
|
}
|
|
)
|
|
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record2.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "failed",
|
|
}
|
|
)
|
|
|
|
result = self.jet_template_test._is_installation_needed(server)
|
|
self.assertTrue(
|
|
result, "Should return True when all installations are completed or failed"
|
|
)
|
|
|
|
# ======================
|
|
# Tests for install_on_servers (from JetTemplate model)
|
|
# ======================
|
|
|
|
def test_install_on_servers_no_dependencies(self):
|
|
"""Test install_on_servers with template that has no dependencies"""
|
|
# pylint: disable=protected-access
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Call install method directly with cetmix_tower_no_commit context
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify installation record was created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create exactly one installation record"
|
|
)
|
|
|
|
def test_install_on_servers_already_installed(self):
|
|
"""Test install_on_servers when template is already installed"""
|
|
# pylint: disable=protected-access
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Add server to template's installed servers
|
|
self.jet_template_test.server_ids = [(4, server.id)]
|
|
|
|
# Call install method - should skip since already installed
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify no new installation record was created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records),
|
|
0,
|
|
"Should not create installation record when already installed",
|
|
)
|
|
|
|
def test_install_on_servers_installation_in_progress(self):
|
|
"""Test install_on_servers when installation is already in progress"""
|
|
# pylint: disable=protected-access
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Create installation record in progress
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Create install line
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Call install method - should skip since installation in progress
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify no additional installation record was created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records),
|
|
1,
|
|
"Should not create additional installation record",
|
|
)
|
|
|
|
def test_install_on_servers_dependency_satisfaction(self):
|
|
"""Test install_on_servers dependency satisfaction logic"""
|
|
# pylint: disable=protected-access
|
|
# Use class-level dependency hierarchy
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Install Tower Core on server
|
|
self.jet_template_tower_core.server_ids = [(4, server.id)]
|
|
|
|
# Call install method directly
|
|
self.jet_template_postgres.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify installation record was created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_postgres.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create exactly one installation record"
|
|
)
|
|
|
|
def test_install_on_servers_multiple_servers(self):
|
|
"""Test install_on_servers with multiple servers"""
|
|
# pylint: disable=protected-access
|
|
# Use existing servers from class setup
|
|
server1 = self.server_test_1
|
|
server2 = self.server_test_2
|
|
|
|
# Add server1 to template's installed servers
|
|
self.jet_template_test.server_ids = [(4, server1.id)]
|
|
|
|
# Call install method directly
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers([server1, server2])
|
|
|
|
# Verify installation record was created only for server2
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server2.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create installation record for server2"
|
|
)
|
|
|
|
# Verify no installation record for server1 (already installed)
|
|
install_records_server1 = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server1.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records_server1),
|
|
0,
|
|
"Should not create installation record for server1 (already installed)",
|
|
)
|
|
|
|
def test_install_on_servers_empty_server_list(self):
|
|
"""Test install_on_servers with empty server list"""
|
|
# pylint: disable=protected-access
|
|
# Call install method with empty list
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers([])
|
|
|
|
# Verify no installation record was created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[("jet_template_id", "=", self.jet_template_test.id)]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records),
|
|
0,
|
|
"Should not create installation record with empty server list",
|
|
)
|
|
|
|
def test_install_on_servers_mixed_server_states(self):
|
|
"""Test install_on_servers with mixed server states"""
|
|
# Use existing servers from class setup
|
|
server1 = self.server_test_1
|
|
server2 = self.server_test_2
|
|
server3 = self.server_test_3
|
|
|
|
# Server1: Already installed
|
|
self.jet_template_test.server_ids = [(4, server1.id)]
|
|
|
|
# Server2: Installation in progress
|
|
install_record = self.JetTemplateInstall.create(
|
|
{
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"server_id": server2.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
self.JetTemplateInstallLine.create(
|
|
{
|
|
"jet_template_install_id": install_record.id,
|
|
"jet_template_id": self.jet_template_test.id,
|
|
"state": "processing",
|
|
}
|
|
)
|
|
|
|
# Server3: Not installed (should trigger installation)
|
|
|
|
# Call install method directly
|
|
self.jet_template_test.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers([server1, server2, server3])
|
|
|
|
# Verify installation record was created only for server3
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_test.id),
|
|
("server_id", "=", server3.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create installation record for server3"
|
|
)
|
|
|
|
def test_install_on_servers_odoo_scenario_complete_installation(self):
|
|
"""Test complete Odoo installation scenario"""
|
|
# Use class-level dependency hierarchy
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Call install for Odoo template
|
|
self.jet_template_odoo.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify installation log is created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_odoo.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create exactly one installation record"
|
|
)
|
|
|
|
install_record = install_records[0]
|
|
self.assertEqual(
|
|
install_record.jet_template_id,
|
|
self.jet_template_odoo,
|
|
"Installation should be for Odoo template",
|
|
)
|
|
self.assertEqual(
|
|
install_record.server_id, server, "Installation should be on test server"
|
|
)
|
|
|
|
# Verify all dependencies are in installation log lines
|
|
install_lines = install_record.line_ids.sorted("order")
|
|
self.assertEqual(
|
|
len(install_lines),
|
|
5,
|
|
"Should have 5 installation lines (Odoo + 4 dependencies)",
|
|
)
|
|
|
|
# Verify all expected templates are included
|
|
template_ids = install_lines.mapped("jet_template_id.id")
|
|
expected_template_ids = [
|
|
self.jet_template_tower_core.id,
|
|
self.jet_template_docker.id,
|
|
self.jet_template_postgres.id,
|
|
self.jet_template_nginx.id,
|
|
self.jet_template_odoo.id,
|
|
]
|
|
self.assertEqual(
|
|
set(template_ids),
|
|
set(expected_template_ids),
|
|
"All expected templates should be in installation lines",
|
|
)
|
|
|
|
# Verify correct order: Odoo first, then Nginx/Postgres (either order),
|
|
# then Docker, then Tower Core.
|
|
odoo_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_odoo
|
|
)
|
|
self.assertEqual(odoo_line.order, 0, "Odoo should be first (order 0)")
|
|
|
|
# Verify dependency relationships are correct
|
|
# Odoo should be first (main template)
|
|
odoo_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_odoo
|
|
)
|
|
self.assertEqual(len(odoo_line), 1, "Should have exactly one Odoo line")
|
|
self.assertEqual(odoo_line.order, 0, "Odoo should be first (order 0)")
|
|
|
|
# Nginx and Postgres should be second and third (direct dependencies of Odoo)
|
|
nginx_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_nginx
|
|
)
|
|
postgres_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_postgres
|
|
)
|
|
self.assertEqual(len(nginx_line), 1, "Should have exactly one Nginx line")
|
|
self.assertEqual(len(postgres_line), 1, "Should have exactly one Postgres line")
|
|
self.assertIn(nginx_line.order, [1, 2], "Nginx should be order 1 or 2")
|
|
self.assertIn(postgres_line.order, [1, 2], "Postgres should be order 1 or 2")
|
|
self.assertNotEqual(
|
|
nginx_line.order,
|
|
postgres_line.order,
|
|
"Nginx and Postgres should have different orders",
|
|
)
|
|
|
|
# Docker should be fourth (dependency of both Postgres and Nginx)
|
|
docker_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_docker
|
|
)
|
|
self.assertEqual(len(docker_line), 1, "Should have exactly one Docker line")
|
|
self.assertEqual(docker_line.order, 3, "Docker should be fourth (order 3)")
|
|
|
|
# Tower Core should be last (dependency of Docker)
|
|
tower_core_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_tower_core
|
|
)
|
|
self.assertEqual(
|
|
len(tower_core_line), 1, "Should have exactly one Tower Core line"
|
|
)
|
|
self.assertEqual(
|
|
tower_core_line.order, 4, "Tower Core should be last (order 4)"
|
|
)
|
|
|
|
def test_install_on_servers_woocommerce_odoo_scenario(self):
|
|
"""Test install_on_servers with WooCommerce with Odoo scenario"""
|
|
# pylint: disable=protected-access
|
|
# Use existing server from common.py
|
|
server = self.server_test_1
|
|
|
|
# Call install for WooCommerce with Odoo template
|
|
self.jet_template_woocommerce_odoo.with_context(
|
|
cetmix_tower_no_commit=True
|
|
).install_on_servers(server)
|
|
|
|
# Verify installation log is created
|
|
install_records = self.JetTemplateInstall.search(
|
|
[
|
|
("jet_template_id", "=", self.jet_template_woocommerce_odoo.id),
|
|
("server_id", "=", server.id),
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
len(install_records), 1, "Should create exactly one installation record"
|
|
)
|
|
|
|
install_record = install_records[0]
|
|
self.assertEqual(
|
|
install_record.jet_template_id,
|
|
self.jet_template_woocommerce_odoo,
|
|
"Installation should be for WooCommerce with Odoo template",
|
|
)
|
|
self.assertEqual(
|
|
install_record.server_id, server, "Installation should be on test server"
|
|
)
|
|
|
|
# Verify all dependencies are in installation log lines
|
|
install_lines = install_record.line_ids.sorted("order")
|
|
# Should have 8 installation lines:
|
|
# WooCommerce + 7 dependencies
|
|
# WordPress, Odoo, MariaDB, Postgres, Nginx, Docker, Tower Core
|
|
self.assertEqual(
|
|
len(install_lines),
|
|
8,
|
|
"Should have 8 installation lines (WooCommerce + 7 dependencies)",
|
|
)
|
|
|
|
# Verify topological constraints:
|
|
# WooCommerce first (root), Tower Core last (deepest leaf),
|
|
# Docker before Nginx/Postgres/MariaDB, etc.
|
|
wc_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_woocommerce_odoo
|
|
)
|
|
self.assertEqual(wc_line.order, 0, "WooCommerce should be first (order 0)")
|
|
|
|
tc_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_tower_core
|
|
)
|
|
self.assertEqual(tc_line.order, 7, "Tower Core should be last (order 7)")
|
|
|
|
docker_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_docker
|
|
)
|
|
nginx_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_nginx
|
|
)
|
|
postgres_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_postgres
|
|
)
|
|
mariadb_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_mariadb
|
|
)
|
|
odoo_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_odoo
|
|
)
|
|
wp_line = install_lines.filtered(
|
|
lambda line: line.jet_template_id == self.jet_template_wordpress
|
|
)
|
|
|
|
self.assertGreater(
|
|
tc_line.order,
|
|
docker_line.order,
|
|
"Tower Core must have higher order than Docker (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
docker_line.order,
|
|
nginx_line.order,
|
|
"Docker must have higher order than Nginx (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
docker_line.order,
|
|
postgres_line.order,
|
|
"Docker must have higher order than Postgres (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
docker_line.order,
|
|
mariadb_line.order,
|
|
"Docker must have higher order than MariaDB (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
nginx_line.order,
|
|
odoo_line.order,
|
|
"Nginx must have higher order than Odoo (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
postgres_line.order,
|
|
odoo_line.order,
|
|
"Postgres must have higher order than Odoo (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
nginx_line.order,
|
|
wp_line.order,
|
|
"Nginx must have higher order than WordPress (installed first)",
|
|
)
|
|
self.assertGreater(
|
|
mariadb_line.order,
|
|
wp_line.order,
|
|
"MariaDB must have higher order than WordPress (installed first)",
|
|
)
|
|
|
|
# Verify all expected templates are included
|
|
template_ids = install_lines.mapped("jet_template_id.id")
|
|
expected_template_ids = [
|
|
self.jet_template_tower_core.id,
|
|
self.jet_template_docker.id,
|
|
self.jet_template_mariadb.id,
|
|
self.jet_template_postgres.id,
|
|
self.jet_template_nginx.id,
|
|
self.jet_template_wordpress.id,
|
|
self.jet_template_odoo.id,
|
|
self.jet_template_woocommerce_odoo.id,
|
|
]
|
|
self.assertEqual(
|
|
set(template_ids),
|
|
set(expected_template_ids),
|
|
"All expected templates should be in installation lines",
|
|
)
|
|
|
|
# ======================
|
|
# Tests for uninstall_from_servers (from JetTemplate model)
|
|
# ======================
|
|
|
|
def test_uninstall_from_servers_template_not_installed(self):
|
|
"""Test uninstall_from_servers when template is not installed"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Ensure template is not installed
|
|
template.write({"server_ids": [(5, 0, 0)]})
|
|
|
|
# Should raise ValidationError when raise_if_not_possible=True
|
|
with self.assertRaises(ValidationError) as context:
|
|
template.uninstall_from_servers(server, raise_if_not_possible=True)
|
|
|
|
error_message = str(context.exception)
|
|
self.assertIn("not installed", error_message.lower())
|
|
self.assertIn(template.name, error_message)
|
|
self.assertIn(server.name, error_message)
|
|
|
|
def test_uninstall_from_servers_template_not_installed_warning(self):
|
|
"""Test uninstall_from_servers shows warning when template is not installed"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Ensure template is not installed
|
|
template.write({"server_ids": [(5, 0, 0)]})
|
|
|
|
# Mock notify_warning to verify it's called
|
|
with patch.object(self.env.user.__class__, "notify_warning") as mock_notify:
|
|
template.uninstall_from_servers(server, raise_if_not_possible=False)
|
|
|
|
# Verify notify_warning was called
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
self.assertIn("message", call_args.kwargs)
|
|
self.assertIn("not installed", call_args.kwargs["message"].lower())
|
|
|
|
def test_uninstall_from_servers_jets_still_exist(self):
|
|
"""Test uninstall_from_servers when jets still exist on server"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Install template on server
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Create a jet on the server
|
|
self.Jet.create(
|
|
{
|
|
"name": "Test Jet Uninstall Still Exist",
|
|
"reference": "test_jet_uninstall_still_exist",
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
}
|
|
)
|
|
|
|
# Should raise ValidationError when raise_if_not_possible=True
|
|
with self.assertRaises(ValidationError) as context:
|
|
template.uninstall_from_servers(server, raise_if_not_possible=True)
|
|
|
|
error_message = str(context.exception)
|
|
self.assertIn("still jets", error_message.lower())
|
|
self.assertIn(template.name, error_message)
|
|
self.assertIn(server.name, error_message)
|
|
|
|
def test_uninstall_from_servers_jets_still_exist_warning(self):
|
|
"""Test uninstall_from_servers shows warning when jets still exist"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Install template on server
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Create a jet on the server
|
|
self.Jet.create(
|
|
{
|
|
"name": "Test Jet Uninstall Still Exist Warning",
|
|
"reference": "test_jet_uninstall_still_exist_warning",
|
|
"jet_template_id": template.id,
|
|
"server_id": server.id,
|
|
}
|
|
)
|
|
|
|
# Mock notify_warning to verify it's called
|
|
with patch.object(self.env.user.__class__, "notify_warning") as mock_notify:
|
|
template.uninstall_from_servers(server, raise_if_not_possible=False)
|
|
|
|
# Verify notify_warning was called
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
self.assertIn("message", call_args.kwargs)
|
|
self.assertIn("still jets", call_args.kwargs["message"].lower())
|
|
|
|
def test_uninstall_from_servers_dependent_templates_installed(self):
|
|
"""Test uninstall_from_servers when dependent templates are installed"""
|
|
server = self.server_test_1
|
|
# Use postgres template which depends on docker
|
|
base_template = self.jet_template_docker
|
|
dependent_template = self.jet_template_postgres
|
|
|
|
# Install both templates on server
|
|
base_template.write({"server_ids": [(4, server.id)]})
|
|
dependent_template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Verify dependency exists
|
|
self.assertTrue(
|
|
dependent_template.template_requires_ids.filtered(
|
|
lambda dep: dep.template_required_id == base_template
|
|
),
|
|
"Postgres should depend on Docker",
|
|
)
|
|
|
|
# Should raise ValidationError when raise_if_not_possible=True
|
|
with self.assertRaises(ValidationError) as context:
|
|
base_template.uninstall_from_servers(server, raise_if_not_possible=True)
|
|
|
|
error_message = str(context.exception)
|
|
self.assertIn("depend", error_message.lower())
|
|
self.assertIn(base_template.name, error_message)
|
|
self.assertIn(server.name, error_message)
|
|
|
|
def test_uninstall_from_servers_dependent_templates_installed_warning(self):
|
|
"""
|
|
Test uninstall_from_servers shows warning
|
|
when dependent templates are installed
|
|
"""
|
|
server = self.server_test_1
|
|
# Use postgres template which depends on docker
|
|
base_template = self.jet_template_docker
|
|
dependent_template = self.jet_template_postgres
|
|
|
|
# Install both templates on server
|
|
base_template.write({"server_ids": [(4, server.id)]})
|
|
dependent_template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Mock notify_warning to verify it's called
|
|
with patch.object(self.env.user.__class__, "notify_warning") as mock_notify:
|
|
base_template.uninstall_from_servers(server, raise_if_not_possible=False)
|
|
|
|
# Verify notify_warning was called
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
self.assertIn("message", call_args.kwargs)
|
|
self.assertIn("depend", call_args.kwargs["message"].lower())
|
|
|
|
def test_uninstall_from_servers_dependent_templates_not_installed(self):
|
|
"""
|
|
Test uninstall_from_servers succeeds
|
|
when dependent templates are not installed
|
|
"""
|
|
server = self.server_test_1
|
|
# Use docker template
|
|
base_template = self.jet_template_docker
|
|
|
|
# Install only base template on server (not the dependent one)
|
|
base_template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Mock uninstall to verify it's called
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall.uninstall"
|
|
) as mock_uninstall:
|
|
base_template.uninstall_from_servers(server, raise_if_not_possible=True)
|
|
|
|
# Verify uninstall was called
|
|
mock_uninstall.assert_called_once_with(
|
|
server=server, template=base_template
|
|
)
|
|
|
|
def test_uninstall_from_servers_success(self):
|
|
"""Test successful uninstall_from_servers"""
|
|
server = self.server_test_1
|
|
template = self.jet_template_test
|
|
|
|
# Clean up any existing jets for this template/server combination
|
|
existing_jets = server.jet_ids.filtered(
|
|
lambda jet: jet.jet_template_id == template
|
|
)
|
|
if existing_jets:
|
|
existing_jets.unlink()
|
|
|
|
# Install template on server
|
|
template.write({"server_ids": [(4, server.id)]})
|
|
|
|
# Ensure no jets exist
|
|
self.assertFalse(
|
|
server.jet_ids.filtered(lambda jet: jet.jet_template_id == template),
|
|
"No jets should exist for this template",
|
|
)
|
|
|
|
# Mock uninstall to verify it's called
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall.uninstall"
|
|
) as mock_uninstall:
|
|
template.uninstall_from_servers(server, raise_if_not_possible=True)
|
|
|
|
# Verify uninstall was called
|
|
mock_uninstall.assert_called_once_with(server=server, template=template)
|
|
|
|
def test_uninstall_from_servers_multiple_servers(self):
|
|
"""Test uninstall_from_servers with multiple servers"""
|
|
server1 = self.server_test_1
|
|
server2 = self.server_test_2
|
|
template = self.jet_template_test
|
|
|
|
# Clean up any existing jets for this template on both servers
|
|
existing_jets_1 = server1.jet_ids.filtered(
|
|
lambda jet: jet.jet_template_id == template
|
|
)
|
|
if existing_jets_1:
|
|
existing_jets_1.unlink()
|
|
existing_jets_2 = server2.jet_ids.filtered(
|
|
lambda jet: jet.jet_template_id == template
|
|
)
|
|
if existing_jets_2:
|
|
existing_jets_2.unlink()
|
|
|
|
# Ensure no dependent templates are installed on these servers
|
|
# Remove any templates that depend on this template from both servers
|
|
for server in [server1, server2]:
|
|
dependent_templates = server.jet_template_ids.filtered(
|
|
lambda t: t.template_requires_ids.filtered(
|
|
lambda dep: dep.template_required_id == template
|
|
)
|
|
)
|
|
if dependent_templates:
|
|
# Remove server from dependent template's server_ids
|
|
for dep_template in dependent_templates:
|
|
dep_template.write({"server_ids": [(3, server.id)]})
|
|
|
|
# Install template on both servers
|
|
template.write({"server_ids": [(4, server1.id), (4, server2.id)]})
|
|
|
|
# Mock uninstall to verify it's called for both servers
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall.uninstall"
|
|
) as mock_uninstall:
|
|
template.uninstall_from_servers(
|
|
[server1, server2], raise_if_not_possible=True
|
|
)
|
|
|
|
# Verify uninstall was called twice (once per server)
|
|
self.assertEqual(mock_uninstall.call_count, 2)
|
|
# Verify both servers were called
|
|
call_args_list = mock_uninstall.call_args_list
|
|
servers_called = [call[1]["server"] for call in call_args_list]
|
|
self.assertIn(server1, servers_called)
|
|
self.assertIn(server2, servers_called)
|
|
|
|
def test_uninstall_from_servers_mixed_validation_states(self):
|
|
"""Test uninstall_from_servers with mixed server validation states"""
|
|
server1 = self.server_test_1
|
|
server2 = self.server_test_2
|
|
server3 = self.server_test_3
|
|
template = self.jet_template_test
|
|
|
|
# Server1: Template not installed
|
|
template.write({"server_ids": [(5, 0, 0)]})
|
|
|
|
# Server2: Jets still exist
|
|
template.write({"server_ids": [(4, server2.id)]})
|
|
self.Jet.create(
|
|
{
|
|
"name": "Test Jet Mixed Validation Server2",
|
|
"reference": "test_jet_mixed_validation_server2",
|
|
"jet_template_id": template.id,
|
|
"server_id": server2.id,
|
|
}
|
|
)
|
|
|
|
# Server3: Valid for uninstallation
|
|
template.write({"server_ids": [(4, server3.id)]})
|
|
|
|
# Mock uninstall and notify_warning
|
|
with patch(
|
|
"odoo.addons.cetmix_tower_server.models.cx_tower_jet_template_install"
|
|
".CxTowerJetTemplateInstall.uninstall"
|
|
) as mock_uninstall, patch.object(
|
|
self.env.user.__class__, "notify_warning"
|
|
) as mock_notify:
|
|
template.uninstall_from_servers(
|
|
[server1, server2, server3], raise_if_not_possible=False
|
|
)
|
|
|
|
# Verify warnings were shown for server1 and server2
|
|
self.assertEqual(mock_notify.call_count, 2)
|
|
|
|
# Verify uninstall was called only for server3
|
|
mock_uninstall.assert_called_once_with(server=server3, template=template)
|