async def test_dont_purge_pending_backup(ha: HaSource, time, config: Config,
                                         supervisor: SimulatedSupervisor,
                                         model: Model, interceptor):
    config.override(Setting.MAX_BACKUPS_IN_HA, 4)
    await ha.create(CreateOptions(time.now(), "Test Name 1"))
    await ha.create(CreateOptions(time.now(), "Test Name 2"))
    await ha.create(CreateOptions(time.now(), "Test Name 3"))
    await ha.create(CreateOptions(time.now(), "Test Name 4"))
    await model.sync(time.now())

    config.override(Setting.NEW_BACKUP_TIMEOUT_SECONDS, 0.1)
    interceptor.setSleep(URL_MATCH_BACKUP_FULL, sleep=2)
    await ha.create(CreateOptions(time.now(), "Test Name"))
    backups = list((await ha.get()).values())
    assert len(backups) == 5
    backup = backups[4]
    assert isinstance(backup, PendingBackup)

    # no backup should get purged yet because the ending backup isn't considered for purging.
    await model.sync(time.now())
    backups = list((await ha.get()).values())
    assert len(backups) == 5

    # Wait for the backup to finish, then verify one gets purged.
    await ha._pending_backup_task
    await model.sync(time.now())
    backups = list((await ha.get()).values())
    assert len(backups) == 4
async def test_partial_snapshot(ha, time, server, config: Config):
    config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 100)
    for folder in all_folders:
        config.override(Setting.EXCLUDE_FOLDERS, folder)
        snapshot: HASnapshot = await ha.create(
            CreateOptions(time.now(), "Test Name"))

        assert snapshot.snapshotType() == "partial"
        for search in all_folders:
            if search == folder:
                assert search not in snapshot.details()['folders']
            else:
                assert search in snapshot.details()['folders']

    for addon in all_addons:
        config.override(Setting.EXCLUDE_ADDONS, addon['slug'])
        snapshot: HASnapshot = await ha.create(
            CreateOptions(time.now(), "Test Name"))
        assert snapshot.snapshotType() == "partial"
        list_of_addons = []
        for included in snapshot.details()['addons']:
            list_of_addons.append(included['slug'])
        for search in list_of_addons:
            if search == addon:
                assert search not in list_of_addons
            else:
                assert search in list_of_addons

    # excluding addon/folders that don't exist should actually make a full snapshot
    config.override(Setting.EXCLUDE_ADDONS, "none,of.these,are.addons")
    config.override(Setting.EXCLUDE_FOLDERS, "not,folders,either")
    snapshot: HASnapshot = await ha.create(
        CreateOptions(time.now(), "Test Name"))
    assert snapshot.snapshotType() == "full"
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_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_dont_purge_pending_snapshot(ha: HaSource, time, config: Config,
                                           supervisor: SimulatedSupervisor,
                                           model: Model, interceptor):
    config.override(Setting.MAX_SNAPSHOTS_IN_HASSIO, 4)
    await ha.create(CreateOptions(time.now(), "Test Name 1"))
    await ha.create(CreateOptions(time.now(), "Test Name 2"))
    await ha.create(CreateOptions(time.now(), "Test Name 3"))
    await ha.create(CreateOptions(time.now(), "Test Name 4"))
    await model.sync(time.now())

    config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 0.1)
    interceptor.setSleep(URL_MATCH_SNAPSHOT_FULL, sleep=2)
    await ha.create(CreateOptions(time.now(), "Test Name"))
    snapshots = list((await ha.get()).values())
    assert len(snapshots) == 5
    snapshot = snapshots[4]
    assert isinstance(snapshot, PendingSnapshot)

    # no snapshot should get purged yet because the ending snapshot isn't considered for purging.
    await model.sync(time.now())
    snapshots = list((await ha.get()).values())
    assert len(snapshots) == 5

    # Wait for the snapshot to finish, then verify one gets purged.
    await ha._pending_snapshot_task
    await model.sync(time.now())
    snapshots = list((await ha.get()).values())
    assert len(snapshots) == 4
Exemplo n.º 6
0
async def test_sync_different_sources(model, time, source, dest):
    snapshot_source = await source.create(CreateOptions(time.now(), "name"))
    snapshot_dest = await dest.create(CreateOptions(time.now(), "name"))

    await model._syncSnapshots([source, dest])
    assert len(model.snapshots) == 2
    assert model.snapshots[snapshot_source.slug()].getSource(
        source.name()) is snapshot_source
    assert model.snapshots[snapshot_dest.slug()].getSource(
        dest.name()) is snapshot_dest
