Add Robust Unit and Integration Tests for Product APITest complete suite#31
Add Robust Unit and Integration Tests for Product APITest complete suite#31VedanshiAwasthi wants to merge 27 commits intoRippling:mainfrom
Conversation
…rror, adjusted Docker Compose setup, and verified API response in the browser.
… a product category and to fetch a list of products belonging to a particular category.
…gister category endpoints
… category/product views
…support test coverage
… category/product views
…support test coverage
| DEBUG = True | ||
|
|
||
| ALLOWED_HOSTS = [] | ||
| ALLOWED_HOSTS = ["*"] |
There was a problem hiding this comment.
We should avoid '*'. This allows requests from any domain
There was a problem hiding this comment.
I have removed '*' from ALLOWED_HOSTS and restricted it to localhost and 127.0.0.1 for development. Also ensured CORS_ALLOWED_ORIGINS only allow localhost addresses. Please let me know if any further changes are needed!
| # ] | ||
|
|
||
|
|
||
| def home(request): |
There was a problem hiding this comment.
Let's remove these from urls.py.
https://nalexn.github.io/separation-of-concerns/
There was a problem hiding this comment.
Thanks for the suggestion! I’ve removed the unnecessary logic from urls.py as it’s no longer needed.
| environment: | ||
| MONGO_INITDB_ROOT_USERNAME: root | ||
| MONGO_INITDB_ROOT_PASSWORD: example | ||
| command: ["mongod", "--auth"] |
There was a problem hiding this comment.
Thanks! Glad the setup is on point!
| from django.contrib import admin | ||
| from .models import Product | ||
|
|
||
| # admin.site.register(Product) No newline at end of file |
There was a problem hiding this comment.
Remove comments which no longer is need. Applicable everywhere.
There was a problem hiding this comment.
I’ve removed the unnecessary comments to keep the code clean and more readable. Let me know if there's anything else I can improve!
| @@ -0,0 +1,18 @@ | |||
| from rest_framework.response import Response | |||
There was a problem hiding this comment.
Checkout File naming convention - https://realpython.com/python-pep8/#how-to-choose-names
There was a problem hiding this comment.
Thanks for the suggestion! I’ll make sure to rename the file according to PEP 8 conventions for better readability and consistency.
| import mongoengine | ||
| import datetime | ||
|
|
||
| class ProductCategory(mongoengine.Document): |
There was a problem hiding this comment.
You can import all the necessary Classes from monogengine directly.
from mongoengine import Document, StringField, DateTimeField
There was a problem hiding this comment.
I’ve updated the imports to directly include only the necessary classes from mongoengine in both CategoryModel and ProductModel
| created_at = mongoengine.DateTimeField(default=lambda: datetime.datetime.now(datetime.UTC)) | ||
| updated_at = mongoengine.DateTimeField(default=lambda: datetime.datetime.now(datetime.UTC)) |
There was a problem hiding this comment.
Since all of the models would need created_at and updated_at. We can create a base class for setting them up and inherit them wherever you need.
class TimestampedDocument(Document):
created_at = DateTimeField(default=datetime.datetime.utcnow)
updated_at = DateTimeField(default=datetime.datetime.utcnow)
meta = {'abstract': True}
There was a problem hiding this comment.
I’ve created a TimestampedDocument base class to handle the created_at and updated_at fields, and now Product and ProductCategory inherits from it. I’ve updated the save() method to use datetime.utcnow() for consistency with the TimestampedDocument base class.
| def save(self, *args, **kwargs): | ||
| self.updated_at = datetime.datetime.now(datetime.UTC) | ||
| return super(ProductCategory, self).save(*args, **kwargs) | ||
|
|
There was a problem hiding this comment.
Great that you have overriden the method to update updated at field.
Explore signals to see how we can do this without override. https://www.tutorialspoint.com/mongoengine/mongoengine_signals.htm#:~:text=Signals%20are%20events%20dispatched%20by,receive%20signals%20from%20many%20senders.
There was a problem hiding this comment.
Thanks for the suggestion! I've explored MongoEngine signals and implemented the pre_save signal to automatically update the updated_at field.
|
|
||
|
|
||
|
|
There was a problem hiding this comment.
Thanks for the suggestion. I will use linter (pylint) to identify and fix any issues, including removing unnecessary imports, formatting corrections, and adding docstrings where necessary.
| from .CategoryModel import ProductCategory | ||
|
|
||
| class Product(mongoengine.Document): | ||
| name = mongoengine.StringField(max_length=255, required=True) |
There was a problem hiding this comment.
I believe the title field should be unique, as each product category needs to have a distinct identifier. Ensuring uniqueness will help prevent any potential issues with having multiple categories with the same name, which could lead to confusion or errors in the system.
| class CategoryRepository: | ||
| @staticmethod | ||
| def create(title, description=None): | ||
| if ProductCategory.objects.filter(title=title).first(): |
There was a problem hiding this comment.
Use Get() instead of filter here.
There was a problem hiding this comment.
I’ve updated both the create and update functions as per your suggestions. I replaced filter() with get() in the create function for better performance and applied proper exception handling in the update function.
| def create(title, description=None): | ||
| if ProductCategory.objects.filter(title=title).first(): | ||
| raise ValueError("Category with this title already exists.") | ||
| category = ProductCategory.objects.create(title=title, description=description) |
There was a problem hiding this comment.
Can you check ProductCategory(title=title, description=description) creates document.
Also do check this out - https://www.dabapps.com/insights/django-models-and-encapsulation/
There was a problem hiding this comment.
I have checked and confirmed that ProductCategory(title=title, description=description) is successfully creating the document as expected.
|
|
||
| @staticmethod | ||
| def get_category_by_title(title): | ||
| return ProductCategory.objects.filter(title__iexact=title).first() |
There was a problem hiding this comment.
Lets store only upper/lowecase for category
There was a problem hiding this comment.
Thank you for your suggestion. At present, the case-insensitive approach is being used and several existing functions depend on it, and they have been thoroughly tested. Transitioning to a case-sensitive implementation would require substantial changes and additional testing. For now, I would prefer to maintain the current approach, but I am open to revisiting this if necessary in the future.
| class ProductRepository: | ||
|
|
||
| @staticmethod | ||
| def createProd(prod_details): |
There was a problem hiding this comment.
Please add types in function definition.
This is for better readability.
There was a problem hiding this comment.
I've updated the functions to include type hints as per your recommendation. This will improve the readability of the code and help with understanding the expected inputs and outputs for each function.
| def seed_database(): | ||
|
|
||
| # Clear existing data | ||
| ProductCategory.objects.delete() |
There was a problem hiding this comment.
Can you add error handling for db errors.
There was a problem hiding this comment.
I've implemented error handling for MongoDB connections and seeding. The db fixture handles connection issues and switches to mongomock if MongoDB is unavailable. The seed_database function also logs and handles exceptions for better debugging.
| ProductCategory.objects.delete() | ||
| Product.objects.delete() | ||
|
|
||
| # Create categories |
There was a problem hiding this comment.
Lets create multiple items for testing.
Also This function currently does many things. Can you create helper function to clear, generate data.
There was a problem hiding this comment.
Refactored seed_database function by creating helper functions for clearing data, generating categories, and creating products. Each function has been equipped with error handling and docstrings for better clarity and maintainability.
| if mongodb_use_local: | ||
|
|
||
| mongoengine.connect( | ||
| db='test_products_db', |
There was a problem hiding this comment.
We should avoid using magic strings.
https://softwareengineering.stackexchange.com/questions/365339/what-is-wrong-with-magic-strings.
Let create constants and use it like below
MONGO_HOST = os.getenv('MONGO_HOST', 'localhost')
MONGO_PORT = int(os.getenv('MONGO_PORT', 27017))
MONGO_USER = os.getenv('MONGO_USER', '')
MONGO_PASS = os.getenv('MONGO_PASS', '')
MONGO_AUTH_DB = os.getenv('MONGO_AUTH_DB', 'admin')
TEST_DB_NAME = 'test_products_db'
There was a problem hiding this comment.
Rrefactored the MongoDB connection to use constants for configuration values, retrieved from environment variables. This avoids magic strings, improving code maintainability and flexibility.
| return is_mongodb_available() | ||
|
|
||
| @pytest.fixture(scope="function") | ||
| def db(mongodb_use_local): |
There was a problem hiding this comment.
Can you add proper loggers and error handling
There was a problem hiding this comment.
Added logging and improved error handling for MongoDB connections and database seeding. Replaced print statements with structured logs for better traceability and debugging.
|
|
||
| categories = ProductCategory.objects(title__in=value) | ||
| if len(categories) != len(value): | ||
| raise serializers.ValidationError("Some categories are invalid.") |
There was a problem hiding this comment.
this doesn't gives us which item is invalid.
Can you also add what item is invalid. This will help in debugging data issues properly.
This PR introduces a set of critical backend improvements that enhance the database configuration, API response reliability, and testing infrastructure. These updates ensure a more maintainable, predictable, and production-ready system.
What’s Included
Model Enhancements
Added db_alias in meta for all MongoEngine models to support multiple database environments (prod, test).
Ensures smoother integration and test isolation.
Database Configuration
Updated mongoengine.connect() to explicitly define uuidRepresentation and read_preference.
Boosts compatibility with new MongoDB drivers and improves replica set behavior.
View Refactor
Improved response format consistency across all API endpoints.
Unified error handling structure for better frontend-debugging and client parsing.
URL Updates
Cleaned and restructured urls.py for clarity and scalability.
Testing Improvements
->Fixed test failures caused by outdated validation logic.
->Added a full testing environment setup:
->pytest.ini for centralized test config
->Seed scripts to initialize DB with sample products and categories.
->Developed integration + unit tests for:
->Category Creation / Update / Fetch
->Product CRUD workflows
->Asserted response structure, HTTP status codes, and DB mutations.
~~ Why It Matters~~
Production Resilience: Cleaner error handling prevents silent failures and speeds up debugging.
Test Reliability: Clear separation of test DB via db_alias and seeded data ensures consistent results.
Scalability: Refactored code and URLs prepare the app for future module expansion.
Developer Velocity: New tests catch regressions early and act as documentation for expected behavior.