Test Report — 18.0.1.0.0
What is tested in Restrict Delete & Export, and how to run the suite.
Result: 47 automated tests, 0 failures, on a freshly created database.
Every figure here comes from an actual run.
Environment
| Odoo | 18.0 (community, source checkout) |
| Python | 3.12.3 |
| PostgreSQL | 16 |
| OS | Ubuntu 24.04 LTS |
| Command | odoo-bin -d ar_test -i security_access_restriction --test-enable --test-tags /security_access_restriction --stop-after-init |
The database is dropped and recreated before every run.
Coverage
| Suite | Tests | Covers |
|---|---|---|
| TestDeleteRestriction | 7 | Blocking deletes, allow-lists, user exceptions, unrestricted models, archived rules, master switch |
| TestOtherOperations | 6 | Archive, duplicate, unarchive, ordinary writes, operation independence |
| TestBlockedAttemptLog | 7 | Logging triggers, recorded detail, per-rule and global switches, immutability, batch handling, logging-failure resilience |
| TestBypasses | 4 | Superuser, Settings group, the bypass toggle, and the last line of defence |
| TestGuarantees | 4 | Self-models, critical models, config exposure, one rule per model |
| TestMessages | 4 | Default message, custom message, placeholders, broken placeholders |
| TestInstall | 7 | Installed state, groups, defaults, cron, safe defaults, role permissions |
| TestExportEndpoint | 3 | End-to-end HTTP through Odoo's real /web/export/csv |
| TestPrintRestriction | 2 | Report rendering blocked and allowed |
| TestRetention | 3 | Purge horizon, keep-forever |
| Total | 47 |
What the tests actually prove
You cannot be locked out. test_superuser_always_bypasses and test_superuser_bypasses_even_with_system_bypass_off assert the superuser escape hatch holds even with every other bypass disabled. test_critical_models_cannot_be_restricted asserts that res.users, res.groups, ir.model.access and ir.rule are refused by the rule form.
A refused export leaks nothing. test_export_is_blocked_for_a_restricted_user performs a real HTTP export against the live endpoint and asserts the record's name does not appear in the response body. The check runs before any data is read.
Operations are independent. test_operations_are_independent restricts only delete, then successfully duplicates and archives the same record — so ticking one box never silently ticks another.
Ordinary work is unaffected. test_ordinary_writes_are_never_blocked asserts a restricted user can still edit a record, and test_unarchiving_is_never_blocked asserts restoring a record is never treated as destructive.
A broken message does not become a traceback. test_a_broken_placeholder_does_not_crash puts an invalid placeholder in a custom message and asserts the user still sees a refusal.
A broken logger does not hide the refusal. test_a_logging_failure_never_hides_the_refusal makes the logger raise and asserts the record is still not deleted.
Two defects found and fixed during testing
A %(...)s placeholder in a view broke installation. The help text for the custom-message field contained %(verb)s, which Odoo's XML loader resolved as an external ID — the module would not install. The percent is now escaped. Caught only because the suite runs against a database created from scratch.
Refusal logging was being discarded. The log was written in the caller's transaction under test, and Odoo's assertRaises wraps the block in a savepoint that rolls back — so the evidence vanished. This mattered beyond the test: in production the AccessError triggers the same rollback. Logging now always goes through a dedicated cursor, so test and production exercise the same path.
A testing note worth recording
Mocking a method on an Odoo model class with MagicMock is unsafe. Odoo's unlink() introspects class attributes for an _ondelete marker, and a MagicMock auto-creates that attribute as truthy — so the ORM calls the mock as a delete hook and the test sees a spurious call. The suite uses a plain recorder function instead.
Known gaps
- No browser tests. The module has no client-side code; the views are standard Odoo.
- Print restriction depends on an installed report. The blocking test skips if no res.partner report exists in the database.
- Scale. Correctness is tested, not behaviour under millions of records. The check is a cached dictionary lookup, so no scale effect is expected.
Reproducing
createdb -O odoo ar_test
odoo-bin -c odoo.conf -d ar_test \
-i security_access_restriction --test-enable \
--test-tags /security_access_restriction --stop-after-initA clean run prints no FAIL: lines. One ERROR line appears from odoo.addons.web.controllers.export — that is the deliberate refusal in the export test, logged by Odoo's controller, not a failure.