selfprivacy-rest-api/tests/test_graphql/test_api_backup.py
2024-09-23 22:15:57 +03:00

782 lines
21 KiB
Python

from pytest import raises
from os import path
from os import mkdir
from tests.test_backup import backups
from tests.common import generate_backup_query
import selfprivacy_api.services as all_services
from selfprivacy_api.services import ServiceManager
from selfprivacy_api.graphql.common_types.service import service_to_graphql_service
from selfprivacy_api.graphql.common_types.backup import (
_AutobackupQuotas,
AutobackupQuotas,
)
from selfprivacy_api.jobs import Jobs, JobStatus
from selfprivacy_api.backup.storage import Storage
from selfprivacy_api.backup.local_secret import LocalBackupSecret
from tests.test_graphql.test_services import (
# TODO: shuffle them to conftest
only_dummy_service_and_api,
only_dummy_service,
dkim_file,
)
from selfprivacy_api.services import CONFIG_STASH_DIR
from tests.test_graphql.common import assert_empty
API_RELOAD_SNAPSHOTS = """
mutation TestSnapshotsReload {
backup {
forceSnapshotsReload {
success
message
code
}
}
}
"""
API_TOTAL_BACKUP = """
mutation TestForcedAutobackup {
backup {
totalBackup{
success
message
code
job{
uid
}
}
}
}
"""
API_TOTAL_RESTORE = """
mutation TestTotalRestore {
backup {
restoreAll{
success
message
code
job{
uid
}
}
}
}
"""
API_SET_AUTOBACKUP_PERIOD_MUTATION = """
mutation TestAutobackupPeriod($period: Int) {
backup {
setAutobackupPeriod(period: $period) {
success
message
code
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
}
}
}
}
"""
API_SET_AUTOBACKUP_QUOTAS_MUTATION = """
mutation TestAutobackupQuotas($input: AutobackupQuotasInput!) {
backup {
setAutobackupQuotas(quotas: $input) {
success
message
code
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
autobackupQuotas {
last
daily
weekly
monthly
yearly
}
}
}
}
}
"""
API_REMOVE_REPOSITORY_MUTATION = """
mutation TestRemoveRepo {
backup {
removeRepository {
success
message
code
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
}
}
}
}
"""
API_INIT_MUTATION = """
mutation TestInitRepo($input: InitializeRepositoryInput!) {
backup {
initializeRepository(repository: $input) {
success
message
code
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
}
}
}
}
"""
API_RESTORE_MUTATION = """
mutation TestRestoreService($snapshot_id: String!) {
backup {
restoreBackup(snapshotId: $snapshot_id) {
success
message
code
job {
uid
status
}
}
}
}
"""
API_FORGET_MUTATION = """
mutation TestForgetSnapshot($snapshot_id: String!) {
backup {
forgetSnapshot(snapshotId: $snapshot_id) {
success
message
code
}
}
}
"""
API_LAST_SLICE_QUERY = """
lastSlice {
id
service {
id
displayName
}
createdAt
reason
}
"""
API_SNAPSHOTS_QUERY = """
allSnapshots {
id
service {
id
displayName
}
createdAt
reason
}
"""
API_BACKUP_SETTINGS_QUERY = """
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
}
"""
API_BACK_UP_MUTATION = """
mutation TestBackupService($service_id: String!) {
backup {
startBackup(serviceId: $service_id) {
success
message
code
job {
uid
status
}
}
}
}
"""
def api_restore(authorized_client, snapshot_id):
response = authorized_client.post(
"/graphql",
json={
"query": API_RESTORE_MUTATION,
"variables": {"snapshot_id": snapshot_id},
},
)
return response
def api_backup(authorized_client, service):
response = authorized_client.post(
"/graphql",
json={
"query": API_BACK_UP_MUTATION,
"variables": {"service_id": service.get_id()},
},
)
return response
def api_forget(authorized_client, snapshot_id):
response = authorized_client.post(
"/graphql",
json={
"query": API_FORGET_MUTATION,
"variables": {"snapshot_id": snapshot_id},
},
)
return response
def api_set_period(authorized_client, period):
response = authorized_client.post(
"/graphql",
json={
"query": API_SET_AUTOBACKUP_PERIOD_MUTATION,
"variables": {"period": period},
},
)
return response
def api_set_quotas(authorized_client, quotas: _AutobackupQuotas):
response = authorized_client.post(
"/graphql",
json={
"query": API_SET_AUTOBACKUP_QUOTAS_MUTATION,
"variables": {"input": quotas.model_dump()},
},
)
return response
def api_remove(authorized_client):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_REPOSITORY_MUTATION,
"variables": {},
},
)
return response
def api_reload_snapshots(authorized_client):
response = authorized_client.post(
"/graphql",
json={
"query": API_RELOAD_SNAPSHOTS,
"variables": {},
},
)
return response
def api_total_backup(authorized_client):
response = authorized_client.post(
"/graphql",
json={
"query": API_TOTAL_BACKUP,
"variables": {},
},
)
return response
def api_restore_all(authorized_client):
response = authorized_client.post(
"/graphql",
json={
"query": API_TOTAL_RESTORE,
"variables": {},
},
)
return response
def api_init(
authorized_client,
kind,
login,
password,
location_name,
location_id,
local_secret=None,
):
response = authorized_client.post(
"/graphql",
json={
"query": API_INIT_MUTATION,
"variables": {
"input": {
"provider": kind,
"locationId": location_id,
"locationName": location_name,
"login": login,
"password": password,
"localSecret": local_secret,
}
},
},
)
return response
def assert_ok(data):
if data["success"] is False:
# convenience for debugging, this should display error
# if empty, consider adding helpful messages
raise ValueError(data["code"], data["message"])
assert data["code"] == 200
assert data["success"] is True
def get_data(response):
assert response.status_code == 200
response = response.json()
if (
"errors" in response.keys()
): # convenience for debugging, this will display error
raise ValueError(response["errors"])
assert response["data"] is not None
data = response["data"]
return data
def api_snapshots(authorized_client):
response = authorized_client.post(
"/graphql",
json={"query": generate_backup_query([API_SNAPSHOTS_QUERY])},
)
data = get_data(response)
result = data["backup"]["allSnapshots"]
assert result is not None
return result
def api_last_slice(authorized_client):
response = authorized_client.post(
"/graphql",
json={"query": generate_backup_query([API_LAST_SLICE_QUERY])},
)
data = get_data(response)
result = data["backup"]["lastSlice"]
assert result is not None
return result
def api_settings(authorized_client):
response = authorized_client.post(
"/graphql",
json={"query": generate_backup_query([API_BACKUP_SETTINGS_QUERY])},
)
data = get_data(response)
result = data["backup"]["configuration"]
assert result is not None
return result
def test_dummy_service_convertible_to_gql(dummy_service):
gql_service = service_to_graphql_service(dummy_service)
assert gql_service is not None
def test_snapshots_empty(authorized_client, dummy_service, backups):
snaps = api_snapshots(authorized_client)
assert snaps == []
def test_snapshots_orphaned_service(authorized_client, dummy_service, backups):
api_backup(authorized_client, dummy_service)
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
all_services.services.remove(dummy_service)
assert ServiceManager.get_service_by_id(dummy_service.get_id()) is None
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
assert "Orphaned" in snaps[0]["service"]["displayName"]
assert dummy_service.get_id() in snaps[0]["service"]["displayName"]
def test_start_backup(authorized_client, dummy_service, backups):
response = api_backup(authorized_client, dummy_service)
data = get_data(response)["backup"]["startBackup"]
assert data["success"] is True
job = data["job"]
assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
snap = snaps[0]
assert snap["id"] is not None
assert snap["id"] != ""
assert snap["service"]["id"] == "testservice"
def test_restore(authorized_client, dummy_service, backups):
api_backup(authorized_client, dummy_service)
snap = api_snapshots(authorized_client)[0]
assert snap["id"] is not None
response = api_restore(authorized_client, snap["id"])
data = get_data(response)["backup"]["restoreBackup"]
assert data["success"] is True
job = data["job"]
assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED
def test_reinit(authorized_client, dummy_service, tmpdir, backups):
test_repo_path = path.join(tmpdir, "not_at_all_sus")
response = api_init(authorized_client, "FILE", "", "", test_repo_path, "")
data = get_data(response)["backup"]["initializeRepository"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["provider"] == "FILE"
assert configuration["locationId"] == ""
assert configuration["locationName"] == test_repo_path
assert len(configuration["encryptionKey"]) > 1
assert configuration["isInitialized"] is True
response = api_backup(authorized_client, dummy_service)
data = get_data(response)["backup"]["startBackup"]
assert data["success"] is True
job = data["job"]
assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED
def test_migrate_backup_repo(authorized_client, dummy_service, tmpdir, backups):
"""
Simulate the workflow of migrating to a new server
"""
# Using an alternative path to be sure that we do not
# match only by incident
test_repo_path = path.join(tmpdir, "not_at_all_sus")
response = api_init(authorized_client, "FILE", "", "", test_repo_path, "")
data = get_data(response)["backup"]["initializeRepository"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert snaps == []
# Now, forget what we just did
del test_repo_path
del response
del data
del snaps
# I am a user at my old machine, I make a backup
response = api_backup(authorized_client, dummy_service)
data = get_data(response)["backup"]["startBackup"]
assert_ok(data)
# Then oh no, we need to migrate, we get our settings.
# Because we have forgot everything 2000 times already
# Was years, was years.
# I still remember login though
configuration = api_settings(authorized_client)
# Ok. Let's now go to another machine
# Another machine will not have any settings at all
Storage.reset()
LocalBackupSecret._full_reset()
# That's it, nothing left
new_configuration = api_settings(authorized_client)
assert new_configuration["isInitialized"] is False
# Reinit
response = api_init(
authorized_client,
kind=configuration["provider"],
login="", # user provides login and password, configuration endpoint does not
password="", # empty for file based repository
location_name=configuration["locationName"],
location_id=configuration["locationId"],
local_secret=configuration["encryptionKey"],
)
data = get_data(response)["backup"]["initializeRepository"]
assert_ok(data)
assert data["configuration"] == configuration
new_configuration = api_settings(authorized_client)
assert new_configuration == configuration
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
def test_remove(authorized_client, generic_userdata, backups):
response = api_remove(authorized_client)
data = get_data(response)["backup"]["removeRepository"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["provider"] == "NONE"
assert configuration["locationId"] == ""
assert configuration["locationName"] == ""
# still generated every time it is missing
assert len(configuration["encryptionKey"]) > 1
assert configuration["isInitialized"] is False
def test_autobackup_quotas_nonzero(authorized_client, backups):
quotas = _AutobackupQuotas(
last=3,
daily=2,
weekly=4,
monthly=13,
yearly=14,
)
response = api_set_quotas(authorized_client, quotas)
data = get_data(response)["backup"]["setAutobackupQuotas"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["autobackupQuotas"] == quotas.model_dump()
def test_autobackup_period_nonzero(authorized_client, backups):
new_period = 11
response = api_set_period(authorized_client, new_period)
data = get_data(response)["backup"]["setAutobackupPeriod"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["autobackupPeriod"] == new_period
def test_autobackup_period_zero(authorized_client, backups):
new_period = 0
# since it is none by default, we better first set it to something non-negative
response = api_set_period(authorized_client, 11)
# and now we nullify it
response = api_set_period(authorized_client, new_period)
data = get_data(response)["backup"]["setAutobackupPeriod"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["autobackupPeriod"] == None
def test_autobackup_period_none(authorized_client, backups):
# since it is none by default, we better first set it to something non-negative
response = api_set_period(authorized_client, 11)
# and now we nullify it
response = api_set_period(authorized_client, None)
data = get_data(response)["backup"]["setAutobackupPeriod"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["autobackupPeriod"] == None
def test_autobackup_period_negative(authorized_client, backups):
# since it is none by default, we better first set it to something non-negative
response = api_set_period(authorized_client, 11)
# and now we nullify it
response = api_set_period(authorized_client, -12)
data = get_data(response)["backup"]["setAutobackupPeriod"]
assert_ok(data)
configuration = data["configuration"]
assert configuration["autobackupPeriod"] == None
# We cannot really check the effect at this level, we leave it to backend tests
# But we still make it run in both empty and full scenarios and ask for snaps afterwards
def test_reload_snapshots_bare_bare_bare(authorized_client, dummy_service, backups):
api_remove(authorized_client)
response = api_reload_snapshots(authorized_client)
data = get_data(response)["backup"]["forceSnapshotsReload"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert snaps == []
def test_total_backup_if_dir_exists(
authorized_client, only_dummy_service_and_api, backups
):
# mkdir(CONFIG_STASH_DIR)
dummy_service = only_dummy_service_and_api
response = api_total_backup(authorized_client)
# raise ValueError(get_data(response))
data = get_data(response)["backup"]["totalBackup"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert len(snaps) == 2
def test_total_backup(authorized_client, only_dummy_service_and_api, backups):
dummy_service = only_dummy_service_and_api
response = api_total_backup(authorized_client)
# raise ValueError(get_data(response))
data = get_data(response)["backup"]["totalBackup"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert len(snaps) == 2
def test_reload_snapshots(authorized_client, dummy_service, backups):
response = api_backup(authorized_client, dummy_service)
data = get_data(response)["backup"]["startBackup"]
response = api_reload_snapshots(authorized_client)
data = get_data(response)["backup"]["forceSnapshotsReload"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
def test_forget_snapshot(authorized_client, dummy_service, backups):
response = api_backup(authorized_client, dummy_service)
data = get_data(response)["backup"]["startBackup"]
snaps = api_snapshots(authorized_client)
assert len(snaps) == 1
response = api_forget(authorized_client, snaps[0]["id"])
data = get_data(response)["backup"]["forgetSnapshot"]
assert_ok(data)
snaps = api_snapshots(authorized_client)
assert len(snaps) == 0
def test_forget_nonexistent_snapshot(authorized_client, dummy_service, backups):
snaps = api_snapshots(authorized_client)
assert len(snaps) == 0
response = api_forget(authorized_client, "898798uekiodpjoiweoiwuoeirueor")
data = get_data(response)["backup"]["forgetSnapshot"]
assert data["code"] == 404
assert data["success"] is False
snaps = api_snapshots(authorized_client)
assert len(snaps) == 0
def test_last_slice(authorized_client, only_dummy_service_and_api, backups):
api_total_backup(authorized_client)
snaps = api_last_slice(authorized_client)
assert len(snaps) == 2
def test_backup_all_restore_all(
authorized_client,
backups,
generic_userdata,
dkim_file,
only_dummy_service_and_api,
catch_nixos_rebuild_calls,
):
dummy_service = only_dummy_service_and_api
fp = catch_nixos_rebuild_calls
fp.pass_command(["restic", fp.any()])
fp.keep_last_process(True)
fp.pass_command(["rclone", fp.any()])
fp.keep_last_process(True)
fp.pass_command(["lsblk", fp.any()])
fp.keep_last_process(True)
response = api_total_backup(authorized_client)
data = get_data(response)["backup"]["totalBackup"]
assert_ok(data)
assert len(api_snapshots(authorized_client)) == 2
response = api_restore_all(authorized_client)
data = get_data(response)["backup"]["restoreAll"]
assert_ok(data)
# Just in case
assert len(api_snapshots(authorized_client)) == 2
def test_unauthorized(client, dummy_service):
quotas = _AutobackupQuotas(
last=3,
daily=2,
weekly=4,
monthly=13,
yearly=14,
)
response = api_set_quotas(client, quotas)
assert_empty(response)
response = api_set_period(client, 11)
assert_empty(response)
response = api_remove(client)
assert_empty(response)
response = api_init(client, "FILE", "", "", "/boguspath", "")
assert_empty(response)
response = api_restore(client, "oiueroiwueo")
assert_empty(response)
response = api_backup(client, dummy_service)
assert_empty(response)
response = api_forget(client, "worieuoweiru")
assert_empty(response)
response = api_total_backup(client)
assert_empty(response)
response = api_restore_all(client)
assert_empty(response)
response = api_reload_snapshots(client)
assert_empty(response)
# TODO: make a special error for this in get_data
with raises(ValueError):
api_last_slice(client)
with raises(ValueError):
api_snapshots(client)