Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spp_drims_sl/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_spp_drims_sl
42 changes: 42 additions & 0 deletions spp_drims_sl/tests/test_spp_drims_sl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Install / data-load sanity tests for spp_drims_sl.

This is a data-only module (Sri Lanka locale configuration for DRIMS) —
it ships seed records but no Python models or methods. The tests below
exercise the install path so CI's per-module coverage matrix has
something to report against, and assert that the headline data records
the rest of the module relies on are actually present after install.
"""

from odoo.tests import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestSppDrimsSl(TransactionCase):
"""Spot-check that the seed data declared in __manifest__.py loaded."""

def test_module_is_installed(self):
module = self.env["ir.module.module"].search([("name", "=", "spp_drims_sl")], limit=1)
self.assertTrue(module, "spp_drims_sl module not registered")
self.assertEqual(
module.state,
"installed",
f"spp_drims_sl expected 'installed', got {module.state}",
)

def test_hazard_category_seed_loaded(self):
"""data/hazard_categories.xml declares at least one category."""
category = self.env.ref("spp_drims_sl.category_natural", raise_if_not_found=False)
self.assertTrue(
category,
"spp_drims_sl.category_natural missing — hazard_categories.xml didn't load",
)

def test_sl_currency_company_config(self):
"""data/company_config.xml activates LKR for the locale."""
currency = self.env.ref("base.LKR", raise_if_not_found=False)
self.assertTrue(currency, "base.LKR currency missing")
self.assertTrue(
currency.active,
"LKR currency expected to be active after spp_drims_sl install",
)
2 changes: 2 additions & 0 deletions spp_hide_menus_base/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_hide_menu
175 changes: 175 additions & 0 deletions spp_hide_menus_base/tests/test_hide_menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Tests for spp_hide_menus_base — hide/show menu visibility logic.

The module patches ``ir.module.module`` to hide a curated list of stock
Odoo menus (Project, Calendar, Stock, ...) from the OpenSPP user group
when an install/upgrade completes. The tests exercise the ``hide_menu``
and ``show_menu`` round-trip on ``spp.hide.menu`` directly so we cover
the model's state transition without depending on a real "Apps install"
flow.
"""

from odoo.tests import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestSppHideMenu(TransactionCase):
"""Exercise the hide / show round-trip on a sample menu."""

@classmethod
def setUpClass(cls):
super().setUpClass()
# Pick any existing menu we can safely toggle in a test transaction.
cls.menu = cls.env["ir.ui.menu"].search([], limit=1)
if not cls.menu:
raise AssertionError("No ir.ui.menu records found to test against")
Comment on lines +22 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Searching for an arbitrary existing menu in the database can lead to flaky tests or unexpected failures if the selected menu already has restricted groups or other custom configurations. Creating a dedicated dummy menu for the test class ensures complete isolation and deterministic test behavior.

Suggested change
# Pick any existing menu we can safely toggle in a test transaction.
cls.menu = cls.env["ir.ui.menu"].search([], limit=1)
if not cls.menu:
raise AssertionError("No ir.ui.menu records found to test against")
# Create a dummy menu to safely toggle in tests without relying on existing database records.
cls.menu = cls.env["ir.ui.menu"].create({"name": "Test Menu"})


def test_module_is_installed(self):
module = self.env["ir.module.module"].search([("name", "=", "spp_hide_menus_base")], limit=1)
self.assertEqual(module.state, "installed")

def test_group_hide_menus_user_seed(self):
"""security/groups.xml must declare the hide-menus-user group."""
group = self.env.ref("spp_hide_menus_base.group_hide_menus_user", raise_if_not_found=False)
self.assertTrue(
group,
"group_hide_menus_user must exist — hide_menu() falls back on it",
)

def test_hide_menu_transition(self):
"""hide_menu() flips state show → hide and snapshots original groups."""
original_groups = self.env["ir.ui.menu"].browse(self.menu.id).group_ids
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since self.menu is already an ir.ui.menu recordset, calling self.env["ir.ui.menu"].browse(self.menu.id) is redundant. You can access group_ids directly from self.menu.

Suggested change
original_groups = self.env["ir.ui.menu"].browse(self.menu.id).group_ids
original_groups = self.menu.group_ids

record = self.env["spp.hide.menu"].create({"menu_id": self.menu.id, "xml_id": "test.hide_menu_target"})
self.assertEqual(record.state, "show")

record.hide_menu()

self.assertEqual(record.state, "hide")
# Original groups were saved on the record so show_menu can restore them.
self.assertEqual(record.default_group_ids, original_groups)

