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

test: Internal APIs #698

Merged
merged 1 commit into from
Nov 26, 2024
Merged

test: Internal APIs #698

merged 1 commit into from
Nov 26, 2024

Conversation

ashwin1111
Copy link
Contributor

@ashwin1111 ashwin1111 commented Nov 25, 2024

Description

Please add PR description here, add screenshots if needed

Clickup

Please add link here

Summary by CodeRabbit

  • Tests
    • Introduced new test functions for validating accounting fields and exported entries.
    • Added tests for the accounting-fields and exported-entry endpoints to ensure correct status codes and responses.
    • Updated authentication headers in existing tests to align with the new API client requirements.

Copy link

coderabbitai bot commented Nov 25, 2024

Walkthrough

This pull request introduces new test functions to validate the functionality of specific methods in the apps.internal.actions module and tests for two API endpoints in the Django application. It includes tests for get_accounting_fields and get_exported_entry, each utilizing mock responses to simulate API interactions. Additionally, modifications were made to the authentication headers in an existing test function. The changes ensure that the tests accurately reflect the expected behavior of the application under various conditions without altering any public entity declarations.

Changes

File Change Summary
tests/test_internal/test_actions.py Added test_get_accounting_fields and test_get_exported_entry to validate corresponding functions.
tests/test_internal/test_views.py Added test_qbo_fields_view and test_exported_entry_view to test API endpoints with various params.
tests/test_workspaces/test_views.py Replaced HTTP_X_E2E_Tests_Client_ID with HTTP_X_Internal_API_Client_ID in test_prepare_e2e_test_view.

Possibly related PRs

  • feat: Add attribute limits #678: The changes in this PR involve modifications to the synchronization logic for various entities, which may relate to the functionality being tested in the main PR's new test functions for get_accounting_fields and get_exported_entry, as both involve API interactions that could be impacted by synchronization limits.

Suggested reviewers

  • abhishek1234321
  • Hrishabh17

Poem

🐇 In the realm of tests, we hop and play,
New functions added, hip-hip-hooray!
Mocks in place, to guide the way,
Validating fields, come what may.
With headers changed, we test anew,
A rabbit's cheer for the code so true! 🐇


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 25, 2024 07:28
@github-actions github-actions bot added the size/M Medium PR label Nov 25, 2024
Copy link

Tests Skipped Failures Errors Time
265 0 💤 0 ❌ 0 🔥 55.650s ⏱️

Copy link

codecov bot commented Nov 25, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (internal-apis@be221fb). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@               Coverage Diff                @@
##             internal-apis     #698   +/-   ##
================================================
  Coverage                 ?   94.54%           
================================================
  Files                    ?       63           
  Lines                    ?     4989           
  Branches                 ?        0           
================================================
  Hits                     ?     4717           
  Misses                   ?      272           
  Partials                 ?        0           

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 (4)
tests/test_internal/test_actions.py (1)

1-31: Consider adding a test configuration file

To improve test maintainability and reusability, consider:

  1. Creating a conftest.py for shared test fixtures
  2. Moving test data to separate fixture files
  3. Adding typing information to improve code clarity
  4. Documenting test scenarios in docstrings

Example structure:

# tests/test_internal/conftest.py
import pytest

@pytest.fixture
def mock_qbo_response():
    return {
        'employee_response': {...},
        'bill_response': {...}
    }

# tests/test_internal/fixtures/responses.py
MOCK_RESPONSES = {
    'accounting_fields': [...],
    'exported_entry': {...}
}
tests/test_internal/test_views.py (2)

10-28: Enhance test coverage and assertions

While the basic test flow is good, consider these improvements:

  1. Add assertions for response content in the successful case
  2. Add negative test cases for invalid resource_type
  3. Validate that the correct parameters are being passed to the mocked function
  4. Consider parameterizing the test for different resource types

Here's a suggested enhancement:

 @pytest.mark.django_db(databases=['default'])
 @patch.object(IsAuthenticatedForInternalAPI, 'has_permission', return_value=True)
-def test_qbo_fields_view(db, api_client, mocker):
+@pytest.mark.parametrize('resource_type,mock_target', [
+    ('employees', 'qbosdk.apis.Employees.get_all_generator'),
+    ('vendors', 'qbosdk.apis.Vendors.get_all_generator'),
+])
+def test_qbo_fields_view(db, api_client, mocker, resource_type, mock_target):
     url = reverse('accounting-fields')
 
     response = api_client.get(url)
     assert response.status_code == 400
+    assert 'message' in response.data
 
     response = api_client.get(url, {'org_id': 'or79Cob97KSh'})
     assert response.status_code == 400
+    assert 'message' in response.data
+
+    # Test invalid resource_type
+    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'invalid'})
+    assert response.status_code == 400
 
     mocker.patch(
-        'qbosdk.apis.Employees.get_all_generator',
+        mock_target,
         return_value=[data['employee_response']]
     )
 
-    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'employees'})
+    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': resource_type})
     assert response.status_code == 200
+    assert isinstance(response.data, list)
+    mock = mocker.patch(mock_target)
+    mock.assert_called_once()

1-50: Consider creating shared test utilities

The test functions follow similar patterns for parameter validation. Consider extracting common test logic into shared utilities:

  1. Create a utility function for testing missing/invalid parameters
  2. Create fixtures for common test data
  3. Consider using pytest's parametrize for testing different resource types and scenarios

Example structure:

@pytest.fixture
def mock_qbo_responses():
    return {
        'bills': {...},
        'employees': {...}
    }

def assert_missing_params(client, url, params_list):
    """Utility to test missing parameters"""
    for params in params_list:
        response = client.get(url, params)
        assert response.status_code == 400
        assert 'message' in response.data
tests/test_workspaces/test_views.py (1)

Line range hint 167-193: Enhance test readability with pytest.mark.parametrize

While the test coverage is comprehensive, consider using pytest.mark.parametrize to make the test cases more maintainable and readable:

@pytest.mark.parametrize('client_id,expected_status,test_case', [
    ('dummy_id', 403, 'invalid_client_id'),
    (settings.TEST_INTERNAL_API_CLIENT_ID, 200, 'valid_credentials'),
    (settings.TEST_INTERNAL_API_CLIENT_ID_INVALID, 400, 'invalid_credentials')
])
def test_prepare_e2e_test_view(client_id, expected_status, test_case, mock_db, mocker, api_client, test_connection):
    url = reverse('setup-e2e-test', kwargs={'workspace_id': 3})
    api_client.credentials(HTTP_X_Internal_API_Client_ID=client_id)
    
    # Setup mocks based on test_case
    if test_case == 'valid_credentials':
        mocker.patch('cryptography.fernet.Fernet.decrypt', return_value=settings.E2E_TESTS_CLIENT_SECRET.encode('utf-8'))
        mocker.patch('apps.quickbooks_online.utils.QBOConnector.sync_dimensions', return_value=None)
        # ... other mocks

    response = api_client.post(url)
    assert response.status_code == expected_status
🧰 Tools
🪛 Gitleaks (8.21.1)

187-187: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


192-192: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between be221fb and 11da9b4.

📒 Files selected for processing (3)
  • tests/test_internal/test_actions.py (1 hunks)
  • tests/test_internal/test_views.py (1 hunks)
  • tests/test_workspaces/test_views.py (3 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.1)
tests/test_workspaces/test_views.py

187-187: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


192-192: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (4)
tests/test_internal/test_actions.py (2)

1-2: LGTM! Imports are clean and appropriate.

The imports are specific to the test requirements, importing only the necessary functions and fixtures.


19-31: 🛠️ Refactor suggestion

Improve test data quality and coverage

The current test has several areas for improvement:

  1. The mock response {'summa': 'hehe'} doesn't represent realistic QuickBooks data
  2. The assertion is too basic
  3. Missing error scenario coverage

Suggested improvements:

+from tests.test_quickbooks_online.fixtures import mock_bill_response  # Add realistic mock data
+
 def test_get_exported_entry(db, mocker):
     query_params = {
         'org_id': 'or79Cob97KSh',
         'resource_type': 'bills',
         'internal_id': '1'
     }
     mocker.patch(
         'qbosdk.apis.Bills.get_by_id',
-        return_value={'summa': 'hehe'}
+        return_value=mock_bill_response
     )

     entry = get_exported_entry(query_params)
-    assert entry is not None
+    assert isinstance(entry, dict), "Should return a dictionary"
+    assert 'Id' in entry, "Should contain QuickBooks Id"
+    assert 'Balance' in entry, "Should contain Balance field"
+
+def test_get_exported_entry_error_cases(db, mocker):
+    # Test missing internal_id
+    query_params = {
+        'org_id': 'or79Cob97KSh',
+        'resource_type': 'bills'
+    }
+    with pytest.raises(ValueError):
+        get_exported_entry(query_params)
+
+    # Test API error handling
+    query_params['internal_id'] = '1'
+    mocker.patch(
+        'qbosdk.apis.Bills.get_by_id',
+        side_effect=Exception('API Error')
+    )
+    with pytest.raises(Exception):
+        get_exported_entry(query_params)

Let's verify the actual QuickBooks response structure:

tests/test_internal/test_views.py (1)

1-8: LGTM: Imports are well-organized and appropriate

The imports are clean, well-organized, and include all necessary components for testing Django API endpoints with pytest.

tests/test_workspaces/test_views.py (1)

168-168: LGTM: Authentication header changes are well-structured

The change from HTTP_X_E2E_Tests_Client_ID to HTTP_X_Internal_API_Client_ID is consistent across all test cases and properly validates different response scenarios.

Also applies to: 178-178, 187-187, 192-192

Comment on lines +5 to +16
def test_get_accounting_fields(db, mocker):
query_params = {
'org_id': 'or79Cob97KSh',
'resource_type': 'employees',
}
mocker.patch(
'qbosdk.apis.Employees.get_all_generator',
return_value=[data['employee_response']]
)

fields = get_accounting_fields(query_params)
assert fields 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

Enhance test coverage and assertions

While the basic test structure is good, consider these improvements:

  1. Add assertions to validate the structure and content of returned fields
  2. Add test cases for error scenarios (invalid org_id, API errors)
  3. Consider parameterizing the test for different resource types
  4. Document the purpose of the hardcoded org_id

Example enhancement:

 def test_get_accounting_fields(db, mocker):
     query_params = {
         'org_id': 'or79Cob97KSh',
         'resource_type': 'employees',
     }
     mocker.patch(
         'qbosdk.apis.Employees.get_all_generator',
         return_value=[data['employee_response']]
     )

     fields = get_accounting_fields(query_params)
-    assert fields is not None
+    # Validate field structure and content
+    assert isinstance(fields, list), "Should return a list of fields"
+    assert len(fields) > 0, "Should return at least one field"
+    for field in fields:
+        assert 'attribute_type' in field, "Each field should have attribute_type"
+        assert 'display_name' in field, "Each field should have display_name"

@pytest.mark.parametrize('resource_type,expected_error', [
    ('invalid_type', ValueError),
    ('employees', None)
])
def test_get_accounting_fields_errors(db, mocker, resource_type, expected_error):
    query_params = {
        'org_id': 'or79Cob97KSh',
        'resource_type': resource_type,
    }
    if expected_error:
        with pytest.raises(expected_error):
            get_accounting_fields(query_params)
    else:
        fields = get_accounting_fields(query_params)
        assert fields is not None

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

Comment on lines +30 to +50
@pytest.mark.django_db(databases=['default'])
@patch.object(IsAuthenticatedForInternalAPI, 'has_permission', return_value=True)
def test_exported_entry_view(db, api_client, mocker):
url = reverse('exported-entry')

response = api_client.get(url)
assert response.status_code == 400

response = api_client.get(url, {'org_id': 'or79Cob97KSh'})
assert response.status_code == 400

response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'bills'})
assert response.status_code == 400

mocker.patch(
'qbosdk.apis.Bills.get_by_id',
return_value={'summa': 'hehe'}
)

response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': '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.

💡 Codebase verification

⚠️ Potential issue

Based on the gathered information, I can now provide the final response:

Improve test data and assertions

The test structure is good, but needs improvements in the mocked response and assertions to match the actual view implementation:

  1. The view wraps the response in a data field: {'data': response}
  2. Error responses include a message field
  3. The view uses get_exported_entry() function which should be properly mocked
 @pytest.mark.django_db(databases=['default'])
 @patch.object(IsAuthenticatedForInternalAPI, 'has_permission', return_value=True)
 def test_exported_entry_view(db, api_client, mocker):
     url = reverse('exported-entry')
 
     response = api_client.get(url)
     assert response.status_code == 400
+    assert 'error' in response.data
 
     response = api_client.get(url, {'org_id': 'or79Cob97KSh'})
     assert response.status_code == 400
+    assert 'error' in response.data
 
     response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'bills'})
     assert response.status_code == 400
+    assert 'error' in response.data
 
+    mock_bill_response = {
+        'Id': '123',
+        'DocNumber': 'BILL-123',
+        'TotalAmt': 100.00,
+        'DueDate': '2024-01-01',
+        'Balance': 100.00
+    }
+
     mocker.patch(
-        'qbosdk.apis.Bills.get_by_id',
-        return_value={'summa': 'hehe'}
+        'apps.internal.utils.get_exported_entry',
+        return_value=mock_bill_response
     )
 
     params = {
         'org_id': 'or79Cob97KSh',
         'resource_type': 'bills',
         'internal_id': '123'
     }
     response = api_client.get(url, params)
     assert response.status_code == 200
-    assert response.data == mock_bill_response
+    assert response.data == {'data': mock_bill_response}
🔗 Analysis chain

Improve test data and assertions

The test structure is good, but there are several areas for improvement:

  1. The mocked response {'summa': 'hehe'} is not representative of real data
  2. Missing assertions for response content
  3. No validation that the correct parameters are passed to the mocked function

Here's a suggested improvement:

 @pytest.mark.django_db(databases=['default'])
 @patch.object(IsAuthenticatedForInternalAPI, 'has_permission', return_value=True)
 def test_exported_entry_view(db, api_client, mocker):
     url = reverse('exported-entry')
 
     response = api_client.get(url)
     assert response.status_code == 400
