Skip to content

test(spp_registry): add test suite with 83% line+branch coverage#214

Merged
kneckinator merged 2 commits into
19.0from
test/spp-registry-coverage
May 26, 2026
Merged

test(spp_registry): add test suite with 83% line+branch coverage#214
kneckinator merged 2 commits into
19.0from
test/spp-registry-coverage

Conversation

@kneckinator
Copy link
Copy Markdown
Contributor

Summary

spp_registry shipped with no tests. This PR adds 240 tests across 14 files, taking the module from 0% to 83% line+branch coverage.

File Cover
models/registrant.py 96%
models/reg_id.py 94%
models/phone_number.py 90%
models/individual.py 84%
models/group.py 81%
models/reg_relationship.py 80%
models/group_membership.py 77%
controllers/mail.py 50%
wizard/disable_registrant.py 100%
TOTAL (824 stmts + 326 branches) 83%

Findings surfaced by the tests

These are real issues in the module, pinned as skipped tests with detailed TODOs so the discovery isn't lost:

  1. controllers/mail.py::mail_message_update_content is broken on Odoo 19. It calls ir.attachment._check_attachments_access, which was removed upstream. Every call to the endpoint raises AttributeError.
  2. _query_members_aggregate filters don't apply on Odoo 19. The impl uses the deprecated expression.expression() API, which produces empty WHERE clauses. As a result the disabled IS NULL and is_ended = False filters never apply — count_individuals() over-counts. Affects every caller that builds group metrics off the result.
  3. _onchange_source_destination_clear_relation clears valid relations. Switching destination from individual to group wipes head, even though head is in the mixed concept group.
  4. _compute_phone_sanitized stores the unsanitized fallback. When parsing fails, the original (non-E164) string lands in phone_sanitized.
  5. _check_age_is_integer is dead code. @api.constrains on a non-stored computed Char field doesn't fire.
  6. _check_date_collected is onchange-only. Programmatic create() with a future date bypasses the guard.
  7. disable_registrant wizard has no idempotency guard (unlike disable_phone / disable_relationship).
  8. count_individuals(relationship_kinds=...) expects display strings, not code slugs. Silent footgun: passing ["head"] returns no matches; ["Head"] works.

What's tested, by file

  • test_constraints.py — group head-uniqueness, registration-date, SPPIDType ADR-007 URI format
  • test_unlink_permissions.py — officer/registrar/manager/admin gates on registrant.unlink
  • test_mail_controllers.py — author/admin/bystander/unauth matrices on both mail endpoints (HttpCase)
  • test_relationships.py — partner-type filter, date constraints, overlap rules, disable/enable
  • test_metric_invalidation.py — group/individual/membership write → invalidation funnel
  • test_wizard_disable_registrant.py — context active_id, audit field stamping
  • test_individual_name.py — name composition, age, future-birthdate onchange
  • test_phone_number.py — date_collected, format fallbacks, disable/enable, registrant sync
  • test_reg_id.py — ID-type picklist, is_verified compute, regex validation, display name, name_search
  • test_membership_constraints.py — duplicate member, ended-date constraint, status/active toggles
  • test_registrant_misc.py — enable, negative-income onchange, company-domain override, ID/relationship counts
  • test_group_aggregation.pycount_individuals + _query_members_aggregate (the SQL builder)

Test plan

  • CI passes the spp_registry test suite
  • No new lint/format violations
  • Manual: run locally with ./scripts/test_single_module.sh spp_registry
  • Confirm the documented skipped tests still surface as skipped (not silently passing) in CI output

The module shipped with no tests; this adds 240 tests across 14 files
covering constraints, computes, onchanges, the CRUD-override metric
invalidation chain, the disable-registrant wizard, the mail controller
overrides, the SQL aggregation builder, and the unlink permission guard.

Tests surfaced and pinned several real issues for follow-up:

- mail.py::mail_message_update_content calls ir.attachment._check_attachments_access,
  which no longer exists in Odoo 19; the endpoint is broken.
- _query_members_aggregate uses the deprecated expression.expression() API,
  which silently produces empty WHERE clauses on Odoo 19. As a result the
  disabled/ended-membership filters never apply and count_individuals()
  over-counts. Affects all callers that build group metrics off the count.
- _onchange_source_destination_clear_relation clears a relation type even
  when it's valid for the new partner-type pair (e.g. 'head' is valid for
  both i2i and mixed).
- _compute_phone_sanitized stores the unsanitized fallback value when
  parsing fails, so phone_sanitized can hold non-E164 strings.
- _check_age_is_integer is dead code (constrains on a non-stored compute).
- _check_date_collected is onchange-only and bypassed by programmatic create.
- disable_registrant wizard has no idempotency guard, unlike its siblings.
- count_individuals(relationship_kinds=...) expects display strings, not
  vocab code slugs — silent footgun.

Each finding has a skipped test with a detailed TODO documenting the
discovery and the remediation path, so the value isn't lost.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive suite of unit and integration tests for the spp_registry module, covering constraints, controllers, wizards, and model logic. The review feedback suggests cleaning up unused imports in test_group_aggregation.py and making the empty phone sanitization assertion in test_phone_number.py more robust by including False in the expected values.

Comment on lines +39 to +42
from datetime import datetime, timedelta

from odoo import fields
from odoo.tests import tagged
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

The imports datetime, timedelta, and fields are imported but never used in this test file. Removing unused imports keeps the codebase clean and maintainable.

Suggested change
from datetime import datetime, timedelta
from odoo import fields
from odoo.tests import tagged
from odoo.tests import tagged

# A whitespace-only string is truthy in Python, so this goes
# through phone_format, which returns either the formatted
# value or empty.
self.assertIn(rec.phone_sanitized, ("", None, " "))
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

In Odoo, empty or null Char fields are typically represented as False in Python when read from the database. To ensure the test is robust and does not fail if the computed field returns False instead of "" or None, it is safer to include False in the allowed values list.

Suggested change
self.assertIn(rec.phone_sanitized, ("", None, " "))
self.assertIn(rec.phone_sanitized, ("", False, None, " "))

@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.49%. Comparing base (dcafa51) to head (28b2a0d).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #214      +/-   ##
==========================================
+ Coverage   71.27%   71.49%   +0.22%     
==========================================
  Files         976      993      +17     
  Lines       57736    58564     +828     
==========================================
+ Hits        41150    41869     +719     
- Misses      16586    16695     +109     
Flag Coverage Δ
spp_analytics 93.13% <ø> (ø)
spp_api_v2 80.33% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_approval 50.29% <ø> (ø)
spp_area 80.07% <ø> (ø)
spp_area_hdx 81.43% <ø> (ø)
spp_audit 72.60% <ø> (ø)
spp_banking 80.00% <ø> (ø)
spp_base_common 90.26% <ø> (ø)
spp_programs 64.84% <ø> (ø)
spp_registry 86.83% <ø> (?)
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_registry/__manifest__.py 0.00% <ø> (ø)

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Replace blind `assertRaises(Exception)` with the actual `UserError`
raised by `phone_validation.phone_format`, drop unused imports, and
apply ruff format to the new test files so pre-commit passes on CI.
@kneckinator kneckinator merged commit 5900a2e into 19.0 May 26, 2026
35 checks passed
@kneckinator kneckinator deleted the test/spp-registry-coverage branch May 26, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants