补充真实配置历史写入和测试账号锁定复核
This commit is contained in:
parent
96e4f70358
commit
b8a4801533
42
test/deployment/test_runtime_config.py
Normal file
42
test/deployment/test_runtime_config.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""配置历史实机探针必须真实提交变更,不能以空表存在代替验收。"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))
|
||||||
|
from runtime_probe import verify_config_history
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeConfigTest(unittest.TestCase):
|
||||||
|
def client(self, final_generation=2, final_level='warn'):
|
||||||
|
client = MagicMock()
|
||||||
|
client.success.side_effect = [
|
||||||
|
{'available': True, 'activeGeneration': 1},
|
||||||
|
{'generation': 1, 'logLevel': 'info', 'databasePasswordStatus': {}},
|
||||||
|
{}, {'generation': 2, 'candidateId': 'candidate'}, {},
|
||||||
|
{'generation': final_generation, 'logLevel': final_level},
|
||||||
|
]
|
||||||
|
return client
|
||||||
|
|
||||||
|
def test_applies_real_change_and_verifies_generation(self):
|
||||||
|
client = self.client()
|
||||||
|
self.assertTrue(verify_config_history(client)['generation_advanced'])
|
||||||
|
calls = client.success.call_args_list
|
||||||
|
request = calls[2].args[1]
|
||||||
|
self.assertEqual(request['expectedGeneration'], 1)
|
||||||
|
self.assertNotIn('databasePasswordStatus', request['payload'])
|
||||||
|
self.assertEqual(calls[3].args[2], 'PUT')
|
||||||
|
self.assertEqual(calls[4].args[1]['candidateId'], 'candidate')
|
||||||
|
|
||||||
|
def test_rejects_unchanged_generation_or_unapplied_payload(self):
|
||||||
|
for generation, level in ((1, 'warn'), (2, 'info')):
|
||||||
|
with self.subTest(generation=generation, level=level):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
verify_config_history(self.client(generation, level))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@ -53,6 +53,7 @@ class RuntimeSQLTest(unittest.TestCase):
|
|||||||
fixture = runtime_sql.SQLFixture(kind)
|
fixture = runtime_sql.SQLFixture(kind)
|
||||||
with patch.object(fixture, 'admin', return_value='0') as admin:
|
with patch.object(fixture, 'admin', return_value='0') as admin:
|
||||||
fixture.create()
|
fixture.create()
|
||||||
|
admin.return_value = 'Y' if kind == 'mysql' else 'f'
|
||||||
fixture.lock()
|
fixture.lock()
|
||||||
statements = '\n'.join(item.args[0] for item in admin.call_args_list)
|
statements = '\n'.join(item.args[0] for item in admin.call_args_list)
|
||||||
self.assertNotIn('DROP ', statements)
|
self.assertNotIn('DROP ', statements)
|
||||||
@ -70,6 +71,15 @@ class RuntimeSQLTest(unittest.TestCase):
|
|||||||
fixture.lock()
|
fixture.lock()
|
||||||
admin.assert_not_called()
|
admin.assert_not_called()
|
||||||
|
|
||||||
|
def test_lock_requires_server_confirmation(self):
|
||||||
|
for kind in ('mysql', 'postgres'):
|
||||||
|
with self.subTest(kind=kind):
|
||||||
|
fixture = runtime_sql.SQLFixture(kind)
|
||||||
|
fixture.account_created = True
|
||||||
|
with patch.object(fixture, 'admin', return_value=''):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
fixture.lock()
|
||||||
|
|
||||||
def test_nonempty_database_fails_first_install_check(self):
|
def test_nonempty_database_fails_first_install_check(self):
|
||||||
fixture = runtime_sql.SQLFixture('postgres')
|
fixture = runtime_sql.SQLFixture('postgres')
|
||||||
with patch.object(fixture, 'admin', return_value='3'):
|
with patch.object(fixture, 'admin', return_value='3'):
|
||||||
|
|||||||
@ -106,6 +106,26 @@ def verify_message_content(raw, message_id, body, attachment):
|
|||||||
assert files[0].get_payload(decode=True) == attachment
|
assert files[0].get_payload(decode=True) == attachment
|
||||||
|
|
||||||
|
|
||||||
|
def verify_config_history(client):
|
||||||
|
# 首次安装不一定产生历史,必须通过正式变更接口写入并读回,不能只检查空表存在。
|
||||||
|
status = client.success('/api/v1/config/status', method='GET')
|
||||||
|
assert status['available']
|
||||||
|
payload = client.success('/api/v1/config/base', method='GET')
|
||||||
|
payload.pop('databasePasswordStatus', None)
|
||||||
|
payload['logLevel'] = 'warn' if payload.get('logLevel') != 'warn' else 'info'
|
||||||
|
request = {'expectedGeneration': status['activeGeneration'], 'payload': payload,
|
||||||
|
'confirmation': {'confirmed': True, 'method': 'recent_auth'}}
|
||||||
|
client.success('/api/v1/config/base/validate', request)
|
||||||
|
candidate = client.success('/api/v1/config/base', request, 'PUT')
|
||||||
|
client.success('/api/v1/config/base/apply', {
|
||||||
|
'generation': candidate['generation'], 'candidateId': candidate['candidateId'],
|
||||||
|
})
|
||||||
|
active = client.success('/api/v1/config/base', method='GET')
|
||||||
|
assert active['logLevel'] == payload['logLevel']
|
||||||
|
assert active['generation'] > status['activeGeneration']
|
||||||
|
return {'configuration_api': True, 'generation_advanced': True}
|
||||||
|
|
||||||
|
|
||||||
def verify_protocol_read(context, codes, message_id, body, attachment):
|
def verify_protocol_read(context, codes, message_id, body, attachment):
|
||||||
# 通过协议取回真实正文,不以数据库存在或欢迎响应替代取信验收。
|
# 通过协议取回真实正文,不以数据库存在或欢迎响应替代取信验收。
|
||||||
client = LocalIMAP(HOST, 993, ssl_context=context, timeout=30)
|
client = LocalIMAP(HOST, 993, ssl_context=context, timeout=30)
|
||||||
@ -332,6 +352,7 @@ def main():
|
|||||||
assert len(recovery['codes']) == 10
|
assert len(recovery['codes']) == 10
|
||||||
recovery.clear()
|
recovery.clear()
|
||||||
result['security_setup'] = {'recovery_codes_generated': 10, 'plaintext_logged': False}
|
result['security_setup'] = {'recovery_codes_generated': 10, 'plaintext_logged': False}
|
||||||
|
result['config_history'] = verify_config_history(client)
|
||||||
if args.proxy:
|
if args.proxy:
|
||||||
from runtime_proxy import configure_proxy
|
from runtime_proxy import configure_proxy
|
||||||
result['proxy'] = configure_proxy(client, context, login, Client, LocalHTTPS)
|
result['proxy'] = configure_proxy(client, context, login, Client, LocalHTTPS)
|
||||||
|
|||||||
@ -124,5 +124,9 @@ class SQLFixture(SQLDatabase):
|
|||||||
user = self.settings['user']
|
user = self.settings['user']
|
||||||
if self.kind == 'mysql':
|
if self.kind == 'mysql':
|
||||||
self.admin("ALTER USER '" + user + "'@'localhost' ACCOUNT LOCK;")
|
self.admin("ALTER USER '" + user + "'@'localhost' ACCOUNT LOCK;")
|
||||||
|
assert self.admin("SELECT account_locked FROM mysql.user WHERE User='" + user +
|
||||||
|
"' AND Host='localhost';") == 'Y', '测试数据库账号锁定未生效'
|
||||||
else:
|
else:
|
||||||
self.admin('ALTER ROLE ' + user + ' NOLOGIN;')
|
self.admin('ALTER ROLE ' + user + ' NOLOGIN;')
|
||||||
|
assert self.admin("SELECT rolcanlogin FROM pg_roles WHERE rolname='" + user +
|
||||||
|
"';") == 'f', '测试数据库账号锁定未生效'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user