Skip to content

Commit 0d9468b

Browse files
fix: address review findings and add tests for spp_cel_registry_search
- Change auto_install to False (feature, not bridge module) - Change category from OpenSPP/Core to OpenSPP (consistent with siblings) - Remove duplicate static class attribute on result buttons - Add icon to Disabled badge for accessibility (colorblind users) - Remove incorrect role="status" from static empty state - Replace non-universal example expression (r.monthly_income) - Fix error state showing misleading "0 results" instead of resetting - Improve catch error message to not blame expression for server errors - Fix invisible hover effect (same color as page background) - Update DESCRIPTION.md and regenerate README.rst - Add 12 tests covering security groups, client action, menu, visibility
1 parent 9c2376e commit 0d9468b

9 files changed

Lines changed: 182 additions & 17 deletions

File tree

spp_cel_registry_search/README.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ CEL Registry Search
2424

2525
Advanced search interface for the registry using CEL (Common Expression
2626
Language) expressions. Provides a dedicated portal where users can write
27-
CEL queries to filter registrants based on demographics, income,
28-
eligibility criteria, or custom data fields. Auto-installs when
29-
``spp_cel_widget`` is present.
27+
CEL queries to filter registrants based on demographics, eligibility
28+
criteria, or custom data fields.
3029

3130
Key Capabilities
3231
~~~~~~~~~~~~~~~~

spp_cel_registry_search/__manifest__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"development_status": "Beta",
88
"author": "OpenSPP.org, OpenSPP Community",
99
"website": "https://github.com/OpenSPP/OpenSPP2",
10-
"category": "OpenSPP/Core",
10+
"category": "OpenSPP",
1111
"depends": [
1212
"spp_registry",
1313
"spp_cel_domain",
@@ -25,6 +25,6 @@
2525
],
2626
},
2727
"installable": True,
28-
"auto_install": True,
28+
"auto_install": False,
2929
"application": False,
3030
}

spp_cel_registry_search/readme/DESCRIPTION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Advanced search interface for the registry using CEL (Common Expression Language) expressions. Provides a dedicated portal where users can write CEL queries to filter registrants based on demographics, income, eligibility criteria, or custom data fields. Auto-installs when `spp_cel_widget` is present.
1+
Advanced search interface for the registry using CEL (Common Expression Language) expressions. Provides a dedicated portal where users can write CEL queries to filter registrants based on demographics, eligibility criteria, or custom data fields.
22

33
### Key Capabilities
44

spp_cel_registry_search/static/description/index.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,8 @@ <h1 class="title">CEL Registry Search</h1>
372372
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_cel_registry_search"><img alt="OpenSPP/OpenSPP2" src="https://img.shields.io/badge/github-OpenSPP%2FOpenSPP2-lightgray.png?logo=github" /></a></p>
373373
<p>Advanced search interface for the registry using CEL (Common Expression
374374
Language) expressions. Provides a dedicated portal where users can write
375-
CEL queries to filter registrants based on demographics, income,
376-
eligibility criteria, or custom data fields. Auto-installs when
377-
<tt class="docutils literal">spp_cel_widget</tt> is present.</p>
375+
CEL queries to filter registrants based on demographics, eligibility
376+
criteria, or custom data fields.</p>
378377
<div class="section" id="key-capabilities">
379378
<h1>Key Capabilities</h1>
380379
<ul class="simple">

spp_cel_registry_search/static/src/css/cel_search_portal.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
}
3737

3838
.o_cel_search_portal .list-group-item:hover {
39-
background-color: #f8f9fa;
39+
background-color: #e9ecef;
4040
}
4141

4242
/* Empty state */

