async def test_upload_session_expired(drive, time, snapshot_helper, interceptor: RequestInterceptor): from_snapshot, data = await snapshot_helper.createFile() interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, status=404) with pytest.raises(GoogleSessionError): await drive.save(from_snapshot, data) assert time.sleeps == []
async def test_resume_session_abandoned_on_http4XX( time, drive: DriveSource, config: Config, server, snapshot_helper, interceptor: RequestInterceptor): from_snapshot, data = await snapshot_helper.createFile() # Configure the upload to fail after the first upload chunk interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, 402, 1) with pytest.raises(ClientResponseError): await drive.save(from_snapshot, data) # Verify a requst was made to start the upload but not cached assert server.wasUrlRequested( "/upload/drive/v3/files/?uploadType=resumable&supportsAllDrives=true") assert drive.drivebackend.last_attempt_count == 1 assert drive.drivebackend.last_attempt_location is None assert drive.drivebackend.last_attempt_metadata is None # upload again, which should retry server.urls.clear() interceptor.clear() data.position(0) snapshot = await drive.save(from_snapshot, data) assert server.wasUrlRequested(URL_START_UPLOAD) # Verify the uploaded bytes are identical from_snapshot.addSource(snapshot) download = await drive.read(from_snapshot) data.position(0) await compareStreams(data, download)
async def test_failed_backup(time, ha: HaSource, supervisor: SimulatedSupervisor, config: Config, interceptor: RequestInterceptor): # create a blocking backup interceptor.setError(URL_MATCH_BACKUP_FULL, 524) config.override(Setting.NEW_BACKUP_TIMEOUT_SECONDS, 0) await supervisor.toggleBlockBackup() backup_immediate = await ha.create(CreateOptions(time.now(), "Some Name")) assert isinstance(backup_immediate, PendingBackup) assert backup_immediate.name() == "Some Name" assert not ha.check() assert not backup_immediate.isFailed() await supervisor.toggleBlockBackup() # let the backup attempt to complete await asyncio.wait({ha._pending_backup_task}) # verify it failed with the expected http error assert backup_immediate.isFailed() assert backup_immediate._exception.status == 524 backups = list((await ha.get()).values()) assert len(backups) == 1 assert backups[0] is backup_immediate # verify we can create a new backup immediately interceptor.clear() await ha.create(CreateOptions(time.now(), "Some Name")) assert len(await ha.get()) == 1
async def test_pending_snapshot_timeout(time: FakeTime, ha: HaSource, config: Config, interceptor: RequestInterceptor): interceptor.setSleep(URL_MATCH_SNAPSHOT_FULL, sleep=5) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 1) config.override(Setting.FAILED_SNAPSHOT_TIMEOUT_SECONDS, 1) config.override(Setting.PENDING_SNAPSHOT_TIMEOUT_SECONDS, 1) snapshot_immediate: PendingSnapshot = await ha.create( CreateOptions(time.now(), "Test Name")) assert isinstance(snapshot_immediate, PendingSnapshot) assert snapshot_immediate.name() == "Test Name" assert not ha.check() assert ha.pending_snapshot is snapshot_immediate await asyncio.wait({ha._pending_snapshot_task}) assert ha.pending_snapshot is snapshot_immediate assert ha.check() assert not ha.check() time.advance(minutes=1) assert ha.check() assert len(await ha.get()) == 0 assert not ha.check() assert ha.pending_snapshot is None assert snapshot_immediate.isStale()
async def test_start_on_boot(ha: HaSource, time, interceptor: RequestInterceptor, config: Config, supervisor: SimulatedSupervisor) -> None: boot_slug = "boot_slug" supervisor.installAddon(boot_slug, "Start on boot", boot=True, started=False) no_boot_slug = "no_boot_slug" supervisor.installAddon(no_boot_slug, "Don't start on boot", boot=False, started=False) config.override(Setting.STOP_ADDONS, ",".join([boot_slug, no_boot_slug])) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0.001) assert supervisor.addon(boot_slug)["state"] == "stopped" assert supervisor.addon(no_boot_slug)["state"] == "stopped" async with supervisor._snapshot_inner_lock: await ha.create(CreateOptions(time.now(), "Test Name")) assert supervisor.addon(boot_slug)["state"] == "stopped" assert supervisor.addon(no_boot_slug)["state"] == "stopped" await ha._pending_snapshot_task assert supervisor.addon(boot_slug)["state"] == "started" assert supervisor.addon(no_boot_slug)["state"] == "stopped" assert len(await ha.get()) == 1 assert not interceptor.urlWasCalled(URL_MATCH_START_ADDON) assert not interceptor.urlWasCalled(URL_MATCH_STOP_ADDON)
async def test_failed_snapshot(time, ha: HaSource, supervisor: SimulatedSupervisor, config: Config, interceptor: RequestInterceptor): # create a blocking snapshot interceptor.setError(URL_MATCH_SNAPSHOT_FULL, 524) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0) await supervisor.toggleBlockSnapshot() snapshot_immediate = await ha.create(CreateOptions(time.now(), "Some Name")) assert isinstance(snapshot_immediate, PendingSnapshot) assert snapshot_immediate.name() == "Some Name" assert not ha.check() assert not snapshot_immediate.isFailed() await supervisor.toggleBlockSnapshot() # let the snapshot attempt to complete await asyncio.wait({ha._pending_snapshot_task}) # verify it failed with the expected http error assert snapshot_immediate.isFailed() assert snapshot_immediate._exception.status == 524 snapshots = list((await ha.get()).values()) assert len(snapshots) == 1 assert snapshots[0] is snapshot_immediate # verify we can create a new snapshot immediately interceptor.clear() await ha.create(CreateOptions(time.now(), "Some Name")) assert len(await ha.get()) == 1
async def test_failed_snapshot_retry(ha: HaSource, time: FakeTime, config: Config, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor): # create a blocking snapshot interceptor.setError(URL_MATCH_SNAPSHOT_FULL, 524) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0) await supervisor.toggleBlockSnapshot() snapshot_immediate = await ha.create(CreateOptions(time.now(), "Some Name")) assert isinstance(snapshot_immediate, PendingSnapshot) assert snapshot_immediate.name() == "Some Name" assert not ha.check() assert not snapshot_immediate.isFailed() await supervisor.toggleBlockSnapshot() # let the snapshot attempt to complete await asyncio.wait({ha._pending_snapshot_task}) # verify it failed with the expected http error assert snapshot_immediate.isFailed() assert snapshot_immediate._exception.status == 524 assert ha.check() assert not ha.check() time.advance(seconds=config.get(Setting.FAILED_SNAPSHOT_TIMEOUT_SECONDS)) # should trigger a sync after the failed snapshot timeout assert ha.check() await ha.get() assert not ha.check()
async def test_upload_resume(drive: DriveSource, time, snapshot_helper: SnapshotHelper, google: SimulatedGoogle, interceptor: RequestInterceptor): from_snapshot, data = await snapshot_helper.createFile() interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, attempts=1, status=500) # Upload, which will fail with pytest.raises(GoogleInternalError): await drive.save(from_snapshot, data) # Verify we uploaded one chunk assert google.chunks == [BASE_CHUNK_SIZE] # Retry the upload, which shoudl now pass interceptor.clear() data.position(0) drive_snapshot = await drive.save(from_snapshot, data) from_snapshot.addSource(drive_snapshot) assert google.chunks == [ BASE_CHUNK_SIZE, BASE_CHUNK_SIZE, (data.size()) - BASE_CHUNK_SIZE * 2 ] # Verify the data is correct data.position(0) await compareStreams(data, await drive.read(from_snapshot))
async def test_failure_logging(updater: HaUpdater, server, time: FakeTime, interceptor: RequestInterceptor): interceptor.setError(URL_MATCH_CORE_API, 501) assert getLast() is None await updater.update() assert getLast() is None time.advance(minutes=1) await updater.update() assert getLast() is None time.advance(minutes=5) await updater.update() assert getLast().msg == REASSURING_MESSAGE.format(501) last_log = getLast() time.advance(minutes=5) await updater.update() assert getLast() is not last_log assert getLast().msg == REASSURING_MESSAGE.format(501) last_log = getLast() interceptor.clear() await updater.update() assert getLast() is last_log
async def test_backup_supervisor_path(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor): supervisor._super_version = Version(2021, 7) await ha.get() assert not interceptor.urlWasCalled(URL_MATCH_BACKUPS) assert interceptor.urlWasCalled(URL_MATCH_SNAPSHOT)
async def test_refresh_problem_with_google(drive: DriveSource, interceptor: RequestInterceptor, config: Config, time): time.advanceDay() interceptor.setError(".*/oauth2/v4/token.*", status=510) drive.drivebackend.creds._secret = None with pytest.raises(CredRefreshGoogleError) as error: await drive.get() assert error.value.data() == {"from_google": "Google returned HTTP 510"}
async def test_failure_backoff_other(updater: HaUpdater, server, time: FakeTime, interceptor: RequestInterceptor): interceptor.setError(URL_MATCH_CORE_API, 400) for x in range(9): await updater.update() assert time.sleeps == [60, 120, 240, 300, 300, 300, 300, 300, 300] interceptor.clear() await updater.update() assert time.sleeps == [60, 120, 240, 300, 300, 300, 300, 300, 300]
async def test_delete_error(time, ha: HaSource, interceptor: RequestInterceptor): snapshot = await ha.create(CreateOptions(time.now(), "Some Name")) full = DummySnapshot(snapshot.name(), snapshot.date(), snapshot.size(), snapshot.slug(), "dummy") full.addSource(snapshot) interceptor.setError(URL_MATCH_SNAPSHOT_DELETE, 400) with pytest.raises(HomeAssistantDeleteError): await ha.delete(full) interceptor.clear() await ha.delete(full)
async def test_delete_error(time, ha: HaSource, interceptor: RequestInterceptor): backup = await ha.create(CreateOptions(time.now(), "Some Name")) full = DummyBackup(backup.name(), backup.date(), backup.size(), backup.slug(), "dummy") full.addSource(backup) interceptor.setError(URL_MATCH_BACKUP_DELETE, 400) with pytest.raises(HomeAssistantDeleteError): await ha.delete(full) interceptor.clear() await ha.delete(full)
async def test_pending_snapshot_nowait(ha: HaSource, time, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, config: Config): interceptor.setSleep(URL_MATCH_SNAPSHOT_FULL, sleep=5) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0.1) snapshot_immediate: PendingSnapshot = await ha.create( CreateOptions(time.now(), "Test Name")) assert isinstance(snapshot_immediate, PendingSnapshot) snapshot_pending: HASnapshot = (await ha.get())['pending'] assert isinstance(snapshot_immediate, PendingSnapshot) assert isinstance(snapshot_pending, PendingSnapshot) assert snapshot_immediate is snapshot_pending assert snapshot_immediate.name() == "Test Name" assert snapshot_immediate.slug() == "pending" assert not snapshot_immediate.uploadable() assert snapshot_immediate.snapshotType() == "Full" assert snapshot_immediate.source() == SOURCE_HA assert snapshot_immediate.date() == time.now() assert not snapshot_immediate.protected() # Might be a little flaky but...whatever await asyncio.wait({ha._pending_snapshot_task}) snapshots = await ha.get() assert 'pending' not in snapshots assert isinstance(next(iter(snapshots.values())), HASnapshot) return # ignroe events for now assert supervisor.getEvents() == [(EVENT_SNAPSHOT_START, { 'snapshot_name': snapshot_immediate.name(), 'snapshot_time': str(snapshot_immediate.date()) })] ha.snapshot_thread.join() assert supervisor.getEvents() == [(EVENT_SNAPSHOT_START, { 'snapshot_name': snapshot_immediate.name(), 'snapshot_time': str(snapshot_immediate.date()) }), (EVENT_SNAPSHOT_END, { 'completed': True, 'snapshot_name': snapshot_immediate.name(), 'snapshot_time': str(snapshot_immediate.date()) })]
async def test_download_timeout(time, drive: DriveSource, config: Config, interceptor: RequestInterceptor, backup_helper): config.override(Setting.DOWNLOAD_TIMEOUT_SECONDS, 0.1) from_backup, data = await backup_helper.createFile() backup = await drive.save(from_backup, data) # Verify the uploaded bytes are identical from_backup.addSource(backup) interceptor.setSleep(URL_MATCH_FILE, sleep=100) download = await drive.read(from_backup) data.position(0) with pytest.raises(GoogleTimeoutError): await compareStreams(data, download)
async def test_upgrade_all_config(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, config: Config, server_url): """Verify that converting all upgradeable config optiosn works as expected""" # overwrite the addon options with old values supervisor._options = { Setting.DEPRECTAED_MAX_BACKUPS_IN_HA.value: 1, Setting.DEPRECTAED_MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 2, Setting.DEPRECATED_DAYS_BETWEEN_BACKUPS.value: 5, Setting.DEPRECTAED_IGNORE_OTHER_BACKUPS.value: True, Setting.DEPRECTAED_IGNORE_UPGRADE_BACKUPS.value: True, Setting.DEPRECTAED_BACKUP_TIME_OF_DAY.value: "01:11", Setting.DEPRECTAED_DELETE_BEFORE_NEW_BACKUP.value: True, Setting.DEPRECTAED_BACKUP_NAME.value: "test", Setting.DEPRECTAED_SPECIFY_BACKUP_FOLDER.value: True, Setting.DEPRECTAED_NOTIFY_FOR_STALE_BACKUPS.value: False, Setting.DEPRECTAED_ENABLE_BACKUP_STALE_SENSOR.value: False, Setting.DEPRECTAED_ENABLE_BACKUP_STATE_SENSOR.value: False, Setting.DEPRECATED_BACKUP_PASSWORD.value: "test password", } await ha.init() assert not config.mustSaveUpgradeChanges() assert interceptor.urlWasCalled(URL_MATCH_SELF_OPTIONS) # Verify the config was upgraded assert supervisor._options == { Setting.MAX_BACKUPS_IN_HA.value: 1, Setting.MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 2, Setting.DAYS_BETWEEN_BACKUPS.value: 5, Setting.IGNORE_OTHER_BACKUPS.value: True, Setting.IGNORE_UPGRADE_BACKUPS.value: True, Setting.BACKUP_TIME_OF_DAY.value: "01:11", Setting.DELETE_BEFORE_NEW_BACKUP.value: True, Setting.BACKUP_NAME.value: "test", Setting.SPECIFY_BACKUP_FOLDER.value: True, Setting.NOTIFY_FOR_STALE_BACKUPS.value: False, Setting.ENABLE_BACKUP_STALE_SENSOR.value: False, Setting.ENABLE_BACKUP_STATE_SENSOR.value: False, Setting.BACKUP_PASSWORD.value: "test password", Setting.CALL_BACKUP_SNAPSHOT.value: True, } interceptor.clear() await ha.init() assert not interceptor.urlWasCalled(URL_MATCH_SELF_OPTIONS)
async def test_upgrade_some_config(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, config: Config, server_url): """Verify that converting a mix of upgradeable and not upgradeable config works""" # overwrite the addon options with old values supervisor._options = { Setting.DEPRECTAED_MAX_BACKUPS_IN_HA.value: 4, Setting.DEPRECTAED_MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DEPRECATED_DAYS_BETWEEN_BACKUPS.value: 3, Setting.DEPRECTAED_BACKUP_TIME_OF_DAY.value: "01:11", Setting.EXCLUDE_ADDONS.value: "test", Setting.USE_SSL.value: False, } await ha.init() assert not config.mustSaveUpgradeChanges() assert interceptor.urlWasCalled(URL_MATCH_SELF_OPTIONS) # Verify the config was upgraded assert supervisor._options == { Setting.MAX_BACKUPS_IN_HA.value: 4, Setting.MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DAYS_BETWEEN_BACKUPS.value: 3, Setting.EXCLUDE_ADDONS.value: "test", Setting.BACKUP_TIME_OF_DAY.value: "01:11", Setting.CALL_BACKUP_SNAPSHOT.value: True, }
async def test_upgrade_default_config(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, config: Config, server_url): """Verify that converting the original default config optiosn works as expected""" # overwrite the addon options with old values supervisor._options = { Setting.DEPRECTAED_MAX_BACKUPS_IN_HA.value: 4, Setting.DEPRECTAED_MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DEPRECATED_DAYS_BETWEEN_BACKUPS.value: 3, Setting.USE_SSL.value: False, } await ha.init() assert not config.mustSaveUpgradeChanges() assert interceptor.urlWasCalled(URL_MATCH_SELF_OPTIONS) # Verify the config was upgraded assert supervisor._options == { Setting.MAX_BACKUPS_IN_HA.value: 4, Setting.MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DAYS_BETWEEN_BACKUPS.value: 3, Setting.CALL_BACKUP_SNAPSHOT.value: True, }
async def test_upgrade_no_config(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, config: Config, server_url): """Verifies that config not in need of an upgrade doesn't get upgraded""" # overwrite the addon options with old values supervisor._options = { Setting.MAX_BACKUPS_IN_HA.value: 4, Setting.MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DAYS_BETWEEN_BACKUPS.value: 3, Setting.BACKUP_TIME_OF_DAY.value: "01:11", Setting.EXCLUDE_ADDONS.value: "test" } await ha.init() assert not config.mustSaveUpgradeChanges() assert not interceptor.urlWasCalled(URL_MATCH_SELF_OPTIONS) # Verify the config was upgraded assert supervisor._options == { Setting.MAX_BACKUPS_IN_HA.value: 4, Setting.MAX_BACKUPS_IN_GOOGLE_DRIVE.value: 4, Setting.DAYS_BETWEEN_BACKUPS.value: 3, Setting.BACKUP_TIME_OF_DAY.value: "01:11", Setting.EXCLUDE_ADDONS.value: "test", }
async def test_ingore_self_when_stopping( ha: HaSource, time, interceptor: RequestInterceptor, config: Config, supervisor: SimulatedSupervisor) -> None: slug = supervisor._addon_slug config.override(Setting.STOP_ADDONS, slug) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0.001) interceptor.setError(URL_MATCH_START_ADDON, 400) assert supervisor.addon(slug)["state"] == "started" async with supervisor._snapshot_inner_lock: await ha.create(CreateOptions(time.now(), "Test Name")) assert supervisor.addon(slug)["state"] == "started" await ha._pending_snapshot_task assert supervisor.addon(slug)["state"] == "started" assert not interceptor.urlWasCalled(URL_MATCH_START_ADDON) assert not interceptor.urlWasCalled(URL_MATCH_STOP_ADDON) assert len(await ha.get()) == 1
async def test_stop_addon_failure(ha: HaSource, time, interceptor: RequestInterceptor, config: Config, supervisor: SimulatedSupervisor) -> None: slug = "test_slug" supervisor.installAddon(slug, "Test decription") config.override(Setting.STOP_ADDONS, slug) config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0.001) interceptor.setError(URL_MATCH_STOP_ADDON, 400) assert supervisor.addon(slug)["state"] == "started" async with supervisor._snapshot_inner_lock: await ha.create(CreateOptions(time.now(), "Test Name")) assert supervisor.addon(slug)["state"] == "started" await ha._pending_snapshot_task assert supervisor.addon(slug)["state"] == "started" assert len(await ha.get()) == 1
async def test_download_timeout(ha: HaSource, time, interceptor: RequestInterceptor, config: Config) -> None: config.override(Setting.NEW_BACKUP_TIMEOUT_SECONDS, 100) backup: HABackup = await ha.create(CreateOptions(time.now(), "Test Name")) from_ha = await ha.harequests.backup(backup.slug()) full = DummyBackup(from_ha.name(), from_ha.date(), from_ha.size(), from_ha.slug(), "dummy") full.addSource(backup) interceptor.setSleep(URL_MATCH_BACKUP_DOWNLOAD, sleep=100) config.override(Setting.DOWNLOAD_TIMEOUT_SECONDS, 1) direct_download = await ha.harequests.download(backup.slug()) with pytest.raises(SupervisorTimeoutError): await direct_download.setup() await direct_download.read(1)
async def verify_upload_resumed(time, drive: DriveSource, config: Config, server: SimulationServer, interceptor: RequestInterceptor, status, snapshot_helper, expected=ClientResponseError): from_snapshot, data = await snapshot_helper.createFile() # Configure the upload to fail after the first upload chunk interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, status, 1) with pytest.raises(expected): await drive.save(from_snapshot, data) # Verify a requst was made to start the upload assert server.wasUrlRequested( "/upload/drive/v3/files/?uploadType=resumable&supportsAllDrives=true") assert drive.drivebackend.last_attempt_location is not None assert drive.drivebackend.last_attempt_metadata is not None last_location = drive.drivebackend.last_attempt_location # Retry the upload and let is succeed server.urls.clear() interceptor.clear() data.position(0) snapshot = await drive.save(from_snapshot, data) # We shoudl nto see the upload "initialize" url assert not server.wasUrlRequested( "/upload/drive/v3/files/?uploadType=resumable&supportsAllDrives=true") # We should see the last location url (which has a unique token) reused to resume the upload assert server.wasUrlRequested(last_location) # The saved metadata should be cleared out. assert drive.drivebackend.last_attempt_count == 1 assert drive.drivebackend.last_attempt_location is None assert drive.drivebackend.last_attempt_metadata is None # Verify the uploaded bytes are identical from_snapshot.addSource(snapshot) download = await drive.read(from_snapshot) data.position(0) await compareStreams(data, download)
async def test_do_nothing_while_snapshotting( supervisor: SimulatedSupervisor, addon_stopper: AddonStopper, config: Config, interceptor: RequestInterceptor) -> None: slug1 = "test_slug_1" supervisor.installAddon(slug1, "Test decription") slug2 = "test_slug_2" supervisor.installAddon(slug2, "Test decription") config.override(Setting.STOP_ADDONS, ",".join([slug1, slug2])) await addon_stopper.start(False) addon_stopper.allowRun() addon_stopper.isSnapshotting(True) assert addon_stopper.must_start == {slug1, slug2} await addon_stopper.check() assert not interceptor.urlWasCalled(URL_MATCH_START_ADDON) assert not interceptor.urlWasCalled(URL_MATCH_STOP_ADDON)
async def test_get_info_failure_on_stop( supervisor: SimulatedSupervisor, addon_stopper: AddonStopper, config: Config, interceptor: RequestInterceptor) -> None: slug1 = "test_slug_1" supervisor.installAddon(slug1, "Test decription") config.override(Setting.STOP_ADDONS, slug1) addon_stopper.allowRun() addon_stopper.must_start = set() assert supervisor.addon(slug1)["state"] == "started" interceptor.setError(URL_MATCH_ADDON_INFO, 400) await addon_stopper.stopAddons("ignore") assert interceptor.urlWasCalled(URL_MATCH_ADDON_INFO) assert getSaved(config) == (set(), set()) assert supervisor.addon(slug1)["state"] == "started" await addon_stopper.check() await addon_stopper.startAddons() assert supervisor.addon(slug1)["state"] == "started" assert getSaved(config) == (set(), set())
async def test_resume_session_reused_on_http408(time, drive: DriveSource, config: Config, server: SimulationServer, backup_helper: BackupHelper, interceptor: RequestInterceptor): from_backup, data = await backup_helper.createFile() # Configure the upload to fail interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, 408) with pytest.raises(GoogleTimeoutError): await drive.save(from_backup, data) # Verify a requst was made to start the upload assert server.wasUrlRequested(URL_START_UPLOAD) location = drive.drivebackend.last_attempt_location assert location is not None server.urls.clear() interceptor.clear() data.position(0) await drive.save(from_backup, data) assert interceptor.urlWasCalled(URL(location).path)
async def test_resume_upload_attempts_exhausted( drive: DriveSource, time, snapshot_helper, interceptor: RequestInterceptor, google: SimulatedGoogle): # Allow an upload to update one chunk and then fail. from_snapshot, data = await snapshot_helper.createFile() interceptor.setError(URL_MATCH_UPLOAD_PROGRESS, attempts=1, status=500) with pytest.raises(GoogleInternalError): await drive.save(from_snapshot, data) assert google.chunks == [BASE_CHUNK_SIZE] # Verify we have a cached location assert drive.drivebackend.last_attempt_location is not None assert drive.drivebackend.last_attempt_count == 1 last_location = drive.drivebackend.last_attempt_location for x in range(1, RETRY_SESSION_ATTEMPTS): data.position(0) with pytest.raises(GoogleInternalError): await drive.save(from_snapshot, data) assert drive.drivebackend.last_attempt_count == x + 1 # We should still be using the same location url assert drive.drivebackend.last_attempt_location == last_location # Another attempt should use another location url with pytest.raises(GoogleInternalError): data.position(0) await drive.save(from_snapshot, data) assert drive.drivebackend.last_attempt_count == 0 assert drive.drivebackend.last_attempt_location is not None assert drive.drivebackend.last_attempt_location != last_location # Now let it succeed interceptor.clear() data.position(0) drive_snapshot = await drive.save(from_snapshot, data) from_snapshot.addSource(drive_snapshot) # And verify the bytes are correct data.position(0) await compareStreams(data, await drive.read(from_snapshot))
async def test_immediate_backup_failure(time: FakeTime, ha: HaSource, config: Config, interceptor: RequestInterceptor): interceptor.setError(URL_MATCH_BACKUP_FULL, 524) with pytest.raises(ClientResponseError) as thrown: await ha.create(CreateOptions(time.now(), "Some Name")) assert thrown.value.status == 524 assert ha.pending_backup is not None backups = list((await ha.get()).values()) assert len(backups) == 1 assert backups[0].isFailed() # Failed backup should go away after it times out assert ha.check() assert not ha.check() time.advance(seconds=config.get(Setting.FAILED_BACKUP_TIMEOUT_SECONDS) + 1) assert ha.check() assert len(await ha.get()) == 0 assert not ha.check()
async def test_old_delete_path(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, time: FakeTime): supervisor._super_version = Version(2020, 8) await ha.get() backup: HABackup = await ha.create(CreateOptions(time.now(), "Test Name")) full = DummyBackup(backup.name(), backup.date(), backup.size(), backup.slug(), "dummy") full.addSource(backup) await ha.delete(full) assert interceptor.urlWasCalled("/snapshots/{0}/remove".format( backup.slug()))