1
+ from unittest .mock import patch
2
+
1
3
import pytest
4
+ from django .core .cache import cache
2
5
from django .core .files .storage import default_storage
3
6
from django .test import override_settings
4
7
from PIL import Image # type: ignore
7
10
from thunderstore .repository .factories import PackageVersionFactory
8
11
from thunderstore .repository .models .package_version import get_version_png_filepath
9
12
10
-
11
- def test_get_storage_class_or_stub (mocker ) -> None :
12
- assert get_storage_class_or_stub ("non.stub.class" ) == "non.stub.class"
13
- mocker .patch ("sys.argv" , ["manage.py" , "migrate" ])
14
- assert (
15
- get_storage_class_or_stub ("non.stub.class" )
16
- == "thunderstore.utils.makemigrations.StubStorage"
17
- )
18
-
19
-
20
- @override_settings (
21
- DEFAULT_FILE_STORAGE = "thunderstore.core.storage.MirroredS3Storage" ,
22
- S3_MIRRORS = (
13
+ mirrored_storage_settings = {
14
+ "DEFAULT_FILE_STORAGE" : "thunderstore.core.storage.MirroredS3Storage" ,
15
+ "S3_MIRRORS" : (
23
16
{
24
17
"access_key" : "thunderstore" ,
25
18
"secret_key" : "thunderstore" ,
@@ -34,7 +27,19 @@ def test_get_storage_class_or_stub(mocker) -> None:
34
27
"object_parameters" : {},
35
28
},
36
29
),
37
- )
30
+ }
31
+
32
+
33
+ def test_get_storage_class_or_stub (mocker ) -> None :
34
+ assert get_storage_class_or_stub ("non.stub.class" ) == "non.stub.class"
35
+ mocker .patch ("sys.argv" , ["manage.py" , "migrate" ])
36
+ assert (
37
+ get_storage_class_or_stub ("non.stub.class" )
38
+ == "thunderstore.utils.makemigrations.StubStorage"
39
+ )
40
+
41
+
42
+ @override_settings (** mirrored_storage_settings )
38
43
@pytest .mark .django_db
39
44
def test_mirrored_storage (dummy_image : Image ) -> None :
40
45
pv = PackageVersionFactory (icon = None , name = "MirrorStorageTest" )
@@ -55,3 +60,48 @@ def test_mirrored_storage(dummy_image: Image) -> None:
55
60
assert not default_storage .exists (icon_path )
56
61
for mirror_storage in default_storage .mirrors :
57
62
assert not mirror_storage .exists (icon_path )
63
+
64
+
65
+ def setup_package_version (dummy_image ):
66
+ pv = PackageVersionFactory (icon = None , name = "MirrorStorageTest" )
67
+ icon_path = get_version_png_filepath (pv , "" )
68
+ pv .icon = dummy_image
69
+ return pv , icon_path
70
+
71
+
72
+ @override_settings (** mirrored_storage_settings )
73
+ @pytest .mark .django_db
74
+ def test_mirrored_storage_save_lock_failure (dummy_image : Image ) -> None :
75
+ """Test that save operation raises an exception if another save is in progress."""
76
+
77
+ message = "Another save operation is in progress for this file."
78
+ pv , icon_path = setup_package_version (dummy_image )
79
+ pv .icon = dummy_image
80
+ cache .add (f"cache_mirror_storage_{ icon_path } " , "LOCKED" , timeout = 5 )
81
+
82
+ with pytest .raises (Exception , match = message ):
83
+ pv .save ()
84
+
85
+ assert cache .get (f"cache_mirror_storage_{ icon_path } " ) == "LOCKED"
86
+
87
+
88
+ @override_settings (** mirrored_storage_settings )
89
+ @pytest .mark .django_db
90
+ @patch ("thunderstore.core.storage.TemporarySpooledCopy" )
91
+ @patch ("django.core.cache.cache.add" )
92
+ @patch ("django.core.cache.cache.delete" )
93
+ def test_mirrored_storage_save_cleanup_on_exception (
94
+ mock_cache_delete , mock_cache_add , mock_temporary_spooled_copy , dummy_image : Image
95
+ ) -> None :
96
+ """Test that the lock is released even if an exception occurs during save."""
97
+
98
+ message = "Error"
99
+ mock_temporary_spooled_copy .side_effect = Exception (message )
100
+ mock_cache_add .return_value = True
101
+ pv , icon_path = setup_package_version (dummy_image )
102
+
103
+ with pytest .raises (Exception , match = message ):
104
+ pv .save ()
105
+
106
+ mock_cache_delete .assert_called_once_with (f"cache_mirror_storage_{ icon_path } " )
107
+ assert cache .get (f"cache_mirror_storage_{ icon_path } " ) is None
0 commit comments