mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2024-11-18 16:39:13 +00:00
539 lines
17 KiB
Python
539 lines
17 KiB
Python
import pytest
|
|
from copy import copy
|
|
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
from selfprivacy_api.jobs import Jobs
|
|
from selfprivacy_api.services import Service, get_all_services
|
|
|
|
from selfprivacy_api.graphql.common_types.backup import (
|
|
BackupReason,
|
|
AutobackupQuotas,
|
|
)
|
|
|
|
from selfprivacy_api.backup import Backups, Snapshot
|
|
from selfprivacy_api.backup.tasks import (
|
|
prune_autobackup_snapshots,
|
|
)
|
|
|
|
from tests.test_backup import backups
|
|
|
|
|
|
def backuppable_services() -> list[Service]:
|
|
return [service for service in get_all_services() if service.can_be_backed_up()]
|
|
|
|
|
|
def dummy_snapshot(date: datetime):
|
|
return Snapshot(
|
|
id=str(hash(date)),
|
|
service_name="someservice",
|
|
created_at=date,
|
|
reason=BackupReason.EXPLICIT,
|
|
)
|
|
|
|
|
|
def test_no_default_autobackup(backups, dummy_service):
|
|
now = datetime.now(timezone.utc)
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert not Backups.is_time_to_backup(now)
|
|
|
|
|
|
# --------------------- Timing -------------------------
|
|
|
|
|
|
def test_set_autobackup_period(backups):
|
|
assert Backups.autobackup_period_minutes() is None
|
|
|
|
Backups.set_autobackup_period_minutes(2)
|
|
assert Backups.autobackup_period_minutes() == 2
|
|
|
|
Backups.disable_all_autobackup()
|
|
assert Backups.autobackup_period_minutes() is None
|
|
|
|
Backups.set_autobackup_period_minutes(3)
|
|
assert Backups.autobackup_period_minutes() == 3
|
|
|
|
Backups.set_autobackup_period_minutes(0)
|
|
assert Backups.autobackup_period_minutes() is None
|
|
|
|
Backups.set_autobackup_period_minutes(3)
|
|
assert Backups.autobackup_period_minutes() == 3
|
|
|
|
Backups.set_autobackup_period_minutes(-1)
|
|
assert Backups.autobackup_period_minutes() is None
|
|
|
|
|
|
def test_autobackup_timer_periods(backups, dummy_service):
|
|
now = datetime.now(timezone.utc)
|
|
backup_period = 13 # minutes
|
|
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert not Backups.is_time_to_backup(now)
|
|
|
|
Backups.set_autobackup_period_minutes(backup_period)
|
|
assert Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert Backups.is_time_to_backup(now)
|
|
|
|
Backups.set_autobackup_period_minutes(0)
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert not Backups.is_time_to_backup(now)
|
|
|
|
|
|
def test_autobackup_timer_enabling(backups, dummy_service):
|
|
now = datetime.now(timezone.utc)
|
|
backup_period = 13 # minutes
|
|
dummy_service.set_backuppable(False)
|
|
|
|
Backups.set_autobackup_period_minutes(backup_period)
|
|
assert Backups.is_time_to_backup(
|
|
now
|
|
) # there are other services too, not just our dummy
|
|
|
|
# not backuppable service is not backuppable even if period is set
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
|
|
dummy_service.set_backuppable(True)
|
|
assert dummy_service.can_be_backed_up()
|
|
assert Backups.is_time_to_backup_service(dummy_service, now)
|
|
|
|
Backups.disable_all_autobackup()
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert not Backups.is_time_to_backup(now)
|
|
|
|
|
|
def test_autobackup_timing(backups, dummy_service):
|
|
backup_period = 13 # minutes
|
|
now = datetime.now(timezone.utc)
|
|
|
|
Backups.set_autobackup_period_minutes(backup_period)
|
|
assert Backups.is_time_to_backup_service(dummy_service, now)
|
|
assert Backups.is_time_to_backup(now)
|
|
|
|
Backups.back_up(dummy_service)
|
|
|
|
now = datetime.now(timezone.utc)
|
|
assert not Backups.is_time_to_backup_service(dummy_service, now)
|
|
|
|
past = datetime.now(timezone.utc) - timedelta(minutes=1)
|
|
assert not Backups.is_time_to_backup_service(dummy_service, past)
|
|
|
|
future = datetime.now(timezone.utc) + timedelta(minutes=backup_period + 2)
|
|
assert Backups.is_time_to_backup_service(dummy_service, future)
|
|
|
|
|
|
# --------------------- What to autobackup and what not to --------------------
|
|
|
|
|
|
def test_services_to_autobackup(backups, dummy_service):
|
|
backup_period = 13 # minutes
|
|
now = datetime.now(timezone.utc)
|
|
|
|
dummy_service.set_backuppable(False)
|
|
services = Backups.services_to_back_up(now)
|
|
assert len(services) == 0
|
|
|
|
dummy_service.set_backuppable(True)
|
|
|
|
services = Backups.services_to_back_up(now)
|
|
assert len(services) == 0
|
|
|
|
Backups.set_autobackup_period_minutes(backup_period)
|
|
|
|
services = Backups.services_to_back_up(now)
|
|
assert len(services) == len(backuppable_services())
|
|
assert dummy_service.get_id() in [
|
|
service.get_id() for service in backuppable_services()
|
|
]
|
|
|
|
|
|
def test_do_not_autobackup_disabled_services(backups, dummy_service):
|
|
now = datetime.now(timezone.utc)
|
|
Backups.set_autobackup_period_minutes(3)
|
|
assert Backups.is_time_to_backup_service(dummy_service, now) is True
|
|
|
|
dummy_service.disable()
|
|
assert Backups.is_time_to_backup_service(dummy_service, now) is False
|
|
|
|
|
|
def test_failed_autoback_prevents_more_autobackup(backups, dummy_service):
|
|
backup_period = 13 # minutes
|
|
now = datetime.now(timezone.utc)
|
|
|
|
Backups.set_autobackup_period_minutes(backup_period)
|
|
assert Backups.is_time_to_backup_service(dummy_service, now)
|
|
|
|
# artificially making an errored out backup job
|
|
dummy_service.set_backuppable(False)
|
|
with pytest.raises(ValueError):
|
|
Backups.back_up(dummy_service)
|
|
dummy_service.set_backuppable(True)
|
|
|
|
assert Backups.get_last_backed_up(dummy_service) is None
|
|
assert Backups.get_last_backup_error_time(dummy_service) is not None
|
|
|
|
assert Backups.is_time_to_backup_service(dummy_service, now) is False
|
|
|
|
|
|
# --------------------- Quotas and Pruning -------------------------
|
|
|
|
|
|
unlimited_quotas = AutobackupQuotas(
|
|
last=-1,
|
|
daily=-1,
|
|
weekly=-1,
|
|
monthly=-1,
|
|
yearly=-1,
|
|
)
|
|
|
|
zero_quotas = AutobackupQuotas(
|
|
last=0,
|
|
daily=0,
|
|
weekly=0,
|
|
monthly=0,
|
|
yearly=0,
|
|
)
|
|
|
|
unlimited_quotas = AutobackupQuotas(
|
|
last=-1,
|
|
daily=-1,
|
|
weekly=-1,
|
|
monthly=-1,
|
|
yearly=-1,
|
|
)
|
|
|
|
zero_quotas = AutobackupQuotas(
|
|
last=0,
|
|
daily=0,
|
|
weekly=0,
|
|
monthly=0,
|
|
yearly=0,
|
|
)
|
|
|
|
|
|
def test_get_empty_quotas(backups):
|
|
quotas = Backups.autobackup_quotas()
|
|
assert quotas is not None
|
|
assert quotas == unlimited_quotas
|
|
|
|
|
|
def test_set_quotas(backups):
|
|
quotas = AutobackupQuotas(
|
|
last=3,
|
|
daily=2343,
|
|
weekly=343,
|
|
monthly=0,
|
|
yearly=-34556,
|
|
)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups.autobackup_quotas() == AutobackupQuotas(
|
|
last=3,
|
|
daily=2343,
|
|
weekly=343,
|
|
monthly=0,
|
|
yearly=-1,
|
|
)
|
|
|
|
|
|
def test_set_zero_quotas(backups):
|
|
quotas = AutobackupQuotas(
|
|
last=0,
|
|
daily=0,
|
|
weekly=0,
|
|
monthly=0,
|
|
yearly=0,
|
|
)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups.autobackup_quotas() == zero_quotas
|
|
|
|
|
|
def test_set_unlimited_quotas(backups):
|
|
quotas = AutobackupQuotas(
|
|
last=-1,
|
|
daily=-1,
|
|
weekly=-1,
|
|
monthly=-1,
|
|
yearly=-1,
|
|
)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups.autobackup_quotas() == unlimited_quotas
|
|
|
|
|
|
def test_set_zero_quotas_after_unlimited(backups):
|
|
quotas = AutobackupQuotas(
|
|
last=-1,
|
|
daily=-1,
|
|
weekly=-1,
|
|
monthly=-1,
|
|
yearly=-1,
|
|
)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups.autobackup_quotas() == unlimited_quotas
|
|
|
|
quotas = AutobackupQuotas(
|
|
last=0,
|
|
daily=0,
|
|
weekly=0,
|
|
monthly=0,
|
|
yearly=0,
|
|
)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups.autobackup_quotas() == zero_quotas
|
|
|
|
|
|
def test_autobackup_snapshots_pruning(backups):
|
|
# Wednesday, fourth week
|
|
now = datetime(year=2023, month=1, day=25, hour=10)
|
|
|
|
snaps = [
|
|
dummy_snapshot(now),
|
|
dummy_snapshot(now - timedelta(minutes=5)),
|
|
dummy_snapshot(now - timedelta(hours=2)),
|
|
dummy_snapshot(now - timedelta(hours=5)),
|
|
dummy_snapshot(now - timedelta(days=1)),
|
|
dummy_snapshot(now - timedelta(days=1, hours=2)),
|
|
dummy_snapshot(now - timedelta(days=1, hours=3)),
|
|
dummy_snapshot(now - timedelta(days=2)),
|
|
dummy_snapshot(now - timedelta(days=7)),
|
|
dummy_snapshot(now - timedelta(days=12)),
|
|
dummy_snapshot(now - timedelta(days=23)),
|
|
dummy_snapshot(now - timedelta(days=28)),
|
|
dummy_snapshot(now - timedelta(days=32)),
|
|
dummy_snapshot(now - timedelta(days=47)),
|
|
dummy_snapshot(now - timedelta(days=64)),
|
|
dummy_snapshot(now - timedelta(days=84)),
|
|
dummy_snapshot(now - timedelta(days=104)),
|
|
dummy_snapshot(now - timedelta(days=365 * 2)),
|
|
]
|
|
old_len = len(snaps)
|
|
|
|
quotas = copy(unlimited_quotas)
|
|
Backups.set_autobackup_quotas(quotas)
|
|
assert Backups._prune_snaps_with_quotas(snaps) == snaps
|
|
|
|
quotas = copy(zero_quotas)
|
|
quotas.last = 2
|
|
quotas.daily = 2
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(now),
|
|
dummy_snapshot(now - timedelta(minutes=5)),
|
|
# dummy_snapshot(now - timedelta(hours=2)),
|
|
# dummy_snapshot(now - timedelta(hours=5)),
|
|
dummy_snapshot(now - timedelta(days=1)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=2)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=3)),
|
|
# dummy_snapshot(now - timedelta(days=2)),
|
|
# dummy_snapshot(now - timedelta(days=7)),
|
|
# dummy_snapshot(now - timedelta(days=12)),
|
|
# dummy_snapshot(now - timedelta(days=23)),
|
|
# dummy_snapshot(now - timedelta(days=28)),
|
|
# dummy_snapshot(now - timedelta(days=32)),
|
|
# dummy_snapshot(now - timedelta(days=47)),
|
|
# dummy_snapshot(now - timedelta(days=64)),
|
|
# dummy_snapshot(now - timedelta(days=84)),
|
|
# dummy_snapshot(now - timedelta(days=104)),
|
|
# dummy_snapshot(now - timedelta(days=365 * 2)),
|
|
]
|
|
|
|
# checking that this function does not mutate the argument
|
|
assert snaps != snaps_to_keep
|
|
assert len(snaps) == old_len
|
|
|
|
quotas = copy(zero_quotas)
|
|
quotas.weekly = 4
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(now),
|
|
# dummy_snapshot(now - timedelta(minutes=5)),
|
|
# dummy_snapshot(now - timedelta(hours=2)),
|
|
# dummy_snapshot(now - timedelta(hours=5)),
|
|
# dummy_snapshot(now - timedelta(days=1)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=2)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=3)),
|
|
# dummy_snapshot(now - timedelta(days=2)),
|
|
dummy_snapshot(now - timedelta(days=7)),
|
|
dummy_snapshot(now - timedelta(days=12)),
|
|
dummy_snapshot(now - timedelta(days=23)),
|
|
# dummy_snapshot(now - timedelta(days=28)),
|
|
# dummy_snapshot(now - timedelta(days=32)),
|
|
# dummy_snapshot(now - timedelta(days=47)),
|
|
# dummy_snapshot(now - timedelta(days=64)),
|
|
# dummy_snapshot(now - timedelta(days=84)),
|
|
# dummy_snapshot(now - timedelta(days=104)),
|
|
# dummy_snapshot(now - timedelta(days=365 * 2)),
|
|
]
|
|
|
|
quotas = copy(zero_quotas)
|
|
quotas.monthly = 7
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(now),
|
|
# dummy_snapshot(now - timedelta(minutes=5)),
|
|
# dummy_snapshot(now - timedelta(hours=2)),
|
|
# dummy_snapshot(now - timedelta(hours=5)),
|
|
# dummy_snapshot(now - timedelta(days=1)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=2)),
|
|
# dummy_snapshot(now - timedelta(days=1, hours=3)),
|
|
# dummy_snapshot(now - timedelta(days=2)),
|
|
# dummy_snapshot(now - timedelta(days=7)),
|
|
# dummy_snapshot(now - timedelta(days=12)),
|
|
# dummy_snapshot(now - timedelta(days=23)),
|
|
dummy_snapshot(now - timedelta(days=28)),
|
|
# dummy_snapshot(now - timedelta(days=32)),
|
|
# dummy_snapshot(now - timedelta(days=47)),
|
|
dummy_snapshot(now - timedelta(days=64)),
|
|
# dummy_snapshot(now - timedelta(days=84)),
|
|
dummy_snapshot(now - timedelta(days=104)),
|
|
dummy_snapshot(now - timedelta(days=365 * 2)),
|
|
]
|
|
|
|
|
|
def test_autobackup_snapshots_pruning_yearly(backups):
|
|
snaps = [
|
|
dummy_snapshot(datetime(year=2055, month=3, day=1)),
|
|
dummy_snapshot(datetime(year=2055, month=2, day=1)),
|
|
dummy_snapshot(datetime(year=2023, month=4, day=1)),
|
|
dummy_snapshot(datetime(year=2023, month=3, day=1)),
|
|
dummy_snapshot(datetime(year=2023, month=2, day=1)),
|
|
dummy_snapshot(datetime(year=2021, month=2, day=1)),
|
|
]
|
|
quotas = copy(zero_quotas)
|
|
quotas.yearly = 2
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(datetime(year=2055, month=3, day=1)),
|
|
dummy_snapshot(datetime(year=2023, month=4, day=1)),
|
|
]
|
|
|
|
|
|
def test_autobackup_snapshots_pruning_bottleneck(backups):
|
|
now = datetime(year=2023, month=1, day=25, hour=10)
|
|
snaps = [
|
|
dummy_snapshot(now),
|
|
dummy_snapshot(now - timedelta(minutes=5)),
|
|
dummy_snapshot(now - timedelta(hours=2)),
|
|
dummy_snapshot(now - timedelta(hours=3)),
|
|
dummy_snapshot(now - timedelta(hours=4)),
|
|
]
|
|
|
|
yearly_quota = copy(zero_quotas)
|
|
yearly_quota.yearly = 2
|
|
|
|
monthly_quota = copy(zero_quotas)
|
|
monthly_quota.monthly = 2
|
|
|
|
weekly_quota = copy(zero_quotas)
|
|
weekly_quota.weekly = 2
|
|
|
|
daily_quota = copy(zero_quotas)
|
|
daily_quota.daily = 2
|
|
|
|
last_quota = copy(zero_quotas)
|
|
last_quota.last = 1
|
|
last_quota.yearly = 2
|
|
|
|
for quota in [last_quota, yearly_quota, monthly_quota, weekly_quota, daily_quota]:
|
|
print(quota)
|
|
Backups.set_autobackup_quotas(quota)
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(now),
|
|
# If there is a vacant quota, we should keep the last snapshot even if it doesn't fit
|
|
dummy_snapshot(now - timedelta(hours=4)),
|
|
]
|
|
|
|
|
|
def test_autobackup_snapshots_pruning_edgeweek(backups):
|
|
# jan 1 2023 is Sunday
|
|
snaps = [
|
|
dummy_snapshot(datetime(year=2023, month=1, day=6)),
|
|
dummy_snapshot(datetime(year=2023, month=1, day=1)),
|
|
dummy_snapshot(datetime(year=2022, month=12, day=31)),
|
|
dummy_snapshot(datetime(year=2022, month=12, day=30)),
|
|
]
|
|
quotas = copy(zero_quotas)
|
|
quotas.weekly = 2
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(datetime(year=2023, month=1, day=6)),
|
|
dummy_snapshot(datetime(year=2023, month=1, day=1)),
|
|
]
|
|
|
|
|
|
def test_autobackup_snapshots_pruning_big_gap(backups):
|
|
snaps = [
|
|
dummy_snapshot(datetime(year=2023, month=1, day=6)),
|
|
dummy_snapshot(datetime(year=2023, month=1, day=2)),
|
|
dummy_snapshot(datetime(year=2022, month=10, day=31)),
|
|
dummy_snapshot(datetime(year=2022, month=10, day=30)),
|
|
]
|
|
quotas = copy(zero_quotas)
|
|
quotas.weekly = 2
|
|
Backups.set_autobackup_quotas(quotas)
|
|
|
|
snaps_to_keep = Backups._prune_snaps_with_quotas(snaps)
|
|
assert snaps_to_keep == [
|
|
dummy_snapshot(datetime(year=2023, month=1, day=6)),
|
|
dummy_snapshot(datetime(year=2022, month=10, day=31)),
|
|
]
|
|
|
|
|
|
def test_quotas_exceeded_with_too_many_autobackups(backups, dummy_service):
|
|
assert Backups.autobackup_quotas()
|
|
quota = copy(zero_quotas)
|
|
quota.last = 2
|
|
Backups.set_autobackup_quotas(quota)
|
|
assert Backups.autobackup_quotas().last == 2
|
|
|
|
snap = Backups.back_up(dummy_service, BackupReason.AUTO)
|
|
assert len(Backups.get_snapshots(dummy_service)) == 1
|
|
snap2 = Backups.back_up(dummy_service, BackupReason.AUTO)
|
|
assert len(Backups.get_snapshots(dummy_service)) == 2
|
|
snap3 = Backups.back_up(dummy_service, BackupReason.AUTO)
|
|
assert len(Backups.get_snapshots(dummy_service)) == 2
|
|
|
|
snaps = Backups.get_snapshots(dummy_service)
|
|
assert snap2 in snaps
|
|
assert snap3 in snaps
|
|
assert snap not in snaps
|
|
|
|
quota.last = -1
|
|
Backups.set_autobackup_quotas(quota)
|
|
snap4 = Backups.back_up(dummy_service, BackupReason.AUTO)
|
|
|
|
snaps = Backups.get_snapshots(dummy_service)
|
|
assert len(snaps) == 3
|
|
assert snap4 in snaps
|
|
|
|
# Retroactivity
|
|
quota.last = 1
|
|
Backups.set_autobackup_quotas(quota)
|
|
job = Jobs.add("trimming", "test.autobackup_trimming", "trimming the snaps!")
|
|
handle = prune_autobackup_snapshots(job)
|
|
handle(blocking=True)
|
|
snaps = Backups.get_snapshots(dummy_service)
|
|
assert len(snaps) == 1
|
|
|
|
snap5 = Backups.back_up(dummy_service, BackupReason.AUTO)
|
|
snaps = Backups.get_snapshots(dummy_service)
|
|
assert len(snaps) == 1
|
|
assert snap5 in snaps
|
|
|
|
# Explicit snaps are not affected
|
|
snap6 = Backups.back_up(dummy_service, BackupReason.EXPLICIT)
|
|
|
|
snaps = Backups.get_snapshots(dummy_service)
|
|
assert len(snaps) == 2
|
|
assert snap5 in snaps
|
|
assert snap6 in snaps
|