-
Notifications
You must be signed in to change notification settings - Fork 42
Add Robust Unit and Integration Tests for Product APITest complete suite #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add Robust Unit and Integration Tests for Product APITest complete suite #31
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid '*'. This allows requests from any domain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove these from urls.py.
https://nalexn.github.io/separation-of-concerns/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comments which no longer is need. Applicable everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkout File naming convention - https://realpython.com/python-pep8/#how-to-choose-names
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can import all the necessary Classes from monogengine directly.
from mongoengine import Document, StringField, DateTimeField
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And linter to fix these issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should title be unique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Get() instead of filter here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets store only upper/lowecase for category
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add types in function definition.
This is for better readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add error handling for db errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add proper loggers and error handling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.