spp_cel_registry_search/static/src/js/cel_search_portal.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export class CelSearchPortal extends Component {
117117

118118
if (result.error) {
119119
this.notification.add(result.error, {type: "danger"});
120+
this.state.hasSearched = false;
120121
this.state.results = [];
121122
this.state.totalCount = 0;
122123
return;
@@ -130,9 +131,13 @@ export class CelSearchPortal extends Component {
130131
this.state.hasMoreResults = count > SEARCH_RESULT_LIMIT;
131132
} catch (error) {
132133
console.error("[CelSearchPortal] Search error:", error);
133-
this.notification.add(_t("Search failed. Please check your expression."), {
134-
type: "danger",
135-
});
134+
this.notification.add(
135+
_t("Search failed. Please try again or check your expression."),
136+
{
137+
type: "danger",
138+
}
139+
);
140+
this.state.hasSearched = false;
136141
this.state.results = [];
137142
this.state.totalCount = 0;
138143
} finally {

spp_cel_registry_search/static/src/xml/cel_search_portal.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@
136136
>
137137
<button
138138
type="button"
139-
class="list-group-item list-group-item-action d-flex align-items-center text-start"
140139
t-attf-class="list-group-item list-group-item-action d-flex align-items-center text-start {{ result.disabled ? 'text-muted opacity-75' : '' }}"
141140
t-on-click="() => this.openRegistrant(result.id, result.is_group)"
142141
role="listitem"
@@ -148,7 +147,10 @@
148147
<span
149148
t-if="result.disabled"
150149
class="badge bg-warning ms-2"
151-
>Disabled</span>
150+
><i
151+
class="fa fa-ban me-1"
152+
aria-hidden="true"
153+
/>Disabled</span>
152154
</h6>
153155
<small class="text-muted">
154156
<t
@@ -198,7 +200,7 @@
198200

199201
<!-- Empty State (no search yet) -->
200202
<t t-else="">
201-
<div class="text-center py-5" role="status">
203+
<div class="text-center py-5">
202204
<i
203205
class="fa fa-code fa-4x text-muted mb-3"
204206
aria-hidden="true"
@@ -216,7 +218,7 @@
216218
>age_years(r.birthdate) >= 18</code> - Adults 18+</li>
217219
<li><code>is_female(r.gender_id)</code> - Females</li>
218220
<li><code
219-
>r.monthly_income &lt; 2500</code> - Low income</li>
221+
>r.registration_date > "2024-01-01"</code> - Recent registrations</li>
220222
</ul>
221223
</div>
222224
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_cel_registry_search
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
"""
4+
Tests for spp_cel_registry_search module.
5+
6+
Validates:
7+
- Module installation
8+
- Security group hierarchy and implied permissions
9+
- Client action configuration
10+
- Menu item configuration and visibility by role
11+
"""
12+
13+
from odoo import Command
14+
from odoo.tests import TransactionCase, tagged
15+
16+
17+
@tagged("post_install", "-at_install")
18+
class TestCelRegistrySearch(TransactionCase):
19+
"""Test security groups, client action, and menu for CEL Registry Search."""
20+
21+
@classmethod
22+
def setUpClass(cls):
23+
super().setUpClass()
24+
25+
# Security groups
26+
cls.group_cel_search_user = cls.env.ref("spp_cel_registry_search.group_cel_search_user")
27+
cls.group_registry_viewer = cls.env.ref("spp_registry.group_registry_viewer")
28+
cls.group_registry_officer = cls.env.ref("spp_registry.group_registry_officer")
29+
30+
# Client action
31+
cls.action = cls.env.ref("spp_cel_registry_search.action_cel_search_portal")
32+
33+
# Menu item
34+
cls.menu = cls.env.ref("spp_cel_registry_search.menu_cel_search_portal")
35+
36+
# --- Security Group Tests ---
37+
38+
def test_group_cel_search_user_exists(self):
39+
"""group_cel_search_user should exist after installation."""
40+
self.assertTrue(self.group_cel_search_user)
41+
self.assertEqual(self.group_cel_search_user.name, "CEL Search User")
42+
43+
def test_group_cel_search_user_implies_registry_viewer(self):
44+
"""group_cel_search_user should imply group_registry_viewer."""
45+
implied_ids = self.group_cel_search_user.implied_ids.ids
46+
self.assertIn(
47+
self.group_registry_viewer.id,
48+
implied_ids,
49+
"group_cel_search_user should imply group_registry_viewer",
50+
)
51+
52+
def test_registry_officer_implies_cel_search_user(self):
53+
"""group_registry_officer should imply group_cel_search_user."""
54+
implied_ids = self.group_registry_officer.implied_ids.ids
55+
self.assertIn(
56+
self.group_cel_search_user.id,
57+
implied_ids,
58+
"group_registry_officer should imply group_cel_search_user",
59+
)
60+
61+
# --- Client Action Tests ---
62+
63+
def test_client_action_exists(self):
64+
"""Client action for CEL Search Portal should exist."""
65+
self.assertTrue(self.action)
66+
self.assertEqual(self.action.name, "Advanced Search")
67+
68+
def test_client_action_tag(self):
69+
"""Client action should have the correct tag matching the JS component."""
70+
self.assertEqual(self.action.tag, "spp_cel_registry_search.cel_search_portal")
71+
72+
def test_client_action_path(self):
73+
"""Client action should have the correct pretty URL path."""
74+
self.assertEqual(self.action.path, "registry-cel")
75+
76+
# --- Menu Item Tests ---
77+
78+
def test_menu_exists(self):
79+
"""Menu item for CEL Search Portal should exist."""
80+
self.assertTrue(self.menu)
81+
self.assertEqual(self.menu.name, "Advanced Search")
82+
83+
def test_menu_parent(self):
84+
"""Menu should be parented under the registry root menu."""
85+
registry_root = self.env.ref("spp_registry.spp_main_menu_root")
86+
self.assertEqual(
87+
self.menu.parent_id.id,
88+
registry_root.id,
89+
"Menu should be under Registry root menu",
90+
)
91+
92+
def test_menu_restricted_to_cel_search_group(self):
93+
"""Menu should be restricted to group_cel_search_user."""
94+
self.assertIn(
95+
self.group_cel_search_user,
96+
self.menu.group_ids,
97+
"Menu should be restricted to group_cel_search_user",
98+
)
99+
100+
# --- Menu Visibility by Role ---
101+
102+
def test_menu_visible_to_cel_search_user(self):
103+
"""A user with group_cel_search_user should see the menu."""
104+
user = self.env["res.users"].create(
105+
{
106+
"name": "Test CEL Search User",
107+
"login": "test_cel_search_user",
108+
"group_ids": [
109+
Command.link(self.env.ref("base.group_user").id),
110+
Command.link(self.group_cel_search_user.id),
111+
],
112+
}
113+
)
114+
visible_menus = self.env["ir.ui.menu"].with_user(user).search([])
115+
self.assertIn(
116+
self.menu,
117+
visible_menus,
118+
"Menu should be visible to CEL Search users",
119+
)
120+
121+
def test_menu_visible_to_registry_officer(self):
122+
"""A registry officer should see the menu via implied group."""
123+
user = self.env["res.users"].create(
124+
{
125+
"name": "Test Registry Officer",
126+
"login": "test_registry_officer_cel",
127+
"group_ids": [
128+
Command.link(self.env.ref("base.group_user").id),
129+
Command.link(self.group_registry_officer.id),
130+
],
131+
}
132+
)
133+
# Verify the officer has the CEL search group via implication
134+
self.assertTrue(
135+
user.has_group("spp_cel_registry_search.group_cel_search_user"),
136+
"Registry officer should have CEL Search group via implication",
137+
)
138+
visible_menus = self.env["ir.ui.menu"].with_user(user).search([])
139+
self.assertIn(
140+
self.menu,
141+
visible_menus,
142+
"Menu should be visible to registry officers",
143+
)
144+
145+
def test_base_user_does_not_have_cel_search_group(self):
146+
"""A base user without explicit assignment should not have the CEL Search group."""
147+
user = self.env["res.users"].create(
148+
{
149+
"name": "Test Base User",
150+
"login": "test_base_user_cel",
151+
"group_ids": [
152+
Command.link(self.env.ref("base.group_user").id),
153+
],
154+
}
155+
)
156+
self.assertFalse(
157+
user.has_group("spp_cel_registry_search.group_cel_search_user"),
158+
"Base user should not have CEL Search group",
159+
)

0 commit comments

Comments
 (0)