Skip to content
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

feat: Add internal API for get exported entry #679

Merged
merged 2 commits into from
Nov 26, 2024

Conversation

ashwin1111
Copy link
Contributor

@ashwin1111 ashwin1111 commented Nov 22, 2024

Description

Please add PR description here, add screenshots if needed

Clickup

https://app.clickup.com/

Summary by CodeRabbit

  • New Features
    • Introduced a new endpoint for retrieving exported entries from NetSuite.
    • Added functionality for retrieving exported entries based on specific parameters.
  • Improvements
    • Enhanced error handling in the accounting fields endpoint.
    • Modularized connection logic for better maintainability.
    • Improved logging for error tracking during vendor payment processing.

Copy link

coderabbitai bot commented Nov 22, 2024

Walkthrough

The changes introduce significant enhancements across several files in the application. A new method, get_netsuite_connection, is added to manage the connection to NetSuite, separating connection logic from the retrieval of accounting fields. Additionally, a new endpoint for ExportedEntryView is created, allowing users to access exported entries. The NetSuiteConnector class gains a method to retrieve entries by export ID, while error handling in existing views is improved for better logging and response management.

Changes

File Change Summary
apps/internal/actions.py - Added methods: get_netsuite_connection(query_params: Dict), get_exported_entry(query_params: Dict)
- Updated logic in get_accounting_fields(query_params: Dict) to use the new connection method.
apps/internal/urls.py - Added URL pattern for ExportedEntryView mapped to exported_entry/ with name exported-entry.
apps/internal/views.py - Added class ExportedEntryView(generics.GenericAPIView) with a get method.
- Enhanced error handling in AccountingFieldsView.
apps/netsuite/connector.py - Added method get_exported_entry(self, resource_type: str, export_id: str) in NetSuiteConnector.
- Enhanced logging in post_vendor_payment method.
tests/test_internal/test_actions.py - Added test case test_get_exported_entry to validate get_exported_entry function.
tests/test_internal/test_views.py - Added test case test_exported_entry_view to validate the behavior of the 'exported-entry' endpoint.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant Actions
    participant Connector

    User->>API: Request exported entry
    API->>Actions: Call get_exported_entry(query_params)
    Actions->>Actions: Call get_netsuite_connection(query_params)
    Actions->>Connector: Get NetSuiteConnector
    Connector->>Connector: Retrieve entry using get_exported_entry(resource_type, export_id)
    Connector-->>Actions: Return entry data
    Actions-->>API: Return structured response
    API-->>User: Send response with entry data
Loading

🐇 "In the fields where bunnies play,
New methods hop in bright array.
With connections clear and errors logged,
Our code now dances, unencumbered, unflogged!
Let's fetch those entries, one by one,
A joyful leap, our work is done!" 🐇

Warning

Rate limit exceeded

@ashwin1111 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 18 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c4a81ea and 92b65ab.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ashwin1111 ashwin1111 requested a review from ruuushhh November 22, 2024 16:58
@github-actions github-actions bot added the size/M Medium PR label Nov 22, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (4)
apps/internal/actions.py (1)

17-30: Consider refactoring to reduce code duplication

The get_accounting_fields and get_exported_entry functions share similar parameter extraction and validation logic.

Consider extracting the common logic into a helper function:

+def _get_resource_params(query_params: Dict) -> tuple:
+    """Extract and validate resource parameters.
+    
+    Args:
+        query_params (Dict): Dictionary containing resource_type and internal_id
+        
+    Returns:
+        tuple: (resource_type, internal_id)
+        
+    Raises:
+        ValueError: If required parameters are missing
+    """
+    resource_type = query_params.get('resource_type')
+    internal_id = query_params.get('internal_id')
+    
+    if not resource_type or not internal_id:
+        raise ValueError('resource_type and internal_id are required in query_params')
+    
+    return resource_type, internal_id
+
 def get_accounting_fields(query_params: Dict):
     ns_connection = get_netsuite_connection(query_params)
-    resource_type = query_params.get('resource_type')
-    internal_id = query_params.get('internal_id')
+    resource_type, internal_id = _get_resource_params(query_params)
     return ns_connection.get_accounting_fields(resource_type, internal_id)

 def get_exported_entry(query_params: Dict):
     ns_connection = get_netsuite_connection(query_params)
