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
7 changes: 7 additions & 0 deletions webservice/data/neutralize.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Drop the OAuth2 secrets of the webservice backends, so that a database
-- copied from another environment does not keep the production credentials.
UPDATE webservice_backend
SET oauth2_clientid = NULL,
oauth2_client_secret = NULL,
oauth2_client_auth_value = NULL,
oauth2_token = NULL;
1 change: 1 addition & 0 deletions webservice/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import test_content_only_compat
from . import test_neutralize
from . import test_oauth2
41 changes: 41 additions & 0 deletions webservice/tests/test_neutralize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2026 Camptocamp SA
# @author Simone Orsi <simone.orsi@camptocamp.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.modules import neutralize
from odoo.tests.common import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestNeutralize(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.backend = cls.env["webservice.backend"].create(
{
"name": "Neutralize OAuth2",
"tech_name": "neutralize_oauth2",
"protocol": "http",
"url": "https://production.example.com/api",
"oauth2_clientid": "prod_client_id",
"oauth2_client_secret": "prod_client_secret",
"oauth2_token": "prod_token",
}
)

def _run_neutralize(self):
"""Run the neutralization queries of the webservice module."""
for query in neutralize.get_neutralization_queries(["webservice"]):
self.env.cr.execute(query)
self.backend.invalidate_recordset()

def test_neutralize_clears_oauth2_secrets(self):
"""OAuth2 client ID, client secret and token are cleared."""
self._run_neutralize()
self.assertFalse(self.backend.oauth2_clientid)
self.assertFalse(self.backend.oauth2_client_secret)
self.assertFalse(self.backend.oauth2_token)

def test_neutralize_keeps_endpoint(self):
"""The endpoint is not touched by the OAuth2 neutralization."""
self._run_neutralize()
self.assertEqual(self.backend.url, "https://production.example.com/api")
8 changes: 8 additions & 0 deletions webservice_core/data/neutralize.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Drop the endpoints and credentials of the webservice backends, so that
-- a database copied from another environment does not call external services.
-- `url` is required, so it gets a placeholder instead of NULL.
UPDATE webservice_backend
SET url = 'https://neutralized.invalid',
username = NULL,
password = NULL,
api_key = NULL;
1 change: 1 addition & 0 deletions webservice_core/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import test_neutralize
from . import test_utils
from . import test_webservice
from . import test_webservice_endpoint
47 changes: 47 additions & 0 deletions webservice_core/tests/test_neutralize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2026 Camptocamp SA
# @author Simone Orsi <simone.orsi@camptocamp.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.modules import neutralize
from odoo.tests.common import TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestNeutralize(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.backend = cls.env["webservice.backend"].create(
{
"name": "Neutralize me",
"tech_name": "neutralize_me",
"protocol": "http",
"url": "https://production.example.com/api",
"username": "prod_user",
"password": "prod_password",
"api_key": "prod_api_key",
}
)

def _run_neutralize(self):
"""Run the neutralization queries of the webservice_core module."""
for query in neutralize.get_neutralization_queries(["webservice_core"]):
self.env.cr.execute(query)
self.backend.invalidate_recordset()

def test_neutralize_replaces_url(self):
"""The production URL is replaced by a placeholder."""
self._run_neutralize()
self.assertEqual(self.backend.url, "https://neutralized.invalid")

def test_neutralize_clears_credentials(self):
"""Username, password and API key of the backend are cleared."""
self._run_neutralize()
self.assertFalse(self.backend.username)
self.assertFalse(self.backend.password)
self.assertFalse(self.backend.api_key)

def test_neutralize_keeps_other_fields(self):
"""Fields that are not endpoints or credentials are left untouched."""
self._run_neutralize()
self.assertEqual(self.backend.name, "Neutralize me")
self.assertEqual(self.backend.protocol, "http")
Loading