1
mirror of https://github.com/home-assistant/core synced 2024-09-18 19:55:20 +02:00

Don't allow in-memory SQLite database (#69616)

This commit is contained in:
Erik Montnemery 2022-04-08 00:43:09 +02:00 committed by GitHub
parent fab1f29a29
commit 949b0e1b65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 40 deletions

View File

@ -177,6 +177,19 @@ FILTER_SCHEMA = INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend(
{vol.Optional(CONF_EXCLUDE, default=EXCLUDE_SCHEMA({})): EXCLUDE_SCHEMA}
)
ALLOW_IN_MEMORY_DB = False
def validate_db_url(db_url: str) -> Any:
"""Validate database URL."""
# Don't allow on-memory sqlite databases
if (db_url == SQLITE_URL_PREFIX or ":memory:" in db_url) and not ALLOW_IN_MEMORY_DB:
raise vol.Invalid("In-memory SQLite database is not supported")
return db_url
CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(DOMAIN, default=dict): vol.All(
@ -190,7 +203,7 @@ CONFIG_SCHEMA = vol.Schema(
vol.Coerce(int), vol.Range(min=1)
),
vol.Optional(CONF_PURGE_INTERVAL, default=1): cv.positive_int,
vol.Optional(CONF_DB_URL): cv.string,
vol.Optional(CONF_DB_URL): vol.All(cv.string, validate_db_url),
vol.Optional(
CONF_COMMIT_INTERVAL, default=DEFAULT_COMMIT_INTERVAL
): cv.positive_int,

View File

@ -910,10 +910,16 @@ def init_recorder_component(hass, add_config=None):
if recorder.CONF_COMMIT_INTERVAL not in config:
config[recorder.CONF_COMMIT_INTERVAL] = 0
with patch("homeassistant.components.recorder.migration.migrate_schema"):
with patch(
"homeassistant.components.recorder.ALLOW_IN_MEMORY_DB",
True,
), patch("homeassistant.components.recorder.migration.migrate_schema"):
assert setup_component(hass, recorder.DOMAIN, {recorder.DOMAIN: config})
assert recorder.DOMAIN in hass.config.components
_LOGGER.info("In-memory recorder successfully started")
_LOGGER.info(
"Test recorder successfully started, database location: %s",
config[recorder.CONF_DB_URL],
)
async def async_init_recorder_component(hass, add_config=None):
@ -924,12 +930,18 @@ async def async_init_recorder_component(hass, add_config=None):
if recorder.CONF_COMMIT_INTERVAL not in config:
config[recorder.CONF_COMMIT_INTERVAL] = 0
with patch("homeassistant.components.recorder.migration.migrate_schema"):
with patch(
"homeassistant.components.recorder.ALLOW_IN_MEMORY_DB",
True,
), patch("homeassistant.components.recorder.migration.migrate_schema"):
assert await async_setup_component(
hass, recorder.DOMAIN, {recorder.DOMAIN: config}
)
assert recorder.DOMAIN in hass.config.components
_LOGGER.info("In-memory recorder successfully started")
_LOGGER.info(
"Test recorder successfully started, database location: %s",
config[recorder.CONF_DB_URL],
)
def mock_restore_cache(hass, states):

View File

@ -451,15 +451,13 @@ async def test_send_with_no_energy(hass, aioclient_mock):
assert "energy" not in postdata
async def test_send_with_no_energy_config(hass, aioclient_mock):
async def test_send_with_no_energy_config(hass, aioclient_mock, recorder_mock):
"""Test send base preferences are defined."""
aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200)
analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True})
assert await async_setup_component(
hass, "energy", {"recorder": {"db_url": "sqlite://"}}
)
assert await async_setup_component(hass, "energy", {})
with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex, patch(
"homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION
@ -475,15 +473,13 @@ async def test_send_with_no_energy_config(hass, aioclient_mock):
assert not postdata["energy"]["configured"]
async def test_send_with_energy_config(hass, aioclient_mock):
async def test_send_with_energy_config(hass, aioclient_mock, recorder_mock):
"""Test send base preferences are defined."""
aioclient_mock.post(ANALYTICS_ENDPOINT_URL, status=200)
analytics = Analytics(hass)
await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True})
assert await async_setup_component(
hass, "energy", {"recorder": {"db_url": "sqlite://"}}
)
assert await async_setup_component(hass, "energy", {})
with patch("uuid.UUID.hex", new_callable=PropertyMock) as hex, patch(
"homeassistant.components.analytics.analytics.HA_VERSION", MOCK_VERSION

View File

@ -29,12 +29,15 @@ from tests.common import async_init_recorder_component
from tests.components.recorder.common import async_wait_recording_done_without_instance
async def setup_integration(hass):
@pytest.fixture
async def setup_integration(recorder_mock):
"""Set up the integration."""
assert await async_setup_component(
hass, "energy", {"recorder": {"db_url": "sqlite://"}}
)
await hass.async_block_till_done()
async def setup_integration(hass):
assert await async_setup_component(hass, "energy", {})
await hass.async_block_till_done()
return setup_integration
def get_statistics_for_entity(statistics_results, entity_id):
@ -45,7 +48,7 @@ def get_statistics_for_entity(statistics_results, entity_id):
return None
async def test_cost_sensor_no_states(hass, hass_storage) -> None:
async def test_cost_sensor_no_states(hass, hass_storage, setup_integration) -> None:
"""Test sensors are created."""
energy_data = data.EnergyManager.default_preferences()
energy_data["energy_sources"].append(
@ -91,6 +94,7 @@ async def test_cost_sensor_price_entity_total_increasing(
hass,
hass_storage,
hass_ws_client,
setup_integration,
initial_energy,
initial_cost,
price_entity,
@ -294,6 +298,7 @@ async def test_cost_sensor_price_entity_total(
hass,
hass_storage,
hass_ws_client,
setup_integration,
initial_energy,
initial_cost,
price_entity,
@ -500,6 +505,7 @@ async def test_cost_sensor_price_entity_total_no_reset(
hass,
hass_storage,
hass_ws_client,
setup_integration,
initial_energy,
initial_cost,
price_entity,
@ -670,7 +676,7 @@ async def test_cost_sensor_price_entity_total_no_reset(
],
)
async def test_cost_sensor_handle_energy_units(
hass, hass_storage, energy_unit, factor
hass, hass_storage, setup_integration, energy_unit, factor
) -> None:
"""Test energy cost price from sensor entity."""
energy_attributes = {
@ -736,7 +742,7 @@ async def test_cost_sensor_handle_energy_units(
],
)
async def test_cost_sensor_handle_price_units(
hass, hass_storage, price_unit, factor
hass, hass_storage, setup_integration, price_unit, factor
) -> None:
"""Test energy cost price from sensor entity."""
energy_attributes = {
@ -798,7 +804,7 @@ async def test_cost_sensor_handle_price_units(
assert state.state == "20.0"
async def test_cost_sensor_handle_gas(hass, hass_storage) -> None:
async def test_cost_sensor_handle_gas(hass, hass_storage, setup_integration) -> None:
"""Test gas cost price from sensor entity."""
energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS,
@ -847,7 +853,9 @@ async def test_cost_sensor_handle_gas(hass, hass_storage) -> None:
assert state.state == "50.0"
async def test_cost_sensor_handle_gas_kwh(hass, hass_storage) -> None:
async def test_cost_sensor_handle_gas_kwh(
hass, hass_storage, setup_integration
) -> None:
"""Test gas cost price from sensor entity."""
energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
@ -898,7 +906,7 @@ async def test_cost_sensor_handle_gas_kwh(hass, hass_storage) -> None:
@pytest.mark.parametrize("state_class", [None])
async def test_cost_sensor_wrong_state_class(
hass, hass_storage, caplog, state_class
hass, hass_storage, setup_integration, caplog, state_class
) -> None:
"""Test energy sensor rejects sensor with wrong state_class."""
energy_attributes = {
@ -960,7 +968,7 @@ async def test_cost_sensor_wrong_state_class(
@pytest.mark.parametrize("state_class", [SensorStateClass.MEASUREMENT])
async def test_cost_sensor_state_class_measurement_no_reset(
hass, hass_storage, caplog, state_class
hass, hass_storage, setup_integration, caplog, state_class
) -> None:
"""Test energy sensor rejects state_class measurement with no last_reset."""
energy_attributes = {

View File

@ -18,11 +18,9 @@ from tests.components.recorder.common import async_wait_recording_done_without_i
@pytest.fixture(autouse=True)
async def setup_integration(hass):
async def setup_integration(hass, recorder_mock):
"""Set up the integration."""
assert await async_setup_component(
hass, "energy", {"recorder": {"db_url": "sqlite://"}}
)
assert await async_setup_component(hass, "energy", {})
@pytest.fixture

View File

@ -1394,3 +1394,11 @@ async def test_database_lock_without_instance(hass):
assert await instance.lock_database()
finally:
assert instance.unlock_database()
async def test_in_memory_database(hass, caplog):
"""Test connecting to an in-memory recorder is not allowed."""
assert not await async_setup_component(
hass, recorder.DOMAIN, {recorder.DOMAIN: {recorder.CONF_DB_URL: "sqlite://"}}
)
assert "In-memory SQLite database is not supported" in caplog.text

View File

@ -43,7 +43,7 @@ async def test_schema_update_calls(hass):
"""Test that schema migrations occur in correct order."""
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test
), patch(
"homeassistant.components.recorder.migration._apply_update",
@ -68,8 +68,9 @@ async def test_migration_in_progress(hass):
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test
):
"homeassistant.components.recorder.ALLOW_IN_MEMORY_DB",
True,
), patch("homeassistant.components.recorder.create_engine", new=create_engine_test):
await async_setup_component(
hass, "recorder", {"recorder": {"db_url": "sqlite://"}}
)
@ -84,7 +85,7 @@ async def test_database_migration_failed(hass):
"""Test we notify if the migration fails."""
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test
), patch(
"homeassistant.components.recorder.migration._apply_update",
@ -117,7 +118,7 @@ async def test_database_migration_encounters_corruption(hass):
sqlite3_exception = DatabaseError("statement", {}, [])
sqlite3_exception.__cause__ = sqlite3.DatabaseError()
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.migration.schema_is_current",
side_effect=[False, True],
), patch(
@ -141,7 +142,7 @@ async def test_database_migration_encounters_corruption_not_sqlite(hass):
"""Test we fail on database error when we cannot recover."""
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.migration.schema_is_current",
side_effect=[False, True],
), patch(
@ -176,8 +177,9 @@ async def test_events_during_migration_are_queued(hass):
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test
):
"homeassistant.components.recorder.ALLOW_IN_MEMORY_DB",
True,
), patch("homeassistant.components.recorder.create_engine", new=create_engine_test):
await async_setup_component(
hass, "recorder", {"recorder": {"db_url": "sqlite://"}}
)
@ -200,7 +202,7 @@ async def test_events_during_migration_queue_exhausted(hass):
assert recorder.util.async_migration_in_progress(hass) is False
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test
), patch.object(recorder, "MAX_QUEUE_BACKLOG", 1):
await async_setup_component(
@ -283,7 +285,7 @@ async def test_schema_migrate(hass, start_version):
migration_version = res.schema_version
migration_done.set()
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.create_engine", new=_create_engine_test
), patch(
"homeassistant.components.recorder.Recorder._setup_run",

View File

@ -332,7 +332,7 @@ async def test_recorder_info_migration_queue_exhausted(hass, hass_ws_client):
migration_done.wait()
return real_migration(*args)
with patch(
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch(
"homeassistant.components.recorder.Recorder.async_periodic_statistics"
), patch(
"homeassistant.components.recorder.create_engine", new=create_engine_test