-    resource_type = query_params.get('resource_type')
-    internal_id = query_params.get('internal_id')
+    resource_type, internal_id = _get_resource_params(query_params)
     return ns_connection.get_exported_entry(resource_type, internal_id)
apps/internal/views.py (2)

45-48: Add docstring and type hints

The view class would benefit from a docstring explaining its purpose and type hints for better maintainability.

Add documentation and type hints:

 class ExportedEntryView(generics.GenericAPIView):
+    """
+    Internal API endpoint to retrieve exported entries.
+    
+    Requires authentication via IsAuthenticatedForInternalAPI.
+    """
     authentication_classes: list = []
     permission_classes = [IsAuthenticatedForInternalAPI]

Line range hint 62-67: Fix incorrect view name in error logging

The error message incorrectly references "AccountingFieldsView" instead of "ExportedEntryView".

Apply this fix:

         except Exception:
-            logger.info(f"Error in AccountingFieldsView: {traceback.format_exc()}")
+            error_traceback = traceback.format_exc()
+            logger.error(f"Error in ExportedEntryView: {error_traceback}")
             return Response(
-                data={'error': traceback.format_exc()},
+                data={'error': 'An error occurred while processing your request'},
                 status=status.HTTP_400_BAD_REQUEST
             )
apps/netsuite/connector.py (1)

1198-1198: Replace getattr with direct attribute access

When accessing a constant attribute like 'get', direct attribute access is cleaner than using getattr.

-response = getattr(module, 'get')(export_id)
+response = module.get(export_id)
🧰 Tools
🪛 Ruff (0.7.0)

1198-1198: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bb8c879 and c4a81ea.

📒 Files selected for processing (4)
  • apps/internal/actions.py (1 hunks)
  • apps/internal/urls.py (1 hunks)
  • apps/internal/views.py (2 hunks)
  • apps/netsuite/connector.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.7.0)
apps/netsuite/connector.py

1198-1198: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

🔇 Additional comments (4)
apps/internal/urls.py (2)

5-5: LGTM! Import statement follows Django conventions.

The import statement is well-structured, using appropriate relative imports and maintaining alphabetical ordering of view classes.


9-9: Verify authentication and consider API versioning.

While the URL pattern is well-structured, since this is an internal API endpoint:

  1. Ensure proper authentication/permission checks are implemented in ExportedEntryView
  2. Consider adding API version prefix (e.g., /v1/exported_entry/) for better API lifecycle management

Let's verify the authentication implementation:

Consider implementing API versioning to make future API changes more manageable. This can be done using Django REST framework's versioning classes or URL prefixes.

✅ Verification successful

Let's gather more information about the authentication implementation in the views file.


Let's verify the implementation of the custom permission class IsAuthenticatedForInternalAPI.


Let's check the implementation of this permission class in the workspaces app.


Authentication is properly implemented, versioning suggestion remains valid

The endpoint is secured with IsAuthenticatedForInternalAPI permission class which:

  • Requires a encrypted client ID in X-Internal-API-Client-ID header
  • Uses Fernet symmetric encryption for secure token validation
  • Validates against a client secret stored in settings

Consider adding API version prefix (e.g., /v1/exported_entry/) for better API lifecycle management.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for authentication/permission decorators or mixins in ExportedEntryView

# Search for ExportedEntryView class definition and its potential security decorators
ast-grep --pattern 'class ExportedEntryView$$$'

# Search for common authentication/permission classes usage
rg -l "IsAuthenticated|SessionAuthentication|BasicAuthentication|TokenAuthentication" apps/internal/

Length of output: 175


Script:

#!/bin/bash
# Check the implementation of ExportedEntryView and its authentication
rg -A 10 "class ExportedEntryView" apps/internal/views.py

# Also check for any permission_classes or authentication_classes declarations
rg "(permission_classes|authentication_classes)" apps/internal/views.py

Length of output: 819


Script:

#!/bin/bash
# Check the implementation of IsAuthenticatedForInternalAPI permission class
rg -A 10 "class IsAuthenticatedForInternalAPI" apps/internal/

