Skip to content

Commit 6bc448e

Browse files
test: add tests for 9 modules to reach 90%+ coverage
Add test suites for spp_case_demo (108 tests), spp_grm_case_link (78 tests), spp_grm_programs (42 tests), spp_grm_registry (35 tests), spp_case_session (29 tests), spp_case_registry (32 tests), spp_case_entitlements (32 tests), spp_case_graduation (22 tests), and expand spp_case_programs (6 → 16 tests).
1 parent a954dae commit 6bc448e

25 files changed

Lines changed: 8525 additions & 0 deletions

spp_case_demo/tests/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
from . import test_case_demo_stories
4+
from . import test_generate_cases
5+
from . import test_case_demo_wizard
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
"""Tests for case_demo_stories module - pure Python, no Odoo model access needed."""
4+
5+
from odoo.tests import TransactionCase, tagged
6+
7+
from ..models.case_demo_stories import (
8+
BACKGROUND_CASES,
9+
CASE_DEMO_STORIES,
10+
RESERVED_CASE_NAMES,
11+
get_all_case_stories,
12+
get_background_case_stories,
13+
get_case_story_by_id,
14+
get_main_case_stories,
15+
)
16+
17+
18+
@tagged("post_install", "-at_install")
19+
class TestCaseDemoStories(TransactionCase):
20+
"""Tests for the demo stories data module."""
21+
22+
def test_case_demo_stories_list_not_empty(self):
23+
"""The main stories list should have entries."""
24+
self.assertTrue(CASE_DEMO_STORIES, "CASE_DEMO_STORIES must not be empty")
25+
26+
def test_background_cases_list_not_empty(self):
27+
"""The background cases list should have entries."""
28+
self.assertTrue(BACKGROUND_CASES, "BACKGROUND_CASES must not be empty")
29+
30+
def test_reserved_case_names_not_empty(self):
31+
"""Reserved names list should have entries."""
32+
self.assertTrue(RESERVED_CASE_NAMES, "RESERVED_CASE_NAMES must not be empty")
33+
34+
def test_each_main_story_has_required_keys(self):
35+
"""Every main story must have id, case_name, case_profile, and journey."""
36+
required_keys = {"id", "case_name", "case_profile", "journey"}
37+
for story in CASE_DEMO_STORIES:
38+
missing = required_keys - set(story.keys())
39+
self.assertFalse(
40+
missing,
41+
f"Story '{story.get('case_name', '?')}' is missing keys: {missing}",
42+
)
43+
44+
def test_each_story_journey_is_list(self):
45+
"""Every story's journey must be a list."""
46+
for story in get_all_case_stories():
47+
self.assertIsInstance(
48+
story["journey"],
49+
list,
50+
f"Journey for '{story.get('case_name', '?')}' must be a list",
51+
)
52+
53+
def test_each_story_case_profile_is_dict(self):
54+
"""Every story's case_profile must be a dict."""
55+
for story in get_all_case_stories():
56+
self.assertIsInstance(
57+
story["case_profile"],
58+
dict,
59+
f"case_profile for '{story.get('case_name', '?')}' must be a dict",
60+
)
61+
62+
def test_story_ids_are_unique(self):
63+
"""All story IDs must be unique."""
64+
all_ids = [s["id"] for s in get_all_case_stories()]
65+
self.assertEqual(
66+
len(all_ids),
67+
len(set(all_ids)),
68+
"Duplicate story IDs detected",
69+
)
70+
71+
def test_get_main_case_stories_returns_main_list(self):
72+
"""get_main_case_stories should return CASE_DEMO_STORIES."""
73+
result = get_main_case_stories()
74+
self.assertEqual(result, CASE_DEMO_STORIES)
75+
76+
def test_get_background_case_stories_returns_background_list(self):
77+
"""get_background_case_stories should return BACKGROUND_CASES."""
78+
result = get_background_case_stories()
79+
self.assertEqual(result, BACKGROUND_CASES)
80+
81+
def test_get_all_case_stories_combines_both_lists(self):
82+
"""get_all_case_stories should combine main and background stories."""
83+
all_stories = get_all_case_stories()
84+
expected_count = len(CASE_DEMO_STORIES) + len(BACKGROUND_CASES)
85+
self.assertEqual(len(all_stories), expected_count)
86+
87+
def test_get_all_case_stories_main_stories_first(self):
88+
"""Main stories should appear before background stories in the combined list."""
89+
all_stories = get_all_case_stories()
90+
main_count = len(CASE_DEMO_STORIES)
91+
self.assertEqual(all_stories[:main_count], CASE_DEMO_STORIES)
92+
self.assertEqual(all_stories[main_count:], BACKGROUND_CASES)
93+
94+
def test_get_case_story_by_id_found(self):
95+
"""get_case_story_by_id should return the correct story when it exists."""
96+
# Use the first story in main stories as a known ID
97+
first_story = CASE_DEMO_STORIES[0]
98+
result = get_case_story_by_id(first_story["id"])
99+
self.assertEqual(result, first_story)
100+
101+
def test_get_case_story_by_id_background_story(self):
102+
"""get_case_story_by_id should also find background case stories."""
103+
first_background = BACKGROUND_CASES[0]
104+
result = get_case_story_by_id(first_background["id"])
105+
self.assertEqual(result, first_background)
106+
107+
def test_get_case_story_by_id_not_found_returns_none(self):
108+
"""get_case_story_by_id should return None for an unknown ID."""
109+
result = get_case_story_by_id("this_id_does_not_exist_xyz")
110+
self.assertIsNone(result)
111+
112+
def test_santos_family_support_story_exists(self):
113+
"""The Santos Family Support story must exist by its known ID."""
114+
story = get_case_story_by_id("santos_family_support")
115+
self.assertIsNotNone(story)
116+
self.assertEqual(story["case_name"], "Santos Family Support")
117+
118+
def test_dela_cruz_emergency_story_exists(self):
119+
"""The Dela Cruz Emergency story must exist."""
120+
story = get_case_story_by_id("dela_cruz_emergency")
121+
self.assertIsNotNone(story)
122+
123+
def test_garcia_elder_care_story_exists(self):
124+
"""The Garcia Elder Care story must exist."""
125+
story = get_case_story_by_id("garcia_elder_care")
126+
self.assertIsNotNone(story)
127+
128+
def test_mendoza_child_protection_story_exists(self):
129+
"""The Mendoza Child Protection story must exist."""
130+
story = get_case_story_by_id("mendoza_child_protection")
131+
self.assertIsNotNone(story)
132+
133+
def test_hassan_resettlement_story_exists(self):
134+
"""The Hassan Resettlement story must exist."""
135+
story = get_case_story_by_id("hassan_resettlement")
136+
self.assertIsNotNone(story)
137+
138+
def test_morales_household_crisis_story_exists(self):
139+
"""The Morales Household Crisis story must exist."""
140+
story = get_case_story_by_id("morales_household_crisis")
141+
self.assertIsNotNone(story)
142+
143+
def test_martinez_disability_support_story_exists(self):
144+
"""The Martinez Disability Support story must exist."""
145+
story = get_case_story_by_id("martinez_disability_support")
146+
self.assertIsNotNone(story)
147+
148+
def test_al_rahman_family_assessment_story_exists(self):
149+
"""The Al-Rahman Family Assessment story must exist."""
150+
story = get_case_story_by_id("al_rahman_family_assessment")
151+
self.assertIsNotNone(story)
152+
153+
def test_said_family_support_story_exists(self):
154+
"""The Said Family Support story must exist."""
155+
story = get_case_story_by_id("said_family_support")
156+
self.assertIsNotNone(story)
157+
158+
def test_story_journey_actions_are_strings(self):
159+
"""Every journey step must have an action key that is a string."""
160+
for story in get_all_case_stories():
161+
for step in story["journey"]:
162+
self.assertIn("action", step, f"Journey step missing 'action' in story {story['id']}")
163+
self.assertIsInstance(step["action"], str)
164+
165+
def test_story_journey_days_back_non_negative(self):
166+
"""Journey steps that specify days_back must have a non-negative value."""
167+
for story in get_all_case_stories():
168+
for step in story["journey"]:
169+
if "days_back" in step:
170+
self.assertGreaterEqual(
171+
step["days_back"],
172+
0,
173+
f"days_back must be >= 0 in story '{story['id']}'",
174+
)
175+
176+
def test_santos_story_has_close_case_action(self):
177+
"""The Santos story should include a close_case journey step."""
178+
story = get_case_story_by_id("santos_family_support")
179+
actions = [step["action"] for step in story["journey"]]
180+
self.assertIn("close_case", actions)
181+
182+
def test_background_pending_intake_story(self):
183+
"""The pending_intake background story must exist."""
184+
story = get_case_story_by_id("pending_intake")
185+
self.assertIsNotNone(story)
186+
187+
def test_background_assessment_phase_story(self):
188+
"""The assessment_phase background story must exist."""
189+
story = get_case_story_by_id("assessment_phase")
190+
self.assertIsNotNone(story)
191+
192+
def test_background_closed_unsuccessful_story(self):
193+
"""The closed_unsuccessful background story must exist and have a close_case step."""
194+
story = get_case_story_by_id("closed_unsuccessful")
195+
self.assertIsNotNone(story)
196+
actions = [step["action"] for step in story["journey"]]
197+
self.assertIn("close_case", actions)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
"""Tests for spp.case.demo.wizard model."""
4+
5+
from odoo.tests import TransactionCase, tagged
6+
7+
8+
@tagged("post_install", "-at_install")
9+
class TestCaseDemoWizard(TransactionCase):
10+
"""Tests for the spp.case.demo.wizard transient model."""
11+
12+
@classmethod
13+
def setUpClass(cls):
14+
super().setUpClass()
15+
16+
Stage = cls.env["spp.case.stage"]
17+
stage_data = [
18+
("Intake", "intake", 10, False),
19+
("Closed", "closure", 60, True),
20+
]
21+
for name, phase, sequence, is_closed in stage_data:
22+
if not Stage.search([("phase", "=", phase)], limit=1):
23+
Stage.create({"name": name, "phase": phase, "sequence": sequence, "is_closed": is_closed})
24+
25+
CaseType = cls.env["spp.case.type"]
26+
if not CaseType.search([("name", "=", "General Support")], limit=1):
27+
CaseType.create({"name": "General Support", "code": "GNWZ"})
28+
29+
# partner_id is required on spp.case; always provide a registrant
30+
cls.beneficiary = (
31+
cls.env["res.partner"]
32+
.with_context(tracking_disable=True)
33+
.create({"name": "Wizard Test Beneficiary", "is_registrant": True})
34+
)
35+
36+
def _make_wizard(self, **kwargs):
37+
defaults = {
38+
"name": "Wizard Test",
39+
"number_of_cases": 2,
40+
"include_stories": False,
41+
"cases_days_back": 30,
42+
"link_to_beneficiaries": True,
43+
"percentage_with_plans": 0.0,
44+
"percentage_with_visits": 0.0,
45+
"percentage_with_notes": 0.0,
46+
"percentage_closed": 0.0,
47+
}
48+
defaults.update(kwargs)
49+
return self.env["spp.case.demo.wizard"].create(defaults)
50+
51+
def test_wizard_inherits_generator(self):
52+
"""spp.case.demo.wizard should inherit spp.case.demo.generator."""
53+
wizard = self._make_wizard()
54+
# Verify the wizard can access all generator fields
55+
self.assertEqual(wizard.number_of_cases, 2)
56+
self.assertFalse(wizard.include_stories)
57+
58+
def test_wizard_generate_cases_returns_action(self):
59+
"""generate_cases called from wizard should return a valid window action."""
60+
wizard = self._make_wizard(number_of_cases=2)
61+
result = wizard.generate_cases()
62+
self.assertEqual(result["type"], "ir.actions.act_window")
63+
self.assertEqual(result["res_model"], "spp.case")
64+
65+
def test_wizard_create_and_read(self):
66+
"""Wizard records should be creatable and readable."""
67+
wizard = self._make_wizard()
68+
self.assertTrue(wizard.id)
69+
self.assertEqual(wizard.name, "Wizard Test")
70+
71+
def test_wizard_model_name(self):
72+
"""The wizard model name should be spp.case.demo.wizard."""
73+
wizard = self._make_wizard()
74+
self.assertEqual(wizard._name, "spp.case.demo.wizard")
75+
76+
def test_wizard_description(self):
77+
"""The wizard model description should be set."""
78+
wizard = self._make_wizard()
79+
self.assertTrue(wizard._description)
80+
81+
def test_wizard_shares_generator_methods(self):
82+
"""Wizard should have access to all generator methods via inheritance."""
83+
wizard = self._make_wizard()
84+
# Check that inherited helper methods are accessible
85+
self.assertTrue(hasattr(wizard, "generate_cases"))
86+
self.assertTrue(hasattr(wizard, "_get_available_beneficiaries"))
87+
self.assertTrue(hasattr(wizard, "_get_or_create_case_type"))
88+
self.assertTrue(hasattr(wizard, "_get_random_case_type"))
89+
self.assertTrue(hasattr(wizard, "_get_random_stage"))
90+
self.assertTrue(hasattr(wizard, "_determine_stage_from_journey"))
91+
self.assertTrue(hasattr(wizard, "_find_or_create_client"))
92+
self.assertTrue(hasattr(wizard, "_get_or_create_service"))
93+
self.assertTrue(hasattr(wizard, "_create_random_case"))
94+
self.assertTrue(hasattr(wizard, "_add_random_plan"))
95+
self.assertTrue(hasattr(wizard, "_add_random_visits"))
96+
self.assertTrue(hasattr(wizard, "_add_random_notes"))
97+
self.assertTrue(hasattr(wizard, "_close_random_case"))
98+
self.assertTrue(hasattr(wizard, "_generate_demo_stories"))
99+
self.assertTrue(hasattr(wizard, "_create_case_from_story"))
100+
self.assertTrue(hasattr(wizard, "_process_case_journey"))
101+
102+
def test_wizard_generates_cases_with_stories(self):
103+
"""Wizard can generate cases including demo stories."""
104+
wizard = self._make_wizard(
105+
number_of_cases=2,
106+
include_stories=True,
107+
)
108+
result = wizard.generate_cases()
109+
self.assertEqual(result["type"], "ir.actions.act_window")
110+
case_ids = result["domain"][0][2]
111+
self.assertTrue(len(case_ids) >= 1)
112+
113+
def test_wizard_percentage_fields_accessible(self):
114+
"""All percentage distribution fields should be accessible on the wizard."""
115+
wizard = self._make_wizard(
116+
percentage_with_plans=50.0,
117+
percentage_with_visits=60.0,
118+
percentage_with_notes=70.0,
119+
percentage_closed=30.0,
120+
)
121+
self.assertAlmostEqual(wizard.percentage_with_plans, 50.0)
122+
self.assertAlmostEqual(wizard.percentage_with_visits, 60.0)
123+
self.assertAlmostEqual(wizard.percentage_with_notes, 70.0)
124+
self.assertAlmostEqual(wizard.percentage_closed, 30.0)

0 commit comments

Comments
 (0)