diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 2f7a8a4e237..83b59fc60a1 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1134,6 +1134,12 @@ class Database(http.Controller): try: dispatch_rpc('db','drop', [master_pwd, name]) request._cr = None # dropping a database leads to an unusable cursor + # if the user is connected to the dropped database, redirect will raise an operational error + # trying to access the registry of this database. + # Removing db from this request will prevent an invalid error message. (logout looks like a good idea in this case) + # https://github.com/odoo/odoo/pull/54102 is proposing a complete fix for this issue. + if request.session.db == name: + request.session.logout() return request.redirect('/web/database/manager') except Exception as e: error = "Database deletion error: %s" % (str(e) or repr(e)) diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py index 62810336e66..031e1b56852 100644 --- a/addons/web/tests/__init__.py +++ b/addons/web/tests/__init__.py @@ -1,5 +1,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import test_db_manager from . import test_health from . import test_image from . import test_js diff --git a/addons/web/tests/test_db_manager.py b/addons/web/tests/test_db_manager.py new file mode 100644 index 00000000000..0ff8512e12a --- /dev/null +++ b/addons/web/tests/test_db_manager.py @@ -0,0 +1,121 @@ +# -*- encoding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import operator +import re +import secrets +from unittest.mock import patch + +import requests + +import odoo +from odoo.tests.common import BaseCase, HttpCase, tagged +from odoo.tools import config + + +class TestDatabaseManager(HttpCase): + def test_database_manager(self): + res = self.url_open('/web/database/manager') + self.assertEqual(res.status_code, 200) + + # check that basic existing db actions are present + self.assertIn('.o_database_backup', res.text) + self.assertIn('.o_database_duplicate', res.text) + self.assertIn('.o_database_delete', res.text) + + # check that basic db actions are present + self.assertIn('.o_database_create', res.text) + self.assertIn('.o_database_restore', res.text) + + +@tagged('-at_install', 'post_install', '-standard', 'database_operations') +class TestDatabaseOperations(BaseCase): + def setUp(self): + self.password = secrets.token_hex() + + # monkey-patch password verification + self.verify_admin_password_patcher = patch( + 'odoo.tools.config.verify_admin_password', self.password.__eq__, + ) + self.verify_admin_password_patcher.start() + self.addCleanup(self.verify_admin_password_patcher.stop) + + self.db_name = config['db_name'] + self.assertTrue(self.db_name) + + # monkey-patch db-filter + self.addCleanup(operator.setitem, config, 'dbfilter', config['dbfilter']) + config['dbfilter'] = self.db_name + '-.*' + + self.base_databases = self.list_dbs() + self.session = requests.Session() + self.session.get(self.url('/web/database/manager')) + + def tearDown(self): + self.assertEqual( + self.list_dbs(), + self.base_databases, + 'No database should have been created or removed at the end of this test', + ) + + def list_dbs(self): + return set(odoo.service.db.list_dbs(True)) + + def list_dbs_filtered(self): + return set(db for db in self.list_dbs() if re.match(db, config['dbfilter'])) + + def url(self, path): + return HttpCase.base_url() + path + + def assertDbs(self, dbs): + self.assertEqual(self.list_dbs() - self.base_databases, set(dbs)) + + def test_database_creation(self): + # check verify_admin_password patch + self.assertTrue(odoo.tools.config.verify_admin_password(self.password)) + + # create a database + test_db_name = self.db_name + '-test-database-creation' + self.assertNotIn(test_db_name, self.list_dbs_filtered()) + res = self.session.post(self.url('/web/database/create'), data={ + 'master_pwd': self.password, + 'name': test_db_name, + 'login': 'admin', + 'password': 'admin', + 'lang': 'en_US', + 'phone': '', + }, allow_redirects=False) + self.assertEqual(res.status_code, 303) + self.assertIn('/web', res.headers['Location']) + self.assertDbs([test_db_name]) + + # delete the created database + res = self.session.post(self.url('/web/database/drop'), data={ + 'master_pwd': self.password, + 'name': test_db_name, + }, allow_redirects=False) + self.assertEqual(res.status_code, 303) + self.assertIn('/web/database/manager', res.headers['Location']) + self.assertDbs([]) + + def test_database_duplicate(self): + # duplicate this database + test_db_name = self.db_name + '-test-database-duplicate' + self.assertNotIn(test_db_name, self.list_dbs_filtered()) + res = self.session.post(self.url('/web/database/duplicate'), data={ + 'master_pwd': self.password, + 'name': self.db_name, + 'new_name': test_db_name, + }, allow_redirects=False) + self.assertEqual(res.status_code, 303) + self.assertIn('/web/database/manager', res.headers['Location']) + self.assertDbs([test_db_name]) + + # delete the created database + res = self.session.post(self.url('/web/database/drop'), data={ + 'master_pwd': self.password, + 'name': test_db_name, + }, allow_redirects=False) + self.assertEqual(res.status_code, 303) + self.assertIn('/web/database/manager', res.headers['Location']) + self.assertDbs([])