# Also check for any imports of this class
rg "IsAuthenticatedForInternalAPI" apps/internal/

Length of output: 366


Script:

#!/bin/bash
# Check the implementation of IsAuthenticatedForInternalAPI in workspaces app
rg -A 10 "class IsAuthenticatedForInternalAPI" apps/workspaces/permissions.py

# Also check for any related authentication settings or middleware
rg "INTERNAL_API" apps/workspaces/

Length of output: 670

apps/internal/actions.py (1)

7-30: Verify error handling in views using these functions

Let's ensure that the views properly handle the exceptions that could be raised by these functions.

✅ Verification successful

Error handling is properly implemented in views

The views using these functions (AccountingFieldsView and ExportedEntryView) have comprehensive error handling:

  • Both views wrap their logic in try-except blocks
  • Input validation is performed using assert_valid before calling the functions
  • Errors are properly logged with full traceback
  • Error responses return HTTP 400 with the error details
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for views using these functions and verify error handling

# Find views importing or using these functions
rg -l "from apps.internal.actions import|get_accounting_fields|get_exported_entry" --type py

# Check error handling in those files
rg "try.*except|@error_handler" --type py

Length of output: 436


Script:

#!/bin/bash
# Let's check the implementation in views.py to see how these functions are used
cat apps/internal/views.py

# Also check the error handling decorator if it exists
rg -A 5 "@error_handler" --type py

Length of output: 2504

apps/internal/views.py (1)

11-11: LGTM!

The import is correctly placed and aligns with the new functionality.

Comment on lines +7 to +14
def get_netsuite_connection(query_params: Dict):
org_id = query_params.get('org_id')
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')

workspace = Workspace.objects.get(fyle_org_id=org_id)
workspace_id = workspace.id
ns_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace.id)

ns_connection = NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)
return NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and input validation

The function needs proper error handling for various failure scenarios:

  1. Missing or invalid org_id in query_params
  2. Non-existent Workspace
  3. Missing NetSuite credentials

Consider applying this improvement:

 def get_netsuite_connection(query_params: Dict):
-    org_id = query_params.get('org_id')
+    """Get NetSuite connection for the given organization.
+    
+    Args:
+        query_params (Dict): Dictionary containing org_id
+        
+    Returns:
+        NetSuiteConnector: Initialized connector instance
+        
+    Raises:
+        ValueError: If org_id is missing
+        Workspace.DoesNotExist: If workspace not found
+        NetSuiteCredentials.DoesNotExist: If credentials not found
+    """
+    org_id = query_params.get('org_id')
+    if not org_id:
+        raise ValueError('org_id is required in query_params')
 
     workspace = Workspace.objects.get(fyle_org_id=org_id)
     workspace_id = workspace.id
     ns_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace.id)
 
     return NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_netsuite_connection(query_params: Dict):
org_id = query_params.get('org_id')
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')
workspace = Workspace.objects.get(fyle_org_id=org_id)
workspace_id = workspace.id
ns_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace.id)
ns_connection = NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)
return NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)
def get_netsuite_connection(query_params: Dict):
"""Get NetSuite connection for the given organization.
Args:
query_params (Dict): Dictionary containing org_id
Returns:
NetSuiteConnector: Initialized connector instance
Raises:
ValueError: If org_id is missing
Workspace.DoesNotExist: If workspace not found
NetSuiteCredentials.DoesNotExist: If credentials not found
"""
org_id = query_params.get('org_id')
if not org_id:
raise ValueError('org_id is required in query_params')
workspace = Workspace.objects.get(fyle_org_id=org_id)
workspace_id = workspace.id
ns_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace.id)
return NetSuiteConnector(netsuite_credentials=ns_credentials, workspace_id=workspace_id)

Comment on lines +25 to +30
def get_exported_entry(query_params: Dict):
ns_connection = get_netsuite_connection(query_params)
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')