Exemplo n.º 7
0
async def test_sync_different_sources(model: Model, time, source, dest):
    backup_source = await source.create(CreateOptions(time.now(), "name"))
    backup_dest = await dest.create(CreateOptions(time.now(), "name"))

    await model._syncBackups([source, dest])
    assert len(model.backups) == 2
    assert model.backups[backup_source.slug()].getSource(
        source.name()) is backup_source
    assert model.backups[backup_dest.slug()].getSource(
        dest.name()) is backup_dest
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_pending_snapshot_replaces_original(
        time, ha: HaSource, config: Config, supervisor: SimulatedSupervisor):
    # now configure a snapshto to start outside of the addon
    config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 100)
    await supervisor.toggleBlockSnapshot()
    with pytest.raises(SnapshotInProgress):
        await ha.create(CreateOptions(time.now(), "Ignored"))
    snapshot_immediate = (await ha.get())['pending']
    await supervisor.toggleBlockSnapshot()
    assert isinstance(snapshot_immediate, PendingSnapshot)
    assert snapshot_immediate.name() == "Pending Snapshot"
    assert ha.check()
    assert ha.pending_snapshot is snapshot_immediate
    assert await ha.get() == {snapshot_immediate.slug(): snapshot_immediate}

    # create a new snapshot behind the scenes, the pending snapshot should get replaced with the new one
    slug = (await ha.harequests.createSnapshot({
        'name': "Suddenly Appears",
        "hardlock": True
    }))['slug']
    results = await ha.get()
    assert len(results) == 1
    assert slug in results
    assert results[slug].name() == "Suddenly Appears"
    assert not results[slug].retained()
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_new_backup(coord: Coordinator, time: FakeTime, source, dest):
    await coord.startBackup(CreateOptions(time.now(), "Test Name"))
    backups = coord.backups()
    assert len(backups) == 1
    assert backups[0].name() == "Test Name"
    assert backups[0].getSource(source.name()) is not None
    assert backups[0].getSource(dest.name()) is None
Exemplo n.º 12
0
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_new_snapshot(coord: Coordinator, time: FakeTime, source, dest):
    await coord.startSnapshot(CreateOptions(time.now(), "Test Name"))
    snapshots = coord.snapshots()
    assert len(snapshots) == 1
    assert snapshots[0].name() == "Test Name"
    assert snapshots[0].getSource(source.name()) is not None
    assert snapshots[0].getSource(dest.name()) is None
Exemplo n.º 14
0
async def test_removal(model, time, source, dest):
    await source.create(CreateOptions(time.now(), "name"))
    await model._syncSnapshots([source, dest])
    assert len(model.snapshots) == 1
    source.current = {}
    await model._syncSnapshots([source, dest])
    assert len(model.snapshots) == 0
Exemplo n.º 15
0
async def test_sync_single_source(model: Model, source, dest, time):
    backup = await source.create(CreateOptions(time.now(), "name"))
    dest.setEnabled(False)
    await model.sync(time.now())
    assert len(model.backups) == 1
    assert backup.slug() in model.backups
    assert model.backups[backup.slug()].getSource(source.name()) is backup
    assert model.backups[backup.slug()].getSource(dest.name()) is None
async def test_check_size_new_snapshot(coord: Coordinator,
                                       source: HelperTestSource,
                                       dest: HelperTestSource, time,
                                       fs: FsFaker):
    skipForWindows()
    fs.setFreeBytes(0)
    with raises(LowSpaceError):
        await coord.startSnapshot(CreateOptions(time.now(), "Test Name"))
Exemplo n.º 17
0
async def test_sync_single_source(model, source, dest, time):
    snapshot = await source.create(CreateOptions(time.now(), "name"))
    dest.setEnabled(False)
    await model.sync(time.now())
    assert len(model.snapshots) == 1
    assert snapshot.slug() in model.snapshots
    assert model.snapshots[snapshot.slug()].getSource(
        source.name()) is snapshot
    assert model.snapshots[snapshot.slug()].getSource(dest.name()) is None