+    assert 'message' in response.data
 
     response = api_client.get(url, {'org_id': 'or79Cob97KSh'})
     assert response.status_code == 400
+    assert 'message' in response.data
 
     response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'bills'})
     assert response.status_code == 400
+    assert 'message' in response.data
 
+    mock_bill_response = {
+        'Id': '123',
+        'DocNumber': 'BILL-123',
+        'TotalAmt': 100.00,
+        'Balance': 100.00,
+        'DueDate': '2024-01-01'
+    }
+
     mocker.patch(
         'qbosdk.apis.Bills.get_by_id',
-        return_value={'summa': 'hehe'}
+        return_value=mock_bill_response
     )
 
-    response = api_client.get(url, {'org_id': 'or79Cob97KSh', 'resource_type': 'bills', 'internal_id': '1'})
+    params = {
+        'org_id': 'or79Cob97KSh',
+        'resource_type': 'bills',
+        'internal_id': '123'
+    }
+    response = api_client.get(url, params)
     assert response.status_code == 200
+    assert response.data == mock_bill_response
+
+    # Verify the mock was called with correct parameters
+    mock = mocker.patch('qbosdk.apis.Bills.get_by_id')
+    mock.assert_called_once_with('123')

Let's verify the actual response structure:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find the actual bill response structure in the codebase
rg -A 10 "class Bills\(" .

Length of output: 28


Script:

#!/bin/bash
# Let's try different approaches to find bill-related code

# Search for bill-related files
fd -t f -i bill

# Search for bill-related imports or usage
rg -i "from.*bill.*import|import.*bill" .

# Search for bill-related classes or functions
ast-grep --pattern 'class $_ {
  $$$
  get_by_id($_) {
    $$$
  }
}'

Length of output: 1214


Script:

#!/bin/bash
# Let's look at the Bill model and related code

# Check Bill model definition
rg -A 10 "class Bill\(" .

# Look for bill-related test data or fixtures
rg -A 5 "mock.*bill" .

# Check the view being tested
rg -A 10 "class ExportedEntryView" .

Length of output: 9303


Script:

#!/bin/bash
# Let's check the test data structure used in other tests

# Look for bill response data in test files
rg -A 15 "bill_response.*=" tests/
rg -A 15 "'bill_response':" tests/

# Check the actual view implementation to understand expected response
rg -A 20 "def get\(self, request" apps/internal/views.py

Length of output: 3245

@@ -184,12 +184,12 @@ def test_prepare_e2e_test_view(mock_db, mocker, api_client, test_connection):
healthy_token.is_expired = False
healthy_token.save()

api_client.credentials(HTTP_X_E2E_Tests_Client_ID='gAAAAABi8oXHBll3lEUPGpMDXnZDhVgSl_LMOkIF0ilfmSCL3wFxZnoTIbpdzwPoOFzS0vFO4qaX51JtAqCG2RBHZaf1e98hug==')
api_client.credentials(HTTP_X_Internal_API_Client_ID='gAAAAABi8oXHBll3lEUPGpMDXnZDhVgSl_LMOkIF0ilfmSCL3wFxZnoTIbpdzwPoOFzS0vFO4qaX51JtAqCG2RBHZaf1e98hug==')
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: Avoid hardcoding API credentials in test files

While these appear to be test credentials, it's best practice to avoid hardcoding them in the source code. Consider:

  1. Moving them to environment variables
  2. Using test fixtures
  3. Generating mock credentials dynamically

This helps prevent accidental credential exposure and makes credential rotation easier.

Example refactor:

- api_client.credentials(HTTP_X_Internal_API_Client_ID='gAAAAABi8oXHBll3lEUPGpMDXnZDhVgSl_LMOkIF0ilfmSCL3wFxZnoTIbpdzwPoOFzS0vFO4qaX51JtAqCG2RBHZaf1e98hug==')
+ api_client.credentials(HTTP_X_Internal_API_Client_ID=settings.TEST_INTERNAL_API_CLIENT_ID)

- api_client.credentials(HTTP_X_Internal_API_Client_ID='gAAAAABi8oWVoonxF0K_g2TQnFdlpOJvGsBYa9rPtwfgM-puStki_qYbi0PdipWHqIBIMip94MDoaTP4MXOfERDeEGrbARCxPw==')
+ api_client.credentials(HTTP_X_Internal_API_Client_ID=settings.TEST_INTERNAL_API_CLIENT_ID_INVALID)

Also applies to: 192-192

🧰 Tools
🪛 Gitleaks (8.21.1)

187-187: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

@ashwin1111 ashwin1111 merged commit 140a47c into internal-apis Nov 26, 2024
6 checks passed
ashwin1111 added a commit that referenced this pull request Nov 27, 2024
* feat: Add support for internal APIs

* test: Internal APIs (#698)

* update path
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