return ns_connection.get_exported_entry(resource_type, internal_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add parameter validation and documentation

Similar to get_accounting_fields, this function needs parameter validation and proper documentation.

Apply this improvement:

 def get_exported_entry(query_params: Dict):
+    """Get exported entry details for the specified resource.
+    
+    Args:
+        query_params (Dict): Dictionary containing org_id, resource_type, and internal_id
+        
+    Returns:
+        Dict: Exported entry details
+        
+    Raises:
+        ValueError: If required parameters are missing
+    """
+    resource_type = query_params.get('resource_type')
+    internal_id = query_params.get('internal_id')
+    
+    if not resource_type or not internal_id:
+        raise ValueError('resource_type and internal_id are required in query_params')
+
     ns_connection = get_netsuite_connection(query_params)
-    resource_type = query_params.get('resource_type')
-    internal_id = query_params.get('internal_id')

     return ns_connection.get_exported_entry(resource_type, internal_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_exported_entry(query_params: Dict):
ns_connection = get_netsuite_connection(query_params)
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')
return ns_connection.get_exported_entry(resource_type, internal_id)
def get_exported_entry(query_params: Dict):
"""Get exported entry details for the specified resource.
Args:
query_params (Dict): Dictionary containing org_id, resource_type, and internal_id
Returns:
Dict: Exported entry details
Raises:
ValueError: If required parameters are missing
"""
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')
if not resource_type or not internal_id:
raise ValueError('resource_type and internal_id are required in query_params')
ns_connection = get_netsuite_connection(query_params)
return ns_connection.get_exported_entry(resource_type, internal_id)

Comment on lines +17 to 22
def get_accounting_fields(query_params: Dict):
ns_connection = get_netsuite_connection(query_params)
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')

return ns_connection.get_accounting_fields(resource_type, internal_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add parameter validation and error handling

The function should validate the required parameters before proceeding.

Apply this improvement:

 def get_accounting_fields(query_params: Dict):
+    """Get accounting fields for the specified resource.
+    
+    Args:
+        query_params (Dict): Dictionary containing org_id, resource_type, and internal_id
+        
+    Returns:
+        Dict: Accounting fields for the resource
+        
+    Raises:
+        ValueError: If required parameters are missing
+    """
+    resource_type = query_params.get('resource_type')
+    internal_id = query_params.get('internal_id')
+    
+    if not resource_type or not internal_id:
+        raise ValueError('resource_type and internal_id are required in query_params')
+
     ns_connection = get_netsuite_connection(query_params)
-    resource_type = query_params.get('resource_type')
-    internal_id = query_params.get('internal_id')
 
     return ns_connection.get_accounting_fields(resource_type, internal_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_accounting_fields(query_params: Dict):
ns_connection = get_netsuite_connection(query_params)
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')
return ns_connection.get_accounting_fields(resource_type, internal_id)
def get_accounting_fields(query_params: Dict):
"""Get accounting fields for the specified resource.
Args:
query_params (Dict): Dictionary containing org_id, resource_type, and internal_id
Returns:
Dict: Accounting fields for the resource
Raises:
ValueError: If required parameters are missing
"""
resource_type = query_params.get('resource_type')
internal_id = query_params.get('internal_id')
if not resource_type or not internal_id:
raise ValueError('resource_type and internal_id are required in query_params')
ns_connection = get_netsuite_connection(query_params)
return ns_connection.get_accounting_fields(resource_type, internal_id)

Comment on lines +37 to +42
except Exception:
logger.info(f"Error in AccountingFieldsView: {traceback.format_exc()}")
return Response(
data={'error': traceback.format_exc()},
status=status.HTTP_400_BAD_REQUEST
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security & Logging Issues in Error Handling

  1. Sending full tracebacks in API responses exposes internal implementation details and could pose a security risk.
  2. Using logger.info for errors is incorrect - should use logger.error for exceptions.

Apply this fix:

         except Exception:
-            logger.info(f"Error in AccountingFieldsView: {traceback.format_exc()}")
+            error_traceback = traceback.format_exc()
+            logger.error(f"Error in AccountingFieldsView: {error_traceback}")
             return Response(
-                data={'error': traceback.format_exc()},
+                data={'error': 'An error occurred while processing your request'},
                 status=status.HTTP_400_BAD_REQUEST
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception:
logger.info(f"Error in AccountingFieldsView: {traceback.format_exc()}")
return Response(
data={'error': traceback.format_exc()},
status=status.HTTP_400_BAD_REQUEST
)
except Exception:
error_traceback = traceback.format_exc()
logger.error(f"Error in AccountingFieldsView: {error_traceback}")
return Response(
data={'error': 'An error occurred while processing your request'},
status=status.HTTP_400_BAD_REQUEST
)

Comment on lines +49 to +61
def get(self, request, *args, **kwargs):
try:
params = request.query_params
assert_valid(params.get('org_id') is not None, 'Org ID is required')
assert_valid(params.get('resource_type') is not None, 'Resource Type is required')
assert_valid(params.get('internal_id') is not None, 'Internal ID is required')

response = get_exported_entry(request.query_params)

return Response(
data={'data': response},
status=status.HTTP_200_OK
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Error handling needs improvement in the export entry flow

The verification reveals several gaps in error handling:

  • The get_exported_entry function in apps/internal/actions.py doesn't have any try-catch blocks despite making critical database queries (Workspace.objects.get, NetSuiteCredentials.objects.get)
  • The NetSuiteConnector.get_exported_entry method in apps/netsuite/connector.py uses dynamic attribute access (getattr) without exception handling
  • Multiple potential exceptions are imported but not handled: NetSuiteRequestError, NetSuiteRateLimitError, NetSuiteLoginError

Key areas that need error handling:

  • Database queries in get_netsuite_connection
  • NetSuite API calls in NetSuiteConnector.get_exported_entry
  • Dynamic attribute access using getattr
🔗 Analysis chain

Verify error handling for get_exported_entry

The view correctly validates required parameters, but we should verify error handling in the get_exported_entry function.

Let's check the implementation of get_exported_entry:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check error handling in get_exported_entry function

# Look for the function definition and its error handling
ast-grep --pattern 'def get_exported_entry($_):
  $$$
'

# Also check for any existing tests
fd -e py test_.*internal.*

Length of output: 535


Script:

#!/bin/bash
# Let's check the implementation of get_netsuite_connection and its error handling
ast-grep --pattern 'def get_netsuite_connection($_):
  $$$
'

# Also check if there are any try-catch blocks around get_exported_entry calls
rg -A 5 "get_exported_entry" 

# Look for any custom exceptions defined
rg "class.*Error" apps/internal/

Length of output: 2261


Script:

#!/bin/bash
# Let's check for exception handling in the NetSuiteConnector class
rg -B 5 -A 10 "class NetSuiteConnector" apps/netsuite/

# Check for any exception handling in Workspace and NetSuiteCredentials models
rg -A 5 "class (Workspace|NetSuiteCredentials)" apps/

# Look for any exception imports
rg "^from.*import.*Error" apps/

Length of output: 7205

Comment on lines +1196 to +1199
def get_exported_entry(self, resource_type: str, export_id: str):
module = getattr(self.connection, resource_type)
response = getattr(module, 'get')(export_id)
return json.loads(json.dumps(response, default=str))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add documentation and error handling

The method needs documentation and error handling improvements:

  1. Add docstring to describe parameters and return value
  2. Add error handling for potential NetSuiteRequestError exceptions, similar to other methods in this class
 def get_exported_entry(self, resource_type: str, export_id: str):
+    """
+    Retrieve an exported entry from NetSuite.
+
+    Args:
+        resource_type (str): The type of resource to fetch (e.g., 'vendor_bills', 'expense_reports', etc.)
+        export_id (str): The NetSuite internal ID of the exported entry
+
+    Returns:
+        dict: JSON response containing the exported entry details
+
+    Raises:
+        NetSuiteRequestError: If the request to NetSuite fails
+    """
+    try:
         module = getattr(self.connection, resource_type)
-        response = getattr(module, 'get')(export_id)
+        response = module.get(export_id)
         return json.loads(json.dumps(response, default=str))
+    except NetSuiteRequestError as e:
+        logger.info('Error while getting exported entry - %s', e)
+        raise
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_exported_entry(self, resource_type: str, export_id: str):
module = getattr(self.connection, resource_type)
response = getattr(module, 'get')(export_id)
return json.loads(json.dumps(response, default=str))
def get_exported_entry(self, resource_type: str, export_id: str):
"""
Retrieve an exported entry from NetSuite.
Args:
resource_type (str): The type of resource to fetch (e.g., 'vendor_bills', 'expense_reports', etc.)
export_id (str): The NetSuite internal ID of the exported entry
Returns:
dict: JSON response containing the exported entry details
Raises:
NetSuiteRequestError: If the request to NetSuite fails
"""
try:
module = getattr(self.connection, resource_type)
response = module.get(export_id)
return json.loads(json.dumps(response, default=str))
except NetSuiteRequestError as e:
logger.info('Error while getting exported entry - %s', e)
raise
🧰 Tools
🪛 Ruff (0.7.0)

1198-1198: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

Copy link

Tests Skipped Failures Errors Time
231 0 💤 0 ❌ 0 🔥 26.170s ⏱️

Copy link

Tests Skipped Failures Errors Time
233 0 💤 0 ❌ 0 🔥 25.364s ⏱️

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
tests/test_internal/test_views.py (2)

46-53: Enhance test coverage with additional error scenarios

Consider adding test cases for:

  1. Invalid org_id
  2. Invalid resource_type values
  3. Invalid internal_id format
  4. Assert error messages in the response body

Example addition:

    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'vendor_bills'})
    assert response.status_code == 400
+
+   # Test invalid org_id
+   response = api_client.get(url, {'org_id': 'invalid', 'resource_type': 'vendor_bills', 'internal_id': '1'})
+   assert response.status_code == 400
+   assert response.json()['message'] == 'Invalid organization id'
+
+   # Test invalid resource_type
+   response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'invalid', 'internal_id': '1'})
+   assert response.status_code == 400
+   assert response.json()['message'] == 'Invalid resource type'

Line range hint 13-13: Fix typo in test function name

The function name contains a typo: test_netsutie_fields_view should be test_netsuite_fields_view

-def test_netsutie_fields_view(db, api_client, mocker):
+def test_netsuite_fields_view(db, api_client, mocker):
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c4a81ea and 92b65ab.

📒 Files selected for processing (2)
  • tests/test_internal/test_actions.py (2 hunks)
  • tests/test_internal/test_views.py (1 hunks)
🔇 Additional comments (2)
tests/test_internal/test_actions.py (1)

1-1: LGTM! Import statement is correctly updated

The import statement properly includes the new get_exported_entry function alongside the existing get_accounting_fields.

tests/test_internal/test_views.py (1)

41-44: Verify URL configuration for 'exported-entry'

The test assumes the existence of a URL pattern named 'exported-entry'. Let's ensure this URL pattern is properly configured.

✅ Verification successful

URL pattern 'exported-entry' is properly configured

The URL pattern 'exported-entry' is correctly defined in apps/internal/urls.py and maps to ExportedEntryView, matching the test's requirements.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the URL pattern exists in the urls.py files
# Expected: Find URL pattern definition for 'exported-entry'

rg -l "exported-entry" --type py

Length of output: 88


Script:

#!/bin/bash
# Check the URL pattern definition in apps/internal/urls.py
rg "exported-entry" apps/internal/urls.py -B 2 -A 2

Length of output: 240

Comment on lines +30 to +42
def test_get_exported_entry(db, mocker):
query_params = {
'org_id': 'or79Cob97KSh',
'resource_type': 'vendor_bills',
'internal_id': '1'
}
mocker.patch(
'netsuitesdk.api.vendor_bills.VendorBills.get',
return_value={'summa': 'hehe'}
)

entry = get_exported_entry(query_params)
assert entry is not None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Test implementation needs strengthening

While the basic test structure is in place, several improvements would make the test more robust and meaningful:

  1. The mock return value {'summa': 'hehe'} should be replaced with realistic test data
  2. The assertion should verify the actual structure and content of the returned data
  3. Error cases should be covered
  4. The test should verify that the mocked function is called with correct parameters

Here's a suggested improvement:

 def test_get_exported_entry(db, mocker):
     query_params = {
         'org_id': 'or79Cob97KSh',
         'resource_type': 'vendor_bills',
         'internal_id': '1'
     }
+    expected_data = {
+        'id': '1',
+        'type': 'vendor_bills',
+        'amount': 100.00,
+        'status': 'PAID',
+        # Add other relevant fields
+    }
+    mock_get = mocker.patch(
         'netsuitesdk.api.vendor_bills.VendorBills.get',
-        return_value={'summa': 'hehe'}
+        return_value=expected_data
     )

     entry = get_exported_entry(query_params)
-    assert entry is not None
+    assert entry == expected_data
+    mock_get.assert_called_once_with('1')
+
+    # Test error case
+    mock_get.side_effect = Exception('NetSuite API error')
+    with pytest.raises(Exception):
+        get_exported_entry(query_params)

Additionally, consider adding test cases for:

  • Different resource types
  • Invalid internal_id
  • Missing required parameters
  • API timeout scenarios

Committable suggestion skipped: line range outside the PR's diff.


💡 Codebase verification

Additional test scenarios needed for get_exported_entry

The current test only covers the 'vendor_bills' resource type, but the implementation shows it's used for multiple resource types. Missing test scenarios:

  • Test with 'custom_segments', 'custom_lists', and 'custom_record_types' resource types
  • Test error cases when required parameters (resource_type, internal_id) are missing
  • Test with invalid NetSuite connection parameters
🔗 Analysis chain

Verify test coverage for all scenarios

Let's ensure the test coverage aligns with the implementation in apps.internal.actions.get_exported_entry.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all possible resource types and parameters used in the implementation
ast-grep --pattern 'def get_exported_entry($$$)' -f 'apps/internal/actions.py' -A 20

# Check for error handling patterns that should be tested
rg -t python "raise\s+\w+Error" apps/internal/actions.py

Length of output: 352


Script:

#!/bin/bash
# Find the implementation of get_exported_entry
ast-grep --pattern 'def get_exported_entry($$$)'

# Find error handling patterns
rg "raise.*Error" 

# Find resource types handling
rg "resource_type.*=|resource_type.*in"

Length of output: 5554


Script:

#!/bin/bash
# Find the complete implementation of get_exported_entry in connector
ast-grep --pattern 'def get_exported_entry($$$)'

# Find all test files related to exported entries
fd test.*actions.py

# Look for resource type validations
rg -B2 -A2 "resource_type.*=" apps/internal/actions.py

Length of output: 619

Comment on lines +55 to +61
mocker.patch(
'netsuitesdk.api.vendor_bills.VendorBills.get',
return_value={'summa': 'hehe'}
)

response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'vendor_bills', 'internal_id': '1'})
assert response.status_code == 200
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve test data and response validation

The current implementation has several areas for improvement:

  1. The mock return value {'summa': 'hehe'} doesn't reflect a realistic NetSuite response structure
  2. The test only verifies the status code without validating the response data
  3. Test data should be moved to the fixtures file for consistency with other tests

Suggested improvements:

+   # Import test data from fixtures
+   from tests.test_netsuite.fixtures import data
+
    mocker.patch(
        'netsuitesdk.api.vendor_bills.VendorBills.get',
-       return_value={'summa': 'hehe'}
+       return_value=data['get_vendor_bill']  # Add realistic test data to fixtures
    )

    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'vendor_bills', 'internal_id': '1'})
    assert response.status_code == 200
+   response_data = response.json()
+   
+   # Verify response structure
+   expected_fields = ['id', 'status', 'amount', ...]  # Add expected fields
+   for field in expected_fields:
+       assert field in response_data

Committable suggestion skipped: line range outside the PR's diff.

@ashwin1111 ashwin1111 merged commit 757cfc5 into tests-get-accounting-field Nov 26, 2024
4 checks passed
ashwin1111 added a commit that referenced this pull request Nov 26, 2024
* tests: get accounting field internal API

* feat: Add internal API for get exported entry (#679)

* feat: Add internal API for get exported entry

* add tests
ashwin1111 added a commit that referenced this pull request Nov 26, 2024
* feat: NetSuite internal APIs

* rename

* custom segment support

* test: get accounting field internal API (#678)

* tests: get accounting field internal API

* feat: Add internal API for get exported entry (#679)

* feat: Add internal API for get exported entry

* add tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size/M Medium PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants