|
20 | 20 | FileLocation, |
21 | 21 | FileMetaDataGet, |
22 | 22 | FileMetaDataGetv010, |
| 23 | + FileUploadCompleteResponse, |
| 24 | + FileUploadCompletionBody, |
| 25 | + FileUploadSchema, |
| 26 | + LinkType, |
23 | 27 | ) |
24 | 28 | from models_library.generics import Envelope |
25 | 29 | from models_library.projects import ProjectID |
| 30 | +from models_library.projects_nodes_io import LocationID, StorageFileID |
26 | 31 | from models_library.users import UserID |
| 32 | +from pydantic import AnyUrl, TypeAdapter |
27 | 33 | from pytest_simcore.helpers.assert_checks import assert_status |
28 | 34 | from pytest_simcore.helpers.logging_tools import log_context |
29 | 35 | from servicelib.aiohttp import status |
@@ -163,6 +169,86 @@ async def _list_dataset_files_metadata(user_id: UserID, request: Request): |
163 | 169 | ] |
164 | 170 | ) |
165 | 171 |
|
| 172 | + @router.put( |
| 173 | + "/locations/{location_id}/files/{file_id:path}", |
| 174 | + response_model=Envelope[FileUploadSchema], |
| 175 | + ) |
| 176 | + async def upload_file( |
| 177 | + user_id: UserID, |
| 178 | + location_id: LocationID, |
| 179 | + file_id: StorageFileID, |
| 180 | + request: Request, |
| 181 | + link_type: LinkType = LinkType.PRESIGNED, |
| 182 | + ): |
| 183 | + assert "json_schema_extra" in FileUploadSchema.model_config |
| 184 | + assert isinstance(FileUploadSchema.model_config["json_schema_extra"], dict) |
| 185 | + assert isinstance( |
| 186 | + FileUploadSchema.model_config["json_schema_extra"]["examples"], list |
| 187 | + ) |
| 188 | + |
| 189 | + abort_url = ( |
| 190 | + URL(f"{request.url}") |
| 191 | + .with_path( |
| 192 | + request.app.url_path_for( |
| 193 | + "abort_upload_file", |
| 194 | + location_id=f"{location_id}", |
| 195 | + file_id=file_id, |
| 196 | + ) |
| 197 | + ) |
| 198 | + .with_query(user_id=user_id) |
| 199 | + ) |
| 200 | + |
| 201 | + complete_url = ( |
| 202 | + URL(f"{request.url}") |
| 203 | + .with_path( |
| 204 | + request.app.url_path_for( |
| 205 | + "complete_upload_file", |
| 206 | + location_id=f"{location_id}", |
| 207 | + file_id=file_id, |
| 208 | + ) |
| 209 | + ) |
| 210 | + .with_query(user_id=user_id) |
| 211 | + ) |
| 212 | + response = FileUploadSchema.model_validate( |
| 213 | + random.choice( # noqa: S311 |
| 214 | + FileUploadSchema.model_config["json_schema_extra"]["examples"] |
| 215 | + ) |
| 216 | + ) |
| 217 | + response.links.abort_upload = TypeAdapter(AnyUrl).validate_python( |
| 218 | + f"{abort_url}" |
| 219 | + ) |
| 220 | + response.links.complete_upload = TypeAdapter(AnyUrl).validate_python( |
| 221 | + f"{complete_url}" |
| 222 | + ) |
| 223 | + |
| 224 | + return Envelope[FileUploadSchema](data=response) |
| 225 | + |
| 226 | + @router.post( |
| 227 | + "/locations/{location_id}/files/{file_id:path}:complete", |
| 228 | + response_model=Envelope[FileUploadCompleteResponse], |
| 229 | + status_code=status.HTTP_202_ACCEPTED, |
| 230 | + ) |
| 231 | + async def complete_upload_file( |
| 232 | + user_id: UserID, |
| 233 | + location_id: LocationID, |
| 234 | + file_id: StorageFileID, |
| 235 | + body: FileUploadCompletionBody, |
| 236 | + request: Request, |
| 237 | + ): |
| 238 | + ... |
| 239 | + |
| 240 | + @router.post( |
| 241 | + "/locations/{location_id}/files/{file_id:path}:abort", |
| 242 | + status_code=status.HTTP_204_NO_CONTENT, |
| 243 | + ) |
| 244 | + async def abort_upload_file( |
| 245 | + user_id: UserID, |
| 246 | + location_id: LocationID, |
| 247 | + file_id: StorageFileID, |
| 248 | + request: Request, |
| 249 | + ): |
| 250 | + ... |
| 251 | + |
166 | 252 | app.include_router(router) |
167 | 253 |
|
168 | 254 | return app |
@@ -403,3 +489,35 @@ async def test_storage_list_filter( |
403 | 489 | for item in data: |
404 | 490 | model = FileMetaDataGet.model_validate(item) |
405 | 491 | assert model |
| 492 | + |
| 493 | + |
| 494 | +@pytest.fixture |
| 495 | +def file_id(faker: Faker) -> StorageFileID: |
| 496 | + return TypeAdapter(StorageFileID).validate_python( |
| 497 | + f"{faker.uuid4()}/{faker.uuid4()}/{faker.file_name()}" |
| 498 | + ) |
| 499 | + |
| 500 | + |
| 501 | +@pytest.mark.parametrize( |
| 502 | + "user_role,expected", |
| 503 | + [ |
| 504 | + # (UserRole.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), |
| 505 | + # (UserRole.GUEST, status.HTTP_200_OK), |
| 506 | + (UserRole.USER, status.HTTP_200_OK), |
| 507 | + # (UserRole.TESTER, status.HTTP_200_OK), |
| 508 | + ], |
| 509 | +) |
| 510 | +async def test_upload_file( |
| 511 | + client: TestClient, |
| 512 | + logged_user: dict[str, Any], |
| 513 | + expected: int, |
| 514 | + file_id: StorageFileID, |
| 515 | +): |
| 516 | + url = f"/v0/storage/locations/0/files/{quote(file_id, safe='')}" |
| 517 | + |
| 518 | + assert url.startswith(PREFIX) |
| 519 | + |
| 520 | + resp = await client.put(url, params={"user_id": logged_user["id"]}) |
| 521 | + data, error = await assert_status(resp, expected) |
| 522 | + assert not error |
| 523 | + assert data |
0 commit comments