Skip to content

Commit 54c3739

Browse files
committed
[Garbage collector] Additional tests
New tests: * Tests based on the API, checking that files added with the /storage/add_* endpoints are scheduled for deletion. * A test to check that a STORE message will dequeue the deletion of the underlying file.
1 parent 455394e commit 54c3739

File tree

6 files changed

+178
-27
lines changed

6 files changed

+178
-27
lines changed

src/aleph/model/scheduled_deletions.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ def from_db(cls, db_value: Dict) -> "ScheduledDeletionInfo":
2222
object_id=db_value["_id"],
2323
)
2424

25+
def to_dict(self):
26+
return {"filename": self.filename, "delete_by": self.delete_by}
27+
2528

2629
class ScheduledDeletion(BaseClass):
2730
COLLECTION = "scheduled_deletions"
@@ -31,7 +34,7 @@ class ScheduledDeletion(BaseClass):
3134

3235
@classmethod
3336
async def insert(cls, scheduled_deletion: ScheduledDeletionInfo):
34-
await cls.collection.insert_one(asdict(scheduled_deletion))
37+
await cls.collection.insert_one(scheduled_deletion.to_dict())
3538

3639
@classmethod
3740
async def files_to_delete(

tests/api/fixtures/fixture_messages.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"type": "POST",
77
"time": 1652126646.5008686,
88
"item_content": "{\"address\":\"0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106\",\"time\":1652126646.5007327,\"content\":{\"title\":\"My first blog post using Aleph.im\",\"body\":\"Using Aleph.im, we can make a decentralized blog.\"},\"type\":\"test\"}",
9+
"item_type": "inline",
910
"item_hash": "4c33dd1ebf61bbb4342d8258b591fcd52cca73fd7c425542f78311d8f45ba274",
1011
"signature": "0x999ab556b92351e6edf894b4a67f01f0344c7023883eb5bafdf4cd0b98ca91781692ac6b95246c1bf940eedcedfd6dc04751accfbc417ee1b1ae13893634e7eb1c",
1112
"confirmed": false,
@@ -27,7 +28,8 @@
2728
"time": 1652126721.497669,
2829
"item_content": "{\"address\":\"0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106\",\"time\":1652126721.4974446,\"item_type\":\"storage\",\"item_hash\":\"5ccdd7bccfbc5955e2e40166dd0cdea0b093154fd87bc2bea57e7c768cde2f21\",\"mime_type\":\"text/plain\"}",
2930
"item_hash": "2953f0b52beb79fc0ed1bc455346fdcb530611605e16c636778a0d673d7184af",
30-
"signature": "0xa10129dd561c1bc93e8655daf09520e9f1694989263e25f330b403ad33563f4b64c9ae18f6cbfb33e8a47a095be7a181b140a369e6205fd04eef55397624a7121b",
31+
"item_type": "inline",
32+
"signature": "0xa10129dd561c1bc93e8655daf09520e9f1694989263e25f330b403ad33563f4b64c9ae18f6cbfb33e8a47a095be7a181b140a369e6205fd04eef55397624a7121b",
3133
"content": {
3234
"address": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106",
3335
"time": 1652126721.4974446,

tests/conftest.py

+23-9
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,44 @@
1-
import pytest
2-
from aleph.model import init_db
3-
from aleph.config import get_defaults
4-
from configmanager import Config
51
import pymongo
2+
import pytest
63
import pytest_asyncio
4+
from configmanager import Config
75

6+
import aleph.config
7+
from aleph.config import get_defaults
8+
from aleph.model import init_db
89

910
TEST_DB = "ccn_automated_tests"
1011

1112

13+
@pytest.fixture
14+
def mock_config():
15+
config = Config(aleph.config.get_defaults())
16+
# To test handle_new_storage
17+
config.storage.store_files.value = True
18+
19+
# We set the global variable directly instead of patching it because of an issue
20+
# with mocker.patch. mocker.patch uses hasattr to determine the properties of
21+
# the mock, which does not work well with configmanager Config objects.
22+
aleph.config.app_config = config
23+
return config
24+
25+
1226
def drop_db(db_name: str, config: Config):
1327
client = pymongo.MongoClient(config.mongodb.uri.value)
1428
client.drop_database(db_name)
1529

1630

1731
@pytest_asyncio.fixture
18-
async def test_db():
32+
async def test_db(mock_config):
1933
"""
2034
Initializes and cleans a MongoDB database dedicated to automated tests.
2135
"""
2236

23-
config = Config(schema=get_defaults())
24-
config.mongodb.database.value = TEST_DB
37+
mock_config.mongodb.database.value = TEST_DB
2538

26-
drop_db(TEST_DB, config)
27-
init_db(config, ensure_indexes=True)
39+
drop_db(TEST_DB, mock_config)
40+
init_db(mock_config, ensure_indexes=True)
2841

2942
from aleph.model import db
43+
3044
yield db
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
Tests for the storage API to check that temporary files are properly marked for deletion.
3+
"""
4+
5+
6+
import datetime as dt
7+
import json
8+
9+
import pytest
10+
import pytz
11+
from aiohttp import FormData
12+
from configmanager import Config
13+
14+
from aleph.model import ScheduledDeletion
15+
from aleph.model.hashes import get_value as read_gridfs_file
16+
from aleph.web import create_app
17+
18+
19+
async def check_scheduled_deletion(
20+
config: Config, file_hash: str, post_datetime: dt.datetime
21+
):
22+
scheduled_deletion = await ScheduledDeletion.collection.find_one(
23+
{"filename": file_hash}
24+
)
25+
26+
assert scheduled_deletion is not None
27+
28+
# Check that the file is scheduled for deletion at least after
29+
# the expected interval.
30+
delete_interval = config.storage.delete_interval.value
31+
delete_by = scheduled_deletion["delete_by"]
32+
assert delete_by >= post_datetime + dt.timedelta(seconds=delete_interval)
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_store_temporary_file(mock_config, test_db, aiohttp_client):
37+
"""
38+
Checks that the garbage collector schedules temporary files uploaded
39+
with /storage/add_file for deletion.
40+
"""
41+
42+
app = create_app()
43+
app["config"] = mock_config
44+
client = await aiohttp_client(app)
45+
46+
file_content = b"Some file I'd like to upload"
47+
48+
data = FormData()
49+
data.add_field("file", file_content)
50+
51+
post_datetime = pytz.utc.localize(dt.datetime.utcnow())
52+
response = await client.post(f"/api/v0/storage/add_file", data=data)
53+
assert response.status == 200, await response.text()
54+
55+
data = await response.json()
56+
assert data["status"] == "success"
57+
file_hash = data["hash"]
58+
59+
db_content = await read_gridfs_file(file_hash)
60+
assert db_content == file_content
61+
62+
await check_scheduled_deletion(mock_config, file_hash, post_datetime)
63+
64+
65+
@pytest.mark.asyncio
66+
async def test_store_temporary_json(mock_config, test_db, aiohttp_client):
67+
"""
68+
Checks that the garbage collector schedules temporary JSON files uploaded
69+
with /storage/add_json for deletion.
70+
"""
71+
72+
app = create_app()
73+
app["config"] = mock_config
74+
client = await aiohttp_client(app)
75+
76+
json_content = {
77+
"title": "A garbage collector for CCNs",
78+
"body": "Discover the new GC for Aleph CCNs. Deletes all the files, even useful ones!",
79+
}
80+
81+
post_datetime = pytz.utc.localize(dt.datetime.utcnow())
82+
response = await client.post(f"/api/v0/storage/add_json", json=json_content)
83+
assert response.status == 200, await response.text()
84+
85+
data = await response.json()
86+
assert data["status"] == "success"
87+
file_hash = data["hash"]
88+
89+
db_content = await read_gridfs_file(file_hash)
90+
assert json.loads(db_content) == json_content
91+
92+
await check_scheduled_deletion(mock_config, file_hash, post_datetime)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import datetime as dt
2+
import json
3+
4+
import pytest
5+
6+
from aleph.chains.common import process_one_message
7+
from aleph.model.hashes import (
8+
get_value as read_gridfs_file,
9+
set_value as store_gridfs_file,
10+
)
11+
from aleph.model.scheduled_deletions import ScheduledDeletionInfo, ScheduledDeletion
12+
13+
14+
@pytest.mark.asyncio
15+
async def test_cancel_scheduled_deletion(test_db):
16+
"""
17+
Test that a file marked for deletion will be preserved once a message
18+
stores that content.
19+
"""
20+
21+
store_message = {
22+
"chain": "ETH",
23+
"channel": "unit-tests",
24+
"sender": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106",
25+
"type": "STORE",
26+
"time": 1652126721.497669,
27+
"item_content": '{"address":"0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106","time":1652126721.4974446,"item_type":"storage","item_hash":"5ccdd7bccfbc5955e2e40166dd0cdea0b093154fd87bc2bea57e7c768cde2f21","mime_type":"text/plain"}',
28+
"item_type": "inline",
29+
"item_hash": "2953f0b52beb79fc0ed1bc455346fdcb530611605e16c636778a0d673d7184af",
30+
"signature": "0xa10129dd561c1bc93e8655daf09520e9f1694989263e25f330b403ad33563f4b64c9ae18f6cbfb33e8a47a095be7a181b140a369e6205fd04eef55397624a7121b",
31+
}
32+
33+
content = json.loads(store_message["item_content"])
34+
file_hash = content["item_hash"]
35+
file_content = b"Hello, Aleph.im!\n"
36+
37+
# Store the file
38+
await store_gridfs_file(file_hash, file_content)
39+
await ScheduledDeletion.insert(
40+
ScheduledDeletionInfo(
41+
filename=file_hash,
42+
delete_by=dt.datetime.utcnow() + dt.timedelta(seconds=3600),
43+
)
44+
)
45+
46+
await process_one_message(store_message)
47+
48+
# Check that the file is no longer marked for deletion
49+
scheduled_deletion = await ScheduledDeletion.collection.find_one(
50+
{"filename": file_hash}
51+
)
52+
assert scheduled_deletion is None
53+
54+
# Check that the file is unmodified
55+
db_file_content = await read_gridfs_file(file_hash)
56+
assert db_file_content == file_content

tests/storage/conftest.py

-16
This file was deleted.

0 commit comments

Comments
 (0)