You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
10 KiB
325 lines
10 KiB
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from app.core.secret_codec import decrypt_secret_config
|
|
from app.models.entities import Backend
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_list_backends_returns_default_local_backend(api_client) -> None:
|
|
response = await api_client.get("/api/backends")
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert len(payload["items"]) >= 1
|
|
assert payload["items"][0]["name"] == "local-default"
|
|
assert payload["items"][0]["type"] == "local_directory"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_local_backend(api_client, runtime_paths) -> None:
|
|
response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "archive-local",
|
|
"type": "local_directory",
|
|
"stability_class": "stable",
|
|
"read_priority": 50,
|
|
"write_priority": 50,
|
|
"config": {
|
|
"base_path": runtime_paths["local_storage_dir"] + "/archive",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()["backend"]
|
|
assert payload["name"] == "archive-local"
|
|
assert payload["type"] == "local_directory"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_s3_backend_metadata(api_client, session_factory) -> None:
|
|
response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "s3-main",
|
|
"type": "s3",
|
|
"stability_class": "stable",
|
|
"read_priority": 80,
|
|
"write_priority": 80,
|
|
"config": {
|
|
"bucket": "iron-test",
|
|
"region": "us-east-1",
|
|
"access_key_id": "access-key",
|
|
"secret_access_key": "secret-key",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()["backend"]
|
|
assert payload["name"] == "s3-main"
|
|
assert payload["type"] == "s3"
|
|
assert payload["config"]["bucket"] == "iron-test"
|
|
assert "access_key_id" not in payload["config"]
|
|
assert "secret_access_key" not in payload["config"]
|
|
assert payload["secret_fields"] == ["access_key_id", "secret_access_key"]
|
|
|
|
async with session_factory() as session:
|
|
backend = (
|
|
await session.execute(select(Backend).where(Backend.name == "s3-main"))
|
|
).scalar_one()
|
|
assert backend.secret_config_json.startswith("fernet:v1:")
|
|
assert "access-key" not in backend.secret_config_json
|
|
assert "secret-key" not in backend.secret_config_json
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_webdav_backend_separates_password(api_client) -> None:
|
|
response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "openlist-main",
|
|
"type": "webdav",
|
|
"stability_class": "stable",
|
|
"read_priority": 60,
|
|
"write_priority": 40,
|
|
"config": {
|
|
"endpoint_url": "http://127.0.0.1:5244/dav",
|
|
"username": "iron",
|
|
"password": "secret-password",
|
|
"root_path": "apps/iron",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()["backend"]
|
|
assert payload["type"] == "webdav"
|
|
assert payload["config"]["endpoint_url"] == "http://127.0.0.1:5244/dav"
|
|
assert payload["config"]["username"] == "iron"
|
|
assert payload["config"]["root_path"] == "apps/iron"
|
|
assert "password" not in payload["config"]
|
|
assert payload["secret_fields"] == ["password"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_check_local_backend(api_client) -> None:
|
|
list_response = await api_client.get("/api/backends")
|
|
backend_id = list_response.json()["items"][0]["id"]
|
|
|
|
response = await api_client.post(f"/api/backends/{backend_id}/check")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "healthy"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_check_s3_backend_returns_healthy(api_client, fake_s3) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "s3-main",
|
|
"type": "s3",
|
|
"stability_class": "stable",
|
|
"read_priority": 80,
|
|
"write_priority": 80,
|
|
"config": {
|
|
"bucket": "iron-test",
|
|
},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
|
|
response = await api_client.post(f"/api/backends/{backend_id}/check")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "healthy"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_disable_backend(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "archive-local",
|
|
"type": "local_directory",
|
|
"stability_class": "stable",
|
|
"read_priority": 50,
|
|
"write_priority": 50,
|
|
"config": {
|
|
"base_path": "/tmp/archive-local",
|
|
},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
|
|
response = await api_client.post(f"/api/backends/{backend_id}/disable")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["backend"]["is_enabled"] is False
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_enable_backend(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "archive-local",
|
|
"type": "local_directory",
|
|
"stability_class": "stable",
|
|
"read_priority": 50,
|
|
"write_priority": 50,
|
|
"config": {
|
|
"base_path": "/tmp/archive-local",
|
|
},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
await api_client.post(f"/api/backends/{backend_id}/disable")
|
|
|
|
response = await api_client.post(f"/api/backends/{backend_id}/enable")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["backend"]["is_enabled"] is True
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_update_s3_backend_preserves_and_replaces_secrets(api_client, session_factory) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "s3-main",
|
|
"type": "s3",
|
|
"stability_class": "stable",
|
|
"read_priority": 80,
|
|
"write_priority": 80,
|
|
"config": {
|
|
"bucket": "iron-test",
|
|
"region": "us-east-1",
|
|
"access_key_id": "access-key",
|
|
"secret_access_key": "secret-key",
|
|
},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
|
|
update_response = await api_client.patch(
|
|
f"/api/backends/{backend_id}",
|
|
json={
|
|
"name": "s3-archive",
|
|
"read_priority": 40,
|
|
"write_priority": 45,
|
|
"config": {
|
|
"bucket": "iron-archive",
|
|
"prefix": "cold",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert update_response.status_code == 200
|
|
payload = update_response.json()["backend"]
|
|
assert payload["name"] == "s3-archive"
|
|
assert payload["read_priority"] == 40
|
|
assert payload["write_priority"] == 45
|
|
assert payload["config"]["bucket"] == "iron-archive"
|
|
assert payload["config"]["prefix"] == "cold"
|
|
assert payload["secret_fields"] == ["access_key_id", "secret_access_key"]
|
|
|
|
async with session_factory() as session:
|
|
backend = await session.get(Backend, backend_id)
|
|
assert decrypt_secret_config(backend.secret_config_json)["secret_access_key"] == "secret-key"
|
|
|
|
replace_response = await api_client.patch(
|
|
f"/api/backends/{backend_id}",
|
|
json={
|
|
"config": {
|
|
"secret_access_key": "next-secret-key",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert replace_response.status_code == 200
|
|
async with session_factory() as session:
|
|
backend = await session.get(Backend, backend_id)
|
|
assert decrypt_secret_config(backend.secret_config_json)["secret_access_key"] == "next-secret-key"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_delete_backend_requires_disabled_and_unreferenced(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "archive-local",
|
|
"type": "local_directory",
|
|
"stability_class": "stable",
|
|
"read_priority": 50,
|
|
"write_priority": 50,
|
|
"config": {
|
|
"base_path": "/tmp/archive-local",
|
|
},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
|
|
enabled_delete = await api_client.delete(f"/api/backends/{backend_id}")
|
|
assert enabled_delete.status_code == 409
|
|
assert enabled_delete.json()["error"]["code"] == "backend_delete_conflict"
|
|
|
|
disable_response = await api_client.post(f"/api/backends/{backend_id}/disable")
|
|
assert disable_response.status_code == 200
|
|
|
|
delete_response = await api_client.delete(f"/api/backends/{backend_id}")
|
|
assert delete_response.status_code == 204
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_delete_backend_rejects_policy_reference(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "s3-main",
|
|
"type": "s3",
|
|
"stability_class": "stable",
|
|
"read_priority": 80,
|
|
"write_priority": 80,
|
|
"config": {"bucket": "iron-test"},
|
|
},
|
|
)
|
|
backend_id = create_response.json()["backend"]["id"]
|
|
await api_client.put(
|
|
"/api/policies/placement/document",
|
|
json={
|
|
"require_local": True,
|
|
"stable_replica_count": 1,
|
|
"opportunistic_replica_count": 0,
|
|
"preferred_backend_ids": [backend_id],
|
|
"excluded_backend_ids": [],
|
|
"max_non_local_size_bytes": None,
|
|
},
|
|
)
|
|
await api_client.post(f"/api/backends/{backend_id}/disable")
|
|
|
|
delete_response = await api_client.delete(f"/api/backends/{backend_id}")
|
|
|
|
assert delete_response.status_code == 409
|
|
assert delete_response.json()["error"]["code"] == "backend_delete_conflict"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_backend_rejects_invalid_config(api_client) -> None:
|
|
response = await api_client.post(
|
|
"/api/backends",
|
|
json={
|
|
"name": "bad-s3",
|
|
"type": "s3",
|
|
"stability_class": "stable",
|
|
"read_priority": 10,
|
|
"write_priority": 10,
|
|
"config": {},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert response.json()["error"]["code"] == "backend_config_invalid"
|
|
|