diff --git a/spp_base/__init__.py b/spp_base/__init__.py index c4ccea794..b4635efae 100644 --- a/spp_base/__init__.py +++ b/spp_base/__init__.py @@ -1,3 +1,4 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. from . import models +from .tests import test_model_unique_id diff --git a/spp_base/tests/__init__.py b/spp_base/tests/__init__.py index 747b628e6..9f662561a 100644 --- a/spp_base/tests/__init__.py +++ b/spp_base/tests/__init__.py @@ -1,3 +1,4 @@ # Part of OpenSPP Registry. See LICENSE file for full copyright and licensing details. from . import test_top_up_id +from . import test_unique_id diff --git a/spp_base/tests/test_model_unique_id.py b/spp_base/tests/test_model_unique_id.py new file mode 100644 index 000000000..9b0276cde --- /dev/null +++ b/spp_base/tests/test_model_unique_id.py @@ -0,0 +1,19 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class SppUniqueIdTest(models.Model): + _name = "spp.unique.id.test" + _inherit = "spp.unique.id" + _description = "Test Model for spp.unique.id" + _log_access = False # Avoid creating unnecessary log entries + + name = fields.Char("Name") + create_date = fields.Datetime("Creation Date", default=fields.Datetime.now) + + def _get_spp_id_prefix(self): + return "TEST" + + def _get_match_spp_id_pattern(self): + return r"^TEST_[2-9A-HJ-NP-Z]{8}$" diff --git a/spp_base/tests/test_unique_id.py b/spp_base/tests/test_unique_id.py new file mode 100644 index 000000000..40f9ce787 --- /dev/null +++ b/spp_base/tests/test_unique_id.py @@ -0,0 +1,61 @@ +# Part of OpenSPP Registry. See LICENSE file for full copyright and licensing details. +import re + +from psycopg2 import IntegrityError + +from odoo.exceptions import ValidationError +from odoo.tests import TransactionCase +from odoo.tools import mute_logger + + +class TestSppUniqueID(TransactionCase): + @mute_logger("py.warnings") + def test_01_spp_id_generation_and_format(self): + """Test that spp_id is generated correctly on creation.""" + test_record = self.env["spp.unique.id.test"].create({"name": "Test Record 1"}) + self.assertTrue(test_record.spp_id, "spp_id should be generated on creation.") + self.assertTrue( + test_record.spp_id.startswith("TEST_"), + "spp_id should start with the correct prefix.", + ) + + # Check format using regex + pattern = re.compile(r"^TEST_[2-9A-HJ-NP-Z]{8}$") + self.assertIsNotNone(pattern.match(test_record.spp_id), "spp_id has an invalid format.") + + @mute_logger("odoo.sql_db") + def test_02_spp_id_uniqueness(self): + """Test the SQL constraint for spp_id uniqueness.""" + test_record_1 = self.env["spp.unique.id.test"].create({"name": "Test Record 2"}) + self.assertTrue(test_record_1.spp_id) + + with self.assertRaises(IntegrityError), self.cr.savepoint(): + self.env["spp.unique.id.test"].create({"name": "Test Record 3", "spp_id": test_record_1.spp_id}) + + @mute_logger("py.warnings") + def test_03_spp_id_format_constraint_invalid_prefix(self): + """Test the python constraint on spp_id with an invalid prefix.""" + test_record = self.env["spp.unique.id.test"].new({"name": "Invalid Prefix", "spp_id": "FAIL_ABCDE123"}) + with self.assertRaisesRegex(ValidationError, "Unique ID is not following correct format!"): + test_record._check_spp_id() + + @mute_logger("py.warnings") + def test_04_spp_id_format_constraint_invalid_chars(self): + """Test the python constraint on spp_id with forbidden characters.""" + test_record = self.env["spp.unique.id.test"].new({"name": "Invalid Chars", "spp_id": "TEST_ABCDE123"}) + with self.assertRaisesRegex(ValidationError, "Unique ID is not following correct format!"): + test_record._check_spp_id() + + @mute_logger("py.warnings") + def test_05_spp_id_not_recomputed(self): + """Test that spp_id is not recomputed on subsequent writes.""" + test_record = self.env["spp.unique.id.test"].create({"name": "Initial Name"}) + original_spp_id = test_record.spp_id + self.assertTrue(original_spp_id) + + test_record.write({"name": "Updated Name"}) + self.assertEqual( + test_record.spp_id, + original_spp_id, + "spp_id should not change on update.", + ) diff --git a/spp_change_request/__init__.py b/spp_change_request/__init__.py index c4ccea794..e6052aee3 100644 --- a/spp_change_request/__init__.py +++ b/spp_change_request/__init__.py @@ -1,3 +1,4 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. from . import models +from .tests import test_models diff --git a/spp_change_request/tests/common.py b/spp_change_request/tests/common.py deleted file mode 100644 index ae82f5ed1..000000000 --- a/spp_change_request/tests/common.py +++ /dev/null @@ -1,53 +0,0 @@ -from unittest.mock import patch - -from odoo.tests import TransactionCase - - -class Common(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._test_individual_1 = cls._create_registrant({"name": "Liu Bei"}) - cls._test_individual_2 = cls._create_registrant({"name": "Guan Yu"}) - cls._test_individual_3 = cls._create_registrant({"name": "Zhang Fei"}) - cls._test_group = cls._create_registrant( - { - "name": "Shu clan", - "is_group": True, - "group_membership_ids": [ - ( - 0, - 0, - { - "individual": cls._test_individual_1.id, - "kind": [ - ( - 4, - cls.env.ref("g2p_registry_membership.group_membership_kind_head").id, - ) - ], - }, - ), - (0, 0, {"individual": cls._test_individual_2.id}), - (0, 0, {"individual": cls._test_individual_3.id}), - ], - } - ) - - @classmethod - def _create_registrant(cls, vals): - cls.assertTrue(isinstance(vals, dict), "Return vals should be a dict!") - vals.update({"is_registrant": True}) - return cls.env["res.partner"].create(vals) - - @classmethod - @patch("odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase._selection_request_type_ref_id") - def _create_change_request(self, mock_request_type_selection): - mock_request_type_selection.return_value = [("test.request.type", "Test Request Type")] - mock_request_type_selection.__name__ = "_mocked__selection_request_type_ref_id" - return self.env["spp.change.request"].create( - { - "name": "Test Request", - "request_type": "test.request.type", - } - ) diff --git a/spp_change_request/tests/test_change_requests.py b/spp_change_request/tests/test_change_requests.py index 16ca2f570..d9b46280b 100644 --- a/spp_change_request/tests/test_change_requests.py +++ b/spp_change_request/tests/test_change_requests.py @@ -1,143 +1,167 @@ +import logging from unittest.mock import patch -from odoo import fields -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError +from odoo.tests.common import TransactionCase -from .common import Common +_logger = logging.getLogger(__name__) -class TestChangeRequests(Common): +class TestChangeRequestBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - cls._test_change_request = cls._create_change_request() - def test_01_create_change_request(self): - self.assertEqual( - self._test_change_request.assign_to_id, - self.env.user, - "Creating user should be default assignee!", - ) - - def test_02_unlink_raise_error_cr(self): - with self.assertRaisesRegex(UserError, "Only draft change requests can be deleted by its creator."): - self._test_change_request.with_user(2).unlink() - self._test_change_request.state = "pending" - with self.assertRaisesRegex(UserError, "Only draft change requests can be deleted by its creator."): - self._test_change_request.unlink() - - def test_03_unlink_cr(self): - self._test_change_request.unlink() - remaining_change_request = self.env["spp.change.request"].search([("request_type", "=", "request_type")]) - self.assertCountEqual( - remaining_change_request.ids, - [], - "Draft change request should unlinkable by its creator!", + # Create test users + cls.user_admin = cls.env.ref("base.user_admin") + cls.user_demo = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user", + "password": "test_password", + } ) - def test_04_compute_applicant_id_domain_cr(self): - self.assertEqual( - self._test_change_request.applicant_id_domain, - [("id", "=", 0)], - "Without registrant, applicant selections should not be available!", + # Create test registrants + cls.registrant_1 = cls.env["res.partner"].create( + { + "name": "Test Partner 1", + "phone": "+639171234567", + "is_registrant": True, + "country_id": cls.env.ref("base.ph").id, + } ) - self._test_change_request.registrant_id = self._test_group - self.assertEqual( - self._test_change_request.applicant_id_domain, - [ - ( - "id", - "in", - self._test_change_request.registrant_id.group_membership_ids.individual.ids, - ) - ], - "With registrant, applicant selection should be available!", + cls.registrant_2 = cls.env["res.partner"].create( + { + "name": "Test Partner 2", + "phone": "+639171234568", + "is_registrant": True, + "is_group": True, + "country_id": cls.env.ref("base.ph").id, + } ) + cls.validation_ids = [] - def test_05_assign_to_user_cr(self): - admin = self.env.ref("base.user_admin") - self._test_change_request.assign_to_user(admin) - self.assertEqual( - self._test_change_request.assign_to_id, - admin, - "Admin should be the one who assigned to this CR!", + # Patch to allow creating spp.change.request + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase._selection_request_type_ref_id" ) - self._test_change_request.state = "pending" - with self.assertRaisesRegex(UserError, "^.*not have any validation sequence defined.$"): - self._test_change_request.assign_to_user(self.env.user) - - def test_06_onchange_scan_qr_code_details_cr(self): - self._test_change_request.qr_code_details = '{"qrcode": "-T-E-S-T-Q-R-C-O-D-E-"}' - with self.assertRaisesRegex(UserError, "^.*no group found with the ID number from the QR Code scanned.$"): - self._test_change_request._onchange_scan_qr_code_details() - self.env["g2p.reg.id"].create( + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type2", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + def _create_test_cr(self): + default_vals = { + "request_type": "test.cr.type2", + "registrant_id": self.registrant_1.id, + "applicant_phone": "+639171234567", + } + + return self.env["spp.change.request"].create(default_vals) + + def test_01_cr_creation(self): + """Test change request creation with default values""" + change_request = self._create_test_cr() + self.assertTrue(change_request.name) + self.assertEqual(change_request.assign_to_id, self.env.user) + self.assertEqual(change_request.request_type, "test.cr.type2") + + def test_02_onchange_request_type(self): + """Test that registrant_id is cleared when request_type changes.""" + change_request = self._create_test_cr() + self.assertEqual(change_request.registrant_id, self.registrant_1) + + # Simulate onchange + change_request._onchange_request_type() + + self.assertFalse(change_request.registrant_id) + + def test_03_onchange_registrant_id(self): + """Test that applicant_id and applicant_phone are cleared when registrant_id changes.""" + change_request = self._create_test_cr() + change_request.applicant_id = self.registrant_1.id + self.assertTrue(change_request.applicant_id) + self.assertTrue(change_request.applicant_phone) + + change_request.registrant_id = self.registrant_2.id + change_request._onchange_registrant_id() + + self.assertFalse(change_request.applicant_id) + self.assertFalse(change_request.applicant_phone) + + def test_04_onchange_applicant_id(self): + """Test that applicant_phone is updated when applicant_id changes.""" + change_request = self._create_test_cr() + change_request.applicant_id = self.registrant_1.id + change_request._onchange_applicant_id() + self.assertEqual(change_request.applicant_phone, self.registrant_1.phone) + + change_request.applicant_id = False + change_request._onchange_applicant_id() + self.assertFalse(change_request.applicant_phone) + + def test_05_check_applicant_phone(self): + """Test applicant phone number validation.""" + change_request = self._create_test_cr() + + # Should not raise error + change_request.applicant_phone = "+639171234567" + change_request._check_applicant_phone() + + def test_06_open_applicant_form(self): + """Test opening applicant form view.""" + change_request = self._create_test_cr() + # No applicant + action = change_request.open_applicant_form() + self.assertEqual(action["type"], "ir.actions.client") + self.assertEqual(action["tag"], "display_notification") + + # With applicant + change_request.applicant_id = self.registrant_1.id + action = change_request.open_applicant_form() + self.assertEqual(action["type"], "ir.actions.act_window") + self.assertEqual(action["res_model"], "res.partner") + self.assertEqual(action["res_id"], self.registrant_1.id) + + def test_07_check_phone_exist(self): + """Test _check_phone_exist method.""" + change_request = self._create_test_cr() + change_request.applicant_phone_required = True + change_request.applicant_phone = False + with self.assertRaises(UserError) as e: + change_request._check_phone_exist() + self.assertEqual(str(e.exception), "Phone No. is required.") + + change_request.applicant_phone = "+639171234567" + self.assertIsNone(change_request._check_phone_exist()) + + def test_08_approve_cr(self): + """Test approve_cr method.""" + change_request = self._create_test_cr() + with self.assertRaises(ValidationError): + change_request.with_user(self.user_demo).approve_cr() + + test_cr_type_record = self.env["test.cr.type2"].create( { - "partner_id": self._test_group.id, - "id_type": self.env.ref("spp_idpass.id_type_idpass").id, - "value": "-T-E-S-T-Q-R-C-O-D-E-", + "change_request_id": change_request.id, } ) - self._test_change_request._onchange_scan_qr_code_details() - self.assertEqual( - self._test_change_request.registrant_id, - self._test_group, - "Registrant on CR should be test group!", - ) - @patch( - "odoo.addons.phone_validation.tools.phone_validation.phone_parse", - return_value="1", - ) - def test_07_open_request_detail_cr(self, phone_parse): - with self.assertRaisesRegex(UserError, "Phone No. is required."): - self._test_change_request.open_request_detail() - self._test_change_request.applicant_phone = "+9647001234567" - res = self._test_change_request.open_request_detail() - self.assertListEqual( - [res.get("type"), res.get("tag"), res.get("params", {}).get("type")], - ["ir.actions.client", "display_notification", "danger"], - "Request Type ID not existed, client should display error notification!", - ) + change_request.request_type_ref_id = test_cr_type_record + self.user_demo.groups_id = [(4, self.env.ref("spp_change_request.group_spp_change_request_external_api").id)] + change_request.with_user(self.user_demo).approve_cr() + self.assertEqual(change_request.state, "applied") - def test_08_cancel_error_cr(self): - with self.assertRaisesRegex(UserError, "^.*request to be cancelled must be in draft.*$"): - self._test_change_request.state = "validated" - self._test_change_request._cancel(self._test_change_request) - - def test_09_cancel_cr(self): - self.assertListEqual( - [ - self._test_change_request.state, - self._test_change_request.cancelled_by_id.id, - self._test_change_request.date_cancelled, - ], - ["draft", False, False], - "Draft CR should not have cancelling info.!", - ) - self._test_change_request._cancel(self._test_change_request) - self.assertListEqual( - [ - self._test_change_request.state, - self._test_change_request.cancelled_by_id, - ], - ["cancelled", self.env.user], - "Cancelled CR should have cancelling info.!", - ) - self.assertLessEqual( - self._test_change_request.date_cancelled, - fields.Datetime.now(), - "Cancelled CR should have date cancelled info.!", - ) + def test_09_action_cancel(self): + """Test action_cancel method.""" + change_request = self._create_test_cr() + action = change_request.action_cancel() + self.assertEqual(action["res_model"], "spp.change.request.cancel.wizard") + self.assertEqual(action["context"]["change_request_id"], change_request.id) - def test_10_check_user_error_cr(self): - self._test_change_request.assign_to_id = None - with self.assertRaisesRegex(UserError, "^.*no user assigned.*$"): - self._test_change_request._check_user(process="Apply") - - def test_11_check_user_cr(self): - with self.assertRaisesRegex(UserError, "^You are not allowed.*$"): - self._test_change_request.with_user(2)._check_user(process="Apply") - self.assertTrue( - self._test_change_request._check_user(process="Apply"), - "Change request creator / assignee should have access!", - ) + def test_10_action_reject(self): + """Test action_reject method.""" + change_request = self._create_test_cr() + action = change_request.action_reject() + self.assertEqual(action["res_model"], "spp.change.request.reject.wizard") diff --git a/spp_change_request/tests/test_models.py b/spp_change_request/tests/test_models.py new file mode 100644 index 000000000..e3102af30 --- /dev/null +++ b/spp_change_request/tests/test_models.py @@ -0,0 +1,48 @@ +from odoo import api, fields, models + + +class TestCRType(models.Model): + _name = "test.cr.type2" + _inherit = "spp.change.request.source.mixin" + _description = "Test CR Type for Source Mixin" + _test = True # Mark this as a test model + + dms_directory_ids = fields.One2many( + "spp.dms.directory", + "change_request_test_cr_id", + string="DMS Directories", + auto_join=True, + ) + dms_file_ids = fields.One2many( + "spp.dms.file", + "change_request_test_cr_id", + string="DMS Files", + auto_join=True, + ) + + validation_ids = fields.Many2many("spp.change.request.validation.sequence", string="Validations") + + def update_live_data(self): + return + + @api.onchange("registrant_id") + def _onchange_registrant_id(self): + pass + + +class SPPDMSDirectory(models.Model): + _inherit = "spp.dms.directory" + + change_request_test_cr_id = fields.Many2one( + "test.cr.type2", + string="Change Request (Test CR Type)", + ) + + +class SPPDMSFile(models.Model): + _inherit = "spp.dms.file" + + change_request_test_cr_id = fields.Many2one( + "test.cr.type2", + string="Change Request (Test CR Type)", + ) diff --git a/spp_change_request_base/__init__.py b/spp_change_request_base/__init__.py index ebdc65039..bdc7863f5 100644 --- a/spp_change_request_base/__init__.py +++ b/spp_change_request_base/__init__.py @@ -2,3 +2,4 @@ from . import models from . import wizard +from .tests import test_models diff --git a/spp_change_request_base/__manifest__.py b/spp_change_request_base/__manifest__.py index 1e8bbb410..72b11ee14 100644 --- a/spp_change_request_base/__manifest__.py +++ b/spp_change_request_base/__manifest__.py @@ -15,6 +15,7 @@ "base", "mail", "spp_dms", + "mail", ], "data": [ "security/ir.model.access.csv", diff --git a/spp_change_request_base/security/ir.model.access.csv b/spp_change_request_base/security/ir.model.access.csv index 9c5cc1806..cf551d824 100644 --- a/spp_change_request_base/security/ir.model.access.csv +++ b/spp_change_request_base/security/ir.model.access.csv @@ -6,3 +6,5 @@ spp_change_request_validation_stage_sysadmin,Change Request Validation Stage Adm spp_change_request_user_assign_wizard_sysadmin,Change Request User Assignment Admin Access,spp_change_request_base.model_spp_change_request_user_assign_wizard,base.group_system,1,1,1,1 spp_change_request_reject_wizard_sysadmin,Change Request Reject Admin Access,spp_change_request_base.model_spp_change_request_reject_wizard,base.group_system,1,1,1,1 spp_change_request_cancel_wizard_sysadmin,Change Request Cancel Admin Access,spp_change_request_base.model_spp_change_request_cancel_wizard,base.group_system,1,1,1,1 + +test_cr_type_admin,Change Request Test CR Admin Access,spp_change_request_base.model_test_cr_type,base.group_system,1,1,1,1 diff --git a/spp_change_request_base/tests/__init__.py b/spp_change_request_base/tests/__init__.py index 3a88db069..3b254e09a 100644 --- a/spp_change_request_base/tests/__init__.py +++ b/spp_change_request_base/tests/__init__.py @@ -1 +1,5 @@ +from . import test_models from . import test_change_requests +from . import test_source_mixin +from . import test_spp_dms_file +from . import test_wizards diff --git a/spp_change_request_base/tests/test_change_requests.py b/spp_change_request_base/tests/test_change_requests.py index fa433685c..fe98078ca 100644 --- a/spp_change_request_base/tests/test_change_requests.py +++ b/spp_change_request_base/tests/test_change_requests.py @@ -1,97 +1,391 @@ -from odoo import fields +import logging +from unittest.mock import patch + from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase -from .common import Common +_logger = logging.getLogger(__name__) -class TestChangeRequestBase(Common): +class TestChangeRequestBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - cls._test_change_request = cls._create_change_request() + # Set context to avoid job queue delay + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test users + cls.user_admin = cls.env.ref("base.user_admin") + cls.user_demo = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user", + "password": "test_password", + } + ) + cls.user_demo2 = cls.env["res.users"].create( + { + "name": "Test User2", + "login": "test_user2", + "password": "test_password2", + } + ) + + # Create test registrants + cls.registrant_1 = cls.env["res.partner"].create( + { + "name": "Test Partner 1", + "phone": "+1234567890", + } + ) + cls.registrant_2 = cls.env["res.partner"].create( + { + "name": "Test Partner 2", + "phone": "+0987654321", + } + ) + cls.validation_ids = [] + + # Patch to allow creating spp.change.request + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase._selection_request_type_ref_id" + ) + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestValidationSequence._selection_request_type_ref_id" + ) + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + def _create_test_cr(self): + default_vals = { + "name": "Test Request", + "request_type": "test.cr.type", + "registrant_id": self.registrant_1.id, + } + + stage_local = self.env["spp.change.request.validation.stage"].create( + { + "name": "Local Stage", + } + ) + stage_global = self.env["spp.change.request.validation.stage"].create( + { + "name": "Global Stage", + } + ) + validations_local = self.env["spp.change.request.validation.sequence"].create( + { + "sequence": 10, + "stage_id": stage_local.id, + "request_type": "test.cr.type", + "validation_group_id": self.env.ref("base.group_system").id, + "validation_group_state": "both", + } + ) + validations_global = self.env["spp.change.request.validation.sequence"].create( + { + "sequence": 20, + "stage_id": stage_global.id, + "request_type": "test.cr.type", + "validation_group_id": self.env.ref("base.group_system").id, + "validation_group_state": "both", + } + ) + self.validation_ids = [(4, validations_local.id), (4, validations_global.id)] + + return self.env["spp.change.request"].create(default_vals) + + def test_01_cr_creation(self): + """Test change request creation with default values""" + change_request = self._create_test_cr() + + self.assertEqual(change_request.state, "draft") + self.assertEqual(change_request.assign_to_id, self.env.user) + self.assertIsNotNone(change_request.date_requested) + self.assertEqual(change_request.request_type, "test.cr.type") + + def test_02_cr_unlink_draft(self): + """Test that draft change requests can be deleted by creator""" + change_request = self._create_test_cr() + change_request.unlink() + + # Verify it's deleted + self.assertFalse(change_request.exists()) + + def test_03_cr_unlink_non_draft_error(self): + """Test that non-draft change requests cannot be deleted""" + change_request = self._create_test_cr() + change_request.state = "pending" + + with self.assertRaises(UserError): + change_request.unlink() + + def test_04_cr_unlink_wrong_user_error(self): + """Test that change requests cannot be deleted by non-creator""" + change_request = self._create_test_cr() + + with self.assertRaises(UserError): + change_request.with_user(self.user_demo).unlink() + + def test_05_assign_to_user(self): + """Test assigning change request to user""" + change_request = self._create_test_cr() + + # Test assignment + change_request.assign_to_user(self.user_demo) + self.assertEqual(change_request.assign_to_id, self.user_demo) + + def test_06_assign_to_user_pending_state_error(self): + """Test assignment error when in pending state without validation sequence""" + change_request = self._create_test_cr() + change_request.state = "pending" + + with self.assertRaises(UserError): + change_request.assign_to_user(self.user_demo) + + def test_07_oncr_type(self): + """Test that registrant_id is cleared when request_type changes.""" + change_request = self._create_test_cr() + self.assertEqual(change_request.registrant_id, self.registrant_1) + + # Simulate onchange + change_request._onchange_request_type() + + self.assertFalse(change_request.registrant_id) + + def test_08_open_cr_form_no_ref_id(self): + """Test opening form without a request_type_ref_id returns a notification.""" + change_request = self._create_test_cr() + action = change_request.open_change_request_form() + + self.assertEqual(action["type"], "ir.actions.client") + self.assertEqual(action["tag"], "display_notification") + self.assertEqual(action["params"]["type"], "danger") + self.assertEqual(action["params"]["message"], "The Request Type field must be filled-up.") - def test_01_assign_to_current_user(self): + def test_09_action_submit_no_ref_id_error(self): + """Test submitting a CR without a request_type_ref_id raises an error.""" + change_request = self._create_test_cr() + + with self.assertRaises(UserError) as e: + change_request.action_submit() + self.assertEqual(str(e.exception), "The change request type must be properly filled-up.") + + def test_10_create_request_detail_not_draft_or_pending_error(self): + """Test creating request detail for a CR not in draft or pending state raises an error.""" + change_request = self._create_test_cr() + change_request.state = "applied" + + with self.assertRaises(UserError) as e: + change_request.create_request_detail() self.assertEqual( - self._test_change_request.assign_to_id, - self.env.user, - "Creating user should be default assignee!", - ) - - def test_02_unlink_and_raise_error(self): - with self.assertRaisesRegex(UserError, "Only draft change requests can be deleted by its creator."): - self._test_change_request.with_user(2).unlink() - self._test_change_request.state = "pending" - with self.assertRaisesRegex(UserError, "Only draft change requests can be deleted by its creator."): - self._test_change_request.unlink() - - def test_03_unlink_cr(self): - self._test_change_request.unlink() - remaining_change_request = self.env["spp.change.request"].search([("request_type", "=", "request_type")]) - self.assertCountEqual( - remaining_change_request.ids, - [], - "Draft change request should unlinkable by its creator!", - ) - - def test_04_assign_to_admin_user(self): - admin = self.env.ref("base.user_admin") - self._test_change_request.assign_to_user(admin) + str(e.exception), "The change request to be created must be in draft or pending validation state." + ) + + def test_11_cancel_cr_not_in_allowed_state_error(self): + """Test that cancelling a CR not in draft, pending, or rejected state raises an error.""" + change_request = self._create_test_cr() + change_request.state = "applied" + with self.assertRaises(UserError) as e: + change_request._cancel(change_request) self.assertEqual( - self._test_change_request.assign_to_id, - admin, - "Admin should be the one who assigned to this CR!", - ) - self._test_change_request.state = "pending" - with self.assertRaisesRegex(UserError, "^.*not have any validation sequence defined.$"): - self._test_change_request.assign_to_user(self.env.user) - - def test_05_open_request_detail(self): - res = self._test_change_request.open_request_detail() - self.assertListEqual( - [res.get("type"), res.get("tag"), res.get("params", {}).get("type")], - ["ir.actions.client", "display_notification", "danger"], - "Request Type ID does not exist, client should display error notification!", - ) - - def test_06_cancel_error(self): - with self.assertRaisesRegex(UserError, "^.*request to be cancelled must be in draft.*$"): - self._test_change_request.state = "validated" - self._test_change_request._cancel(self._test_change_request) - - def test_07_cancel_cr(self): - self.assertListEqual( - [ - self._test_change_request.state, - self._test_change_request.cancelled_by_id.id, - self._test_change_request.date_cancelled, - ], - ["draft", False, False], - "Draft CR should not have cancel info!", - ) - self._test_change_request._cancel(self._test_change_request) - self.assertListEqual( - [ - self._test_change_request.state, - self._test_change_request.cancelled_by_id, - ], - ["cancelled", self.env.user], - "Cancelled CR should have cancel info!", - ) - self.assertLessEqual( - self._test_change_request.date_cancelled, - fields.Datetime.now(), - "Cancelled CR should have date cancelled info!", - ) - - def test_8_check_user_error(self): - self._test_change_request.assign_to_id = None - with self.assertRaisesRegex(UserError, "^.*no user assigned.*$"): - self._test_change_request._check_user(process="Apply") - - def test_9_check_user(self): - with self.assertRaisesRegex(UserError, "^You are not allowed.*$"): - self._test_change_request.with_user(2)._check_user(process="Apply") - self.assertTrue( - self._test_change_request._check_user(process="Apply"), - "Change request creator / assignee should have access!", + str(e.exception), "The request to be cancelled must be in draft, pending, or rejected validation state." + ) + + def test_12_cancel_cr_in_draft_state(self): + """Test cancelling a CR in draft state.""" + change_request = self._create_test_cr() + change_request._cancel(change_request) + + self.assertEqual(change_request.state, "cancelled") + self.assertEqual(change_request.cancelled_by_id, self.env.user) + self.assertIsNotNone(change_request.date_cancelled) + + def test_13_check_user_not_assigned_error(self): + """Test _check_user when no user is assigned raises an error.""" + change_request = self._create_test_cr() + change_request.assign_to_id = False + + with self.assertRaises(UserError) as e: + change_request._check_user("validate") + self.assertEqual(str(e.exception), "There are no user assigned to this change request.") + + def test_14_check_user_wrong_user_error(self): + """Test _check_user when a different user tries to process raises an error.""" + change_request = self._create_test_cr() + change_request.assign_to_id = self.user_demo.id + + with self.assertRaises(UserError) as e: + change_request.with_user(self.user_admin)._check_user("validate") + self.assertEqual(str(e.exception), "You are not allowed to validate this change request") + + def test_15_check_user_correct_user(self): + """Test _check_user with the correct assigned user.""" + change_request = self._create_test_cr() + change_request.assign_to_id = self.user_demo.id + + result = change_request.with_user(self.user_demo)._check_user("validate") + self.assertTrue(result) + + @patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase.ADMIN_GROUP_NAME", + "base.group_system", + ) + def test_16_cr_open_wiz(self): + """Test opening the change request wizard.""" + change_request = self._create_test_cr() + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + change_request.assign_to_id = self.user_demo2.id + action = change_request.open_user_assignment_wiz() + + self.assertEqual(action["type"], "ir.actions.act_window") + self.assertEqual(action["res_model"], "spp.change.request.user.assign.wizard") + + def test_17_open_change_request_form(self): + """Test opening the change request form with a valid request_type_ref_id.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + change_request.request_type_ref_id = test_cr_type_record + self.env["ir.ui.view"].create( + { + "name": "test.cr.type.form", + "type": "form", + "model": "test.cr.type", + "arch_db": """
+ + + + + +
""", + } ) + action = change_request.with_user(self.user_demo).open_request_detail() + self.assertEqual(action["res_model"], "test.cr.type") + + def test_18_create_request_detail(self): + """Test creating request detail without redirection.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + + action = change_request.create_request_detail() + self.assertEqual(action["res_model"], "test.cr.type") + + @patch( + "odoo.addons.spp_change_request_base.models.mixins.source_mixin.ChangeRequestSourceMixin.AUTO_APPLY_CHANGES", + False, + ) + def test_19_action_validate(self): + """Test action_validate method.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + test_cr_type_record.validation_ids = self.validation_ids + change_request.request_type_ref_id = test_cr_type_record + change_request.state = "pending" + change_request.assign_to_id = self.user_demo.id + + # Validate with correct user + change_request.with_user(self.user_demo).action_validate() + # Validate again to move to validated state as there are 2 validations + change_request.with_user(self.user_demo).action_validate() + + self.assertEqual(change_request.state, "validated") + + def test_20_action_apply(self): + """Test action_apply method.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + test_cr_type_record.validation_ids = self.validation_ids + change_request.request_type_ref_id = test_cr_type_record + change_request.state = "validated" + change_request.assign_to_id = self.user_demo.id + + # Apply with correct user + change_request.with_user(self.user_demo).action_apply() + self.assertEqual(change_request.state, "applied") + self.assertEqual(change_request.applied_by_id, self.user_demo) + + def test_21_action_cancel(self): + """Test action_cancel method.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + test_cr_type_record.validation_ids = self.validation_ids + change_request.request_type_ref_id = test_cr_type_record + change_request.state = "pending" + + # Cancel the change request + change_request._cancel(change_request) + self.assertEqual(change_request.state, "cancelled") + self.assertEqual(change_request.cancelled_by_id, self.env.user) + + def test_22_action_reset_to_draft(self): + """Test action_reset_to_draft method.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + test_cr_type_record.validation_ids = self.validation_ids + change_request.request_type_ref_id = test_cr_type_record + change_request.state = "rejected" + + # Reset to draft + change_request.action_reset_to_draft() + self.assertEqual(change_request.state, "draft") + + def test_23_action_reject(self): + """Test action_reject method.""" + change_request = self._create_test_cr() + change_request.request_type = "test.cr.type" + test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + self.user_demo.groups_id = [(4, self.env.ref("base.group_system").id)] + test_cr_type_record.validation_ids = self.validation_ids + change_request.request_type_ref_id = test_cr_type_record + change_request.state = "pending" + change_request.assign_to_id = self.user_demo.id + + # Reject with correct user + action = change_request.with_user(self.user_demo).action_reject() + self.assertEqual(action["res_model"], "spp.change.request.reject.wizard") diff --git a/spp_change_request_base/tests/test_models.py b/spp_change_request_base/tests/test_models.py new file mode 100644 index 000000000..b2e39576d --- /dev/null +++ b/spp_change_request_base/tests/test_models.py @@ -0,0 +1,48 @@ +from odoo import api, fields, models + + +class TestCRType(models.Model): + _name = "test.cr.type" + _inherit = "spp.change.request.source.mixin" + _description = "Test CR Type for Source Mixin" + _test = True # Mark this as a test model + + dms_directory_ids = fields.One2many( + "spp.dms.directory", + "change_request_test_cr_id", + string="DMS Directories", + auto_join=True, + ) + dms_file_ids = fields.One2many( + "spp.dms.file", + "change_request_test_cr_id", + string="DMS Files", + auto_join=True, + ) + + validation_ids = fields.Many2many("spp.change.request.validation.sequence", string="Validations") + + def update_live_data(self): + return + + @api.onchange("registrant_id") + def _onchange_registrant_id(self): + pass + + +class SPPDMSDirectory(models.Model): + _inherit = "spp.dms.directory" + + change_request_test_cr_id = fields.Many2one( + "test.cr.type", + string="Change Request (Test CR Type)", + ) + + +class SPPDMSFile(models.Model): + _inherit = "spp.dms.file" + + change_request_test_cr_id = fields.Many2one( + "test.cr.type", + string="Change Request (Test CR Type)", + ) diff --git a/spp_change_request_base/tests/test_source_mixin.py b/spp_change_request_base/tests/test_source_mixin.py new file mode 100644 index 000000000..f2f3c1ecf --- /dev/null +++ b/spp_change_request_base/tests/test_source_mixin.py @@ -0,0 +1,254 @@ +from unittest.mock import patch + +from odoo.exceptions import UserError, ValidationError +from odoo.tests.common import TransactionCase + + +class TestChangeRequestSourceMixin(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.user_admin = cls.env.ref("base.group_system") + cls.user_demo = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user", + } + ) + cls.user_demo.groups_id = [(4, cls.user_admin.id)] + + # Patch to allow creating spp.change.request + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase._selection_request_type_ref_id" + ) + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestValidationSequence._selection_request_type_ref_id" + ) + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + def setUp(self): + super().setUp() + self.change_request = self.env["spp.change.request"].create( + { + "name": "Test CR", + "request_type": "test.cr.type", + } + ) + + self.test_cr_type_record = self.env["test.cr.type"].create( + { + "change_request_id": self.change_request.id, + } + ) + stage_local = self.env["spp.change.request.validation.stage"].create( + { + "name": "Local Stage", + } + ) + stage_global = self.env["spp.change.request.validation.stage"].create( + { + "name": "Global Stage", + } + ) + validations_local = self.env["spp.change.request.validation.sequence"].create( + { + "sequence": 10, + "stage_id": stage_local.id, + "request_type": "test.cr.type", + "validation_group_id": self.env.ref("base.user_admin").id, + "validation_group_state": "both", + } + ) + validations_global = self.env["spp.change.request.validation.sequence"].create( + { + "sequence": 20, + "stage_id": stage_global.id, + "request_type": "test.cr.type", + "validation_group_id": self.env.ref("base.user_admin").id, + "validation_group_state": "both", + } + ) + self.test_cr_type_record.validation_ids = [(4, validations_local.id), (4, validations_global.id)] + + def test_01_update_registrant_id(self): + """Test that registrant_id is updated based on request_type_ref_id.""" + # Initially, registrant_id should be empty + self.assertFalse(self.change_request.registrant_id) + + # Set a registrant and request_type_ref_id + partner = self.env["res.partner"].create({"name": "Test Partner"}) + self.test_cr_type_record.registrant_id = partner.id + self.change_request.request_type_ref_id = self.test_cr_type_record + # Simulate onchange + self.test_cr_type_record._update_registrant_id(self.test_cr_type_record) + + # Now, registrant_id should be cleared + self.assertEqual(self.change_request.registrant_id, partner) + + def test_02_on_submit_draft_state(self): + """Test _on_submit when the request is in 'draft' state.""" + dms_directory = self.env["spp.dms.directory"].create({"name": "Test Directory"}) + self.change_request.dms_directory_ids = [(4, dms_directory.id)] + self.assertEqual(self.change_request.state, "draft") + self.test_cr_type_record.action_submit() + self.assertEqual(self.change_request.state, "pending") + self.assertIsNotNone(self.change_request.date_requested) + + def test_03_on_submit_not_draft_state_error(self): + """Test _on_submit raises ValidationError if not in 'draft' state.""" + self.change_request.state = "pending" + with self.assertRaises(ValidationError) as e: + self.test_cr_type_record._on_submit(self.change_request) + self.assertEqual(str(e.exception), "The request must be in draft state to be set to pending validation.") + + def test_04_apply_not_validated_state_error(self): + """Test _apply raises ValidationError if not in 'validated' state.""" + self.change_request.state = "pending" + with self.assertRaises(ValidationError) as e: + self.test_cr_type_record._apply(self.change_request) + self.assertEqual(str(e.exception), "The request must be in validated state for changes to be applied.") + + def test_05_apply_success(self): + """Test _apply success path.""" + self.change_request.state = "validated" + self.change_request.assign_to_id = self.env.user + + # Mock update_live_data to avoid NotImplementedError and check it's called + with patch.object(type(self.test_cr_type_record), "update_live_data") as mock_update: + self.test_cr_type_record._apply(self.change_request) + mock_update.assert_called_once() + + self.assertEqual(self.change_request.state, "applied") + self.assertEqual(self.change_request.applied_by_id, self.env.user) + self.assertIsNotNone(self.change_request.date_applied) + + def test_06_cancel_in_valid_state(self): + """Test _cancel when request is in a cancellable state.""" + self.change_request.state = "pending" + self.test_cr_type_record._cancel(self.change_request) + self.assertEqual(self.change_request.state, "cancelled") + self.assertEqual(self.change_request.cancelled_by_id, self.env.user) + self.assertIsNotNone(self.change_request.date_cancelled) + + def test_07_cancel_in_invalid_state_error(self): + """Test _cancel raises UserError if in a non-cancellable state.""" + self.change_request.state = "applied" + with self.assertRaises(UserError) as e: + self.test_cr_type_record._cancel(self.change_request) + self.assertEqual( + str(e.exception), "The request to be cancelled must be in draft, pending, or rejected validation state." + ) + + def test_08_reset_to_draft_in_rejected_state(self): + """Test _reset_to_draft when request is in 'rejected' state.""" + self.change_request.state = "rejected" + self.test_cr_type_record._reset_to_draft(self.change_request) + self.assertEqual(self.change_request.state, "draft") + self.assertEqual(self.change_request.reset_to_draft_by_id, self.env.user) + + def test_09_reset_to_draft_not_in_rejected_state_error(self): + """Test _reset_to_draft raises UserError if not in 'rejected' state.""" + self.change_request.state = "pending" + with self.assertRaises(UserError) as e: + self.test_cr_type_record._reset_to_draft(self.change_request) + self.assertEqual( + str(e.exception), "The request to be cancelled must be in draft, pending, or rejected validation state." + ) + + def test_10_on_validate(self): + """Test _on_validate success path.""" + self.change_request.assign_to_id = self.user_demo.id + self.change_request.request_type_ref_id = self.test_cr_type_record + self.test_cr_type_record.action_submit() + self.test_cr_type_record.with_user(self.user_demo).action_validate() + # Second Call for Global Stage Validation + self.test_cr_type_record.with_user(self.user_demo).action_validate() + self.assertEqual(self.change_request.state, "applied") + self.assertIsNotNone(self.change_request.date_validated) + + def test_11_approve_cr_directly(self): + """Test approving a CR directly without validations.""" + self.change_request.request_type_ref_id = self.test_cr_type_record + self.test_cr_type_record._approve_cr(self.change_request) + self.assertEqual(self.change_request.state, "applied") + + def test_12_call_action_cancel(self): + """Test action_cancel method.""" + self.change_request.state = "pending" + action = self.test_cr_type_record.action_cancel() + self.assertEqual(action["res_model"], "spp.change.request.cancel.wizard") + + def test_13_call_action_reject(self): + """Test action_reject method.""" + self.change_request.state = "pending" + action = self.test_cr_type_record.action_reject() + self.assertEqual(action["res_model"], "spp.change.request.reject.wizard") + + def test_14_on_reject(self): + """Test _on_reject method.""" + self.change_request.state = "pending" + self.test_cr_type_record._on_reject(self.change_request, "Rejection Reason") + self.assertEqual(self.change_request.state, "rejected") + + @patch( + "odoo.addons.spp_change_request_base.models.mixins.source_mixin.ChangeRequestSourceMixin.ADMIN_GROUP_NAME", + "base.group_system", + ) + def test_15_call_open_user_assignment_wiz(self): + """Test open_user_assignment_wiz method.""" + self.change_request.assign_to_id = False + action = self.test_cr_type_record.with_user(self.user_demo).open_user_assignment_wiz() + # First without assigned user + self.assertEqual(self.change_request.assign_to_id, self.user_demo) + + action = self.test_cr_type_record.with_user(self.user_demo).open_user_assignment_wiz() + + self.assertEqual(action["res_model"], "spp.change.request.user.assign.wizard") + + def test_16_call_open_user_assignment_to_wiz(self): + """Test open_user_assignment_to_wiz method.""" + action = self.test_cr_type_record.with_user(self.user_demo).open_user_assignment_to_wiz() + self.assertEqual(action["res_model"], "spp.change.request.user.assign.wizard") + + @patch( + "odoo.addons.spp_change_request_base.models.mixins.source_mixin.ChangeRequestSourceMixin.REGISTRANT_FORM_ID", + "base.view_partner_form", + ) + def test_17_call_open_registrant_details_form(self): + """Test open_user_assignment_wiz method.""" + action = self.test_cr_type_record.with_user(self.user_demo).open_registrant_details_form() + + self.assertEqual(action["res_model"], "res.partner") + + def test_18_on_validate_raise_user_error(self): + """Test _on_validate error path.""" + self.change_request.assign_to_id = self.user_demo.id + self.change_request.request_type_ref_id = self.test_cr_type_record + self.test_cr_type_record.action_submit() + self.test_cr_type_record.with_user(self.user_demo).action_validate() + + # Second Call for Global Stage Validation + self.user_demo.groups_id = [(6, 0, [self.env.ref("base.group_user").id])] + with self.assertRaises(UserError): + self.test_cr_type_record.with_user(self.user_demo).action_validate() + + def test_19_call_action_attach_documents(self): + """Test action_attach_documents method.""" + category = self.env["spp.dms.category"].create({"name": "Test Category"}) + dms_directory = self.env["spp.dms.directory"].create( + { + "name": "Test Directory", + } + ) + self.test_cr_type_record.dms_directory_ids = [(4, dms_directory.id)] + action = self.test_cr_type_record.with_context({"category_id": category.id}).action_attach_documents() + self.assertEqual(action["res_model"], "spp.dms.file") diff --git a/spp_change_request_base/tests/test_spp_dms_file.py b/spp_change_request_base/tests/test_spp_dms_file.py new file mode 100644 index 000000000..1d334fb7b --- /dev/null +++ b/spp_change_request_base/tests/test_spp_dms_file.py @@ -0,0 +1,53 @@ +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestDMSFile(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.directory_id = cls.env["spp.dms.directory"].create( + { + "name": "Test Directory", + } + ) + + def test_01_action_save_and_close(self): + """Test action_save_and_close method.""" + dms_file = self.env["spp.dms.file"].create( + { + "name": "Test DMS File", + "directory_id": self.directory_id.id, + } + ) + result = dms_file.action_save_and_close() + self.assertEqual(result, {"type": "ir.actions.act_window_close"}) + + def test_02_action_close(self): + """Test action_close method.""" + dms_file = self.env["spp.dms.file"].create( + { + "name": "Test DMS File", + "directory_id": self.directory_id.id, + } + ) + result = dms_file.action_close() + self.assertEqual(result, {"type": "ir.actions.act_window_close"}) + + def test_03_action_attach_documents(self): + """Test action_attach_documents method.""" + dms_file = self.env["spp.dms.file"].create( + { + "name": "Test DMS File", + "directory_id": self.directory_id.id, + } + ) + result = dms_file.action_attach_documents() + self.assertEqual(result["type"], "ir.actions.act_window") + self.assertEqual(result["res_model"], "spp.dms.file") + self.assertEqual(result["res_id"], dms_file.id) + self.assertIn("Upload Document", result["name"]) + self.assertIn("category_readonly", result["context"]) diff --git a/spp_change_request_base/tests/test_wizards.py b/spp_change_request_base/tests/test_wizards.py new file mode 100644 index 000000000..c425b36f5 --- /dev/null +++ b/spp_change_request_base/tests/test_wizards.py @@ -0,0 +1,241 @@ +import json +import logging +from unittest.mock import patch + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestWizards(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Set context to avoid job queue delay + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + # Create test users + cls.user_admin = cls.env.ref("base.group_system") + cls.user_demo = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user", + "password": "test_password", + } + ) + cls.user_demo.groups_id = [(4, cls.user_admin.id)] + cls.user_demo2 = cls.env["res.users"].create( + { + "name": "Test User2", + "login": "test_user2", + "password": "test_password2", + } + ) + # Create test registrants + cls.registrant_1 = cls.env["res.partner"].create( + { + "name": "Test Partner 2", + "phone": "+0987654321", + } + ) + + # Patch to allow creating spp.change.request + patcher = patch( + "odoo.addons.spp_change_request_base.models.change_request.ChangeRequestBase._selection_request_type_ref_id" + ) + mock_selection = patcher.start() + mock_selection.return_value = [("test.cr.type", "Test CR Type")] + mock_selection.__name__ = "_mocked__selection_request_type_ref_id" + cls.addClassCleanup(patcher.stop) + + def _create_test_cr(self): + default_vals = { + "name": "Test Request", + "request_type": "test.cr.type", + "registrant_id": self.registrant_1.id, + } + + return self.env["spp.change.request"].create(default_vals) + + def test_01_cancel_wizard_with_cr_type(self): + """Test cancel_change_request_wizard functionality.""" + change_request = self._create_test_cr() + self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + wizard = self.env["spp.change.request.cancel.wizard"].create( + { + "change_request_id": change_request.id, + } + ) + wizard.cancel_change_request() + self.assertEqual(change_request.state, "cancelled") + + def test_02_cancel_wizard_without_cr_type(self): + """Test cancel_change_request_wizard functionality.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.cancel.wizard"].create( + { + "change_request_id": change_request.id, + } + ) + wizard.cancel_change_request() + self.assertEqual(change_request.state, "cancelled") + + def test_03_cancel_wizard_compute_message(self): + """Test _compute_message functionality.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.cancel.wizard"].create( + { + "change_request_id": change_request.id, + } + ) + wizard._compute_message() + expected_message = "Are you sure you would like to cancel this request: %s" % change_request.name + self.assertEqual(wizard.dialog_message, expected_message) + + def test_04_reject_wizard_compute_message(self): + """Test _compute_message for reject wizard.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.reject.wizard"].create( + { + "change_request_id": change_request.id, + "rejected_remarks": "Test rejection", + } + ) + wizard._compute_message() + expected_message = "Are you sure you would like to reject this request: %s" % change_request.name + self.assertEqual(wizard.dialog_message, expected_message) + + def test_05_reject_wizard_reject_change_request(self): + """Test reject_change_request for reject wizard.""" + change_request = self._create_test_cr() + cr_type = self.env["test.cr.type"].create( + { + "change_request_id": change_request.id, + } + ) + change_request.request_type_ref_id = cr_type + change_request.state = "pending" + + wizard = self.env["spp.change.request.reject.wizard"].create( + { + "change_request_id": change_request.id, + "rejected_remarks": "Test rejection", + } + ) + + with patch.object(type(cr_type), "_on_reject", autospec=True) as mock_on_reject: + wizard.reject_change_request() + mock_on_reject.assert_called_once_with(cr_type, change_request, "Test rejection") + + def test_06_assign_wizard_assign_to_user(self): + """Test assign_to_user for user assignment wizard.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.user.assign.wizard"].create( + { + "change_request_id": change_request.id, + "assign_to_id": self.user_demo.id, + } + ) + wizard.assign_to_user() + self.assertEqual(change_request.assign_to_id, self.user_demo) + + def test_07_assign_wizard_compute_message(self): + """Test _compute_message_assignment for user assignment wizard.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.user.assign.wizard"].create( + { + "change_request_id": change_request.id, + } + ) + wizard._compute_message_assignment() + self.assertEqual(wizard.dialog_message, "Assign this change request to:") + self.assertTrue(wizard.assign_to_any) + + def test_08_assign_wizard_compute_domain(self): + """Test _compute_assign_to_id_domain for user assignment wizard.""" + change_request = self._create_test_cr() + wizard = self.env["spp.change.request.user.assign.wizard"].create( + { + "change_request_id": change_request.id, + } + ) + + group1 = self.env["res.groups"].create({"name": "Test Group 1"}) + group2 = self.env["res.groups"].create({"name": "Test Group 2"}) + self.user_demo.groups_id = [(4, group1.id)] + + with patch.object( + type(wizard.with_user(self.user_demo)), "_get_group_ids", return_value=[group1.id, group2.id] + ) as mock_get_groups: + wizard.with_user(self.user_demo)._compute_assign_to_id_domain() + mock_get_groups.assert_called_once() + domain = json.loads(wizard.assign_to_id_domain) + self.assertEqual(domain, [["groups_id", "in", [group1.id]]]) + + def test_09_assign_wizard_default_get(self): + """Test default_get for user assignment wizard.""" + change_request = self._create_test_cr() + change_request.assign_to_id = self.user_demo + + wizard = ( + self.env["spp.change.request.user.assign.wizard"] + .with_context(active_id=change_request.id, curr_assign_to_id=self.user_demo2.id) + .with_user(self.user_demo) + .default_get([]) + ) + + self.assertEqual(wizard["change_request_id"], change_request.id) + self.assertEqual(wizard["curr_assign_to_id"], self.user_demo2.id) + self.assertEqual(wizard["assign_to_id"], self.user_demo) + + def test_10_cancel_wizard_default_get(self): + """Test default_get for cancel change request wizard.""" + change_request = self._create_test_cr() + + wizard = ( + self.env["spp.change.request.cancel.wizard"] + .with_context(active_id=change_request.id) + .with_user(self.user_demo) + .default_get([]) + ) + + self.assertEqual(wizard["change_request_id"], change_request.id) + + wizard_change_request = ( + self.env["spp.change.request.cancel.wizard"] + .with_context(change_request_id=change_request.id) + .with_user(self.user_demo) + .default_get([]) + ) + + self.assertEqual(wizard_change_request["change_request_id"], change_request.id) + + def test_11_reject_wizard_default_get(self): + """Test default_get for reject change request wizard.""" + change_request = self._create_test_cr() + + wizard = ( + self.env["spp.change.request.reject.wizard"] + .with_context(active_id=change_request.id) + .with_user(self.user_demo) + .default_get([]) + ) + + self.assertEqual(wizard["change_request_id"], change_request.id) + + wizard_change_request = ( + self.env["spp.change.request.reject.wizard"] + .with_context(change_request_id=change_request.id) + .with_user(self.user_demo) + .default_get([]) + ) + + self.assertEqual(wizard_change_request["change_request_id"], change_request.id) diff --git a/spp_event_data/__init__.py b/spp_event_data/__init__.py index ebdc65039..9252b5be7 100644 --- a/spp_event_data/__init__.py +++ b/spp_event_data/__init__.py @@ -2,3 +2,4 @@ from . import models from . import wizard +from .tests import test_model_event_data_type diff --git a/spp_event_data/tests/__init__.py b/spp_event_data/tests/__init__.py index 11e66a378..bc0496a1c 100644 --- a/spp_event_data/tests/__init__.py +++ b/spp_event_data/tests/__init__.py @@ -1 +1,2 @@ from . import test_event_data +from . import test_create_event_wizard diff --git a/spp_event_data/tests/test_create_event_wizard.py b/spp_event_data/tests/test_create_event_wizard.py new file mode 100644 index 000000000..c655ce4a1 --- /dev/null +++ b/spp_event_data/tests/test_create_event_wizard.py @@ -0,0 +1,62 @@ +from datetime import date +from unittest.mock import patch + +from odoo.tests import TransactionCase + + +class TestCreateEventWizard(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.registrant = cls.env["res.partner"].create( + { + "name": "Test Registrant for Event", + "is_registrant": True, + } + ) + cls.mock_wizard_model_name = "spp.create.event.data.test.wizard" + cls.mock_model_name = "spp.event.data.test" + + def test_next_page_flow(self): + """Test the full flow of the create_event_wizard.""" + # Patch the selection field to include our mock model for the test + selection_patcher = patch.object( + type(self.env["spp.create.event.wizard"]).event_data_model, + "selection", + new=[("default", "None"), (self.mock_model_name, "Test Event")], + ) + + with selection_patcher: + # Create the wizard *after* the selection field has been patched + wizard = self.env["spp.create.event.wizard"].create( + { + "event_data_model": self.mock_model_name, + "partner_id": self.registrant.id, + "registrar": "Test Registrar", + "collection_date": date(2024, 1, 15), + "expiry_date": date(2025, 1, 15), + } + ) + + # Create a simple view for the mock wizard model + self.env["ir.ui.view"].create( + { + "name": "Test Event Data Wizard Form", + "type": "form", + "model": self.mock_wizard_model_name, + "arch_db": """
""", + } + ) + action = wizard.next_page() + + # 1. Verify that an spp.event.data record was created with correct values + event_data = self.env["spp.event.data"].search([("partner_id", "=", self.registrant.id)]) + self.assertEqual(len(event_data), 1, "An spp.event.data record should have been created.") + self.assertEqual(event_data.model, self.mock_model_name) + self.assertEqual(event_data.registrar, "Test Registrar") + + # 2. Verify that the action to open the next wizard is correct + self.assertEqual(action["res_model"], self.mock_wizard_model_name) + self.assertEqual(action["view_mode"], "form") + self.assertEqual(action["target"], "new") + self.assertEqual(action["type"], "ir.actions.act_window") diff --git a/spp_event_data/tests/test_model_event_data_type.py b/spp_event_data/tests/test_model_event_data_type.py new file mode 100644 index 000000000..6e1cb13ee --- /dev/null +++ b/spp_event_data/tests/test_model_event_data_type.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class TestEventDataType(models.Model): + _name = "spp.event.data.test" + _description = "Test Event Data Type" + + +class SPPCreateEventDataTestWizard(models.TransientModel): + _name = "spp.create.event.data.test.wizard" + _description = "Test Create Event Data Wizard" + + name = fields.Char(string="Name") + event_id = fields.Many2one("spp.event.data", string="Event Data", required=True) diff --git a/spp_programs/tests/test_cycle.py b/spp_programs/tests/test_cycle.py index 1d39826c7..5fc77fb32 100644 --- a/spp_programs/tests/test_cycle.py +++ b/spp_programs/tests/test_cycle.py @@ -1,11 +1,9 @@ -from freezegun import freeze_time - +from odoo import fields from odoo.exceptions import ValidationError from .common import Common -@freeze_time("2024-07-19") class TestCycle(Common): def test_check_dates_constrains(self): with self.assertRaisesRegex(ValidationError, 'The "End Date" cannot be earlier than the "Start Date".'): @@ -21,3 +19,59 @@ def test_check_dates_constrains(self): "start_date": "2024-07-18", } ) + + def test_get_previous_and_next_cycle(self): + # The `cycle` from `Common` is created first. + # To test previous/next, we need to control creation order. + # Cycles are sorted by `create_date`. + + # Create a new program for this test to avoid interference from other tests + self.program = self.env["g2p.program"].create( + { + "name": "Test Program for Cycle Navigation", + } + ) + + # This will be the first cycle chronologically by create_date + first_cycle = self.env["g2p.cycle"].create( + { + "name": "First Cycle", + "program_id": self.program.id, + "start_date": fields.Date.today(), + "end_date": fields.Date.today(), + } + ) + + # This is the middle cycle, created after first_cycle + middle_cycle = self.env["g2p.cycle"].create( + { + "name": "Middle Cycle", + "program_id": self.program.id, + "start_date": fields.Date.today(), + "end_date": fields.Date.today(), + } + ) + + # The `self.cycle` from `setUp` is now the last one created. + # Let's rename it for clarity in this test. + last_cycle = self.env["g2p.cycle"].create( + { + "name": "Last Cycle", + "program_id": self.program.id, + "start_date": fields.Date.today(), + "end_date": fields.Date.today(), + } + ) + + self.assertIsNone(first_cycle.get_previous_cycle(), "First cycle should have no previous cycle.") + self.assertEqual( + first_cycle.get_next_cycle(), middle_cycle, "Next cycle for first_cycle should be middle_cycle." + ) + self.assertEqual( + middle_cycle.get_previous_cycle(), first_cycle, "Previous cycle for middle_cycle should be first_cycle." + ) + self.assertEqual(middle_cycle.get_next_cycle(), last_cycle, "Next cycle for middle_cycle should be last_cycle.") + self.assertEqual( + last_cycle.get_previous_cycle(), middle_cycle, "Previous cycle for last_cycle should be middle_cycle." + ) + self.assertIsNone(last_cycle.get_next_cycle(), "Last cycle should have no next cycle.") diff --git a/spp_programs/tests/test_entitlement.py b/spp_programs/tests/test_entitlement.py index aeccea703..80fee621f 100644 --- a/spp_programs/tests/test_entitlement.py +++ b/spp_programs/tests/test_entitlement.py @@ -249,3 +249,96 @@ def test_12_approve_entitlement(self): self.cycle.write({"state": "approved"}) with self.assertRaisesRegex(UserError, "No Entitlement Manager defined."): entitlement_id.approve_entitlement() + + def test_13_reject_entitlement_wizard(self): + """Test that `reject_entitlement` returns the correct wizard action.""" + entitlement_id = self.env["g2p.entitlement"].create( + { + "partner_id": self.registrant.id, + "initial_amount": 1.0, + "cycle_id": self.cycle.id, + "state": "draft", + "valid_until": fields.Date.add(fields.Date.today(), days=1), + } + ) + self.entitlement = entitlement_id + action = self.entitlement.reject_entitlement() + self.assertEqual(action["res_model"], "spp.reject.entitlement.wizard") + self.assertEqual(action["view_mode"], "form") + self.assertEqual(action["target"], "new") + self.assertIn("to_state", action["context"]) + self.assertEqual(action["context"]["to_state"], "reject") + + def test_14_internal_reject_entitlement(self): + """Test the internal `_reject_entitlement` method.""" + # Test rejection from a valid state ('draft') + entitlement_id = self.env["g2p.entitlement"].create( + { + "partner_id": self.registrant.id, + "initial_amount": 1.0, + "cycle_id": self.cycle.id, + "state": "draft", + "valid_until": fields.Date.add(fields.Date.today(), days=1), + } + ) + self.entitlement = entitlement_id + self.entitlement.state = "draft" + rejection_reason = "Invalid data provided." + + with patch("odoo.fields.Date.today", return_value=date(2024, 1, 1)): + action = self.entitlement._reject_entitlement(to_state="reject", reject_reason=rejection_reason) + + self.assertEqual(self.entitlement.state, "reject") + self.assertEqual(self.entitlement.rejected_reason, rejection_reason) + self.assertEqual(self.entitlement.date_rejected, date(2024, 1, 1)) + + # Check notification + self.assertEqual(action["type"], "ir.actions.client") + self.assertEqual(action["tag"], "display_notification") + self.assertEqual(action["params"]["type"], "danger") + self.assertEqual(action["params"]["message"], "Entitlement Rejected") + + # Test rejection from an invalid state ('approved') + self.entitlement.state = "approved" + self.entitlement._reject_entitlement(to_state="reject", reject_reason="Should not work") + self.assertEqual(self.entitlement.state, "approved", "Entitlement in 'approved' state should not be rejected.") + + def test_15_reset_to_pending_wizard(self): + """Test that `reset_to_pending` returns the correct wizard action.""" + entitlement_id = self.env["g2p.entitlement"].create( + { + "partner_id": self.registrant.id, + "initial_amount": 1.0, + "cycle_id": self.cycle.id, + "state": "draft", + "valid_until": fields.Date.add(fields.Date.today(), days=1), + } + ) + self.entitlement = entitlement_id + action = self.entitlement.reset_to_pending() + self.assertEqual(action["res_model"], "spp.reset.pending.entitlement.wizard") + self.assertEqual(action["view_mode"], "form") + self.assertEqual(action["target"], "new") + + def test_16_internal_reset_to_pending(self): + """Test the internal `_reset_to_pending` method.""" + entitlement_id = self.env["g2p.entitlement"].create( + { + "partner_id": self.registrant.id, + "initial_amount": 1.0, + "cycle_id": self.cycle.id, + "state": "draft", + "valid_until": fields.Date.add(fields.Date.today(), days=1), + } + ) + self.entitlement = entitlement_id + self.entitlement.state = "reject" + action = self.entitlement._reset_to_pending() + + self.assertEqual(self.entitlement.state, "pending_validation") + + # Check notification + self.assertEqual(action["type"], "ir.actions.client") + self.assertEqual(action["tag"], "display_notification") + self.assertEqual(action["params"]["type"], "success") + self.assertEqual(action["params"]["message"], "Entitlement Reset to Pending") diff --git a/spp_registry_approval/tests/__init__.py b/spp_registry_approval/tests/__init__.py new file mode 100644 index 000000000..d57d215f9 --- /dev/null +++ b/spp_registry_approval/tests/__init__.py @@ -0,0 +1 @@ +from . import test_res_partner diff --git a/spp_registry_approval/tests/test_res_partner.py b/spp_registry_approval/tests/test_res_partner.py new file mode 100644 index 000000000..44dc2ae5b --- /dev/null +++ b/spp_registry_approval/tests/test_res_partner.py @@ -0,0 +1,287 @@ +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class SppRegistryApprovalTest(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Set context to avoid job queue delay + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Get the security groups + cls.approve_group = cls.env.ref("spp_registry_approval.approve_registry") + cls.reject_group = cls.env.ref("spp_registry_approval.reject_registry") + cls.reset_group = cls.env.ref("spp_registry_approval.reset_to_draft_registry") + + # Create test users with different permissions + cls.user_with_approve = cls.env["res.users"].create( + { + "name": "User with Approve Permission", + "login": "approve_user", + "email": "approve@test.com", + "groups_id": [(6, 0, [cls.approve_group.id])], + } + ) + + cls.user_with_reject = cls.env["res.users"].create( + { + "name": "User with Reject Permission", + "login": "reject_user", + "email": "reject@test.com", + "groups_id": [(6, 0, [cls.reject_group.id])], + } + ) + + cls.user_with_reset = cls.env["res.users"].create( + { + "name": "User with Reset Permission", + "login": "reset_user", + "email": "reset@test.com", + "groups_id": [(6, 0, [cls.reset_group.id])], + } + ) + + cls.user_without_permissions = cls.env["res.users"].create( + { + "name": "User without Permissions", + "login": "no_perms_user", + "email": "noperms@test.com", + "groups_id": [(6, 0, [])], + } + ) + + # Create test registries + cls.registry_draft = cls.env["res.partner"].create( + { + "name": "Test Registry Draft", + "is_registrant": True, + "is_group": False, + } + ) + + cls.registry_approved = cls.env["res.partner"].create( + { + "name": "Test Registry Approved", + "is_registrant": True, + "is_group": False, + "state": "approved", + } + ) + + cls.registry_rejected = cls.env["res.partner"].create( + { + "name": "Test Registry Rejected", + "is_registrant": True, + "is_group": False, + "state": "rejected", + } + ) + + def test_01_default_state(self): + """Test that new registries default to draft state""" + new_registry = self.env["res.partner"].create( + { + "name": "New Test Registry", + "is_registrant": True, + "is_group": False, + } + ) + self.assertEqual( + new_registry.state, + "draft", + "New registries should default to draft state", + ) + + def test_02_approve_registry_with_permission(self): + """Test approve_registry method with proper permissions""" + # Test with user having approve permission + self.registry_draft.with_user(self.user_with_approve).approve_registry() + self.assertEqual( + self.registry_draft.state, + "approved", + "Registry should be approved when user has permission", + ) + + def test_03_approve_registry_without_permission(self): + """Test approve_registry method without proper permissions""" + # Reset to draft first + self.registry_draft.state = "draft" + + # Test with user without approve permission + self.registry_draft.with_user(self.user_without_permissions).approve_registry() + self.assertEqual( + self.registry_draft.state, + "draft", + "Registry should remain draft when user lacks permission", + ) + + def test_04_reject_registry_with_permission(self): + """Test reject_registry method with proper permissions""" + # Test with user having reject permission + self.registry_draft.with_user(self.user_with_reject).reject_registry() + self.assertEqual( + self.registry_draft.state, + "rejected", + "Registry should be rejected when user has permission", + ) + + def test_05_reject_registry_without_permission(self): + """Test reject_registry method without proper permissions""" + # Reset to draft first + self.registry_draft.state = "draft" + + # Test with user without reject permission + self.registry_draft.with_user(self.user_without_permissions).reject_registry() + self.assertEqual( + self.registry_draft.state, + "draft", + "Registry should remain draft when user lacks permission", + ) + + def test_06_reset_to_draft_registry_with_permission(self): + """Test reset_to_draft_registry method with proper permissions""" + # Test with user having reset permission + self.registry_approved.with_user(self.user_with_reset).reset_to_draft_registry() + self.assertEqual( + self.registry_approved.state, + "draft", + "Registry should be reset to draft when user has permission", + ) + + def test_07_reset_to_draft_registry_without_permission(self): + """Test reset_to_draft_registry method without proper permissions""" + # Reset to approved first + self.registry_approved.state = "approved" + + # Test with user without reset permission + self.registry_approved.with_user(self.user_without_permissions).reset_to_draft_registry() + self.assertEqual( + self.registry_approved.state, + "approved", + "Registry should remain approved when user lacks permission", + ) + + def test_08_multiple_records_approval(self): + """Test approve_registry method with multiple records""" + # Create multiple registries + registries = self.env["res.partner"].create( + [ + { + "name": "Registry 1", + "is_registrant": True, + "is_group": False, + "state": "draft", + }, + { + "name": "Registry 2", + "is_registrant": True, + "is_group": False, + "state": "draft", + }, + ] + ) + + # Approve all registries + registries.with_user(self.user_with_approve).approve_registry() + + # Verify all are approved + for registry in registries: + self.assertEqual( + registry.state, + "approved", + f"Registry {registry.name} should be approved", + ) + + def test_09_state_constants(self): + """Test that state constants are properly defined""" + self.assertEqual( + self.env["res.partner"].DRAFT, + "draft", + "DRAFT constant should be 'draft'", + ) + self.assertEqual( + self.env["res.partner"].APPROVED, + "approved", + "APPROVED constant should be 'approved'", + ) + self.assertEqual( + self.env["res.partner"].REJECTED, + "rejected", + "REJECTED constant should be 'rejected'", + ) + + def test_10_edge_case_empty_recordset(self): + """Test methods with empty recordset""" + empty_recordset = self.env["res.partner"].browse([]) + + # These should not raise errors + empty_recordset.with_user(self.user_with_approve).approve_registry() + empty_recordset.with_user(self.user_with_reject).reject_registry() + empty_recordset.with_user(self.user_with_reset).reset_to_draft_registry() + + def test_11_mixed_permissions_user(self): + """Test user with multiple permissions""" + # Create user with multiple permissions + user_multi = self.env["res.users"].create( + { + "name": "User with Multiple Permissions", + "login": "multi_user", + "email": "multi@test.com", + "groups_id": [(6, 0, [self.approve_group.id, self.reject_group.id, self.reset_group.id])], + } + ) + + # Test all operations with multi-permission user + registry = self.env["res.partner"].create( + { + "name": "Multi Test Registry", + "is_registrant": True, + "is_group": False, + "state": "draft", + } + ) + + # Approve + registry.with_user(user_multi).approve_registry() + self.assertEqual(registry.state, "approved") + + # Reject + registry.with_user(user_multi).reject_registry() + self.assertEqual(registry.state, "rejected") + + # Reset to draft + registry.with_user(user_multi).reset_to_draft_registry() + self.assertEqual(registry.state, "draft") + + def test_12_sudo_usage(self): + """Test that sudo() is properly used in methods""" + # This test verifies that the methods use sudo() correctly + # by checking that the state changes are applied even when + # the user doesn't have direct write access to the record + + # Create a registry with restricted access + registry = self.env["res.partner"].create( + { + "name": "Restricted Registry", + "is_registrant": True, + "is_group": False, + "state": "draft", + } + ) + + # Test that sudo() allows the operation to succeed + registry.with_user(self.user_with_approve).approve_registry() + self.assertEqual( + registry.state, + "approved", + "sudo() should allow approval even with restricted access", + )