Skip to content

tests: refresh core unit/integration/e2e tests; training: add minimal helpers; update changelog#86

Closed
uelkerd wants to merge 82 commits into
mainfrom
fix/testing-and-training-only
Closed

tests: refresh core unit/integration/e2e tests; training: add minimal helpers; update changelog#86
uelkerd wants to merge 82 commits into
mainfrom
fix/testing-and-training-only

Conversation

@uelkerd
Copy link
Copy Markdown
Owner

@uelkerd uelkerd commented Aug 16, 2025

Summary by Sourcery

Refresh core tests for consistent CI feedback, introduce minimal training helper scripts and Colab setup, and update the changelog accordingly

New Features:

  • Introduce minimal training utilities for fixed focal loss training including data preparation, dataloader creation, training loop, and evaluation in fixed_focal_training.py
  • Add Colab environment setup script for automated dependency installation, GPU configuration, and notebook generation

Enhancements:

  • Refresh and deduplicate core unit, integration, and end-to-end tests with formatting cleanup, fixture resets, and removal of duplicate definitions
  • Standardize whitespace and code style across all test files and ensure state isolation between tests

Documentation:

  • Update CHANGELOG.md with a new Tests & Training section documenting the test refresh and training helper additions

Summary by CodeRabbit

  • New Features

    • Training pipeline improvements: larger, more diverse dataset, robust tokenization, threshold sweep with best-threshold reporting, and saved results.
    • Colab setup now provides a ready-to-run notebook to install dependencies, clone the repo, verify GPU, and run CI.
  • Documentation

    • Added a “Tests & Training” subsection to the changelog.
  • Tests

    • Expanded and added many integration and unit tests (including large-payload and rate-limit edge cases); removed obsolete test scripts and applied broad formatting/cleanup.

@uelkerd uelkerd self-assigned this Aug 16, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Aug 16, 2025

Reviewer's Guide

This PR refreshes the core testing suite by consolidating and cleaning up unit, integration, and e2e tests (removing duplicates, standardizing fixtures, and formatting), introduces minimal helper routines in training scripts, tidies testing configuration and scripts formatting, and records these updates in the changelog.

Sequence diagram for the refreshed training workflow with helpers

sequenceDiagram
    participant Main
    participant TrainingHelper
    participant Model
    participant Logger
    Main->>TrainingHelper: create_proper_training_data()
    TrainingHelper->>Logger: info (Created training/val/test samples)
    Main->>Model: SimpleBERTClassifier()
    Main->>TrainingHelper: create_dataloader(train_data, model)
    Main->>TrainingHelper: train_model(model, train_dataloader, val_dataloader, device, epochs)
    TrainingHelper->>Logger: info (Training started)
    TrainingHelper->>Model: to(device)
    TrainingHelper->>Model: optimizer.step()
    TrainingHelper->>Logger: info (Epoch results)
    Main->>Model: load_state_dict(best_model)
    Main->>TrainingHelper: evaluate_model(model, test_dataloader, device)
    TrainingHelper->>Logger: info (Evaluation results)
    Main->>Logger: info (Training completed)
Loading

Class diagram for new minimal training helpers in training scripts

classDiagram
    class SimpleBERTClassifier
    class FocalLoss
    class TrainingHelper {
        +create_proper_training_data()
        +create_dataloader(data, model, batch_size)
        +train_model(model, train_data, val_data, device, epochs)
        +evaluate_model(model, test_data, device)
    }
    SimpleBERTClassifier <|-- TrainingHelper
    FocalLoss <|-- TrainingHelper
Loading

Class diagram for updated testing config helper

classDiagram
    class TestingConfig {
        +_get_base_url()
    }
    TestingConfig : env_url = (API_BASE_URL or CLOUD_RUN_API_URL or MODEL_API_BASE_URL)
Loading

File-Level Changes

Change Details Files
Standardize and consolidate core tests
  • Removed duplicate test definitions and deleted empty test scripts
  • Unified fixture resets and environment setup across tests
  • Cleaned up trailing whitespace and blank lines for consistency
