Don't allow in-memory SQLite database (#69616)
parent
fab1f29a29
commit
949b0e1b65
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue