Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .coverage
Binary file not shown.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ POSTGRES_USER=name
POSTGRES_PASSWORD=password
POSTGRES_DB=database-name
POSTGRES_HOST=localhost
DEBUG=True
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Django CI](https://github.com/NoobCoder12/DevSocial/actions/workflows/django-tests.yml/badge.svg)](https://github.com/NoobCoder12/DevSocial/actions/workflows/django-tests.yml)

> Version 1.2.1
> Version 1.3.0

DevSocialApp is a social media platform designed for developers to share posts, interact with each other through likes and comments, and follow their peers.

Expand Down Expand Up @@ -40,7 +40,8 @@ The project taught me:
- **Database**: PostgreSQL
- **Testing**:
- **Pytest-Django**: For robust integration testing.
- **Model Bakery**: For efficient test data generation and relationship handling.
- **Model Bakery**: For efficient test data generation and relationship handling.
- **pytest-cov**: For test coverage reporting.
- **DevOps**: Docker, Django Debug Toolbar.

## Project Structure
Expand Down Expand Up @@ -80,9 +81,9 @@ The project taught me:
├── requirements.txt # Python dependencies
├── .env.example # Environment variables template
├── .gitignore
├── init.sql # Initial database setup script
├── docker-compose.yml
├── Dockerfile
├── init.sql
└── README.md
```

Expand All @@ -100,10 +101,11 @@ To ensure the reliability of the social interactions and data integrity, the pro
To run tests locally with coverage:

```
docker compose up -d
pytest --cov=backend/apps --cov-report=term-missing
```

All test files are located in app's folders.
All test files are located in app's folders. The test suite includes 90+ tests (integration tests for DRF API endpoints and Django unit tests).

## Setup Instructions

Expand All @@ -122,6 +124,7 @@ POSTGRES_USER=name
POSTGRES_PASSWORD=password
POSTGRES_DB=database-name
POSTGRES_HOST=localhost
DEBUG=True
```


Expand All @@ -148,6 +151,12 @@ Things I'd add if I continue this project:

## Changelog

### v1.3.0
- Added pytest for DRF endpoints.
- 93% code coverage achieved across 90 tests.
- Added GitHub Actions CI/CD pipeline with coverage reporting.
- Added type hints to views.

### v1.2.1
- Improved Swagger documentation with endpoint descriptions and parameter types

Expand Down
144 changes: 144 additions & 0 deletions backend/apps/api/tests/test_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import pytest
from django.urls import reverse
from model_bakery import baker
from backend.apps.posts.models import Post


@pytest.fixture
def create_comment(create_post, user_data):
"""
Fixture for one post
"""
post_id = create_post[1].get("id")
post = Post.objects.get(id=post_id)
comment = baker.make(
'interactions.Comment',
user=user_data,
post=post,
body='This is comment body'
)
return comment


@pytest.fixture
def create_two_comments(create_post, user_data):
"""
Fixture for creation of 2 comments
"""
post_id = create_post[1].get("id")
post = Post.objects.get(id=post_id)
comment = baker.make(
'interactions.Comment',
user=user_data,
post=post,
body='This is comment body'
)
comment2 = baker.make(
'interactions.Comment',
user=user_data,
post=post,
body='And the second one'
)
return comment, comment2


class TestAllComments:
URL = reverse("comments-list")

@pytest.mark.comments
def test_get_my_comments(self, create_comment, authorized_client, user_data):
"""
Test of GET method for comment of current user
"""
response = authorized_client.get(self.URL)

assert response.status_code == 200
assert response.json() is not None
assert len(response.json()) == 1
data = response.json()[0]

# Get each data from comment
assert data.get("body") == 'This is comment body'
assert data.get("user") == user_data.id
assert data.get("post") == create_comment.post.id
assert data.get("created_at") is not None
assert data.get("id") == create_comment.id

@pytest.mark.comments
def test_get_my_two_comments(
self,
create_two_comments,
authorized_client,
user_data
):
"""
Test of GET method for 2 comments of current user
"""
response = authorized_client.get(self.URL)

assert response.status_code == 200
assert response.json() is not None
assert len(response.json()) == 2

@pytest.mark.comments
def test_get_comments_401(
self,
api_client
):
"""
Test of GET method for unauthorized user
"""
response = api_client.get(self.URL)
assert response.status_code == 401
data = response.json()
assert data is not None
assert data.get("detail") == "Authentication credentials were not provided."


class TestCommentById:
@pytest.mark.comments
def test_get_comment_by_id(self, create_two_comments, authorized_client, user_data):
"""
Test of GET comment by id
"""
comment = create_two_comments[1]
comment_id = comment.id
URL = reverse("comments-detail", args=[comment_id])
response = authorized_client.get(URL)
assert response.status_code == 200
data = response.json()
assert data is not None

# Check each field
assert data.get("id") == comment_id
assert data.get("body") == comment.body
assert data.get("created_at") is not None
assert data.get("user") == user_data.id
assert data.get("post") == comment.post.id

@pytest.mark.comments
def test_get_comments_by_id_404(self, authorized_client):
"""
Test of GET comment by wrong id
"""
URL = reverse("comments-detail", args=[999])
response = authorized_client.get(URL)
assert response.status_code == 404

data = response.json()
assert data is not None

assert data.get("detail") == 'No Comment matches the given query.'

@pytest.mark.comments
def test_get_comments_by_id_401(self, api_client):
"""
Test of GET comment by unauthorized user
"""
URL = reverse("comments-detail", args=[1])
response = api_client.get(URL)
assert response.status_code == 401

data = response.json()
assert data is not None
assert data.get("detail") == "Authentication credentials were not provided."
Empty file.
73 changes: 73 additions & 0 deletions backend/apps/api/tests/test_feed_endpoint/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import pytest
from backend.apps.posts.models import Post
from backend.apps.interactions.models import Comment, Like


@pytest.fixture
def posts_created_by_followed_user(create_follow, second_user_data):
post1 = Post.objects.create(
title="First test post",
body='This is first post for feed test',
author=second_user_data
)
post2 = Post.objects.create(
title="Dear Recruiters",
body='I hope you got to this point and the second test post is visible for you',
author=second_user_data
)

return post1, post2


@pytest.fixture
def third_user_post(third_user_data):
"""
Fixture for post creation by different user
"""
post = Post.objects.create(
title="Post of third user",
body='This is a post of user non-followed by current user',
author=third_user_data
)

return post, post.id


@pytest.fixture
def add_comments_to_post(posts_created_by_followed_user, user_data, second_user_data):
"""
Fixture for creating comments under a post
"""
post1, _ = posts_created_by_followed_user
comment1 = Comment.objects.create(
user=user_data,
post=post1,
body="This post is great, I love testing it"
)
comment2 = Comment.objects.create(
user=second_user_data,
post=post1,
body="I totally agree! Testing is amazing."
)

return comment1, comment2


@pytest.fixture
def post1_id(posts_created_by_followed_user):
"""
Fixture for post1 ID
"""
return posts_created_by_followed_user[0].id


@pytest.fixture
def add_likes_to_post(posts_created_by_followed_user, user_data, second_user_data):
"""
Fixture for creating likes under a post
"""
post1, _ = posts_created_by_followed_user
like1 = Like.objects.create(user=user_data, post=post1)
like2 = Like.objects.create(user=second_user_data, post=post1)

return like1, like2
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import pytest
from django.urls import reverse
from backend.apps.interactions.models import Like
from backend.apps.posts.models import Post


@pytest.fixture
def create_like(user_data, post1_id):
"""
Fixture for creating like under one post
"""
post = Post.objects.get(id=post1_id)
like = Like.objects.create(user=user_data, post=post)
return like


class TestDeleteLikesByPostId:
@pytest.mark.feed
def test_delete_feed_likes(self, create_like, authorized_client, post1_id):
"""
Test for DELETE method for like in feed
"""
# Check data first
URL_POST = reverse('feed-detail', args=[post1_id])
response_before = authorized_client.get(URL_POST)
assert response_before.status_code == 200
assert response_before.json().get('likes_count') == 1

# Delete like
URL_DELETE = reverse('feed-like', args=[post1_id])
response_delete = authorized_client.delete(URL_DELETE)
assert response_delete.status_code == 200
data = response_delete.json()
assert data is not None
assert data.get("message") == "Like deleted"

# Check after
response_before = authorized_client.get(URL_POST)
assert response_before.status_code == 200
assert response_before.json().get('likes_count') == 0

@pytest.mark.feed
def test_feed_delete_likes_404(
self,
authorized_client
):
"""
Test of DELETE method for a like with invalid post id
"""
URL = reverse('feed-like', args=[99999])
response_post = authorized_client.delete(URL)
assert response_post.status_code == 404
data = response_post.json()
assert data is not None
assert data.get("detail") == 'No Post matches the given query.'

@pytest.mark.feed
def test_feed_delete_like_401(
self,
api_client,
):
"""
Test of DELETE method for a like with post id as unauthorized user
"""
URL = reverse('feed-like', args=[99999])
response_post = api_client.delete(URL)
assert response_post.status_code == 401
data = response_post.json()
assert data is not None
assert data.get("detail") == 'Authentication credentials were not provided.'
Loading
Loading