async def test_purge_old_states_encouters_database_corruption( hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT): """Test database image image is malformed while deleting old states.""" instance = await async_setup_recorder_instance(hass) await _add_test_states(hass, instance) await async_wait_recording_done_without_instance(hass) sqlite3_exception = DatabaseError("statement", {}, []) sqlite3_exception.__cause__ = sqlite3.DatabaseError() with patch("homeassistant.components.recorder.move_away_broken_database" ) as move_away, patch( "homeassistant.components.recorder.purge.purge_old_data", side_effect=sqlite3_exception, ): await hass.services.async_call(recorder.DOMAIN, recorder.SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done_without_instance(hass) assert move_away.called # Ensure the whole database was reset due to the database error with session_scope(hass=hass) as session: states_after_purge = session.query(States) assert states_after_purge.count() == 0
async def test_database_corruption_while_running(hass, tmpdir, caplog): """Test we can recover from sqlite3 db corruption.""" def _create_tmpdir_for_test_db(): return tmpdir.mkdir("sqlite").join("test.db") test_db_file = await hass.async_add_executor_job(_create_tmpdir_for_test_db) dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: dburl}}) await hass.async_block_till_done() caplog.clear() hass.states.async_set("test.lost", "on", {}) sqlite3_exception = DatabaseError("statement", {}, []) sqlite3_exception.__cause__ = sqlite3.DatabaseError() with patch.object( hass.data[DATA_INSTANCE].event_session, "close", side_effect=OperationalError("statement", {}, []), ): await async_wait_recording_done_without_instance(hass) await hass.async_add_executor_job(corrupt_db_file, test_db_file) await async_wait_recording_done_without_instance(hass) with patch.object( hass.data[DATA_INSTANCE].event_session, "commit", side_effect=[sqlite3_exception, None], ): # This state will not be recorded because # the database corruption will be discovered # and we will have to rollback to recover hass.states.async_set("test.one", "off", {}) await async_wait_recording_done_without_instance(hass) assert "Unrecoverable sqlite3 database corruption detected" in caplog.text assert "The system will rename the corrupt database file" in caplog.text assert "Connected to recorder database" in caplog.text # This state should go into the new database hass.states.async_set("test.two", "on", {}) await async_wait_recording_done_without_instance(hass) def _get_last_state(): with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 1 assert db_states[0].event_id > 0 return db_states[0].to_native() state = await hass.async_add_executor_job(_get_last_state) assert state.entity_id == "test.two" assert state.state == "on" hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() hass.stop()