def test_show_menu_restores_original_groups(self):
"""show_menu() restores the snapshot taken at hide time."""
original_groups = self.env["ir.ui.menu"].browse(self.menu.id).group_ids
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, calling self.env["ir.ui.menu"].browse(self.menu.id) here is redundant. You can access group_ids directly from self.menu.

Suggested change
original_groups = self.env["ir.ui.menu"].browse(self.menu.id).group_ids
original_groups = self.menu.group_ids

record = self.env["spp.hide.menu"].create({"menu_id": self.menu.id, "xml_id": "test.hide_menu_target"})
record.hide_menu()
# Menu is now restricted to the hide-menus-user group only.
self.assertNotEqual(self.menu.group_ids, original_groups)

record.show_menu()
self.assertEqual(record.state, "show")
self.assertEqual(self.menu.group_ids, original_groups)

def test_hide_menu_noop_when_already_hidden(self):
"""Calling hide_menu twice doesn't change state or groups again."""
record = self.env["spp.hide.menu"].create({"menu_id": self.menu.id, "xml_id": "test.hide_menu_target"})
record.hide_menu()
snapshot = record.default_group_ids
record.hide_menu() # second call — guarded by state == "show"
self.assertEqual(record.state, "hide")
# Original snapshot must not be overwritten by the second call.
self.assertEqual(record.default_group_ids, snapshot)

def test_menu_app_catalog_is_well_formed(self):
"""ir.module.module.MENU_APP entries must point to a menu xml_id."""
IrModuleModule = self.env["ir.module.module"]
for module_name, info in IrModuleModule.MENU_APP.items():
self.assertIn(
"menu_xml_id",
info,
f"MENU_APP[{module_name!r}] missing required 'menu_xml_id'",
)
self.assertTrue(
info["menu_xml_id"],
f"MENU_APP[{module_name!r}].menu_xml_id is empty",
)

def test_hide_menus_processes_catalog(self):
"""``ir.module.module.hide_menus()`` walks MENU_APP and creates a
``spp.hide.menu`` record (state=hide) for every entry whose menu
xml_id resolves in the current DB.
"""
IrModuleModule = self.env["ir.module.module"]
HideMenu = self.env["spp.hide.menu"]

# Figure out which catalog entries are actually resolvable here —
# most stock Odoo modules in MENU_APP (mail, contacts, ...) are
# present in any test DB, but a few (mass_mailing, survey, ...)
# may not be installed.
resolvable = []
for module_name, info in IrModuleModule.MENU_APP.items():
menu = self.env.ref(info["menu_xml_id"], raise_if_not_found=False)
module = IrModuleModule.search([("name", "=", module_name)], limit=1)
if menu and module:
resolvable.append((module_name, menu.id))

if not resolvable:
self.skipTest("No MENU_APP entries are resolvable in this test DB")

# Wipe any pre-existing spp.hide.menu so the test's assertions are
# clearly about hide_menus()'s effect, not the install hook.
HideMenu.search([]).unlink()

IrModuleModule.hide_menus()

for module_name, menu_id in resolvable:
record = HideMenu.search([("menu_id", "=", menu_id)], limit=1)
self.assertTrue(
record,
f"hide_menus() didn't create a spp.hide.menu for {module_name!r}",
)
self.assertEqual(
record.state,
"hide",
f"spp.hide.menu for {module_name!r} expected state=hide, got {record.state}",
)

def test_hide_menus_is_idempotent(self):
"""Calling hide_menus() twice doesn't double-hide already-hidden menus.

After the first pass every resolvable entry is in state=hide. A
second pass must leave them in state=hide (the inner guard
``elif hidden_menus.state == "show"`` skips them).
"""
IrModuleModule = self.env["ir.module.module"]
HideMenu = self.env["spp.hide.menu"]

HideMenu.search([]).unlink()
IrModuleModule.hide_menus()
after_first = HideMenu.search([])
self.assertTrue(
after_first,
"hide_menus() didn't create any records — nothing to check idempotency against",
)

IrModuleModule.hide_menus()
after_second = HideMenu.search([])
# No duplicates created and every record stayed in state=hide.
self.assertEqual(set(after_first.ids), set(after_second.ids))
for record in after_second:
self.assertEqual(record.state, "hide")

def test_hide_menus_skips_unknown_modules(self):
"""An ir.module.module record whose name isn't in MENU_APP must be
ignored by hide_menus() — no spp.hide.menu record is created for it.
"""
IrModuleModule = self.env["ir.module.module"]
HideMenu = self.env["spp.hide.menu"]

# ``base`` is always installed and is NOT in MENU_APP.
self.assertNotIn("base", IrModuleModule.MENU_APP)

before = HideMenu.search([]).ids
IrModuleModule.hide_menus()
after = HideMenu.search([]).ids

