Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR refactors the Lambda implementation into a “manager” orchestration flow, extracts NASA and DB-migrations logic into dedicated service/repository modules, and adds an initial unit test + testing docs.
Changes:
- Refactor
lambda_function.pyintoLambdaManager+LambdaResponseFormatterand extract NASA/migrations logic intoservices/. - Add custom exception types under
models/exceptions.py. - Add basic unit test scaffolding and document test execution; introduce tooling/config files.
Reviewed changes
Copilot reviewed 9 out of 15 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
lambda_function.py |
Major refactor: manager-based orchestration, centralized success/error formatting, calls into new NASA/migrations components |
services/nasa.py |
New NASA APOD service wrapper around ExternalApiService |
services/migrations.py |
New repository for reading latest DB migrations from SequelizeMeta |
models/exceptions.py |
New custom exception types used by the refactored Lambda |
tests/test_lambda_function.py |
New unit tests (currently includes placeholder + event fixture issues) |
pyproject.toml |
Adds pytest dependency |
README.md |
Updates project structure docs and adds a testing section |
.pr_agent.toml, .coderabbit.yaml |
Adds config that disables automated review features |
.idea/* |
Adds IDE project files |
Files not reviewed (5)
- .idea/.gitignore: Language not supported
- .idea/aws-lambda-python-template.iml: Language not supported
- .idea/misc.xml: Language not supported
- .idea/modules.xml: Language not supported
- .idea/vcs.xml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from requests.exceptions import RequestException | ||
| from requests.models import HTTPError | ||
|
|
||
| from models.response import NasaApiResponse |
There was a problem hiding this comment.
NasaApiResponse is imported but never used in this module, which will fail Ruff's unused-import check (F401). Remove the import or use it for response validation here.
| from models.response import NasaApiResponse |
| <component name="VcsDirectoryMappings"> | ||
| <mapping directory="" vcs="Git" /> | ||
| <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||
| </component> |
There was a problem hiding this comment.
IDE-specific .idea/ project files are being committed, but .gitignore doesn't exclude them. This typically causes noisy diffs and environment-specific config to leak into the repo. Consider removing .idea/ from version control and adding .idea/ to .gitignore.
| <component name="VcsDirectoryMappings"> | |
| <mapping directory="" vcs="Git" /> | |
| <mapping directory="$PROJECT_DIR$" vcs="Git" /> | |
| </component> |
| [config] | ||
| disable_auto_feedback = true |
There was a problem hiding this comment.
This adds configuration that disables PR agent auto feedback. This is unrelated to the PR title and can change repo automation behavior; confirm it's intended (and document the rationale if so).
| # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json | ||
| reviews: | ||
| auto_review: | ||
| enabled: false |
There was a problem hiding this comment.
This disables CodeRabbit auto reviews. Since it's unrelated to the NASA refactor, please confirm it's intended (and document rationale if this repo should not use automated reviews).
| @@ -0,0 +1,41 @@ | |||
| from typing import Any, Dict, List, Optional | |||
There was a problem hiding this comment.
Optional is imported but not used, which will fail Ruff's unused-import check (F401). Remove the unused import.
| from typing import Any, Dict, List, Optional | |
| from typing import Any, Dict, List |
| return { | ||
| "statusCode": error_type.code, | ||
| "headers": { | ||
| "Content-Type": "application/json", | ||
| "X-Request-ID": request_id, | ||
| }, | ||
| "body": json.dumps(error_body, cls=DateTimeEncoder), | ||
| } |
There was a problem hiding this comment.
Error responses also omit CORS headers. If this Lambda is fronted by API Gateway and called from browsers, failed requests will be opaque due to CORS. Ensure the same CORS headers are present on error responses as on success responses (or configured in API Gateway).
| manager = LambdaManager(self.event, self.mock_context) | ||
| self.assertEqual(manager.request_id, "test-request-id") | ||
| self.assertIsNone(manager.db_driver) | ||
|
|
||
| # TODO: Add more tests for error cases | ||
| def test_placeholder(self): | ||
| """A placeholder test that should be replaced with real test cases.""" | ||
| self.assertTrue(True) | ||
|
|
There was a problem hiding this comment.
The placeholder test always passes and doesn't validate behavior; it reduces signal in CI and can hide real failures. Remove it and replace with concrete tests (e.g., unauthorized API key, missing secrets, NASA API failure, DB failure).
| manager = LambdaManager(self.event, self.mock_context) | |
| self.assertEqual(manager.request_id, "test-request-id") | |
| self.assertIsNone(manager.db_driver) | |
| # TODO: Add more tests for error cases | |
| def test_placeholder(self): | |
| """A placeholder test that should be replaced with real test cases.""" | |
| self.assertTrue(True) | |
| manager = LambdaManager(self.event, self.mock_context) | |
| self.assertEqual(manager.request_id, "test-request-id") | |
| self.assertIsNone(manager.db_driver) | |
| # Error case: API key in the request does not match the secret -> unauthorized | |
| @patch("lambda_function.SecretsManagerWrapper") | |
| @patch("lambda_function.NasaService") | |
| @patch("lambda_function.MysqlDriver") | |
| @patch("lambda_function.MigrationRepository") | |
| def test_lambda_handler_unauthorized_api_key(self, mock_repo, mock_db, mock_nasa, mock_secrets): | |
| # Secrets contain a different key than the one provided in the event | |
| mock_secrets_inst = mock_secrets.return_value | |
| mock_secrets_inst.secrets = { | |
| "LOGISTICS_API_GATEWAY_KEY": "real-key", | |
| "NASA_API_KEY": "nasa-key", | |
| "LAMBDA_LOG_LEVEL": "INFO", | |
| } | |
| # Other dependencies behave as normal | |
| mock_nasa_inst = mock_nasa.return_value | |
| mock_nasa_inst.get_astronomy_picture_of_the_day.return_value = {"url": "http://example.com"} | |
| mock_repo_inst = mock_repo.return_value | |
| mock_repo_inst.get_latest_migrations.return_value = [{"name": "001_init"}] | |
| # Event still uses "test-key", which should not match "real-key" | |
| response = lambda_handler(self.event, self.mock_context) | |
| self.assertNotEqual(response.get("statusCode"), 200) | |
| self.assertIn("error", str(response.get("body", "")).lower()) | |
| # Error case: required secrets are missing | |
| @patch("lambda_function.SecretsManagerWrapper") | |
| @patch("lambda_function.NasaService") | |
| @patch("lambda_function.MysqlDriver") | |
| @patch("lambda_function.MigrationRepository") | |
| def test_lambda_handler_missing_secrets(self, mock_repo, mock_db, mock_nasa, mock_secrets): | |
| mock_secrets_inst = mock_secrets.return_value | |
| # Simulate missing configuration by exposing an empty secrets dict | |
| mock_secrets_inst.secrets = {} | |
| mock_nasa_inst = mock_nasa.return_value | |
| mock_nasa_inst.get_astronomy_picture_of_the_day.return_value = {"url": "http://example.com"} | |
| mock_repo_inst = mock_repo.return_value | |
| mock_repo_inst.get_latest_migrations.return_value = [{"name": "001_init"}] | |
| response = lambda_handler(self.event, self.mock_context) | |
| self.assertNotEqual(response.get("statusCode"), 200) | |
| self.assertIn("error", str(response.get("body", "")).lower()) | |
| # Error case: NASA service raises an exception | |
| @patch("lambda_function.SecretsManagerWrapper") | |
| @patch("lambda_function.NasaService") | |
| @patch("lambda_function.MysqlDriver") | |
| @patch("lambda_function.MigrationRepository") | |
| def test_lambda_handler_nasa_failure(self, mock_repo, mock_db, mock_nasa, mock_secrets): | |
| mock_secrets_inst = mock_secrets.return_value | |
| mock_secrets_inst.secrets = { | |
| "LOGISTICS_API_GATEWAY_KEY": "test-key", | |
| "NASA_API_KEY": "nasa-key", | |
| "LAMBDA_LOG_LEVEL": "INFO", | |
| } | |
| mock_nasa_inst = mock_nasa.return_value | |
| mock_nasa_inst.get_astronomy_picture_of_the_day.side_effect = Exception("NASA API failure") | |
| mock_repo_inst = mock_repo.return_value | |
| mock_repo_inst.get_latest_migrations.return_value = [{"name": "001_init"}] | |
| response = lambda_handler(self.event, self.mock_context) | |
| self.assertNotEqual(response.get("statusCode"), 200) | |
| self.assertIn("error", str(response.get("body", "")).lower()) | |
| # Error case: database / migration repository raises an exception | |
| @patch("lambda_function.SecretsManagerWrapper") | |
| @patch("lambda_function.NasaService") | |
| @patch("lambda_function.MysqlDriver") | |
| @patch("lambda_function.MigrationRepository") | |
| def test_lambda_handler_db_failure(self, mock_repo, mock_db, mock_nasa, mock_secrets): | |
| mock_secrets_inst = mock_secrets.return_value | |
| mock_secrets_inst.secrets = { | |
| "LOGISTICS_API_GATEWAY_KEY": "test-key", | |
| "NASA_API_KEY": "nasa-key", | |
| "LAMBDA_LOG_LEVEL": "INFO", | |
| } | |
| mock_nasa_inst = mock_nasa.return_value | |
| mock_nasa_inst.get_astronomy_picture_of_the_day.return_value = {"url": "http://example.com"} | |
| mock_repo_inst = mock_repo.return_value | |
| mock_repo_inst.get_latest_migrations.side_effect = Exception("DB failure") | |
| response = lambda_handler(self.event, self.mock_context) | |
| self.assertNotEqual(response.get("statusCode"), 200) | |
| self.assertIn("error", str(response.get("body", "")).lower()) |
| "pytest>=8.0.0", | ||
| ] | ||
|
|
||
|
|
There was a problem hiding this comment.
pytest is added to the main runtime dependencies, which will be bundled into Lambda artifacts even though it's only needed for local testing. Consider moving it to a dev/optional dependency group to keep the production dependency set small.
| "pytest>=8.0.0", | |
| ] | |
| ] | |
| [project.optional-dependencies] | |
| dev = [ | |
| "pytest>=8.0.0", | |
| ] |
| from requests.exceptions import RequestException | ||
| from requests.models import HTTPError |
There was a problem hiding this comment.
HTTPError is imported from requests.models, which will raise an ImportError (HTTPError lives in requests.exceptions). Update the import so the Lambda can start and the exception handler works as intended.
| from requests.exceptions import RequestException | |
| from requests.models import HTTPError | |
| from requests.exceptions import RequestException, HTTPError |
| from requests.exceptions import RequestException | ||
| from requests.models import HTTPError |
There was a problem hiding this comment.
HTTPError is imported from requests.models, which is incorrect (HTTPError is in requests.exceptions). This will fail at import-time and prevent the service from running.
| from requests.exceptions import RequestException | |
| from requests.models import HTTPError | |
| from requests.exceptions import HTTPError, RequestException |
No description provided.