Exemplo n.º 18
0
async def test_CRUD(ha: HaSource, time, interceptor: RequestInterceptor,
                    data_cache: DataCache) -> None:
    snapshot: HASnapshot = await ha.create(
        CreateOptions(time.now(), "Test Name"))

    assert snapshot.name() == "Test Name"
    assert type(snapshot) is HASnapshot
    assert not snapshot.retained()
    assert snapshot.snapshotType() == "full"
    assert not snapshot.protected()
    assert snapshot.name() == "Test Name"
    assert snapshot.source() == SOURCE_HA
    assert not snapshot.ignore()
    assert snapshot.madeByTheAddon()
    assert "pending" not in data_cache.snapshots

    # read the item directly, its metadata should match
    from_ha = await ha.harequests.snapshot(snapshot.slug())
    assert from_ha.size() == snapshot.size()
    assert from_ha.slug() == snapshot.slug()
    assert from_ha.source() == SOURCE_HA

    snapshots = await ha.get()
    assert len(snapshots) == 1
    assert snapshot.slug() in snapshots

    full = DummySnapshot(from_ha.name(), from_ha.date(), from_ha.size(),
                         from_ha.slug(), "dummy")
    full.addSource(snapshot)

    # download the item, its bytes should match up
    download = await ha.read(full)
    await download.setup()
    direct_download = await ha.harequests.download(snapshot.slug())
    await direct_download.setup()
    while True:
        from_file = await direct_download.read(1024 * 1024)
        from_download = await download.read(1024 * 1024)
        if len(from_file.getbuffer()) == 0:
            assert len(from_download.getbuffer()) == 0
            break
        assert from_file.getbuffer() == from_download.getbuffer()

    # update retention
    assert not snapshot.retained()
    await ha.retain(full, True)
    assert (await ha.get())[full.slug()].retained()
    await ha.retain(full, False)
    assert not (await ha.get())[full.slug()].retained()

    # Delete the item, make sure its gone
    await ha.delete(full)
    assert full.getSource(ha.name()) is None
    snapshots = await ha.get()
    assert len(snapshots) == 0
Exemplo n.º 19
0
    async def sync(self, now: datetime):
        if self.simulate_error is not None:
            if self.simulate_error.startswith("test"):
                raise Exception(self.simulate_error)
            else:
                raise SimulatedError(self.simulate_error)
        await self._syncBackups([self.source, self.dest])

        self.source.checkBeforeChanges()
        self.dest.checkBeforeChanges()

        if not self.dest.needsConfiguration():
            if self.source.enabled():
                await self._purge(self.source)
            if self.dest.enabled():
                await self._purge(self.dest)

        self._handleBackupDetails()
        next_backup = self.nextBackup(now)
        if next_backup and now >= next_backup and self.source.enabled() and not self.dest.needsConfiguration():
            if self.config.get(Setting.DELETE_BEFORE_NEW_BACKUP):
                await self._purge(self.source, pre_purge=True)
            await self.createBackup(CreateOptions(now, self.config.get(Setting.BACKUP_NAME)))
            await self._purge(self.source)
            self._handleBackupDetails()

        if self.dest.enabled() and self.dest.upload():
            # get the backups we should upload
            uploads = []
            for backup in self.backups.values():
                if backup.getSource(self.source.name()) is not None and backup.getSource(self.source.name()).uploadable() and backup.getSource(self.dest.name()) is None and not backup.ignore():
                    uploads.append(backup)
            uploads.sort(key=lambda s: s.date())
            uploads.reverse()
            for upload in uploads:
                # only upload if doing so won't result in it being deleted next
                dummy = DummyBackup(
                    "", upload.date(), self.dest.name(), "dummy_slug_name")
                proposed = list(self.backups.values())
                proposed.append(dummy)
                if self._nextPurge(self.dest, proposed) != dummy:
                    if self.config.get(Setting.DELETE_BEFORE_NEW_BACKUP):
                        await self._purge(self.dest, pre_purge=True)
                    upload.addSource(await self.dest.save(upload, await self.source.read(upload)))
                    await self._purge(self.dest)
                    self._handleBackupDetails()
                else:
                    break
            if self.config.get(Setting.DELETE_AFTER_UPLOAD):
                await self._purge(self.source)
        self._handleBackupDetails()
        self.source.postSync()
        self.dest.postSync()
        self._data_cache.saveIfDirty()
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_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()))
Exemplo n.º 23
0
 async def snapshot(self, request: Request) -> Any:
     custom_name = request.query.get("custom_name", None)
     retain_drive = BoolValidator.strToBool(
         request.query.get("retain_drive", False))
     retain_ha = BoolValidator.strToBool(
         request.query.get("retain_ha", False))
     options = CreateOptions(self._time.now(), custom_name, {
         SOURCE_GOOGLE_DRIVE: retain_drive,
         SOURCE_HA: retain_ha
     })
     snapshot = await self._coord.startSnapshot(options)
     return web.json_response({"message": "Requested snapshot '{0}'".format(snapshot.name())})
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())
                                      })]
Exemplo n.º 25
0
async def test_sync_source_and_dest(model: Model, time, source,
                                    dest: HelperTestSource):
    backup_source = await source.create(CreateOptions(time.now(), "name"))
    await model._syncBackups([source, dest])
    assert len(model.backups) == 1

    backup_dest = await dest.save(model.backups[backup_source.slug()])
    await model._syncBackups([source, dest])
    assert len(model.backups) == 1
    assert model.backups[backup_source.slug()].getSource(
        source.name()) is backup_source
    assert model.backups[backup_source.slug()].getSource(
        dest.name()) is backup_dest
