Skip to content

Commit

Permalink
test: restructured test config, add test to media endpoint" (#8)
Browse files Browse the repository at this point in the history
* hotfix: update gitignore

* hotfix: update docker-compose file - remove env-files config

* hotfix: structured and enhancement project (#3)

* hotfix: update readme project

* feat: add new environment variable

* feat: add package beanie-odm

* fix: remove unsing config in docker-compose file

* feat: update botoclient function

* feat: cleanup error codes and add news

* feat: remove unusing functions and update utils functionnal

* feat: define bucket policy config

* test: add tests for endpoints and functional (#5)

* test: restructured test config, add test to media endpoint (#7)

* test: add tests for endpoints and functional

* test: restructured test config, add test to media endpoint
  • Loading branch information
flavien-hugs authored Nov 2, 2024
1 parent 0dcbeab commit 1ff458a
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/routers/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
status_code=status.HTTP_201_CREATED,
)
async def create_bucket(bucket: BucketSchema = Body(...), botoclient: boto3.client = Depends(get_boto_client)):
new_bucket = await create_new_bucket(bucket, botoclient=botoclient)
new_bucket = await create_new_bucket(bucket=bucket, botoclient=botoclient)
return new_bucket


Expand Down
10 changes: 5 additions & 5 deletions src/routers/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Optional

import boto3
from fastapi import APIRouter, BackgroundTasks, Body, Depends, File, Query, status, UploadFile
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, Query, status, UploadFile
from fastapi_pagination.async_paginator import paginate as async_paginate
from pymongo import ASCENDING, DESCENDING

Expand All @@ -29,9 +29,9 @@
status_code=status.HTTP_202_ACCEPTED,
)
async def upload_file_to_buckect(
bucket_name: str,
file: UploadFile = File(...),
tags: Optional[str] = Body(
bucket_name: str = Form(..., description="Bucket name to upload the file"),
file: UploadFile = File(..., description="File to be uploaded"),
tags: Optional[str] = Form(
None,
examples=['{"category":"documents","author":"John Doe"}'],
description="Tags to be added to the file as a JSON string",
Expand Down Expand Up @@ -59,7 +59,7 @@ async def list_media(
):
search = {}
if query.bucket_name:
await check_bucket_exists(bucket_name=query.bucket_name, botoclient=botoclient)
check_bucket_exists(bucket_name=query.bucket_name, botoclient=botoclient)
search.update({"bucket_name": {"$regex": query.bucket_name, "$options": "i"}})
if query.tags:
del query["tags"]
Expand Down
Empty file added tests/config/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions tests/config/test_mongo_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from unittest import mock

import pytest

from src.config.database import shutdown_db_client, startup_db_client


@pytest.mark.asyncio
@mock.patch("src.config.database.init_beanie", return_value=None)
async def test_startup_db_client(mock_init_beanie, fixture_client_mongo, mock_app_instance, fixture_models):
await startup_db_client(app=mock_app_instance, models=[fixture_models.Bucket, fixture_models.Media])

assert mock_app_instance.mongo_db_client is not None
assert fixture_client_mongo.is_mongos is True

"""
mock_init_beanie.assert_called_once_with(
database=mock_app_instance.mongo_db_client[settings.MONGO_DB],
document_models=[fixture_models.Bucket, fixture_models.Media],
multiprocessing_mode=True,
)
"""


@pytest.mark.asyncio
async def test_shutdown_db_client(mock_app_instance):
mock_app_instance.mongo_db_client = mock.AsyncMock()
await shutdown_db_client(app=mock_app_instance)
mock_app_instance.mongo_db_client.close.assert_called_once()
15 changes: 14 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,21 @@ async def default_bucket(fixture_models, bucket_data):
return result


@pytest.fixture()
def media_data(fake_data):
file_name = fake_data.file_name()
return {"filename": file_name, "bucket_name": "unsta-storage", "name_in_minio": file_name, "tags": {"tag": "value"}}


@pytest.mark.asyncio
@pytest.fixture()
async def default_media(fixture_models, media_data, fake_data):
result = await fixture_models.Media(**media_data, url=fake_data.url()).create()
return result


@pytest.fixture
async def http_bucket_api(mock_app_instance, fixture_client_mongo, mock_boto_client):
async def http_client_api(mock_app_instance, fixture_client_mongo, mock_boto_client):
from src.common.boto_client import get_boto_client

mock_app_instance.dependency_overrides[get_boto_client] = lambda: mock_boto_client
Expand Down
40 changes: 20 additions & 20 deletions tests/routers/test_bucket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@


@pytest.mark.asyncio
async def test_ping_api(http_bucket_api):
response = await http_bucket_api.get("/sfs/@ping")
async def test_ping_api(http_client_api):
response = await http_client_api.get("/sfs/@ping")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json() == {"message": "pong !"}


@pytest.mark.asyncio
async def test_create_bucket_success(http_bucket_api, mock_boto_client, default_bucket, bucket_data):
async def test_create_bucket_success(http_client_api, mock_boto_client, default_bucket, bucket_data):
bucket_data.update({"bucket_name": "storage-unsta-pictures"})

# Simuler que le bucket n'existe pas en levant une exception 404
Expand All @@ -28,7 +28,7 @@ async def test_create_bucket_success(http_bucket_api, mock_boto_client, default_
mock_boto_client.create_bucket.return_value = {}
mock_boto_client.put_bucket_policy.return_value = {}

response = await http_bucket_api.post("/buckets", json=bucket_data)
response = await http_client_api.post("/buckets", json=bucket_data)

assert response.status_code == status.HTTP_201_CREATED, response.text
assert response.json()["bucket_name"] == bucket_data.get("bucket_name")
Expand All @@ -41,13 +41,13 @@ async def test_create_bucket_success(http_bucket_api, mock_boto_client, default_
)


async def test_create_bucket_failure(http_bucket_api, mock_boto_client, bucket_data):
async def test_create_bucket_failure(http_client_api, mock_boto_client, bucket_data):
mock_boto_client.head_bucket.side_effect = exceptions.ClientError(
error_response={"Error": {"Code": "400", "Message": "Error checking bucket"}}, operation_name="HeadBucket"
)

bucket_data.update({"bucket_name": "storage-pictures"})
response = await http_bucket_api.post("/buckets", json=bucket_data)
response = await http_client_api.post("/buckets", json=bucket_data)

assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert response.json()["error_code"] == SfsErrorCodes.SFS_INVALID_NAME
Expand All @@ -59,41 +59,41 @@ async def test_create_bucket_failure(http_bucket_api, mock_boto_client, bucket_d


@pytest.mark.asyncio
async def test_list_bucket_with_data(http_bucket_api, mock_boto_client, default_bucket):
response = await http_bucket_api.get("/buckets")
async def test_list_bucket_with_data(http_client_api, default_bucket):
response = await http_client_api.get("/buckets")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["total"] >= 1
assert response.json()["items"][0]["bucket_name"] == default_bucket.bucket_name


@pytest.mark.asyncio
async def test_list_bucket_without_data(http_bucket_api, mock_boto_client):
response = await http_bucket_api.get("/buckets")
async def test_list_bucket_without_data(http_client_api):
response = await http_client_api.get("/buckets")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json() == {"items": [], "total": 0, "page": 1, "size": None, "pages": 0}


@pytest.mark.asyncio
async def test_list_filter(http_bucket_api, mock_boto_client, default_bucket, fake_data):
async def test_list_buckect_filter(http_client_api, default_bucket, fake_data):
# Test filter by bucket_name
response = await http_bucket_api.get("/buckets", params={"bucket_name": f"{default_bucket.bucket_name}"})
response = await http_client_api.get("/buckets", params={"bucket_name": f"{default_bucket.bucket_name}"})
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["items"][0]["bucket_name"] == default_bucket.bucket_name

# Test filter by description
response = await http_bucket_api.get("/buckets", params={"description": default_bucket.description})
response = await http_client_api.get("/buckets", params={"description": default_bucket.description})
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["items"][0]["description"] == default_bucket.description

# Test filter by created_at
query_date = fake_data.date()
response = await http_bucket_api.get("/buckets", params={"created_at": query_date})
response = await http_client_api.get("/buckets", params={"created_at": query_date})
assert response.status_code == status.HTTP_200_OK, response.text
assert "items" in response.json()


@pytest.mark.asyncio
async def test_get_bucket_and_create_bucket_if_not_exist(http_bucket_api, mock_boto_client):
async def test_get_bucket_and_create_bucket_if_not_exist(http_client_api, mock_boto_client):
# Simuler que le bucket n'existe pas en levant une exception 404
mock_boto_client.head_bucket.side_effect = exceptions.ClientError(
error_response={"Error": {"Code": "404", "Message": "Not Found"}}, operation_name="HeadBucket"
Expand All @@ -103,7 +103,7 @@ async def test_get_bucket_and_create_bucket_if_not_exist(http_bucket_api, mock_b
mock_boto_client.create_bucket.return_value = {}
mock_boto_client.put_bucket_policy.return_value = {}

response = await http_bucket_api.get("/buckets/unsta-pictures", params={"create_bucket_if_not_exist": True})
response = await http_client_api.get("/buckets/unsta-pictures", params={"create_bucket_if_not_exist": True})
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["bucket_name"] == "unsta-pictures"

Expand All @@ -116,18 +116,18 @@ async def test_get_bucket_and_create_bucket_if_not_exist(http_bucket_api, mock_b


@pytest.mark.asyncio
async def test_get_bucket_and_create_bucket_if_exist(http_bucket_api, mock_boto_client, default_bucket):
response = await http_bucket_api.get(f"/buckets/{default_bucket.bucket_name}", params={"create_bucket_if_not_exist": False})
async def test_get_bucket_and_create_bucket_if_exist(http_client_api, mock_boto_client, default_bucket):
response = await http_client_api.get(f"/buckets/{default_bucket.bucket_name}", params={"create_bucket_if_not_exist": False})
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert response.json()["error_code"] == SfsErrorCodes.SFS_BUCKET_NOT_FOUND
assert response.json()["error_message"] == f"Bucket '{default_bucket.bucket_name}' not found."


@pytest.mark.asyncio
async def test_delete_bucket(http_bucket_api, mock_boto_client, default_bucket):
async def test_delete_bucket(http_client_api, mock_boto_client, default_bucket):
mock_boto_client.delete_bucket.return_value = mock.Mock()

response = await http_bucket_api.delete(f"/buckets/{default_bucket.bucket_name}")
response = await http_client_api.delete(f"/buckets/{default_bucket.bucket_name}")

assert response.status_code == status.HTTP_200_OK, response.text

Expand Down
114 changes: 114 additions & 0 deletions tests/routers/test_media_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import pytest
from starlette import status
from src.common.error_codes import SfsErrorCodes


@pytest.mark.asyncio
async def test_get_all_media_without_data(http_client_api):
response = await http_client_api.get("/media")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json() == {"items": [], "total": 0, "page": 1, "size": None, "pages": 0}


@pytest.mark.asyncio
async def test_list_media_with_data(http_client_api, default_media):
response = await http_client_api.get("/media")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["total"] >= 1
assert response.json()["items"][0]["bucket_name"] == default_media.bucket_name


@pytest.mark.asyncio
async def test_list_media_filter(http_client_api, default_media, fake_data):
# Test filter by bucket_name
response = await http_client_api.get("/media", params={"bucket_name": f"{default_media.bucket_name}"})
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["items"][0]["bucket_name"] == default_media.bucket_name

# Test filter by description
response = await http_client_api.get("/media", params={"tags.tag": "value"})
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["items"][0]["tags"] == default_media.tags


@pytest.mark.asyncio
async def test_get_media_url(http_client_api, default_media):
response = await http_client_api.get(f"/media/{default_media.bucket_name}/{default_media.filename}")
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["url"] == default_media.url


@pytest.mark.skip
@pytest.mark.asyncio
async def test_get_media_url_download(http_client_api, default_media):
response = await http_client_api.get(
f"/media/{default_media.bucket_name}/{default_media.filename}", params={"download": True}
)
assert response.status_code == status.HTTP_200_OK, response.text
assert response.json()["url"] == default_media.url


@pytest.mark.asyncio
async def test_get_media_view(http_client_api):
response = await http_client_api.get("/media/filename")
assert response.status_code == status.HTTP_200_OK, response.text


@pytest.mark.skip
@pytest.mark.asyncio
async def test_upload_media_success(http_client_api, default_bucket, fake_data):
response = await http_client_api.post(
"/media",
data={"bucket_name": default_bucket.bucket_name, "tags": '{"tag": "value"}'},
files={"file": ("test.txt", fake_data.text().encode("utf-8"))},
)
assert response.status_code == status.HTTP_201_CREATED, response.text
assert response.json()["bucket_name"] == default_bucket.bucket_name
assert response.json()["tags"] == {"tag": "value"}
assert response.json()["url"] is not None


@pytest.mark.asyncio
async def test_upload_media_invalid_tags(http_client_api, default_bucket, fake_data):
response = await http_client_api.post(
"/media",
data={"bucket_name": default_bucket.bucket_name, "tags": "invalid"},
files={"file": ("test.txt", fake_data.text().encode("utf-8"))},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert response.json()["error_code"] == SfsErrorCodes.SFS_INVALID_TAGS
assert response.json()["error_message"] == "Invalid JSON string for tags."


@pytest.mark.skip
@pytest.mark.asyncio
async def test_upload_media_invalid_bucket_name(http_client_api, fake_data):
response = await http_client_api.post(
"/media",
data={"bucket_name": "invalid", "tags": '{"tag": "value"}'},
files={"file": ("test.txt", fake_data.text().encode("utf-8"))},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert response.json()["error_code"] == SfsErrorCodes.SFS_BUCKET_NOT_FOUND
assert response.json()["error_message"] == "Bucket not found."


@pytest.mark.asyncio
async def test_upload_media_invalid_file(http_client_api, default_bucket):
response = await http_client_api.post(
"/media",
data={"bucket_name": default_bucket.bucket_name, "tags": "{'tag': 'value'}"},
files={"file": ("fooooo", "foooo")},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert response.json()["error_code"] == SfsErrorCodes.SFS_INVALID_TAGS
assert response.json()["error_message"] == "Invalid JSON string for tags."


@pytest.mark.asyncio
async def test_delete_media(http_client_api, default_media):

bucket_name, filename = default_media.bucket_name, default_media.filename

response = await http_client_api.delete(f"/media/{bucket_name}/{filename}")
assert response.status_code == status.HTTP_204_NO_CONTENT, response.text

0 comments on commit 1ff458a

Please sign in to comment.