# Whatever new records appeared, none should belong to the ``base`` menu.
new_ids = set(after) - set(before)
for record in HideMenu.browse(list(new_ids)):
self.assertNotEqual(
record.menu_id.id,
self.env.ref("base.menu_administration").id
if self.env.ref("base.menu_administration", raise_if_not_found=False)
else 0,
"hide_menus() shouldn't touch base.menu_administration",
)
2 changes: 2 additions & 0 deletions spp_indicator_studio/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_spp_indicator_studio
55 changes: 55 additions & 0 deletions spp_indicator_studio/tests/test_spp_indicator_studio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Install / view-load sanity tests for spp_indicator_studio.

This is a UI-bridge module — it ships act_window actions and form/list
views for ``spp.indicator`` and ``spp.indicator.category`` but no Python
models or methods of its own. The tests verify the install path and that
the headline view + action records loaded.
"""

from odoo.tests import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestSppIndicatorStudio(TransactionCase):
def test_module_is_installed(self):
module = self.env["ir.module.module"].search([("name", "=", "spp_indicator_studio")], limit=1)
self.assertTrue(module, "spp_indicator_studio not registered")
self.assertEqual(
module.state,
"installed",
f"spp_indicator_studio expected 'installed', got {module.state}",
)

def test_indicator_views_loaded(self):
"""views/indicator_views.xml declares list/form/kanban/action records."""
for xml_id in (
"spp_indicator_studio.spp_statistic_view_list",
"spp_indicator_studio.spp_statistic_view_form",
"spp_indicator_studio.spp_statistic_view_kanban",
"spp_indicator_studio.spp_statistic_action",
):
with self.subTest(record=xml_id):
self.assertTrue(
self.env.ref(xml_id, raise_if_not_found=False),
f"{xml_id} missing — indicator_views.xml didn't load",
)

def test_indicator_category_views_loaded(self):
"""views/indicator_category_views.xml declares list/form/action records."""
for xml_id in (
"spp_indicator_studio.spp_metric_category_view_list",
"spp_indicator_studio.spp_metric_category_view_form",
"spp_indicator_studio.spp_metric_category_action",
):
with self.subTest(record=xml_id):
self.assertTrue(
self.env.ref(xml_id, raise_if_not_found=False),
f"{xml_id} missing — indicator_category_views.xml didn't load",
)

def test_indicator_action_targets_spp_indicator(self):
"""The act_window must point at the spp.indicator model."""
action = self.env.ref("spp_indicator_studio.spp_statistic_action", raise_if_not_found=False)
self.assertTrue(action)
self.assertEqual(action.res_model, "spp.indicator")
2 changes: 2 additions & 0 deletions spp_starter_farmer_registry/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_spp_starter_farmer_registry
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Install / bundle-sanity tests for spp_starter_farmer_registry.

This is a meta-module / bundle — it only declares ``depends`` plus a
single ``ir.config_parameter`` seed. The tests verify that:

- the bundle itself installs cleanly,
- every farmer-registry dependency it bundles is reachable + installed,
- the seed config parameter loaded.
"""

from odoo.tests import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestSppStarterFarmerRegistry(TransactionCase):
"""Spot-check that the bundle declared in __manifest__.py installs cleanly."""

BUNDLE_DEPS = (
"spp_starter_social_registry",
"spp_farmer_registry",
"spp_farmer_registry_vocabularies",
"spp_land_record",
"spp_irrigation",
"spp_gis",
"spp_programs",
)

def test_module_is_installed(self):
module = self.env["ir.module.module"].search([("name", "=", "spp_starter_farmer_registry")], limit=1)
self.assertTrue(module, "spp_starter_farmer_registry not registered")
self.assertEqual(
module.state,
"installed",
f"spp_starter_farmer_registry expected 'installed', got {module.state}",
)

def test_bundle_dependencies_installed(self):
"""Every module in ``depends`` is itself installed."""
Module = self.env["ir.module.module"]
for name in self.BUNDLE_DEPS:
with self.subTest(dep=name):
module = Module.search([("name", "=", name)], limit=1)
self.assertTrue(module, f"Bundle dep {name!r} not registered")
self.assertEqual(
module.state,
"installed",
f"Bundle dep {name!r} expected 'installed', got {module.state}",
)

def test_smallholder_threshold_param_loaded(self):
"""data/config_parameters.xml declares the smallholder threshold."""
param = self.env.ref(
"spp_starter_farmer_registry.config_smallholder_threshold",
raise_if_not_found=False,
)
self.assertTrue(
param,
"config_smallholder_threshold missing — config_parameters.xml didn't load",
)
Loading