async def test_bump_last_seen(ha: HaSource, time: Time, config: Config,
                              supervisor: SimulatedSupervisor, model: Model,
                              interceptor, data_cache: DataCache):
    backup = await ha.create(CreateOptions(time.now(), "Test Name"))
    time.advance(days=1)
    assert backup.slug() in await ha.get()
    assert data_cache.backup(
        backup.slug())[KEY_LAST_SEEN] == time.now().isoformat()

    time.advance(days=1)
    assert backup.slug() in await ha.get()
    assert data_cache.backup(
        backup.slug())[KEY_LAST_SEEN] == time.now().isoformat()
async def test_backup_password(ha: HaSource, config: Config, time):
    config.override(Setting.NEW_BACKUP_TIMEOUT_SECONDS, 100)
    backup: HABackup = await ha.create(CreateOptions(time.now(), "Test Name"))
    assert not backup.protected()

    config.override(Setting.BACKUP_PASSWORD, 'test')
    backup = await ha.create(CreateOptions(time.now(), "Test Name"))
    assert backup.protected()

    config.override(Setting.BACKUP_PASSWORD, 'test')
    assert Password(ha.config).resolve() == 'test'

    config.override(Setting.BACKUP_PASSWORD, '!secret for_unit_tests')
    assert Password(ha.config).resolve() == 'password value'

    config.override(Setting.BACKUP_PASSWORD, '!secret bad_key')
    with pytest.raises(BackupPasswordKeyInvalid):
        Password(config).resolve()

    config.override(Setting.SECRETS_FILE_PATH, "/bad/file/path")
    config.override(Setting.BACKUP_PASSWORD, '!secret for_unit_tests')
    with pytest.raises(BackupPasswordKeyInvalid):
        Password(ha.config).resolve()
async def test_pending_backup_already_in_progress(
        ha, time, config: Config, supervisor: SimulatedSupervisor):
    await ha.create(CreateOptions(time.now(), "Test Name"))
    assert len(await ha.get()) == 1

    config.override(Setting.NEW_BACKUP_TIMEOUT_SECONDS, 100)
    await supervisor.toggleBlockBackup()
    with pytest.raises(BackupInProgress):
        await ha.create(CreateOptions(time.now(), "Test Name"))
    backups = list((await ha.get()).values())
    assert len(backups) == 2
    backup = backups[1]

    assert isinstance(backup, PendingBackup)
    assert backup.name() == "Pending Backup"
    assert backup.slug() == "pending"
    assert not backup.uploadable()
    assert backup.backupType() == "unknown"
    assert backup.source() == SOURCE_HA
    assert backup.date() == time.now()
    assert not backup.protected()

    with pytest.raises(BackupInProgress):
        await ha.create(CreateOptions(time.now(), "Test Name"))
async def test_snapshot_password(ha: HaSource, config: Config, time):
    config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 100)
    snapshot: HASnapshot = await ha.create(
        CreateOptions(time.now(), "Test Name"))
    assert not snapshot.protected()

    config.override(Setting.SNAPSHOT_PASSWORD, 'test')
    snapshot = await ha.create(CreateOptions(time.now(), "Test Name"))
    assert snapshot.protected()

    config.override(Setting.SNAPSHOT_PASSWORD, 'test')
    assert Password(ha.config).resolve() == 'test'

    config.override(Setting.SNAPSHOT_PASSWORD, '!secret for_unit_tests')
    assert Password(ha.config).resolve() == 'password value'

    config.override(Setting.SNAPSHOT_PASSWORD, '!secret bad_key')
    with pytest.raises(SnapshotPasswordKeyInvalid):
        Password(config).resolve()

    config.override(Setting.SECRETS_FILE_PATH, "/bad/file/path")
    config.override(Setting.SNAPSHOT_PASSWORD, '!secret for_unit_tests')
    with pytest.raises(SnapshotPasswordKeyInvalid):
        Password(ha.config).resolve()
async def test_pending_snapshot_already_in_progress(
        ha, time, config: Config, supervisor: SimulatedSupervisor):
    await ha.create(CreateOptions(time.now(), "Test Name"))
    assert len(await ha.get()) == 1

    config.override(Setting.NEW_SNAPSHOT_TIMEOUT_SECONDS, 100)
    await supervisor.toggleBlockSnapshot()
    with pytest.raises(SnapshotInProgress):
        await ha.create(CreateOptions(time.now(), "Test Name"))
    snapshots = list((await ha.get()).values())
    assert len(snapshots) == 2
    snapshot = snapshots[1]

    assert isinstance(snapshot, PendingSnapshot)
    assert snapshot.name() == "Pending Snapshot"
    assert snapshot.slug() == "pending"
    assert not snapshot.uploadable()
    assert snapshot.snapshotType() == "unknown"
    assert snapshot.source() == SOURCE_HA
    assert snapshot.date() == time.now()
    assert not snapshot.protected()

    with pytest.raises(SnapshotInProgress):
        await ha.create(CreateOptions(time.now(), "Test Name"))