tests/integration/test_priority1_features.py
tests/unit/*
tests/e2e/test_complete_workflows.py
tests/conftest.py
scripts/testing/test_api_startup.py
scripts/testing/test_e2e_simple.py
Add minimal training helpers
  • Added functions to generate and split proper training data
  • Provided a simple dataloader builder for BERT model
  • Applied formatting cleanup and removed trailing whitespace
scripts/training/fixed_focal_training.py
Clean up testing scripts and configuration
  • Refined multiline environment-variable logic in test config
  • Standardized formatting in cloud-run API test scripts
  • Removed redundant code blocks in testing utilities
scripts/testing/config.py
scripts/testing/test_cloud_run_api_endpoints.py
scripts/testing/test_model_status.py
scripts/testing/test_vertex_setup.py
Update changelog for tests & training
  • Documented the refresh of core tests and addition of training helpers under Unreleased
  • Clarified scope and intent of test and training updates
CHANGELOG.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@uelkerd has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 12 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 2a1da4a and 0e332e2.

⛔ Files ignored due to path filters (23)
  • src/models/__pycache__/__init__.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/__init__.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/api_demo.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/bert_classifier.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/dataset_loader.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/hf_loader.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/labels.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/emotion_detection/__pycache__/training_pipeline.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/secure_loader/__pycache__/__init__.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/secure_loader/__pycache__/integrity_checker.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/secure_loader/__pycache__/model_validator.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/secure_loader/__pycache__/sandbox_executor.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/secure_loader/__pycache__/secure_model_loader.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/summarization/__pycache__/__init__.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/summarization/__pycache__/api_demo.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/summarization/__pycache__/dataset_loader.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/summarization/__pycache__/t5_summarizer.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/summarization/__pycache__/training_pipeline.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/voice_processing/__pycache__/__init__.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/voice_processing/__pycache__/api_demo.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/voice_processing/__pycache__/audio_preprocessor.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/voice_processing/__pycache__/transcription_api.cpython-313.pyc is excluded by !**/*.pyc
  • src/models/voice_processing/__pycache__/whisper_transcriber.cpython-313.pyc is excluded by !**/*.pyc
📒 Files selected for processing (58)
  • CHANGELOG.md (1 hunks)
  • Dockerfile (0 hunks)
  • constraints.txt (2 hunks)
  • deployment/DOCKERFILE_SECURITY_GUIDE.md (1 hunks)
  • deployment/README.md (1 hunks)
  • deployment/cloud-run/Dockerfile (0 hunks)
  • deployment/cloud-run/Dockerfile.consolidated (1 hunks)
  • deployment/cloud-run/Dockerfile.emotion_arch_fixed (1 hunks)
  • deployment/cloud-run/Dockerfile.minimal (0 hunks)
  • deployment/cloud-run/Dockerfile.secure (0 hunks)
  • deployment/cloud-run/Dockerfile.unified (0 hunks)
  • deployment/cloud-run/README-consolidated-dockerfile.md (1 hunks)
  • deployment/cloud-run/debug_api_import.py (1 hunks)
  • deployment/cloud-run/requirements.txt (1 hunks)
  • deployment/cloud-run/requirements_minimal.txt (1 hunks)
  • deployment/cloud-run/requirements_onnx.txt (1 hunks)
  • deployment/cloud-run/requirements_production.txt (1 hunks)
  • deployment/cloud-run/requirements_secure.txt (1 hunks)
  • deployment/deploy.sh (1 hunks)
  • deployment/docker/dockerfile (1 hunks)
  • deployment/gcp/Dockerfile (1 hunks)
  • deployment/gcp/requirements.txt (1 hunks)
  • deployment/local/requirements.txt (1 hunks)
  • deployment/local/start.sh (1 hunks)
  • deployment/requirements.txt (1 hunks)
  • docker/vertex_ai_training.Dockerfile (1 hunks)
  • docs/summaries/code-review-fixes-summary.md (1 hunks)
  • docs/summaries/integrated-security-optimization-summary.md (1 hunks)
  • docs/summaries/simple-tokenizer-fix-summary.md (1 hunks)
  • docs/summaries/simple-tokenizer-fix-summary.md.backup (1 hunks)
  • environment.yml (1 hunks)
  • pyproject.toml (2 hunks)
  • requirements-api.txt (1 hunks)
  • requirements-audio.txt (1 hunks)
  • requirements-dev.txt (1 hunks)
  • requirements-ml.txt (1 hunks)
  • scripts/database/check_pgvector.py (1 hunks)
  • scripts/deployment/create_model_deployment_package.py (1 hunks)
  • scripts/deployment/integrate_security_fixes.py (1 hunks)
  • scripts/deployment/security_deployment_fix.py (1 hunks)
  • scripts/fix_linting_issues.py (1 hunks)
  • scripts/requirements_vertex_ai.txt (1 hunks)
  • scripts/testing/config.py (4 hunks)
  • scripts/training/comprehensive_domain_adaptation_training.py (1 hunks)
  • scripts/training/robust_domain_adaptation_training.py (1 hunks)
  • scripts/training/setup_colab_environment.py (6 hunks)
  • src/api_rate_limiter.py (10 hunks)
  • src/input_sanitizer.py (7 hunks)
  • src/models/emotion_detection/dataset_loader.py (8 hunks)
  • src/models/secure_loader/__init__.py (1 hunks)
  • src/models/secure_loader/integrity_checker.py (8 hunks)
  • src/models/secure_loader/model_validator.py (11 hunks)
  • src/models/secure_loader/sandbox_executor.py (7 hunks)
  • src/models/secure_loader/secure_model_loader.py (13 hunks)
  • src/monitoring/dashboard.py (6 hunks)
  • src/security/jwt_manager.py (6 hunks)
  • src/security_headers.py (9 hunks)
  • src/unified_ai_api.py (62 hunks)

Walkthrough

Adds a “Tests & Training” note to CHANGELOG. Removes two legacy test scripts. Introduces new/expanded tests (integration, rate-limiter, security, large-payload API tests). Updates fixed_focal_training with expanded data, tokenization padding/truncation, threshold sweep and persisted results. Enhances Colab setup to install more deps, handle errors broadly, and generate a runnable multi-cell notebook.

Changes

Cohort / File(s) Summary of changes
Documentation
CHANGELOG.md
Added “Tests & Training” subsection under Unreleased; documentation-only.
Training pipeline & Colab
scripts/training/fixed_focal_training.py, scripts/training/setup_colab_environment.py
Expanded training dataset, tokenization uses padding=True/truncation=True, evaluation sweeps thresholds (0.1–0.9) to pick best_threshold saved in results; Colab setup now installs many more dependencies, adds broader exception handling, logs stdout on CI run success, and generates a full multi-cell ipynb.
Testing helpers & infra
scripts/testing/config.py, scripts/testing/test_cloud_run_api_endpoints.py, scripts/testing/test_model_status.py, scripts/testing/test_vertex_setup.py
config.py and others: formatting/whitespace tweaks. test_cloud_run_api_endpoints.py: added test_extremely_large_payloads, improved rate-limiting handling (record "rate_limited"), and extended test_model_loading result payload with successful_tests and models_loaded.
Removed legacy test scripts
scripts/testing/test_api_startup.py, scripts/testing/test_e2e_simple.py
Deleted files.
Integration tests (expanded)
tests/integration/test_priority1_features.py
Significant expansion: new batch transcription boundary/permission/partial/all-fail tests, deterministic mock side effects, WebSocket auth checks, expanded JWT manager tests, monitoring endpoint permission/metrics tests; increases coverage and control-flow in tests.
End-to-end tests
tests/e2e/test_complete_workflows.py
Minor whitespace adjustment only.
Unit tests — new coverage + formatting
tests/unit/test_api_rate_limiter.py, tests/unit/test_api_security.py, tests/unit/test_validation_enhanced.py, tests/unit/*.py (others)
Added edge-case rate-limiter tests (zero/negative rates), added rate-limiter per-User-Agent and abuse-reset tests, added validation test checking missing-value percentages; many other unit tests had only whitespace/formatting changes.
Test configuration
tests/conftest.py
Whitespace-only tweaks in api_client fixture; behavior unchanged.

Sequence Diagram(s)

sequenceDiagram
  participant Trainer
  participant DataLoader
  participant Tokenizer
  participant Model
  participant Evaluator
  participant ResultsStore

  Trainer->>DataLoader: build expanded dataset
  DataLoader->>Tokenizer: tokenize(padding=True, truncation=True, max_length=...)
  Tokenizer-->>DataLoader: tokenized batches
  DataLoader->>Model: train/eval batches
  Model-->>Evaluator: logits/probabilities
  loop thresholds 0.1..0.9
    Evaluator->>Evaluator: compute F1/Precision/Recall
  end
  Evaluator-->>Trainer: best_threshold + metrics
  Trainer->>ResultsStore: save results JSON (incl. best_threshold)
Loading
sequenceDiagram
  participant User
  participant SetupScript
  participant Pip
  participant GitHub
  participant GPU
  participant CI

  User->>SetupScript: run setup_colab_environment.py
  SetupScript->>Pip: install expanded dependencies
  SetupScript->>GitHub: git clone repo
  SetupScript->>GPU: check CUDA availability
  alt success
    SetupScript->>User: generate runnable Colab notebook
    User->>CI: run CI cell (logs stdout on success)
  else failure
    SetupScript-->>User: log error and return False
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

I hop through tokens, padded tight,
I sweep thresholds by moonlight.
A Colab warren, deps in stack,
Old tests thump out — no turning back.
Logs saved, metrics neat and bright. 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/testing-and-training-only

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@deepsource-io
Copy link
Copy Markdown
Contributor

deepsource-io Bot commented Aug 16, 2025

Here's the code health analysis summary for commits 560f195..0e332e2. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Test coverage LogoTest coverage⚠️ Artifact not reportedTimed out: Artifact was never reportedView Check ↗
DeepSource Python LogoPython❌ Failure
❗ 70 occurences introduced
🎯 150 occurences resolved
View Check ↗
DeepSource Terraform LogoTerraform✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets✅ SuccessView Check ↗
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @uelkerd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request primarily focuses on refreshing and standardizing the existing unit, integration, and end-to-end test suites across the project. It also introduces minor helper utilities for the training scripts. The changes aim to improve code consistency, ensure core functionalities are robustly tested, and provide foundational support for future training efforts, all while keeping the scope minimal to validate core flows and CI signal.

Highlights

  • Test Suite Refresh: Extensive updates across various test files (scripts/testing/, tests/) to standardize formatting, remove redundant empty lines, and apply minor logical adjustments to existing test cases. This includes comprehensive API tests, model status checks, and security feature validations.
  • Training Helper Enhancements: Minor formatting and structural cleanups in training-related scripts (scripts/training/fixed_focal_training.py, scripts/training/setup_colab_environment.py) to improve readability and consistency.
  • Changelog Update: The CHANGELOG.md file has been updated to reflect the refresh of core tests and the addition of minimal training helpers, emphasizing a small scope for validating core flows and CI signal.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider splitting the whitespace-only formatting changes into a separate commit or PR to reduce review noise and clearly isolate functional updates.
  • The TestWebSocketAuthentication tests currently use pass and have no assertions—either implement them or mark them as skipped to avoid silent test passes.
  • Add lightweight validation tests for the new training helpers (e.g. verifying data lengths and label distributions) to ensure they produce the expected structures.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider splitting the whitespace-only formatting changes into a separate commit or PR to reduce review noise and clearly isolate functional updates.
- The TestWebSocketAuthentication tests currently use pass and have no assertions—either implement them or mark them as skipped to avoid silent test passes.
- Add lightweight validation tests for the new training helpers (e.g. verifying data lengths and label distributions) to ensure they produce the expected structures.

## Individual Comments

### Comment 1
<location> `tests/integration/test_priority1_features.py:487` </location>
<code_context>
         pass
-    
+
     def test_websocket_with_valid_token(self):
         """Test WebSocket connection with valid token."""
         # This would require a WebSocket client test
</code_context>

<issue_to_address>
WebSocket valid token test is a placeholder and does not verify functionality.

Please implement a test that connects via WebSocket and verifies authentication with a valid token, or remove the placeholder if this cannot be tested.
</issue_to_address>

### Comment 2
<location> `tests/integration/test_priority1_features.py:227` </location>
<code_context>
-    
+
     @patch('src.unified_ai_api.voice_transcriber')
     def test_batch_transcription(self, mock_transcriber):
         """Test batch transcription endpoint."""
</code_context>

<issue_to_address>
No test for empty batch input in batch transcription endpoint.

Add a test to ensure the endpoint returns a meaningful error when called with an empty batch.
</issue_to_address>

### Comment 3
<location> `tests/integration/test_priority1_features.py:295` </location>
<code_context>
-    
+
     @patch('src.unified_ai_api.voice_transcriber')
     def test_batch_transcription_partial_failures(self, mock_transcriber):
         """Test batch transcription with partial failures."""
</code_context>

<issue_to_address>
No test for all failures in batch transcription.

Please add a test that covers the scenario where every file in the batch fails, verifying the API's response and error handling in this case.
</issue_to_address>

### Comment 4
<location> `tests/integration/test_priority1_features.py:496` </location>
<code_context>
     """Test API endpoint validation and error handling."""
-    
+
     def test_voice_transcription_file_size_validation(self):
         """Test file size validation for voice transcription."""
         # Login to get token
</code_context>

<issue_to_address>
No test for file size exactly at the limit for voice transcription.

Add a test case with an audio file size equal to the limit to confirm correct boundary handling.

Suggested implementation:

```python
    def test_voice_transcription_file_size_validation(self):
        """Test file size validation for voice transcription."""
        # Login to get token

    def test_voice_transcription_file_size_at_limit(self):
        """Test file size exactly at the limit for voice transcription."""
        # Login to get token
        client = self.client if hasattr(self, "client") else None
        if client is None:
            from fastapi.testclient import TestClient
            from main import app
            client = TestClient(app)

        login_data = {
            "username": "testuser",
            "password": "testpassword"
        }
        login_response = client.post("/auth/login", json=login_data)
        access_token = login_response.json()["access_token"]

        # Assume the file size limit is defined somewhere, e.g. 5MB
        FILE_SIZE_LIMIT = 5 * 1024 * 1024  # 5MB in bytes

        # Create a dummy audio file exactly at the limit
        audio_content = b"\0" * FILE_SIZE_LIMIT
        files = {
            "audio": ("test_limit.wav", audio_content, "audio/wav")
        }
        headers = {"Authorization": f"Bearer {access_token}"}
        response = client.post("/voice/transcribe", files=files, headers=headers)

        # Expect success (200 or 202) or whatever is correct for your API
        assert response.status_code in (200, 202), f"Unexpected status code: {response.status_code}"

```

- Adjust `FILE_SIZE_LIMIT` to match the actual limit enforced by your API if it differs.
- If your test setup uses fixtures for `client`, `login_data`, or other values, adapt the test to use those instead.
- If the endpoint or expected status code is different, update accordingly.
</issue_to_address>

### Comment 5
<location> `tests/unit/test_api_security.py:64` </location>
<code_context>
         self.assertEqual(stats['concurrent_requests'], 0)
-    
+
     def test_rate_limit_exceeded(self):
         """Test rate limit exceeded scenario."""
         client_ip = "192.168.1.2"
</code_context>

<issue_to_address>
No test for rate limiting with multiple user agents from the same IP.

Please add a test to ensure rate limiting distinguishes between user agents sharing the same IP, verifying correct behavior per client key.
</issue_to_address>

### Comment 6
<location> `tests/unit/test_api_security.py:116` </location>
<code_context>
         self.assertEqual(reason, "IP not allowed")
-    
+
     def test_abuse_detection(self):
         """Test abuse detection functionality."""
         client_ip = "192.168.1.4"
</code_context>

<issue_to_address>
No test for abuse detection reset after cooldown period.

Add a test to confirm that, after the cooldown period, abuse detection resets and requests from the client are permitted.
</issue_to_address>

### Comment 7
<location> `scripts/testing/test_cloud_run_api_endpoints.py:156` </location>
<code_context>
     def test_invalid_inputs(self) -> Dict[str, Any]:
         """Test invalid input handling"""
         logger.info("Testing invalid inputs...")
</code_context>

<issue_to_address>
No test for extremely large payloads in invalid input tests.

Add a test for the emotion detection endpoint using an extremely large payload to ensure the API remains stable under such conditions.
</issue_to_address>

### Comment 8
<location> `tests/unit/test_validation_enhanced.py:26` </location>
<code_context>
     def test_check_missing_values_basic(self):
         """Test basic missing values check."""
         missing_stats = self.validator.check_missing_values(self.test_df)
</code_context>

<issue_to_address>
No test for missing values in all columns.

Add a test where every column in the DataFrame has missing values to ensure the validator handles this case correctly.
</issue_to_address>

### Comment 9
<location> `tests/unit/test_api_rate_limiter.py:59` </location>
<code_context>
     def test_allow_request_rate_limit_exceeded(self):
         """Test that allow_request returns False when rate limit exceeded."""
         config = RateLimitConfig(
-            requests_per_minute=1, 
+            requests_per_minute=1,
             burst_size=1,
             enable_user_agent_analysis=False,  # Disable abuse detection for testing
             enable_request_pattern_analysis=False
</code_context>

<issue_to_address>
Consider adding tests for edge cases such as zero or negative rate limits.

Please add tests for cases where `requests_per_minute` or `burst_size` are zero or negative to verify correct handling.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    def test_allow_request_rate_limit_exceeded(self):
        """Test that allow_request returns False when rate limit exceeded."""
        config = RateLimitConfig(
            requests_per_minute=1,
            burst_size=1,
            enable_user_agent_analysis=False,  # Disable abuse detection for testing
            enable_request_pattern_analysis=False
=======
    def test_allow_request_rate_limit_exceeded(self):
        """Test that allow_request returns False when rate limit exceeded."""
        config = RateLimitConfig(
            requests_per_minute=1,
            burst_size=1,
            enable_user_agent_analysis=False,  # Disable abuse detection for testing
            enable_request_pattern_analysis=False

    def test_allow_request_zero_rate_limits(self):
        """Test that allow_request returns False when rate limits are zero."""
        config = RateLimitConfig(
            requests_per_minute=0,
            burst_size=0,
            enable_user_agent_analysis=False,
            enable_request_pattern_analysis=False
        )
        limiter = ApiRateLimiter(config)
        request = self._make_request()
        allowed = limiter.allow_request(request)
        assert allowed is False

    def test_allow_request_negative_rate_limits(self):
        """Test that allow_request returns False when rate limits are negative."""
        config = RateLimitConfig(
            requests_per_minute=-1,
            burst_size=-5,
            enable_user_agent_analysis=False,
            enable_request_pattern_analysis=False
        )
        limiter = ApiRateLimiter(config)
        request = self._make_request()
        allowed = limiter.allow_request(request)
        assert allowed is False
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

pass

def test_websocket_with_valid_token(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (testing): WebSocket valid token test is a placeholder and does not verify functionality.

Please implement a test that connects via WebSocket and verifies authentication with a valid token, or remove the placeholder if this cannot be tested.


@patch('src.unified_ai_api.voice_transcriber')
def test_batch_transcription(self, mock_transcriber):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): No test for empty batch input in batch transcription endpoint.

Add a test to ensure the endpoint returns a meaningful error when called with an empty batch.


@patch('src.unified_ai_api.voice_transcriber')
def test_batch_transcription_partial_failures(self, mock_transcriber):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): No test for all failures in batch transcription.

Please add a test that covers the scenario where every file in the batch fails, verifying the API's response and error handling in this case.

"""Test API endpoint validation and error handling."""

def test_voice_transcription_file_size_validation(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): No test for file size exactly at the limit for voice transcription.

Add a test case with an audio file size equal to the limit to confirm correct boundary handling.

Suggested implementation:

    def test_voice_transcription_file_size_validation(self):
        """Test file size validation for voice transcription."""
        # Login to get token

    def test_voice_transcription_file_size_at_limit(self):
        """Test file size exactly at the limit for voice transcription."""
        # Login to get token
        client = self.client if hasattr(self, "client") else None
        if client is None:
            from fastapi.testclient import TestClient
            from main import app
            client = TestClient(app)

        login_data = {
            "username": "testuser",
            "password": "testpassword"
        }
        login_response = client.post("/auth/login", json=login_data)
        access_token = login_response.json()["access_token"]

        # Assume the file size limit is defined somewhere, e.g. 5MB
        FILE_SIZE_LIMIT = 5 * 1024 * 1024  # 5MB in bytes

        # Create a dummy audio file exactly at the limit
        audio_content = b"\0" * FILE_SIZE_LIMIT
        files = {
            "audio": ("test_limit.wav", audio_content, "audio/wav")
        }
        headers = {"Authorization": f"Bearer {access_token}"}
        response = client.post("/voice/transcribe", files=files, headers=headers)

        # Expect success (200 or 202) or whatever is correct for your API
        assert response.status_code in (200, 202), f"Unexpected status code: {response.status_code}"
  • Adjust FILE_SIZE_LIMIT to match the actual limit enforced by your API if it differs.
  • If your test setup uses fixtures for client, login_data, or other values, adapt the test to use those instead.
  • If the endpoint or expected status code is different, update accordingly.

self.assertEqual(stats['concurrent_requests'], 0)

def test_rate_limit_exceeded(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): No test for rate limiting with multiple user agents from the same IP.

Please add a test to ensure rate limiting distinguishes between user agents sharing the same IP, verifying correct behavior per client key.

Comment on lines 82 to 90
for i in range(100):
# Mock different request contexts
with self.app.test_request_context('/'):
from flask import g, request
request.remote_addr = f'192.168.1.{i}'

# Generate request ID
self.middleware._before_request()
request_ids.add(g.request_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread scripts/testing/config.py
Comment on lines 29 to 33
# Check multiple environment variables for flexibility
env_url = (os.environ.get("API_BASE_URL") or
os.environ.get("CLOUD_RUN_API_URL") or
env_url = (os.environ.get("API_BASE_URL") or
os.environ.get("CLOUD_RUN_API_URL") or
os.environ.get("MODEL_API_BASE_URL"))
if env_url:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)

Suggested change
# Check multiple environment variables for flexibility
env_url = (os.environ.get("API_BASE_URL") or
os.environ.get("CLOUD_RUN_API_URL") or
env_url = (os.environ.get("API_BASE_URL") or
os.environ.get("CLOUD_RUN_API_URL") or
os.environ.get("MODEL_API_BASE_URL"))
if env_url:
if env_url := (
os.environ.get("API_BASE_URL")
or os.environ.get("CLOUD_RUN_API_URL")
or os.environ.get("MODEL_API_BASE_URL")
):

@@ -26,10 +26,10 @@ def test_vertex_setup():
config_dir = Path("configs/vertex_ai")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:


if torch.cuda.is_available():
logger.info(f"🎮 GPU detected: {torch.cuda.get_device_name(0)}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

Comment on lines 293 to 301
anomalies = self.sanitizer.detect_anomalies(large_data)
self.assertGreater(len(anomalies), 0)
self.assertIn("Large string", anomalies[0])

# Potential SQL injection anomaly
sql_data = {"text": "SELECT * FROM users"}
anomalies = self.sanitizer.detect_anomalies(sql_data)
self.assertGreater(len(anomalies), 0)
self.assertIn("SQL injection", anomalies[0])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Extract duplicate code into method (extract-duplicate-method)

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request primarily focuses on refreshing tests and standardizing code formatting, which is a good step towards improving code consistency. The changes are mostly about removing extra whitespace. I've identified a critical syntax error in one of the test files due to incorrect indentation, which must be fixed. Additionally, I've suggested a refactoring for a repetitive code block in a training script to enhance maintainability. Once the critical issue is resolved, this PR will be in good shape.

}
is_valid, info = self.validator.validate_version_compatibility(test_config)
# Note: This may fail with current PyTorch version, but that's expected behavior
# Note: This may fail with current PyTorch version, but that's expected behavior
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The indentation of this comment is incorrect, which will cause a SyntaxError: unexpected indent. Please fix the indentation.

Suggested change
# Note: This may fail with current PyTorch version, but that's expected behavior
# Note: This may fail with current PyTorch version, but that's expected behavior

Copy link
Copy Markdown

@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: 1

🔭 Outside diff range comments (3)
scripts/testing/test_cloud_run_api_endpoints.py (1)

22-26: Ensure base_url override is applied when instantiating the API client

The tester computes and stores self.base_url, but calling create_api_client() always builds a new TestConfig() (and thus its own base_url), so your requests may go to a different endpoint than you print.

• File: scripts/testing/test_cloud_run_api_endpoints.py
– Change how you build the client to use your overridden URL.

Proposed diff:

 from test_config import create_test_config, create_api_client
+from test_config import TestConfig, APIClient

 class CloudRunAPITester:
     def __init__(self, base_url: str = None):
-        config = create_test_config()
-        self.base_url = base_url or config.base_url
-        self.client = create_api_client()
+        # Use the override (if provided) when constructing the TestConfig
+        config = TestConfig(base_url=base_url)
+        self.base_url = config.base_url
+        # Instantiate APIClient directly so it picks up our config.base_url
+        self.client = APIClient(config)

If you prefer keeping the create_api_client factory, add a base_url parameter to its signature and pass it through to TestConfig there instead.

scripts/testing/test_model_status.py (1)

69-73: Propagate Base URL override to the API client

Your test mutates config.base_url, but create_api_client() builds its own config internally and ignores this change. To ensure the override takes effect:

• Update scripts/testing/test_config.py:
– Change create_api_client() to accept an optional base_url parameter.
– Apply base_url.rstrip('/') inside the factory before constructing the client.

• Update scripts/testing/test_model_status.py:
– Pass the overridden config.base_url into create_api_client().

# scripts/testing/test_config.py
-def create_api_client() -> APIClient:
+def create_api_client(base_url: Optional[str] = None) -> APIClient:
     """Factory function to create API client"""
-    config = create_test_config()
+    config = create_test_config()
+    if base_url:
+        config.base_url = base_url.rstrip('/')
     return APIClient(config)
# scripts/testing/test_model_status.py
-    client = create_api_client()
+    client = create_api_client(base_url=config.base_url)

If you also need base-URL overrides in non-test code, mirror this change in scripts/testing/config.py.

scripts/testing/config.py (1)

138-149: Fix NameError risk from requests.Response type hints

APIClient.get/post return annotations reference requests.Response, but requests isn’t imported at module scope. Without postponed annotations, this raises at import time in Python <3.11. Two straightforward fixes:

  • Add a module-level import requests (and remove the local one), or
  • Use postponed annotations (from __future__ import annotations) and/or quote annotations.

Minimal, explicit import approach below.

@@
 import os
 import argparse
 import time
 from typing import Optional
+import requests
@@
 def create_api_client():
     """Create a reusable API client with common functionality."""
-    import requests
     from requests.adapters import HTTPAdapter
     from urllib3.util.retry import Retry

Alternatively (outside the selected lines), add at the top after the docstring:
"""
from future import annotations
"""
and change return annotations to strings:
def get(...) -> "requests.Response": ...
def post(...) -> "requests.Response": ...

Also applies to: 7-11, 111-111

🧹 Nitpick comments (41)
tests/unit/test_csp_config.py (3)

44-51: Avoid globally patching os.path.join; patch module-local join instead

Patching 'os.path.join' globally can affect code outside the intended path resolution (including imports deep in libraries), making the test brittle. Patch the function inside the module under test to scope the effect.

Suggested change:

-            with patch('os.path.join', return_value=config_path):
+            with patch('security_headers.os.path.join', return_value=config_path):
                 middleware = SecurityHeadersMiddleware(self.app, self.config)

80-86: Scope the path patch to the target module for invalid YAML test

Same rationale as above—limit the monkey patch to the module under test.

-            with patch('os.path.join', return_value=config_path):
+            with patch('security_headers.os.path.join', return_value=config_path):
                 middleware = SecurityHeadersMiddleware(self.app, self.config)

107-114: Limit os.path.join patch in “missing CSP key” test to avoid test flakiness

Patch within the security_headers module namespace rather than globally.

-            with patch('os.path.join', return_value=config_path):
+            with patch('security_headers.os.path.join', return_value=config_path):
                 middleware = SecurityHeadersMiddleware(self.app, self.config)
tests/unit/test_sandbox_executor.py (2)

122-129: Rename unused loop variable to underscore

The loop control variable isn’t used. Rename to underscore to silence linters and clarify intent.

-        threads = []
-        for i in range(5):
+        threads = []
+        for _ in range(5):
             thread = threading.Thread(target=worker_function)
             threads.append(thread)
             thread.start()

157-167: Timeout test doesn’t actually test timeout

self.executor is configured with max_wall_time=15, but slow_function sleeps for 2 seconds. The test never triggers a timeout, contradicting the docstring/comment. Consider creating a short-timeout executor inside this test.

     def test_timeout_handling(self):
         """Test timeout handling."""
-        def slow_function():
+        # Use a short wall time to force timeout
+        local_executor = SandboxExecutor(max_wall_time=1, max_cpu_time=1)
+
+        def slow_function():
             time.sleep(2)  # Sleep longer than max_wall_time
             return "Should timeout"
 
-        result, meta = self.executor.execute_safely(slow_function)
+        result, meta = local_executor.execute_safely(slow_function)
 
-        # Should either timeout or complete within limits
-        self.assertIsNotNone(result or meta.get('error'))
+        # Should timeout and return an error meta
+        self.assertIn('error', meta)

If SandboxExecutor’s timeout is enforced via signals, this test may only pass on the main thread/Unix. If you need me to gate this test by platform, I can add a skip decorator accordingly.

tests/unit/test_secure_model_loader.py (5)

486-493: Prefer pathlib for existence check and remove redundant open() mode

Small cleanup to modernize and satisfy linters:

  • Use Path.exists() instead of os.path.exists
  • open() defaults to read mode; the explicit 'r' is unnecessary
-        audit_log_path = os.path.join(self.temp_dir, "audit.log")
-        self.assertTrue(os.path.exists(audit_log_path))
+        from pathlib import Path
+        audit_log_path = Path(self.temp_dir) / "audit.log"
+        self.assertTrue(audit_log_path.exists())
 
         # Check audit log contains entries
-        with open(audit_log_path, 'r') as f:
+        with open(audit_log_path) as f:
             log_content = f.read()
             self.assertIn('AUDIT:', log_content)

263-269: Optional: adopt pathlib for path assembly

Not required, but replacing os.path.join with Path improves readability and lint friendliness.

-        self.model_file = os.path.join(self.temp_dir, "test_model.pt")
+        from pathlib import Path
+        self.model_file = str(Path(self.temp_dir) / "test_model.pt")

Note: Cast to str if downstream APIs expect a string path.


378-383: Optional: pathlib for model_file in integration tests

Same as above; minor readability win.

-        self.model_file = os.path.join(self.temp_dir, "test_model.pt")
+        from pathlib import Path
+        self.model_file = str(Path(self.temp_dir) / "test_model.pt")

448-456: Optional: pathlib for tampered model path

-        tampered_model_file = os.path.join(self.temp_dir, "tampered_model.pt")
+        from pathlib import Path
+        tampered_model_file = str(Path(self.temp_dir) / "tampered_model.pt")

224-231: Minor: comment indentation

The inline comment about potential version mismatch appears over-indented, which can read as a continuation of the previous block. It’s harmless, but aligning it to the method’s indentation improves readability.

-        is_valid, info = self.validator.validate_version_compatibility(test_config)
-            # Note: This may fail with current PyTorch version, but that's expected behavior
+        is_valid, info = self.validator.validate_version_compatibility(test_config)
+        # Note: This may fail with current PyTorch version, but that's expected behavior
tests/unit/test_hash_security.py (1)

45-49: Prefer setting remote_addr via environ_overrides instead of assigning request.remote_addr

Assigning request.remote_addr directly can be brittle as it's a derived property in Flask. Use environ_overrides in test_request_context for reliability.

Apply this diff within the context block:

-        with self.app.test_request_context('/'):
-            # Mock request.remote_addr
-            request.remote_addr = '192.168.1.1'
+        with self.app.test_request_context('/', environ_overrides={'REMOTE_ADDR': '192.168.1.1'}):
+            # request.remote_addr will reflect environ override
tests/unit/test_api_security.py (4)

86-89: Rename unused loop variable to _ (ruff B007)

The loop variable isn’t used. Rename to _ to satisfy linters and clarify intent.

-        for i in range(3):
+        for _ in range(3):
             allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
             self.assertTrue(allowed)

103-105: Rename unused loop variable to _ (ruff B007)

Same rationale as above.

-        for i in range(3):
+        for _ in range(3):
             self.rate_limiter.release_request(client_ip, user_agent)

121-124: Rename unused loop variable to _ (ruff B007)

The index isn’t used; this is a simulated rapid-fire append.

-        for i in range(11):  # More than 10 requests in 1 second
+        for _ in range(11):  # More than 10 requests in 1 second
             self.rate_limiter.request_history[self.rate_limiter._get_client_key(client_ip, user_agent)].append(time.time())

135-140: Rename unused loop variable to _ (ruff B007)

Minor lint/readability fix.

-        for i in range(5):
+        for _ in range(5):
             allowed, _, _ = self.rate_limiter.allow_request(client_ip, user_agent)
             self.assertTrue(allowed)
             self.rate_limiter.release_request(client_ip, user_agent)
tests/conftest.py (1)

102-112: Yield the TestClient from the fixture to ensure proper cleanup

Using a context manager avoids lingering connections/resources and keeps the fixture idiomatic.

 @pytest.fixture
 def api_client():
     """Provide FastAPI test client."""
-    client = TestClient(app)
-
-    # Reset rate limiter state before each test
-    if hasattr(app.state, 'rate_limiter'):
-        app.state.rate_limiter.reset_state()
-
-    return client
+    with TestClient(app) as client:
+        # Reset rate limiter state before each test
+        if hasattr(app.state, 'rate_limiter'):
+            app.state.rate_limiter.reset_state()
+        yield client
CHANGELOG.md (1)

7-10: Nice, concise Unreleased notes

Clear scope and intent for the tests/training refresh. Consider linking the PR for traceability, e.g., “(see PR #86)”.

tests/integration/test_priority1_features.py (5)

164-174: Test is a no-op: it patches but never exercises the endpoint

The test sets a return value but doesn’t call /transcribe/voice or assert any behavior. Add a minimal call and assertions.

 @patch('src.unified_ai_api.voice_transcriber')
 def test_voice_transcription_endpoint(self, mock_transcriber):
     """Test enhanced voice transcription endpoint."""
     # Mock transcription result
     mock_transcriber.return_value.transcribe.return_value = {
         "text": "This is a test transcription",
         "language": "en",
         "confidence": 0.95,
         "duration": 10.5
     }
+    # Login to get token
+    login_data = {"username": "testuser@example.com", "password": "testpassword123"}
+    login_response = client.post("/auth/login", json=login_data)
+    access_token = login_response.json()["access_token"]
+    headers = {"Authorization": f"Bearer {access_token}"}
+    # Create a small temp wav payload
+    with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
+        tmp.write(b"fake audio data")
+        path = tmp.name
+    try:
+        with open(path, "rb") as fh:
+            files = {"audio_file": ("test.wav", fh, "audio/wav")}
+            data = {"language": "en", "model_size": "base"}
+            resp = client.post("/transcribe/voice", files=files, data=data, headers=headers)
+        assert resp.status_code == 200
+        body = resp.json()
+        assert "text" in body
+        assert "language" in body
+        assert "confidence" in body
+        assert "duration" in body
+    finally:
+        Path(path).unlink(missing_ok=True)

411-420: Summarization test also doesn’t hit the endpoint

Same as above: patch is set, but no /summarize/text call or assertions are made.

 @patch('src.unified_ai_api.text_summarizer')
 def test_text_summarization_endpoint(self, mock_summarizer):
     """Test enhanced text summarization endpoint."""
     # Mock summarization result
     mock_summarizer.return_value.summarize.return_value = {
         "summary": "This is a test summary of the input text.",
         "key_emotions": ["neutral"],
         "compression_ratio": 0.75
     }
+    # Login to get token
+    login_data = {"username": "testuser@example.com", "password": "testpassword123"}
+    login_response = client.post("/auth/login", json=login_data)
+    access_token = login_response.json()["access_token"]
+    headers = {"Authorization": f"Bearer {access_token}"}
+    # Exercise the endpoint
+    data = {"text": "A sufficiently long input to summarize.", "model": "t5-small"}
+    resp = client.post("/summarize/text", data=data, headers=headers)
+    assert resp.status_code == 200
+    body = resp.json()
+    assert "summary" in body
+    assert "key_emotions" in body
+    assert "compression_ratio" in body

195-201: Unify patching style for voice_transcriber to reduce surprises

Some tests use mock_transcriber.return_value.transcribe, others use mock_transcriber.transcribe. Prefer one consistent approach that matches how unified_ai_api references voice_transcriber (module vs. instance). If it’s a module-level function, patch the attribute and use mock.transcribe directly; if it’s an object instance, use return_value consistently.

Also applies to: 226-236, 298-307, 344-347, 378-381


506-516: Avoid allocating >50MB in tests; simulate size-limit check to keep CI lean

Constructing >50MB in memory can slow CI or cause OOM. Prefer:

  • Lowering the limit via a test-only config/env override (if supported) and generating a small payload just over that limit.
  • Or monkeypatching the size check used by the endpoint.

If you share how the endpoint enforces the max size (constant, config, or filesystem stat), I can propose a concrete patch to simulate the violation without large allocations.


836-843: Timing-based assertion may be flaky in CI

Asserting <100ms completion can fail under load. Consider relaxing to a higher threshold (e.g., <500ms) or asserting completion without strict timing.

-        # Should complete quickly (less than 100ms)
-        assert (end_time - start_time) < 0.1
+        # Should complete quickly; allow headroom for CI load
+        assert (end_time - start_time) < 0.5
scripts/testing/test_cloud_run_api_endpoints.py (5)

49-64: Health endpoint fields appear too strict vs deployed server response

The required fields ["status", "service", "version", "emotions_supported"] may not match the server’s health payload (e.g., the Cloud Run server snippet returns status, model_loaded, model_loading, port, timestamp). Recommend validating minimally on status and treating the rest as optional.

-            # Validate expected fields for minimal API
-            required_fields = ["status", "service", "version", "emotions_supported"]
+            # Validate minimal health response; extended fields are optional
+            required_fields = ["status"]
+            optional_fields = ["service", "version", "emotions_supported"]
             if missing_fields := [field for field in required_fields if field not in data]:
                 return {
                     "success": False,
                     "error": f"Missing required fields: {missing_fields}",
                     "response": data
                 }

Optionally, log which optional fields are present to aid debugging.


171-178: “None payload” test path still sends an empty JSON body

When test_case is None, the code posts {} instead of testing a truly missing/absent JSON body. If you want to test “no body,” you’ll need a raw requests.post(url) without the json= parameter, or update create_api_client to support a “no JSON” call path.

Would you like me to adjust the client to support sending requests without a JSON body for this test case?


259-261: Unused variable: rate_limited

rate_limited is computed but unused. Either remove it or include it in the returned payload to aid debugging.

-        # Check if any requests were rate limited (429 status)
-        rate_limited = any(r.get("status") == "rate_limited" for r in rapid_requests)
+        # Check if any requests were rate limited (429 status)
+        rate_limiting_observed = any(r.get("status") == "rate_limited" for r in rapid_requests)

And include it in the response:

         return {
             "success": success,
             "rate_limiting_tested": True,
+            "rate_limiting_observed": rate_limiting_observed,
             "security_headers_tested": security_headers.get("tested", False),
             "note": "Minimal API - basic security features only"
         }

296-301: Assigned-but-unused variable: data

You assign data = self.client.post(...) but never use data. Remove the assignment to avoid the unused variable.

-                data = self.client.post("/predict", payload)
+                self.client.post("/predict", payload)

438-446: Prefer pathlib for directory creation; handle errors consistently

Minor style/performance: use Path.mkdir(parents=True, exist_ok=True) over os.makedirs. Also, consider logging the exception via logging for consistency with the rest of the script.

-    output_file = "test_reports/cloud_run_api_test_results.json"
-    try:
-        os.makedirs("test_reports", exist_ok=True)
+    output_file = "test_reports/cloud_run_api_test_results.json"
+    try:
+        from pathlib import Path
+        Path("test_reports").mkdir(parents=True, exist_ok=True)
scripts/testing/test_model_status.py (2)

100-101: Use sys.exit instead of exit

exit() is intended for interactive shells. Use sys.exit() in scripts.

-    success = test_model_status(args.base_url)
-    exit(0 if success else 1)
+    success = test_model_status(args.base_url)
+    sys.exit(0 if success else 1)

Add the import if missing:

import sys

84-91: Summary/return omits model_status_success

You compute and print model_status_success but the final return excludes it. If intentional, ignore. Otherwise, include it for stricter coverage.

-    return health_success and emotions_success and prediction_success
+    return health_success and emotions_success and model_status_success and prediction_success
tests/unit/test_api_rate_limiter.py (1)

79-88: LGTM on middleware attachment test

Basic middleware presence check is sufficient here. Optionally, asserting limiter presence on app state can strengthen the test: hasattr(app.state, 'rate_limiter').

scripts/testing/config.py (3)

30-32: Minor readability nit: align multiline env var lookup

The logic is fine; the indentation on Line 31 is over-indented relative to Lines 30 and 32. Consider aligning for readability.

-        env_url = (os.environ.get("API_BASE_URL") or
-                      os.environ.get("CLOUD_RUN_API_URL") or
-                  os.environ.get("MODEL_API_BASE_URL"))
+        env_url = (
+            os.environ.get("API_BASE_URL")
+            or os.environ.get("CLOUD_RUN_API_URL")
+            or os.environ.get("MODEL_API_BASE_URL")
+        )

117-123: Enable retries for POST requests

By default, urllib3’s Retry does not retry POST. Since your client uses POST in test_prediction/test_batch_prediction, include POST in allowed_methods so 429/5xx transient failures are retried.

-    retry_strategy = Retry(
-        total=3,
-        backoff_factor=1,
-        status_forcelist=[429, 500, 502, 503, 504],
-    )
+    retry_strategy = Retry(
+        total=3,
+        backoff_factor=1,
+        status_forcelist=[429, 500, 502, 503, 504],
+        allowed_methods=frozenset(["HEAD", "GET", "OPTIONS", "POST"])
+    )

138-149: Make URL joining robust (avoid double/missing slashes)

Current concatenation assumes endpoint starts with “/”. Join defensively to handle both "/foo" and "foo".

-        url = f"{self.base_url}{endpoint}"
+        url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
@@
-        url = f"{self.base_url}{endpoint}"
+        url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
scripts/training/fixed_focal_training.py (3)

320-324: Save/load the best model inside the created models/ directory

You create a models/ directory but save/load from the project root. Store the checkpoint under models/ for consistency.

-            torch.save(model.state_dict(), "best_focal_model.pth")
+            torch.save(model.state_dict(), "models/best_focal_model.pth")
@@
-    trained_model.load_state_dict(torch.load("best_focal_model.pth"))
+    trained_model.load_state_dict(torch.load("models/best_focal_model.pth"))

Also applies to: 415-417


392-399: Optional: set seeds for reproducibility

Results will vary across runs due to shuffling and model initialization. Seed randomness if reproducible training is desired (especially for CI).

Add near device setup:

import os
seed = 42
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
    torch.cuda.manual_seed_all(seed)
os.environ["PYTHONHASHSEED"] = str(seed)

61-71: Dataset only covers 8/28 classes

You define 28 emotion slots but provide labeled examples for 8 classes. That’s fine for a minimal demo, but be aware the remaining 20 outputs will learn only negatives and can skew weighted metrics. Consider either:

  • adding a few positives per missing class, or
  • restricting num_classes and label space to the covered classes for this minimal example.

Also applies to: 188-228

scripts/training/setup_colab_environment.py (5)

138-145: Colab notebook is missing some dependencies you install via the script

The install cell omits pandas, numpy, scikit-learn, accelerate, python-dotenv, pytest-cov, pytest-asyncio which you install in install_dependencies(). Colab may have some preinstalled, but versions can diverge; this can cause runtime mismatches.

-        "!pip install transformers>=4.30.0,<5.0.0 datasets>=2.10.0,<3.0.0 tokenizers>=0.13.0,<1.0.0\\n",
+        "!pip install transformers>=4.30.0,<5.0.0 datasets>=2.10.0,<3.0.0 tokenizers>=0.13.0,<1.0.0\\n",
+        "!pip install pandas>=2.0.0,<3.0.0 numpy>=1.24.0,<2.0.0 scikit-learn>=1.3.0,<2.0.0 accelerate>=0.20.0,<1.0.0 python-dotenv>=1.0.0,<2.0.0\\n",
@@
-        "!pip install pytest>=7.0.0,<8.0.0 black>=23.0.0,<24.0.0 ruff>=0.1.0,<1.0.0"
+        "!pip install pytest>=7.0.0,<8.0.0 pytest-cov>=4.0.0,<5.0.0 pytest-asyncio>=0.21.0,<1.0.0 black>=23.0.0,<24.0.0 ruff>=0.1.0,<1.0.0"

227-233: Address linter hint: add explicit check to subprocess.run

Ruff PLW1510: be explicit about check. You already handle return codes, so make it check=False.

-        result = subprocess.run(
+        result = subprocess.run(
             [sys.executable, "scripts/ci/run_full_ci_pipeline.py"],
             capture_output=True,
             text=True,
-            timeout=600  # 10 minute timeout
+            timeout=600,  # 10 minute timeout
+            check=False
         )

71-74: Improve pip failure diagnostics by logging stderr/stdout

On install failure, log captured stdout/stderr to aid debugging rather than only the exception object.

-        except subprocess.CalledProcessError as e:
-            logger.error(f"❌ Failed to install {package}: {e}")
+        except subprocess.CalledProcessError as e:
+            logger.error(f"❌ Failed to install {package}: returncode={e.returncode}")
+            if e.stdout:
+                logger.error(f"stdout:\\n{e.stdout}")
+            if e.stderr:
+                logger.error(f"stderr:\\n{e.stderr}")
             return False

239-242: On CI failure, also log stdout (not just stderr)

This makes troubleshooting easier when tools write useful info to stdout.

-        else:
-            logger.error("❌ CI pipeline verification failed")
-            logger.error(result.stderr)
+        else:
+            logger.error("❌ CI pipeline verification failed")
+            if result.stdout:
+                logger.error("stdout:")
+                logger.error(result.stdout)
+            if result.stderr:
+                logger.error("stderr:")
+                logger.error(result.stderr)
             return False

20-29: Optional: more robust Colab detection

COLAB_GPU can be absent until a GPU is attached. A common pattern is to try importing google.colab.

def detect_colab_environment():
    try:
        import google.colab  # type: ignore
        logger.info("🎯 Detected Google Colab environment")
        logger.info(f"📊 Colab GPU: {os.environ.get('COLAB_GPU', 'unknown')}")
        return True
    except Exception:
        logger.info("💻 Running in local environment")
        return False
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 560f195 and 9f4f697.

📒 Files selected for processing (23)
  • CHANGELOG.md (1 hunks)
  • scripts/testing/config.py (2 hunks)
  • scripts/testing/test_api_startup.py (0 hunks)
  • scripts/testing/test_cloud_run_api_endpoints.py (17 hunks)
  • scripts/testing/test_e2e_simple.py (0 hunks)
  • scripts/testing/test_model_status.py (2 hunks)
  • scripts/testing/test_vertex_setup.py (2 hunks)
  • scripts/training/fixed_focal_training.py (12 hunks)
  • scripts/training/setup_colab_environment.py (6 hunks)
  • tests/conftest.py (1 hunks)
  • tests/e2e/test_complete_workflows.py (1 hunks)
  • tests/integration/test_priority1_features.py (29 hunks)
  • tests/unit/test_admin_endpoints.py (7 hunks)
  • tests/unit/test_anomaly_detection.py (10 hunks)
  • tests/unit/test_api_rate_limiter.py (2 hunks)
  • tests/unit/test_api_security.py (11 hunks)
  • tests/unit/test_csp_config.py (5 hunks)
  • tests/unit/test_hash_security.py (3 hunks)
  • tests/unit/test_http_exception_handler.py (0 hunks)
  • tests/unit/test_jwt_manager_extra.py (0 hunks)
  • tests/unit/test_sandbox_executor.py (2 hunks)
  • tests/unit/test_secure_model_loader.py (22 hunks)
  • tests/unit/test_validation_enhanced.py (13 hunks)
💤 Files with no reviewable changes (4)
  • tests/unit/test_jwt_manager_extra.py
  • tests/unit/test_http_exception_handler.py
  • scripts/testing/test_api_startup.py
  • scripts/testing/test_e2e_simple.py
🧰 Additional context used
🧬 Code Graph Analysis (10)
tests/conftest.py (2)
tests/integration/test_priority1_features.py (1)
  • reset_state (57-70)
src/api_rate_limiter.py (1)
  • reset_state (436-444)
scripts/testing/config.py (1)
scripts/testing/test_config.py (1)
  • get (77-91)
tests/unit/test_anomaly_detection.py (2)
src/security_headers.py (3)
  • SecurityHeadersConfig (21-41)
  • SecurityHeadersMiddleware (43-424)
  • _analyze_user_agent_enhanced (236-335)
src/api_rate_limiter.py (3)
  • _get_client_key (177-180)
  • _analyze_request_patterns (253-287)
  • _detect_abuse (289-333)
tests/unit/test_api_rate_limiter.py (1)
src/api_rate_limiter.py (1)
  • add_rate_limiting (447-477)
tests/unit/test_sandbox_executor.py (1)
src/models/secure_loader/sandbox_executor.py (4)
  • _get_safe_builtins (96-107)
  • SandboxExecutor (38-283)
  • execute_safely (164-183)
  • sandbox_context (126-148)
tests/unit/test_api_security.py (2)
src/api_rate_limiter.py (8)
  • get_stats (396-410)
  • allow_request (347-387)
  • _get_client_key (177-180)
  • _refill_bucket (335-345)
  • add_to_blacklist (412-416)
  • remove_from_blacklist (418-422)
  • add_to_whitelist (424-428)
  • remove_from_whitelist (430-434)
src/input_sanitizer.py (5)
  • sanitize_text (91-134)
  • validate_emotion_request (170-206)
  • validate_batch_request (208-258)
  • validate_content_type (260-277)
  • detect_anomalies (307-339)
scripts/testing/test_cloud_run_api_endpoints.py (4)
tests/integration/test_summarizer_voice_endpoints.py (1)
  • client (26-28)
scripts/testing/config.py (2)
  • get (138-142)
  • post (144-148)
deployment/cloud-run/secure_api_server.py (6)
  • get (259-280)
  • get (394-405)
  • get (415-424)
  • get (433-447)
  • post (293-329)
  • post (342-387)
scripts/testing/test_config.py (4)
  • get (77-91)
  • post (93-107)
  • create_test_config (110-112)
  • get_rate_limit_requests (64-66)
tests/integration/test_priority1_features.py (3)
tests/integration/test_summarizer_voice_endpoints.py (1)
  • client (26-28)
src/unified_ai_api.py (1)
  • refresh_token (933-965)
src/security/jwt_manager.py (8)
  • create_access_token (57-67)
  • create_refresh_token (69-80)
  • create_token_pair (82-90)
  • verify_token (92-108)
  • blacklist_token (124-132)
  • has_permission (143-146)
  • is_token_blacklisted (134-136)
  • cleanup_expired_tokens (148-161)
tests/unit/test_hash_security.py (2)
src/api_rate_limiter.py (2)
  • RateLimitConfig (22-43)
  • _get_client_key (177-180)
src/security_headers.py (1)
  • _before_request (79-92)
tests/unit/test_validation_enhanced.py (1)
src/data/validation.py (4)
  • check_data_types (52-98)
  • check_text_quality (100-140)
  • validate_journal_entries (142-209)
  • validate_text_input (212-254)
🪛 Ruff (0.12.2)
tests/unit/test_sandbox_executor.py

124-124: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

tests/unit/test_secure_model_loader.py

63-63: os.path.join() should be replaced by Path with / operator

(PTH118)


263-263: os.path.join() should be replaced by Path with / operator

(PTH118)


378-378: os.path.join() should be replaced by Path with / operator

(PTH118)


448-448: os.path.join() should be replaced by Path with / operator

(PTH118)


486-486: os.path.join() should be replaced by Path with / operator

(PTH118)


487-487: os.path.exists() should be replaced by Path.exists()

(PTH110)


490-490: Unnecessary mode argument

Remove mode argument

(UP015)

tests/unit/test_api_security.py

86-86: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


103-103: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


122-122: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


136-136: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


444-444: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

scripts/testing/test_cloud_run_api_endpoints.py

260-260: Local variable rate_limited is assigned to but never used

Remove assignment to unused variable rate_limited

(F841)


297-297: Local variable data is assigned to but never used

Remove assignment to unused variable data

(F841)


439-439: os.makedirs() should be replaced by Path.mkdir(parents=True)

(PTH103)

tests/integration/test_priority1_features.py

90-90: Possible hardcoded password assigned to: "token_type"

(S105)

scripts/testing/test_model_status.py

101-101: Use sys.exit() instead of exit

Replace exit with sys.exit()

(PLR1722)

scripts/training/setup_colab_environment.py

227-227: subprocess.run without explicit check argument

Add explicit check=False

(PLW1510)

🔇 Additional comments (15)
tests/e2e/test_complete_workflows.py (1)

175-178: Whitespace-only tweak looks good

No functional impact. Keeping the section tight after response_data assignment improves readability.

tests/unit/test_anomaly_detection.py (2)

47-61: UA scoring checks read clean and align with implementation behavior

The expectations allow for both “legitimate_bot” and “normal,” which matches the documented categorization for low/negative scores.


123-130: Burst pattern detection assertions are reasonable

The test setup and the lower bound on score reflect the heuristic thresholds in _analyze_request_patterns.

tests/unit/test_hash_security.py (1)

150-159: LGTM: Hash algorithm verification is precise

Comparing against hashlib.sha256 of the expected input string is correct and guards against regressions in _get_client_key.

tests/unit/test_api_security.py (1)

318-335: Good coverage on deeply nested JSON sanitizer behavior

The test robustly asserts either a warning about depth or blocked content, which matches resilient expectations for sanitizer implementations.

tests/integration/test_priority1_features.py (1)

56-71: State-reset fixture is solid

Good autouse fixture to reset rate limiter and JWT blacklist, and to inject test-only permissions. This reduces test flakiness and cross-test coupling.

tests/unit/test_api_rate_limiter.py (1)

56-64: LGTM on rate-limit exceeded test

Assertions align with expected behavior; disabling abuse detection avoids flakiness. No changes needed.

tests/unit/test_validation_enhanced.py (3)

12-24: LGTM: robust fixture matching schema

The fixture aligns with expected schema used by validation functions, keeping tests deterministic.


93-120: LGTM: comprehensive result structure assertions

Good coverage across is_valid, validated_df, missing_values, and text quality columns.


198-207: LGTM: invalid type cases handled

Covers None and non-string inputs; matches contract in validate_text_input.

tests/unit/test_admin_endpoints.py (3)

24-33: LGTM: conditional import with SkipTest fallback

Graceful handling when the model isn’t available prevents CI flakes.


41-48: LGTM: environment setup/teardown for admin API key

Tests isolate side effects by setting and removing ADMIN_API_KEY.


49-118: LGTM: auth and validation coverage across admin endpoints

Positive and negative paths (missing/wrong key, missing IP) are covered well.

scripts/testing/config.py (1)

186-203: LGTM on formatting tweak

Trimming the trailing space in the closing brace is harmless and consistent with the rest of the file.

scripts/testing/test_vertex_setup.py (1)

28-35: LGTM: formatting-only changes

The added blank lines improve readability around the logging checkpoints without impacting behavior.

Also applies to: 41-47

Comment thread scripts/training/setup_colab_environment.py
cursoragent and others added 19 commits August 16, 2025 11:06
…ian bookworm versions; use requirements-api.txt; remove non-existent files
Co-authored-by: denizcan.uelker <denizcan.uelker@mercedes-benz.com>
…used vars and redundant flag; AST-only indentation detection; add backup option and efficient line-by-line whitespace fix; configurable excluded dirs; consistent 4-space indentation\n- check_pgvector: fix mis-indented comment to match PEP8
…to Added/Changed sections\n- testing/config: fix multiline continuation indent for env URL\n- debug script: dedent one-line docstring; suppress T201 on final print\n- rate_limiter: adopt built-in generics for allow_request return type\n- check_pgvector: log DB errors at error level with traceback
…lize docstring; adopt built-in generics; remove redundant 'r' mode for open; include file path in error messages; use extend() for file discovery\n- testing config: simplify env var resolution with walrus op
…erics everywhere; include file path in errors; extend() for discovery\n- check_pgvector: keep lines under limit if practical (no functional change)
…ops; minor IO tweaks\n\n- check_pgvector: use with-statement for conn/cursor; logging.exception; version-agnostic apt hint; configure logging in __main__\n- fix_linting_issues: pathlib/contextlib for temp cleanup; built-in generics already updated; cleaner backup and open usage
Co-authored-by: denizcan.uelker <denizcan.uelker@mercedes-benz.com>
uelkerd and others added 20 commits August 16, 2025 19:06
- ✅ Replace 8 separate Dockerfiles with 1 consolidated Dockerfile.consolidated
- ✅ Support for minimal, secure, and unified variants via build args
- ✅ All variants tested and working: minimal (Flask), secure, unified (full ML)
- ✅ Improved logging for HF model loading fallback (less alarming messages)
- ✅ Fixed minimal variant requirements and entrypoint
- ✅ Cleaned up old Dockerfiles to reduce maintenance overhead
- ✅ Maintains all security features and DOK-DL3008 compliance
…esolve dependency conflicts

Resolved issues in src/unified_ai_api.py with DeepSource Autofix
…esolve dependency conflicts

Resolved issues in the following files with DeepSource Autofix:
1. src/input_sanitizer.py
2. src/models/secure_loader/__init__.py
3. src/models/secure_loader/integrity_checker.py
4. src/models/secure_loader/model_validator.py
5. src/models/secure_loader/sandbox_executor.py
6. src/models/secure_loader/secure_model_loader.py
7. src/monitoring/dashboard.py
8. src/security_headers.py
9. src/security/jwt_manager.py
Fix Docker builds: remove architecture-specific package pinning and resolve dependency conflicts
Copilot AI review requested due to automatic review settings August 17, 2025 00:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refreshes the core test suite to ensure consistent CI feedback and introduces minimal training helper scripts. The changes focus on improving test coverage, removing duplicate code, and adding basic training utilities for the project.

Key changes:

  • Enhanced test coverage with new test cases for edge cases and boundary conditions
  • Cleaned up whitespace and removed empty files in test directories
  • Added a Tests & Training section to the changelog documenting these improvements

Reviewed Changes

Copilot reviewed 10 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/test_validation_enhanced.py Added comprehensive test for missing values validation across all columns
tests/unit/test_jwt_manager_extra.py Removed trailing whitespace
tests/unit/test_http_exception_handler.py Removed trailing whitespace
tests/unit/test_api_security.py Added tests for multiple user agents and abuse detection cooldown scenarios
tests/unit/test_api_rate_limiter.py Added edge case tests for zero and negative rate limits
tests/integration/test_priority1_features.py Added tests for empty batch transcription and file size limit validation
scripts/testing/test_cloud_run_api_endpoints.py Added large payload testing functionality
scripts/testing/test_e2e_simple.py Removed empty file
scripts/testing/test_api_startup.py Removed empty file
CHANGELOG.md Added Tests & Training section documenting the refresh
Comments suppressed due to low confidence (1)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

response = client.post("/transcribe/voice", files=files, data=data, headers=headers)

# Should accept files at the exact limit
assert response.status_code in [200, 202], f"Unexpected status code: {response.status_code}"
Copy link

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

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

Creating a 50MB byte string in memory could cause memory issues in test environments. Consider using a mock file or streaming approach instead of allocating large amounts of memory.

Suggested change
assert response.status_code in [200, 202], f"Unexpected status code: {response.status_code}"
# Create a dummy audio file exactly at the limit using a temporary file
with tempfile.NamedTemporaryFile(suffix=".wav") as tmp_file:
tmp_file.seek(FILE_SIZE_LIMIT - 1)
tmp_file.write(b"\0")
tmp_file.flush()
tmp_file.seek(0)
files = {"audio_file": ("test_limit.wav", tmp_file, "audio/wav")}
headers = {"Authorization": f"Bearer {access_token}"}
data = {"language": "en", "model_size": "base"}
response = client.post("/transcribe/voice", files=files, data=data, headers=headers)
# Should accept files at the exact limit
assert response.status_code in [200, 202], f"Unexpected status code: {response.status_code}"

Copilot uses AI. Check for mistakes.

large_payload_tests = [
{"text": large_text, "description": "1MB text payload"},
{"text": extremely_large_text, "description": "10MB text payload"}
Copy link

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

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

Creating large text strings in memory (1MB and 10MB) could cause memory pressure during testing. Consider using generators or mock data instead of allocating large strings.

Suggested change
{"text": extremely_large_text, "description": "10MB text payload"}
# Helper to generate large text payloads without allocating them in memory
def large_text_generator(char, size):
for _ in range(size):
yield char
def get_large_text_stream(char, size):
# Returns a file-like object for streaming text
return io.StringIO(''.join(large_text_generator(char, size)))
large_payload_tests = [
{"text_stream": get_large_text_stream("A", 1024 * 1024), "description": "1MB text payload"},
{"text_stream": get_large_text_stream("B", 10 * 1024 * 1024), "description": "10MB text payload"}

Copilot uses AI. Check for mistakes.

# Test with very large text payload
large_text = "A" * (1024 * 1024) # 1MB text
extremely_large_text = "B" * (10 * 1024 * 1024) # 10MB text
Copy link

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

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

Creating a 10MB text string in memory could cause significant memory usage. Consider using a more memory-efficient approach for testing large payloads.

Suggested change
extremely_large_text = "B" * (10 * 1024 * 1024) # 10MB text
# Use a temporary file to avoid creating a 10MB string in memory
temp_10mb_file = tempfile.NamedTemporaryFile(mode='w+', delete=False)
temp_10mb_file.write("B" * (10 * 1024 * 1024)) # Write 10MB of 'B' to file
temp_10mb_file.flush()
temp_10mb_file.seek(0)
# Read the file in chunks to avoid loading all into memory
def read_10mb_file_in_chunks(file_obj, chunk_size=1024*1024):
file_obj.seek(0)
chunks = []
while True:
data = file_obj.read(chunk_size)
if not data:
break
chunks.append(data)
return ''.join(chunks)
extremely_large_text = read_10mb_file_in_chunks(temp_10mb_file)
temp_10mb_file.close()
os.unlink(temp_10mb_file.name)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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

♻️ Duplicate comments (3)
tests/unit/test_validation_enhanced.py (1)

36-55: Good coverage for “all columns have missing values” (addresses prior feedback).

This test solidly validates behavior when every column contains missing values and closes out the earlier request to add such coverage.

tests/unit/test_api_rate_limiter.py (1)

75-100: Edge-case coverage for zero/negative rate limits looks good.

Both tests align with the current allow_request behavior (bucket remains at/below threshold; reason contains "Rate limit"). This addresses prior suggestions to cover these cases.

tests/integration/test_priority1_features.py (1)

237-260: Solid negative test for empty batch input (matches earlier request).

Asserts both status and a meaningful error message. Nice use of a dedicated permission header for the batch endpoint.

🧹 Nitpick comments (5)
tests/unit/test_validation_enhanced.py (2)

56-61: Use tolerant float comparisons for missing-value percentages.

Direct float equality can be brittle depending on how percentages are computed. Prefer pytest.approx or math.isclose with a small tolerance.

Apply this diff for clearer, robust comparisons:

-        assert missing_stats['user_id'] == 0.4   # 2 out of 5 missing (40%)
-        assert missing_stats['title'] == 0.4     # 2 out of 5 missing (40%)
-        assert missing_stats['content'] == 0.6   # 3 out of 5 missing (60%)
-        assert missing_stats['created_at'] == 0.4  # 2 out of 5 missing (40%)
-        assert missing_stats['is_private'] == 0.4  # 2 out of 5 missing (40%)
+        import math
+        assert math.isclose(missing_stats['user_id'], 0.4, rel_tol=1e-9, abs_tol=1e-9)
+        assert math.isclose(missing_stats['title'], 0.4, rel_tol=1e-9, abs_tol=1e-9)
+        assert math.isclose(missing_stats['content'], 0.6, rel_tol=1e-9, abs_tol=1e-9)
+        assert math.isclose(missing_stats['created_at'], 0.4, rel_tol=1e-9, abs_tol=1e-9)
+        assert math.isclose(missing_stats['is_private'], 0.4, rel_tol=1e-9, abs_tol=1e-9)

137-139: Remove redundant local import of pandas inside the test.

pd is already imported at module level; re-importing within the test is unnecessary.

Apply this diff:

-        import pandas as pd
         assert isinstance(results['validated_df'], pd.DataFrame)
tests/unit/test_api_rate_limiter.py (1)

75-100: Optional: Parametrize zero/negative rate limit tests to reduce duplication.

Both tests follow the same structure and can be consolidated with parametrization for clarity.

Apply this refactor:

+import pytest
@@
-    def test_allow_request_zero_rate_limits(self):
-        """Test that allow_request returns False when rate limits are zero."""
-        config = RateLimitConfig(
-            requests_per_minute=0,
-            burst_size=0,
-            enable_user_agent_analysis=False,
-            enable_request_pattern_analysis=False
-        )
-        rate_limiter = TokenBucketRateLimiter(config)
-        allowed, reason, _ = rate_limiter.allow_request("127.0.0.1")
-        assert allowed is False
-        assert "rate limit" in reason.lower()
-
-    def test_allow_request_negative_rate_limits(self):
-        """Test that allow_request returns False when rate limits are negative."""
-        config = RateLimitConfig(
-            requests_per_minute=-1,
-            burst_size=-5,
-            enable_user_agent_analysis=False,
-            enable_request_pattern_analysis=False
-        )
-        rate_limiter = TokenBucketRateLimiter(config)
-        allowed, reason, _ = rate_limiter.allow_request("127.0.0.1")
-        assert allowed is False
-        assert "rate limit" in reason.lower()
+    @pytest.mark.parametrize(
+        "requests_per_minute,burst_size",
+        [(0, 0), (-1, -5)],
+    )
+    def test_allow_request_invalid_rate_limits(self, requests_per_minute, burst_size):
+        """Test that allow_request returns False when rate limits are zero or negative."""
+        config = RateLimitConfig(
+            requests_per_minute=requests_per_minute,
+            burst_size=burst_size,
+            enable_user_agent_analysis=False,
+            enable_request_pattern_analysis=False,
+        )
+        rate_limiter = TokenBucketRateLimiter(config)
+        allowed, reason, _ = rate_limiter.allow_request("127.0.0.1")
+        assert allowed is False
+        assert "rate limit" in reason.lower()
tests/unit/test_api_security.py (1)

168-170: Rename unused loop variable to underscore for clarity and lint compliance.

Silences Ruff B007 and improves readability where the index isn’t used.

Apply this diff:

-        for i in range(11):  # More than 10 requests in 1 second
+        for _ in range(11):  # More than 10 requests in 1 second
@@
-        for i in range(5):
+        for _ in range(5):
@@
-        for i in range(15):  # Trigger abuse detection
+        for _ in range(15):  # Trigger abuse detection

Also applies to: 190-194, 498-500

scripts/testing/test_cloud_run_api_endpoints.py (1)

217-265: Large-payload test is valuable; align annotations and result capture.

  • Prefer built-in generics: dict[str, Any] over Dict[str, Any].
  • Your client returns parsed JSON (not a Response), so status_code will be "N/A". If you need status codes, consider using the lower-level client from scripts/testing/config.py for this test only.

Apply this diff:

-    def test_extremely_large_payloads(self) -> Dict[str, Any]:
+    def test_extremely_large_payloads(self) -> dict[str, Any]:
@@
-                response = self.client.post("/predict", {"text": test_case["text"]})
+                response = self.client.post("/predict", {"text": test_case["text"]})
@@
-                    "status_code": response.status_code if hasattr(response, 'status_code') else 'N/A'
+                    "status_code": response.status_code if hasattr(response, 'status_code') else "N/A"

If you want to capture HTTP status codes, I can help refactor this method to temporarily use scripts/testing/config.py’s client for this test. Let me know.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4f697 and 2a1da4a.

📒 Files selected for processing (5)
  • scripts/testing/test_cloud_run_api_endpoints.py (17 hunks)
  • tests/integration/test_priority1_features.py (28 hunks)
  • tests/unit/test_api_rate_limiter.py (2 hunks)
  • tests/unit/test_api_security.py (11 hunks)
  • tests/unit/test_validation_enhanced.py (12 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
tests/integration/test_priority1_features.py (1)
tests/integration/test_summarizer_voice_endpoints.py (1)
  • client (26-28)
scripts/testing/test_cloud_run_api_endpoints.py (3)
scripts/testing/config.py (2)
  • get (138-142)
  • post (144-148)
deployment/cloud-run/secure_api_server.py (6)
  • get (259-280)
  • get (394-405)
  • get (415-424)
  • get (433-447)
  • post (293-329)
  • post (342-387)
scripts/testing/test_config.py (4)
  • get (77-91)
  • post (93-107)
  • create_test_config (110-112)
  • get_rate_limit_requests (64-66)
tests/unit/test_api_security.py (4)
src/api_rate_limiter.py (9)
  • get_stats (396-410)
  • allow_request (347-387)
  • release_request (389-394)
  • _get_client_key (177-180)
  • _refill_bucket (335-345)
  • add_to_blacklist (412-416)
  • remove_from_blacklist (418-422)
  • add_to_whitelist (424-428)
  • remove_from_whitelist (430-434)
deployment/secure_api_server.py (2)
  • add_to_blacklist (604-617)
  • add_to_whitelist (621-634)
src/input_sanitizer.py (5)
  • sanitize_text (91-134)
  • validate_emotion_request (170-206)
  • validate_batch_request (208-258)
  • validate_content_type (260-277)
  • detect_anomalies (307-339)
src/security_headers.py (1)
  • _build_permissions_policy (169-200)
🪛 Ruff (0.12.2)
tests/integration/test_priority1_features.py

90-90: Possible hardcoded password assigned to: "token_type"

(S105)


508-508: Do not assert blind exception: Exception

(B017)

scripts/testing/test_cloud_run_api_endpoints.py

217-217: Use dict instead of Dict for type annotation

Replace with dict

(UP006)


267-267: Use dict instead of Dict for type annotation

Replace with dict

(UP006)


310-310: Local variable rate_limited is assigned to but never used

Remove assignment to unused variable rate_limited

(F841)


347-347: Local variable data is assigned to but never used

Remove assignment to unused variable data

(F841)


489-489: os.makedirs() should be replaced by Path.mkdir(parents=True)

(PTH103)

tests/unit/test_api_security.py

118-118: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


135-135: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


154-154: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


168-168: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


190-190: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


498-498: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
tests/unit/test_api_security.py (1)

64-95: Per-UA bucketing verification is on-point.

Validates separate client keys per (IP, UA) and mirrors _get_client_key logic. Cleanly releases concurrency after each request.

Comment on lines 505 to +513
def test_websocket_authentication_required(self):
"""Test that WebSocket requires authentication."""
# This would require a WebSocket client test
# For now, we'll test the authentication logic
pass

# Test that WebSocket endpoint requires authentication
with pytest.raises(Exception): # WebSocket connection should fail without auth
# This simulates the authentication requirement
pass
# Mark as implemented but requires WebSocket client for full testing
assert True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Placeholder WebSocket test fails: using bare Exception with no raise causes DID NOT RAISE.

This test will fail at collection/execution and break CI. Either implement a proper WebSocket test or skip/xfail it until a client is available.

Apply one of these diffs:

Option A — skip with reason:

     def test_websocket_authentication_required(self):
         """Test that WebSocket requires authentication."""
-        # Test that WebSocket endpoint requires authentication
-        with pytest.raises(Exception):  # WebSocket connection should fail without auth
-            # This simulates the authentication requirement
-            pass
-        # Mark as implemented but requires WebSocket client for full testing
-        assert True
+        pytest.skip("WebSocket auth test requires a WebSocket client; skipping for now.")

Option B — mark xfail:

+    @pytest.mark.xfail(reason="WebSocket client not available; implement when ready")
     def test_websocket_authentication_required(self):
         """Test that WebSocket requires authentication."""
-        with pytest.raises(Exception):
-            pass
-        assert True
+        # Placeholder: will be implemented with a WebSocket client
+        assert True
📝 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 test_websocket_authentication_required(self):
"""Test that WebSocket requires authentication."""
# This would require a WebSocket client test
# For now, we'll test the authentication logic
pass
# Test that WebSocket endpoint requires authentication
with pytest.raises(Exception): # WebSocket connection should fail without auth
# This simulates the authentication requirement
pass
# Mark as implemented but requires WebSocket client for full testing
assert True
def test_websocket_authentication_required(self):
"""Test that WebSocket requires authentication."""
pytest.skip("WebSocket auth test requires a WebSocket client; skipping for now.")
Suggested change
def test_websocket_authentication_required(self):
"""Test that WebSocket requires authentication."""
# This would require a WebSocket client test
# For now, we'll test the authentication logic
pass
# Test that WebSocket endpoint requires authentication
with pytest.raises(Exception): # WebSocket connection should fail without auth
# This simulates the authentication requirement
pass
# Mark as implemented but requires WebSocket client for full testing
assert True
@pytest.mark.xfail(reason="WebSocket client not available; implement when ready")
def test_websocket_authentication_required(self):
"""Test that WebSocket requires authentication."""
# Placeholder: will be implemented with a WebSocket client
assert True
🧰 Tools
🪛 Ruff (0.12.2)

508-508: Do not assert blind exception: Exception

(B017)

🤖 Prompt for AI Agents
In tests/integration/test_priority1_features.py around lines 505 to 513 the
test_websocket_authentication_required() currently uses a with
pytest.raises(Exception): block that contains no raise, causing a "DID NOT
RAISE" failure; fix by either skipping or xfail-ing the placeholder test: Option
A - replace the body with pytest.skip("WebSocket client not available; skipping
until a real client is provided") so the test is skipped at runtime, or Option B
- mark the test with @pytest.mark.xfail(reason="WebSocket client not available",
strict=False) and simplify the body to a noop/assert True so it is reported as
an expected failure rather than breaking CI.

Comment on lines +545 to +568
def test_voice_transcription_file_size_at_limit(self):
"""Test file size exactly at the limit for voice transcription."""
# Login to get token
login_data = {
"username": "testuser@example.com",
"password": "testpassword123"
}
login_response = client.post("/auth/login", json=login_data)
access_token = login_response.json()["access_token"]

# Assume the file size limit is 50MB (adjust if different)
FILE_SIZE_LIMIT = 50 * 1024 * 1024 # 50MB in bytes

# Create a dummy audio file exactly at the limit
audio_content = b"\0" * FILE_SIZE_LIMIT
files = {"audio_file": ("test_limit.wav", audio_content, "audio/wav")}
headers = {"Authorization": f"Bearer {access_token}"}
data = {"language": "en", "model_size": "base"}

response = client.post("/transcribe/voice", files=files, data=data, headers=headers)

# Should accept files at the exact limit
assert response.status_code in [200, 202], f"Unexpected status code: {response.status_code}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm the file-size limit matches the implementation before locking the assertion.

The test assumes a 50MB limit. If the actual limit differs, this can cause flakes. Verify the enforced limit in the API and align the test accordingly.

Run this script to locate the effective file-size limit and the /transcribe/voice handler:


🏁 Script executed:

#!/bin/bash
# Find constants or checks that imply upload/file size limits
rg -n -C2 -i 'file.?size|upload|limit|max[_- ]?size|50.?mb|50\s*\*\s*1024\s*\*\s*1024|content-length' --type py

# Inspect the voice transcription endpoint(s)
rg -n -C3 -i 'transcribe.*voice|/transcribe/voice' --type py

Length of output: 8524


🏁 Script executed:

#!/bin/bash
# Locate how large_content is defined in the integration tests
rg -n 'large_content' tests/integration/test_priority1_features.py

# Show the context around the test that references large_content and the size limit
sed -n '530,590p' tests/integration/test_priority1_features.py

Length of output: 3013


#!/bin/bash
# Extract the transcribe_voice handler implementation
sed -n '1350,1450p' src/unified_ai_api.py

🏁 Script executed:

#!/bin/bash
# Extract the voice transcription endpoint implementation to find file size validation
sed -n '1350,1450p' src/unified_ai_api.py

# Search for the "too large" error to locate the limit enforcement
rg -n -C2 -i "too large" src/unified_ai_api.py

Length of output: 5105


Align test’s file-size assumption with the implementation’s 45 MB limit

The voice‐transcription handler enforces

MAX_AUDIO_BYTES = 45 * 1024 * 1024  # 45 MB

but the test hard‐codes a 50 MB limit. Update the integration test to match the real threshold, for example:

  • In tests/integration/test_priority1_features.py (around line 545), replace

    # Assume the file size limit is 50MB (adjust if different)
    FILE_SIZE_LIMIT = 50 * 1024 * 1024  # 50MB in bytes

    with either:

    from src.unified_ai_api import MAX_AUDIO_BYTES
    
    FILE_SIZE_LIMIT = MAX_AUDIO_BYTES

    or

    FILE_SIZE_LIMIT = 45 * 1024 * 1024  # 45MB in bytes
  • Remove or update the accompanying comment.

This ensures the “exact‐limit” test actually passes against the 45 MB enforcement in the code.

🤖 Prompt for AI Agents
In tests/integration/test_priority1_features.py around lines 545 to 568, the
test assumes a 50 MB audio limit but the application enforces MAX_AUDIO_BYTES =
45 * 1024 * 1024; update the test to match the implementation by replacing the
hard-coded 50MB value and comment with either an import of the canonical
constant (e.g., from src.unified_ai_api import MAX_AUDIO_BYTES and set
FILE_SIZE_LIMIT = MAX_AUDIO_BYTES) or by changing it to FILE_SIZE_LIMIT = 45 *
1024 * 1024 and update the comment accordingly so the “exact-limit” test aligns
with the real 45 MB threshold.

Comment on lines +162 to +183
def test_abuse_detection_reset_after_cooldown(self):
"""Test that abuse detection resets after cooldown period."""
client_ip = "192.168.1.6"
user_agent = "test-agent"

# Simulate rapid-fire requests to trigger abuse detection
for i in range(11): # More than 10 requests in 1 second
self.rate_limiter.request_history[self.rate_limiter._get_client_key(client_ip, user_agent)].append(time.time())

# Verify abuse detection is triggered
allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
self.assertFalse(allowed)
self.assertEqual(reason, "Abuse detected")

# Simulate cooldown period (e.g., 60 seconds) by clearing request history
client_key = self.rate_limiter._get_client_key(client_ip, user_agent)
self.rate_limiter.request_history[client_key] = []

# After cooldown, requests should be allowed again
allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
self.assertTrue(allowed, f"Request should be allowed after cooldown, but got: {reason}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Test erroneously allows requests while the client remains blocked (cooldown not simulated).

After "Abuse detected", the client is added to blocked_clients for block_duration_seconds. Clearing request_history alone does not unblock the client; allow_request checks _is_client_blocked first and will keep rejecting during the block window.

Fix by expiring or removing the block before re-trying:

     def test_abuse_detection_reset_after_cooldown(self):
         """Test that abuse detection resets after cooldown period."""
         client_ip = "192.168.1.6"
         user_agent = "test-agent"
@@
-        # Simulate cooldown period (e.g., 60 seconds) by clearing request history
-        client_key = self.rate_limiter._get_client_key(client_ip, user_agent)
-        self.rate_limiter.request_history[client_key] = []
-
-        # After cooldown, requests should be allowed again
-        allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
+        # Simulate cooldown by clearing history AND removing/expiring the block
+        client_key = self.rate_limiter._get_client_key(client_ip, user_agent)
+        self.rate_limiter.request_history[client_key] = []
+        # Unblock the client explicitly for the test (no waiting)
+        self.rate_limiter.blocked_clients.pop(client_key, None)
+
+        # After cooldown/unblock, requests should be allowed again
+        allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
         self.assertTrue(allowed, f"Request should be allowed after cooldown, but got: {reason}")

Alternative: Patch time to simulate the block_duration elapsing:

from unittest.mock import patch
now = time.time()
with patch('api_rate_limiter.time.time', return_value=now + self.config.block_duration_seconds + 1):
    allowed, reason, meta = self.rate_limiter.allow_request(client_ip, user_agent)
    self.assertTrue(allowed)
🧰 Tools
🪛 Ruff (0.12.2)

168-168: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

🤖 Prompt for AI Agents
In tests/unit/test_api_security.py around lines 162-183, the test clears
request_history but does not remove/expire the client from
rate_limiter.blocked_clients so allow_request still rejects; fix by either
removing the client key from rate_limiter.blocked_clients (e.g., pop the client
key) before the second allow_request, or simulate the cooldown elapsing by
patching time.time to return now + self.config.block_duration_seconds + 1 when
calling allow_request so the block is considered expired.

@uelkerd uelkerd closed this Aug 17, 2025
@uelkerd uelkerd deleted the fix/testing-and-training-only branch August 17, 2025 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants