|
2 | 2 | from pathlib import Path |
3 | 3 | import time |
4 | 4 |
|
| 5 | +import cottoncandy as cc |
| 6 | + |
5 | 7 | from ..localclient import LocalClient |
6 | 8 |
|
7 | 9 |
|
@@ -134,3 +136,85 @@ def test_move_cleans_up_empty_directories(cci, object_name): |
134 | 136 | and cci.download_object(object_name + '/source') == content |
135 | 137 | cci.rm(object_name + '/source') |
136 | 138 | time.sleep(cci.wait_time) |
| 139 | + |
| 140 | + |
| 141 | +def test_move_cleans_up_source_bucket_directories_across_buckets(tmp_path): |
| 142 | + """Moving across buckets should remove empty source directories.""" |
| 143 | + source_bucket = tmp_path / 'source-bucket' |
| 144 | + destination_bucket = tmp_path / 'destination-bucket' |
| 145 | + source_bucket.mkdir() |
| 146 | + destination_bucket.mkdir() |
| 147 | + |
| 148 | + cci_src = cc.get_interface( |
| 149 | + bucket_name=str(source_bucket), |
| 150 | + backend='local', |
| 151 | + verbose=False, |
| 152 | + ) |
| 153 | + cci_dest = cc.get_interface( |
| 154 | + bucket_name=str(destination_bucket), |
| 155 | + backend='local', |
| 156 | + verbose=False, |
| 157 | + ) |
| 158 | + source_key = 'nested/a/b/file.txt' |
| 159 | + destination_key = 'moved/file.txt' |
| 160 | + content = b'cross bucket move content' |
| 161 | + |
| 162 | + cci_src.upload_object(source_key, BytesIO(content)) |
| 163 | + |
| 164 | + source_root = source_bucket / 'nested' |
| 165 | + source_a = source_root / 'a' |
| 166 | + source_b = source_a / 'b' |
| 167 | + source_file = source_b / 'file.txt' |
| 168 | + |
| 169 | + assert source_file.exists() |
| 170 | + |
| 171 | + # mv from src --> dest, calling from src client |
| 172 | + cci_src.mv( |
| 173 | + source_name=source_key, |
| 174 | + dest_name=destination_key, |
| 175 | + source_bucket=str(source_bucket), |
| 176 | + dest_bucket=str(destination_bucket), |
| 177 | + overwrite=True, |
| 178 | + ) |
| 179 | + |
| 180 | + moved_file = destination_bucket / 'moved' / 'file.txt' |
| 181 | + moved_metadata = destination_bucket / 'moved' / 'file.txt.meta.json' |
| 182 | + |
| 183 | + assert moved_file.exists() |
| 184 | + assert cci_dest.download_object(destination_key) == content |
| 185 | + assert moved_metadata.exists() |
| 186 | + assert not cci_src.exists_object(source_key, bucket_name=str(source_bucket)) |
| 187 | + |
| 188 | + # The source bucket should be cleaned up back to the bucket root. |
| 189 | + assert not source_file.exists() |
| 190 | + assert not source_b.exists() |
| 191 | + assert not source_a.exists() |
| 192 | + assert not source_root.exists() |
| 193 | + assert source_bucket.exists() |
| 194 | + |
| 195 | + # Cleanup |
| 196 | + cci_dest.rm(destination_key) |
| 197 | + assert not moved_file.exists() |
| 198 | + assert not moved_metadata.exists() |
| 199 | + assert not (destination_bucket / 'moved').exists() |
| 200 | + |
| 201 | + # Now try the same thing, but calling `.mv()` from the destination client instead. |
| 202 | + cci_src.upload_object(source_key, BytesIO(content)) |
| 203 | + cci_dest.mv( |
| 204 | + source_name=source_key, |
| 205 | + dest_name=destination_key, |
| 206 | + source_bucket=str(source_bucket), |
| 207 | + dest_bucket=str(destination_bucket), |
| 208 | + overwrite=True, |
| 209 | + ) |
| 210 | + |
| 211 | + assert moved_file.exists() |
| 212 | + assert cci_dest.download_object(destination_key) == content |
| 213 | + assert moved_metadata.exists() |
| 214 | + assert not cci_src.exists_object(source_key, bucket_name=str(source_bucket)) |
| 215 | + |
| 216 | + assert not source_file.exists() |
| 217 | + assert not source_b.exists() |
| 218 | + assert not source_a.exists() |
| 219 | + assert not source_root.exists() |
| 220 | + assert source_bucket.exists() |
0 commit comments