Skip to content

Commit ba54515

Browse files
committed
[IMP] cetmix_tower_product: add tests
1 parent 55dad01 commit ba54515

File tree

4 files changed

+395
-0
lines changed

4 files changed

+395
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (C) 2025 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from . import test_attribute_sync
5+
from . import test_attribute_value
6+
from . import test_attribute_safety
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright (C) 2025 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo.exceptions import UserError
5+
6+
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
7+
8+
9+
class TestAttributeSafety(TestTowerCommon):
10+
"""
11+
Test create and unlink constraints that protect data integrity
12+
"""
13+
14+
@classmethod
15+
def setUpClass(cls):
16+
super().setUpClass()
17+
18+
# Create a Tower variable and option
19+
cls.tower_variable = cls.env["cx.tower.variable"].create(
20+
{
21+
"name": "Safety Test Variable",
22+
"reference": "safety_test_variable",
23+
"variable_type": "o", # Options type
24+
}
25+
)
26+
27+
cls.tower_option = cls.env["cx.tower.variable.option"].create(
28+
{
29+
"variable_id": cls.tower_variable.id,
30+
"name": "Safety Option",
31+
"value_char": "safety_option_value",
32+
}
33+
)
34+
35+
# Create a Tower-linked attribute
36+
cls.linked_attribute = cls.env["product.attribute"].create(
37+
{
38+
"name": "Linked Safety Attribute",
39+
"tower_variable_id": cls.tower_variable.id,
40+
"auto_sync_tower_values": False,
41+
}
42+
)
43+
44+
# Create a synced attribute value from it
45+
cls.linked_attribute.action_sync_tower_values()
46+
cls.tower_value = cls.env["product.attribute.value"].search(
47+
[
48+
("attribute_id", "=", cls.linked_attribute.id),
49+
("tower_option_id", "=", cls.tower_option.id),
50+
]
51+
)
52+
53+
# Create a standard, non-linked attribute
54+
cls.regular_attribute = cls.env["product.attribute"].create(
55+
{
56+
"name": "Regular Safety Attribute",
57+
}
58+
)
59+
60+
# Create a value for the regular attribute
61+
cls.regular_value = cls.env["product.attribute.value"].create(
62+
{
63+
"name": "Regular Safety Value",
64+
"attribute_id": cls.regular_attribute.id,
65+
}
66+
)
67+
68+
def test_01_forbid_manual_value_creation(self):
69+
"""Test that manual creation of values for Tower-linked attributes
70+
is forbidden"""
71+
with self.assertRaises(UserError):
72+
self.env["product.attribute.value"].create(
73+
{
74+
"name": "Manual Value",
75+
"attribute_id": self.linked_attribute.id,
76+
}
77+
)
78+
79+
def test_02_allow_manual_value_creation_for_non_linked(self):
80+
"""Test that manual creation works for non-Tower-linked attributes"""
81+
new_value = self.env["product.attribute.value"].create(
82+
{
83+
"name": "Manual Regular Value",
84+
"attribute_id": self.regular_attribute.id,
85+
}
86+
)
87+
88+
self.assertTrue(new_value.exists())
89+
self.assertEqual(new_value.name, "Manual Regular Value")
90+
self.assertEqual(new_value.attribute_id, self.regular_attribute)
91+
self.assertFalse(new_value.tower_option_id)
92+
self.assertFalse(new_value.is_from_tower)
93+
94+
def test_03_forbid_unlink_of_tower_value(self):
95+
"""Test that unlinking Tower-synchronized values is forbidden"""
96+
with self.assertRaises(UserError):
97+
self.tower_value.unlink()
98+
99+
self.assertTrue(self.tower_value.exists())
100+
101+
def test_04_allow_unlink_of_regular_value(self):
102+
"""Test that unlinking regular values works normally"""
103+
value_id = self.regular_value.id
104+
105+
self.regular_value.unlink()
106+
107+
remaining_values = self.env["product.attribute.value"].search(
108+
[("id", "=", value_id)]
109+
)
110+
self.assertEqual(len(remaining_values), 0)
111+
112+
def test_05_forbid_manual_value_creation_multi(self):
113+
"""Test create constraint with multiple values in create_multi"""
114+
with self.assertRaises(UserError):
115+
self.env["product.attribute.value"].create(
116+
[
117+
{
118+
"name": "Valid Value 1",
119+
"attribute_id": self.regular_attribute.id,
120+
},
121+
{
122+
"name": "Invalid Value",
123+
"attribute_id": self.linked_attribute.id,
124+
},
125+
{
126+
"name": "Valid Value 2",
127+
"attribute_id": self.regular_attribute.id,
128+
},
129+
]
130+
)
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Copyright (C) 2025 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
5+
6+
7+
class TestAttributeSync(TestTowerCommon):
8+
"""
9+
Test synchronization between Tower variables and product attributes
10+
"""
11+
12+
@classmethod
13+
def setUpClass(cls):
14+
super().setUpClass()
15+
16+
# Create a Tower variable of type 'Options'
17+
cls.tower_variable = cls.env["cx.tower.variable"].create(
18+
{
19+
"name": "Test Product Options",
20+
"reference": "test_product_options",
21+
"variable_type": "o", # Options type
22+
}
23+
)
24+
25+
# Create two options for this variable
26+
cls.option_1 = cls.env["cx.tower.variable.option"].create(
27+
{
28+
"variable_id": cls.tower_variable.id,
29+
"name": "Option 1",
30+
"value_char": "option_1",
31+
}
32+
)
33+
cls.option_2 = cls.env["cx.tower.variable.option"].create(
34+
{
35+
"variable_id": cls.tower_variable.id,
36+
"name": "Option 2",
37+
"value_char": "option_2",
38+
}
39+
)
40+
41+
# Create a product attribute for manual syncing
42+
cls.manual_sync_attribute = cls.env["product.attribute"].create(
43+
{
44+
"name": "Manual Sync Test Attribute",
45+
"tower_variable_id": cls.tower_variable.id,
46+
"auto_sync_tower_values": False,
47+
}
48+
)
49+
50+
# Create a product attribute for automatic syncing
51+
cls.auto_sync_attribute = cls.env["product.attribute"].create(
52+
{
53+
"name": "Auto Sync Test Attribute",
54+
"tower_variable_id": cls.tower_variable.id,
55+
"auto_sync_tower_values": True,
56+
}
57+
)
58+
59+
def test_01_manual_sync_creates_values(self):
60+
"""Test that manual sync creates attribute values correctly"""
61+
# Initial state - no values should exist
62+
initial_count = self.env["product.attribute.value"].search_count(
63+
[("attribute_id", "=", self.manual_sync_attribute.id)]
64+
)
65+
self.assertEqual(initial_count, 0)
66+
67+
# Call action_sync_tower_values
68+
result = self.manual_sync_attribute.action_sync_tower_values()
69+
70+
# Assert that exactly two product.attribute.value records are created
71+
final_count = self.env["product.attribute.value"].search_count(
72+
[("attribute_id", "=", self.manual_sync_attribute.id)]
73+
)
74+
self.assertEqual(final_count, 2)
75+
76+
# Assert each new value is correctly linked to its corresponding Tower option
77+
values = self.env["product.attribute.value"].search(
78+
[("attribute_id", "=", self.manual_sync_attribute.id)]
79+
)
80+
81+
tower_option_ids = values.mapped("tower_option_id").ids
82+
expected_option_ids = [self.option_1.id, self.option_2.id]
83+
self.assertEqual(set(tower_option_ids), set(expected_option_ids))
84+
85+
# Check that names match the option names
86+
value_names = values.mapped("name")
87+
expected_names = ["Option 1", "Option 2"]
88+
self.assertEqual(set(value_names), set(expected_names))
89+
90+
# Verify the result structure
91+
self.assertIn("created", result)
92+
self.assertEqual(len(result["created"]), 2)
93+
94+
def test_02_manual_sync_is_idempotent(self):
95+
"""Test that running manual sync twice doesn't create duplicates"""
96+
# First sync
97+
self.manual_sync_attribute.action_sync_tower_values()
98+
99+
first_count = self.env["product.attribute.value"].search_count(
100+
[("attribute_id", "=", self.manual_sync_attribute.id)]
101+
)
102+
self.assertEqual(first_count, 2)
103+
104+
# Second sync
105+
result = self.manual_sync_attribute.action_sync_tower_values()
106+
107+
second_count = self.env["product.attribute.value"].search_count(
108+
[("attribute_id", "=", self.manual_sync_attribute.id)]
109+
)
110+
self.assertEqual(second_count, 2) # Count should remain the same
111+
112+
# No new values should be created in the second sync
113+
self.assertEqual(len(result["created"]), 0)
114+
115+
def test_03_auto_sync_on_option_creation(self):
116+
"""Test that auto sync triggers when new options are created"""
117+
# First, run initial sync on auto_sync_attribute
118+
self.auto_sync_attribute.action_sync_tower_values()
119+
120+
auto_sync_initial_count = self.env["product.attribute.value"].search_count(
121+
[("attribute_id", "=", self.auto_sync_attribute.id)]
122+
)
123+
self.assertEqual(auto_sync_initial_count, 2)
124+
125+
# Also sync manual attribute to compare
126+
self.manual_sync_attribute.action_sync_tower_values()
127+
128+
manual_sync_initial_count = self.env["product.attribute.value"].search_count(
129+
[("attribute_id", "=", self.manual_sync_attribute.id)]
130+
)
131+
self.assertEqual(manual_sync_initial_count, 2)
132+
133+
# Create a third option for the Tower variable
134+
option_3 = self.env["cx.tower.variable.option"].create(
135+
{
136+
"variable_id": self.tower_variable.id,
137+
"name": "Option 3",
138+
"value_char": "option_3",
139+
}
140+
)
141+
142+
# Assert that auto_sync_attribute now automatically has three values
143+
auto_sync_final_count = self.env["product.attribute.value"].search_count(
144+
[("attribute_id", "=", self.auto_sync_attribute.id)]
145+
)
146+
self.assertEqual(auto_sync_final_count, 3)
147+
148+
# Verify the new value exists and is linked correctly
149+
new_value = self.env["product.attribute.value"].search(
150+
[
151+
("attribute_id", "=", self.auto_sync_attribute.id),
152+
("tower_option_id", "=", option_3.id),
153+
]
154+
)
155+
self.assertEqual(len(new_value), 1)
156+
self.assertEqual(new_value.name, "Option 3")
157+
158+
# Assert that manual_sync_attribute still only has two values
159+
manual_sync_final_count = self.env["product.attribute.value"].search_count(
160+
[("attribute_id", "=", self.manual_sync_attribute.id)]
161+
)
162+
self.assertEqual(manual_sync_final_count, 2)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright (C) 2025 Cetmix OÜ
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
5+
6+
7+
class TestAttributeValue(TestTowerCommon):
8+
"""
9+
Test computed fields and helper methods on product.attribute.value model
10+
"""
11+
12+
@classmethod
13+
def setUpClass(cls):
14+
super().setUpClass()
15+
16+
# Create a Tower variable and option
17+
cls.tower_variable = cls.env["cx.tower.variable"].create(
18+
{
19+
"name": "Test Variable",
20+
"reference": "test_variable",
21+
"variable_type": "o", # Options type
22+
}
23+
)
24+
25+
cls.tower_option = cls.env["cx.tower.variable.option"].create(
26+
{
27+
"variable_id": cls.tower_variable.id,
28+
"name": "Tower Option",
29+
"value_char": "tower_option_value",
30+
}
31+
)
32+
33+
# Create a product attribute linked to Tower
34+
cls.linked_attribute = cls.env["product.attribute"].create(
35+
{
36+
"name": "Linked Attribute",
37+
"tower_variable_id": cls.tower_variable.id,
38+
"auto_sync_tower_values": False,
39+
}
40+
)
41+
42+
# Create a Tower-linked attribute value by syncing
43+
cls.linked_attribute.action_sync_tower_values()
44+
cls.tower_value = cls.env["product.attribute.value"].search(
45+
[
46+
("attribute_id", "=", cls.linked_attribute.id),
47+
("tower_option_id", "=", cls.tower_option.id),
48+
]
49+
)
50+
51+
# Create a standard, non-linked product attribute and value
52+
cls.regular_attribute = cls.env["product.attribute"].create(
53+
{
54+
"name": "Regular Attribute",
55+
}
56+
)
57+
58+
cls.regular_value = cls.env["product.attribute.value"].create(
59+
{
60+
"name": "Regular Value",
61+
"attribute_id": cls.regular_attribute.id,
62+
}
63+
)
64+
65+
def test_01_computed_fields(self):
66+
"""Test computed fields is_from_tower and tower_variable_id"""
67+
# Test Tower-linked value
68+
self.assertTrue(self.tower_value.is_from_tower)
69+
self.assertEqual(self.tower_value.tower_variable_id, self.tower_variable)
70+
71+
# Test regular value
72+
self.assertFalse(self.regular_value.is_from_tower)
73+
self.assertFalse(self.regular_value.tower_variable_id)
74+
75+
def test_02_get_tower_actual_value(self):
76+
"""Test get_tower_actual_value method"""
77+
# Test Tower-linked value returns the Tower option's value_char
78+
tower_actual_value = self.tower_value.get_tower_actual_value()
79+
self.assertEqual(tower_actual_value, "tower_option_value")
80+
81+
# Test regular value returns its own name
82+
regular_actual_value = self.regular_value.get_tower_actual_value()
83+
self.assertEqual(regular_actual_value, "Regular Value")
84+
85+
def test_03_tower_variable_reference_field(self):
86+
"""Test that tower_variable_reference is correctly set"""
87+
# Tower-linked value should have the variable reference
88+
self.assertEqual(self.tower_value.tower_variable_reference, "test_variable")
89+
90+
# Regular value should not have a tower variable reference
91+
self.assertFalse(self.regular_value.tower_variable_reference)
92+
93+
def test_04_tower_option_relationship(self):
94+
"""Test the relationship with Tower option"""
95+
# Tower-linked value should be linked to the correct option
96+
self.assertEqual(self.tower_value.tower_option_id, self.tower_option)
97+
self.assertEqual(self.tower_value.tower_option_id.name, "Tower Option")

0 commit comments

Comments
 (0)