From 126df5b7d1b2d605340e7e3eae9b4a2758fa85c2 Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:01:53 +0000 Subject: [PATCH 1/4] Add mock providers and update testing workflows for API-independent execution - Introduced `MockLLMProvider` and `MockTranscriptionProvider` to facilitate testing without external API dependencies, allowing for consistent and controlled test environments. - Created `run-no-api-tests.sh` script to execute tests that do not require API keys, ensuring separation of API-dependent and independent tests. - Updated Robot Framework test configurations to utilize mock services, enhancing test reliability and reducing external dependencies. - Modified existing test workflows to include new configurations and ensure proper handling of results for tests excluding API keys. - Added `mock-services.yml` configuration to disable external API services while maintaining core functionality for testing purposes. - Enhanced documentation to reflect the new tagging system for tests requiring API keys, improving clarity on test execution requirements. --- .github/workflows/full-tests-with-api.yml | 255 +++++++++++++++ .github/workflows/pr-tests-with-api.yml | 294 ++++++++++++++++++ .github/workflows/robot-tests.yml | 82 ++--- .../memory/providers/mock_llm_provider.py | 130 ++++++++ .../services/transcription/mock_provider.py | 82 +++++ tests/configs/mock-services.yml | 52 ++++ tests/integration/integration_test.robot | 2 +- tests/run-no-api-tests.sh | 180 +++++++++++ tests/setup/test_manager_keywords.robot | 24 ++ tests/tags.md | 19 +- 10 files changed, 1063 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/full-tests-with-api.yml create mode 100644 .github/workflows/pr-tests-with-api.yml create mode 100644 backends/advanced/src/advanced_omi_backend/services/memory/providers/mock_llm_provider.py create mode 100644 backends/advanced/src/advanced_omi_backend/services/transcription/mock_provider.py create mode 100644 tests/configs/mock-services.yml create mode 100755 tests/run-no-api-tests.sh diff --git a/.github/workflows/full-tests-with-api.yml b/.github/workflows/full-tests-with-api.yml new file mode 100644 index 00000000..be54b987 --- /dev/null +++ b/.github/workflows/full-tests-with-api.yml @@ -0,0 +1,255 @@ +name: Robot Framework Tests (Full - With API Keys) + +on: + push: + branches: + - dev + - main + paths: + - 'tests/**/*.robot' + - 'tests/**/*.py' + - 'backends/advanced/src/**' + - '.github/workflows/full-tests-with-api.yml' + workflow_dispatch: # Allow manual triggering + +permissions: + contents: read + pull-requests: write + issues: write + pages: write + id-token: write + +jobs: + full-robot-tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify required secrets + env: + DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + HF_TOKEN: ${{ secrets.HF_TOKEN }} + run: | + echo "Verifying required secrets..." + if [ -z "$DEEPGRAM_API_KEY" ]; then + echo "❌ ERROR: DEEPGRAM_API_KEY secret is not set" + exit 1 + fi + if [ -z "$OPENAI_API_KEY" ]; then + echo "❌ ERROR: OPENAI_API_KEY secret is not set" + exit 1 + fi + if [ -z "$HF_TOKEN" ]; then + echo "⚠️ WARNING: HF_TOKEN secret is not set (speaker recognition will be disabled)" + else + echo "✓ HF_TOKEN is set (length: ${#HF_TOKEN})" + fi + echo "✓ DEEPGRAM_API_KEY is set (length: ${#DEEPGRAM_API_KEY})" + echo "✓ OPENAI_API_KEY is set (length: ${#OPENAI_API_KEY})" + echo "✓ Required secrets verified" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: | + image=moby/buildkit:latest + network=host + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ hashFiles('backends/advanced/Dockerfile', 'backends/advanced/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Install Robot Framework and dependencies + run: | + uv pip install --system robotframework robotframework-requests python-dotenv websockets + + - name: Create test config.yml + run: | + echo "Copying test configuration file..." + mkdir -p config + cp tests/configs/deepgram-openai.yml config/config.yml + echo "✓ Test config.yml created from tests/configs/deepgram-openai.yml" + ls -lh config/config.yml + + - name: Create plugins.yml from template + run: | + echo "Creating plugins.yml from template..." + if [ -f "config/plugins.yml.template" ]; then + cp config/plugins.yml.template config/plugins.yml + echo "✓ plugins.yml created from template" + ls -lh config/plugins.yml + else + echo "❌ ERROR: config/plugins.yml.template not found" + exit 1 + fi + + - name: Run Full Robot Framework tests + working-directory: tests + env: + # Required for test runner script + DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + HF_TOKEN: ${{ secrets.HF_TOKEN }} + CLEANUP_CONTAINERS: "false" # Don't cleanup in CI - handled by workflow + run: | + # Use the full test script (includes all tests with API keys) + ./run-robot-tests.sh + TEST_EXIT_CODE=$? + echo "test_exit_code=$TEST_EXIT_CODE" >> $GITHUB_ENV + exit 0 # Don't fail here, we'll fail at the end after uploading artifacts + + - name: Show service logs + if: always() + working-directory: backends/advanced + run: | + echo "=== Backend Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 chronicle-backend-test + echo "" + echo "=== Worker Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 workers-test + + - name: Check if test results exist + if: always() + id: check_results + run: | + if [ -f tests/results/output.xml ]; then + echo "results_exist=true" >> $GITHUB_OUTPUT + else + echo "results_exist=false" >> $GITHUB_OUTPUT + echo "⚠️ No test results found in tests/results/" + ls -la tests/results/ || echo "Results directory doesn't exist" + fi + + - name: Upload Robot Framework HTML reports + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-artifact@v4 + with: + name: robot-test-reports-html-full + path: | + tests/results/report.html + tests/results/log.html + retention-days: 30 + + - name: Publish HTML Report as GitHub Pages artifact + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-pages-artifact@v3 + with: + path: tests/results + + - name: Deploy to GitHub Pages + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/deploy-pages@v4 + id: deployment + + - name: Generate test summary + if: always() && steps.check_results.outputs.results_exist == 'true' + id: test_summary + run: | + # Parse test results + python3 << 'PYTHON_SCRIPT' > test_summary.txt + import xml.etree.ElementTree as ET + tree = ET.parse('tests/results/output.xml') + root = tree.getroot() + stats = root.find('.//total/stat') + if stats is not None: + passed = stats.get("pass", "0") + failed = stats.get("fail", "0") + total = int(passed) + int(failed) + print(f"PASSED={passed}") + print(f"FAILED={failed}") + print(f"TOTAL={total}") + PYTHON_SCRIPT + + # Source the variables + source test_summary.txt + + # Set outputs + echo "passed=$PASSED" >> $GITHUB_OUTPUT + echo "failed=$FAILED" >> $GITHUB_OUTPUT + echo "total=$TOTAL" >> $GITHUB_OUTPUT + + - name: Upload Robot Framework XML output + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-artifact@v4 + with: + name: robot-test-results-xml-full + path: tests/results/output.xml + retention-days: 30 + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: robot-test-logs-full + path: | + backends/advanced/.env + tests/setup/.env.test + retention-days: 7 + + - name: Display test results summary + if: always() + run: | + if [ -f tests/results/output.xml ]; then + echo "Full test results generated successfully (With API Keys)" + echo "========================================" + python3 << 'PYTHON_SCRIPT' + import xml.etree.ElementTree as ET + tree = ET.parse('tests/results/output.xml') + root = tree.getroot() + stats = root.find('.//total/stat') + if stats is not None: + passed = stats.get("pass", "0") + failed = stats.get("fail", "0") + print(f'✅ Passed: {passed}') + print(f'❌ Failed: {failed}') + print(f'📊 Total: {int(passed) + int(failed)}') + PYTHON_SCRIPT + echo "========================================" + echo "" + echo "ℹ️ Full test suite including API-dependent tests" + echo "" + echo "📊 FULL TEST REPORTS AVAILABLE:" + echo " 1. Go to the 'Summary' tab at the top of this page" + echo " 2. Scroll down to 'Artifacts' section" + echo " 3. Download 'robot-test-reports-html-full'" + echo " 4. Extract and open report.html or log.html in your browser" + echo "" + echo "The HTML reports provide:" + echo " - report.html: Executive summary with statistics" + echo " - log.html: Detailed step-by-step execution log" + echo "" + fi + + - name: Cleanup + if: always() + working-directory: backends/advanced + run: | + docker compose -f docker-compose-test.yml down -v + + - name: Fail workflow if tests failed + if: always() + run: | + if [ "${{ env.test_exit_code }}" != "0" ]; then + echo "❌ Tests failed with exit code ${{ env.test_exit_code }}" + exit 1 + else + echo "✅ All tests passed" + fi diff --git a/.github/workflows/pr-tests-with-api.yml b/.github/workflows/pr-tests-with-api.yml new file mode 100644 index 00000000..0ccff169 --- /dev/null +++ b/.github/workflows/pr-tests-with-api.yml @@ -0,0 +1,294 @@ +name: Robot Framework Tests (PR - Label Triggered) + +on: + pull_request: + types: [labeled, synchronize] + +permissions: + contents: read + pull-requests: write + issues: write + pages: write + id-token: write + +jobs: + pr-full-tests: + # Only run if PR has the 'test-with-api-keys' label + if: contains(github.event.pull_request.labels.*.name, 'test-with-api-keys') + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify required secrets + env: + DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + HF_TOKEN: ${{ secrets.HF_TOKEN }} + run: | + echo "Verifying required secrets for label-triggered full test run..." + if [ -z "$DEEPGRAM_API_KEY" ]; then + echo "❌ ERROR: DEEPGRAM_API_KEY secret is not set" + exit 1 + fi + if [ -z "$OPENAI_API_KEY" ]; then + echo "❌ ERROR: OPENAI_API_KEY secret is not set" + exit 1 + fi + if [ -z "$HF_TOKEN" ]; then + echo "⚠️ WARNING: HF_TOKEN secret is not set (speaker recognition will be disabled)" + else + echo "✓ HF_TOKEN is set (length: ${#HF_TOKEN})" + fi + echo "✓ DEEPGRAM_API_KEY is set (length: ${#DEEPGRAM_API_KEY})" + echo "✓ OPENAI_API_KEY is set (length: ${#OPENAI_API_KEY})" + echo "✓ Required secrets verified" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: | + image=moby/buildkit:latest + network=host + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ hashFiles('backends/advanced/Dockerfile', 'backends/advanced/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Install Robot Framework and dependencies + run: | + uv pip install --system robotframework robotframework-requests python-dotenv websockets + + - name: Create test config.yml + run: | + echo "Copying test configuration file..." + mkdir -p config + cp tests/configs/deepgram-openai.yml config/config.yml + echo "✓ Test config.yml created from tests/configs/deepgram-openai.yml" + ls -lh config/config.yml + + - name: Create plugins.yml from template + run: | + echo "Creating plugins.yml from template..." + if [ -f "config/plugins.yml.template" ]; then + cp config/plugins.yml.template config/plugins.yml + echo "✓ plugins.yml created from template" + ls -lh config/plugins.yml + else + echo "❌ ERROR: config/plugins.yml.template not found" + exit 1 + fi + + - name: Run Full Robot Framework tests + working-directory: tests + env: + # Required for test runner script + DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + HF_TOKEN: ${{ secrets.HF_TOKEN }} + CLEANUP_CONTAINERS: "false" # Don't cleanup in CI - handled by workflow + run: | + # Use the full test script (includes all tests with API keys) + ./run-robot-tests.sh + TEST_EXIT_CODE=$? + echo "test_exit_code=$TEST_EXIT_CODE" >> $GITHUB_ENV + exit 0 # Don't fail here, we'll fail at the end after uploading artifacts + + - name: Show service logs + if: always() + working-directory: backends/advanced + run: | + echo "=== Backend Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 chronicle-backend-test + echo "" + echo "=== Worker Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 workers-test + + - name: Check if test results exist + if: always() + id: check_results + run: | + if [ -f tests/results/output.xml ]; then + echo "results_exist=true" >> $GITHUB_OUTPUT + else + echo "results_exist=false" >> $GITHUB_OUTPUT + echo "⚠️ No test results found in tests/results/" + ls -la tests/results/ || echo "Results directory doesn't exist" + fi + + - name: Upload Robot Framework HTML reports + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-artifact@v4 + with: + name: robot-test-reports-html-pr-labeled + path: | + tests/results/report.html + tests/results/log.html + retention-days: 30 + + - name: Publish HTML Report as GitHub Pages artifact + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-pages-artifact@v3 + with: + path: tests/results + + - name: Deploy to GitHub Pages + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/deploy-pages@v4 + id: deployment + + - name: Generate test summary + if: always() && steps.check_results.outputs.results_exist == 'true' + id: test_summary + run: | + # Parse test results + python3 << 'PYTHON_SCRIPT' > test_summary.txt + import xml.etree.ElementTree as ET + tree = ET.parse('tests/results/output.xml') + root = tree.getroot() + stats = root.find('.//total/stat') + if stats is not None: + passed = stats.get("pass", "0") + failed = stats.get("fail", "0") + total = int(passed) + int(failed) + print(f"PASSED={passed}") + print(f"FAILED={failed}") + print(f"TOTAL={total}") + PYTHON_SCRIPT + + # Source the variables + source test_summary.txt + + # Set outputs + echo "passed=$PASSED" >> $GITHUB_OUTPUT + echo "failed=$FAILED" >> $GITHUB_OUTPUT + echo "total=$TOTAL" >> $GITHUB_OUTPUT + + - name: Post PR comment with test results + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const passed = '${{ steps.test_summary.outputs.passed }}'; + const failed = '${{ steps.test_summary.outputs.failed }}'; + const total = '${{ steps.test_summary.outputs.total }}'; + const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`; + const pagesUrl = '${{ steps.deployment.outputs.page_url }}'; + + const status = failed === '0' ? '✅ All tests passed!' : '❌ Some tests failed'; + const emoji = failed === '0' ? '🎉' : '⚠️'; + + const comment = `## ${emoji} Robot Framework Test Results (Label-Triggered Full Suite) + + **Status**: ${status} + + 🏷️ **Note**: This run was triggered by the \`test-with-api-keys\` label. + All tests including API-dependent tests have been executed. + + | Metric | Count | + |--------|-------| + | ✅ Passed | ${passed} | + | ❌ Failed | ${failed} | + | 📊 Total | ${total} | + + ### 📊 View Reports + + **GitHub Pages (Live Reports):** + - [📋 Test Report](${pagesUrl}report.html) + - [📝 Detailed Log](${pagesUrl}log.html) + + **Download Artifacts:** + - [robot-test-reports-html-pr-labeled](${runUrl}) - HTML reports + - [robot-test-results-xml-pr-labeled](${runUrl}) - XML output + + --- + *[View full workflow run](${runUrl})*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + - name: Upload Robot Framework XML output + if: always() && steps.check_results.outputs.results_exist == 'true' + uses: actions/upload-artifact@v4 + with: + name: robot-test-results-xml-pr-labeled + path: tests/results/output.xml + retention-days: 30 + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: robot-test-logs-pr-labeled + path: | + backends/advanced/.env + tests/setup/.env.test + retention-days: 7 + + - name: Display test results summary + if: always() + run: | + if [ -f tests/results/output.xml ]; then + echo "Label-triggered full test results generated successfully" + echo "========================================" + python3 << 'PYTHON_SCRIPT' + import xml.etree.ElementTree as ET + tree = ET.parse('tests/results/output.xml') + root = tree.getroot() + stats = root.find('.//total/stat') + if stats is not None: + passed = stats.get("pass", "0") + failed = stats.get("fail", "0") + print(f'✅ Passed: {passed}') + print(f'❌ Failed: {failed}') + print(f'📊 Total: {int(passed) + int(failed)}') + PYTHON_SCRIPT + echo "========================================" + echo "" + echo "🏷️ This run was triggered by the 'test-with-api-keys' label" + echo "ℹ️ Full test suite including API-dependent tests" + echo "" + echo "📊 FULL TEST REPORTS AVAILABLE:" + echo " 1. Go to the 'Summary' tab at the top of this page" + echo " 2. Scroll down to 'Artifacts' section" + echo " 3. Download 'robot-test-reports-html-pr-labeled'" + echo " 4. Extract and open report.html or log.html in your browser" + echo "" + fi + + - name: Cleanup + if: always() + working-directory: backends/advanced + run: | + docker compose -f docker-compose-test.yml down -v + + - name: Fail workflow if tests failed + if: always() + run: | + if [ "${{ env.test_exit_code }}" != "0" ]; then + echo "❌ Tests failed with exit code ${{ env.test_exit_code }}" + exit 1 + else + echo "✅ All tests passed" + fi diff --git a/.github/workflows/robot-tests.yml b/.github/workflows/robot-tests.yml index b48b5e75..486273dc 100644 --- a/.github/workflows/robot-tests.yml +++ b/.github/workflows/robot-tests.yml @@ -1,4 +1,4 @@ -name: Robot Framework Tests +name: Robot Framework Tests (No API Keys) on: pull_request: @@ -24,30 +24,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Verify required secrets - env: - DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - HF_TOKEN: ${{ secrets.HF_TOKEN }} - run: | - echo "Verifying required secrets..." - if [ -z "$DEEPGRAM_API_KEY" ]; then - echo "❌ ERROR: DEEPGRAM_API_KEY secret is not set" - exit 1 - fi - if [ -z "$OPENAI_API_KEY" ]; then - echo "❌ ERROR: OPENAI_API_KEY secret is not set" - exit 1 - fi - if [ -z "$HF_TOKEN" ]; then - echo "❌ ERROR: HF_TOKEN secret is not set" - exit 1 - fi - echo "✓ DEEPGRAM_API_KEY is set (length: ${#DEEPGRAM_API_KEY})" - echo "✓ OPENAI_API_KEY is set (length: ${#OPENAI_API_KEY})" - echo "✓ HF_TOKEN is set (length: ${#HF_TOKEN})" - echo "✓ All required secrets verified" - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: @@ -79,10 +55,11 @@ jobs: - name: Create test config.yml run: | - echo "Copying test configuration file..." + echo "Copying mock services configuration file..." mkdir -p config - cp tests/configs/deepgram-openai.yml config/config.yml - echo "✓ Test config.yml created from tests/configs/deepgram-openai.yml" + cp tests/configs/mock-services.yml config/config.yml + echo "✓ Test config.yml created from tests/configs/mock-services.yml" + echo "ℹ️ This config disables external API dependencies (transcription, LLM)" ls -lh config/config.yml - name: Create plugins.yml from template @@ -97,17 +74,13 @@ jobs: exit 1 fi - - name: Run Robot Framework tests + - name: Run Robot Framework tests (No API Keys) working-directory: tests env: - # Required for test runner script - DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - HF_TOKEN: ${{ secrets.HF_TOKEN }} CLEANUP_CONTAINERS: "false" # Don't cleanup in CI - handled by workflow run: | - # Use the unified test script that mirrors local development - ./run-robot-tests.sh + # Use the no-API test script (excludes tests tagged with requires-api-keys) + ./run-no-api-tests.sh TEST_EXIT_CODE=$? echo "test_exit_code=$TEST_EXIT_CODE" >> $GITHUB_ENV exit 0 # Don't fail here, we'll fail at the end after uploading artifacts @@ -126,29 +99,29 @@ jobs: if: always() id: check_results run: | - if [ -f tests/results/output.xml ]; then + if [ -f tests/results-no-api/output.xml ]; then echo "results_exist=true" >> $GITHUB_OUTPUT else echo "results_exist=false" >> $GITHUB_OUTPUT - echo "⚠️ No test results found in tests/results/" - ls -la tests/results/ || echo "Results directory doesn't exist" + echo "⚠️ No test results found in tests/results-no-api/" + ls -la tests/results-no-api/ || echo "Results directory doesn't exist" fi - name: Upload Robot Framework HTML reports if: always() && steps.check_results.outputs.results_exist == 'true' uses: actions/upload-artifact@v4 with: - name: robot-test-reports-html + name: robot-test-reports-html-no-api path: | - tests/results/report.html - tests/results/log.html + tests/results-no-api/report.html + tests/results-no-api/log.html retention-days: 30 - name: Publish HTML Report as GitHub Pages artifact if: always() && steps.check_results.outputs.results_exist == 'true' uses: actions/upload-pages-artifact@v3 with: - path: tests/results + path: tests/results-no-api - name: Deploy to GitHub Pages if: always() && steps.check_results.outputs.results_exist == 'true' @@ -162,7 +135,7 @@ jobs: # Parse test results python3 << 'PYTHON_SCRIPT' > test_summary.txt import xml.etree.ElementTree as ET - tree = ET.parse('tests/results/output.xml') + tree = ET.parse('tests/results-no-api/output.xml') root = tree.getroot() stats = root.find('.//total/stat') if stats is not None: @@ -197,10 +170,13 @@ jobs: const status = failed === '0' ? '✅ All tests passed!' : '❌ Some tests failed'; const emoji = failed === '0' ? '🎉' : '⚠️'; - const comment = `## ${emoji} Robot Framework Test Results + const comment = `## ${emoji} Robot Framework Test Results (No API Keys) **Status**: ${status} + ℹ️ **Note**: This run excludes tests requiring external API keys (Deepgram, OpenAI). + Tests tagged with \`requires-api-keys\` will run on dev/main branches. + | Metric | Count | |--------|-------| | ✅ Passed | ${passed} | @@ -214,8 +190,8 @@ jobs: - [📝 Detailed Log](${pagesUrl}log.html) **Download Artifacts:** - - [robot-test-reports-html](${runUrl}) - HTML reports - - [robot-test-results-xml](${runUrl}) - XML output + - [robot-test-reports-html-no-api](${runUrl}) - HTML reports + - [robot-test-results-xml-no-api](${runUrl}) - XML output --- *[View full workflow run](${runUrl})*`; @@ -231,8 +207,8 @@ jobs: if: always() && steps.check_results.outputs.results_exist == 'true' uses: actions/upload-artifact@v4 with: - name: robot-test-results-xml - path: tests/results/output.xml + name: robot-test-results-xml-no-api + path: tests/results-no-api/output.xml retention-days: 30 - name: Upload logs on failure @@ -248,12 +224,12 @@ jobs: - name: Display test results summary if: always() run: | - if [ -f tests/results/output.xml ]; then - echo "Test results generated successfully" + if [ -f tests/results-no-api/output.xml ]; then + echo "Test results generated successfully (No API Keys mode)" echo "========================================" python3 << 'PYTHON_SCRIPT' import xml.etree.ElementTree as ET - tree = ET.parse('tests/results/output.xml') + tree = ET.parse('tests/results-no-api/output.xml') root = tree.getroot() stats = root.find('.//total/stat') if stats is not None: @@ -265,10 +241,12 @@ jobs: PYTHON_SCRIPT echo "========================================" echo "" + echo "ℹ️ Tests excluded: requires-api-keys (run on dev/main branches)" + echo "" echo "📊 FULL TEST REPORTS AVAILABLE:" echo " 1. Go to the 'Summary' tab at the top of this page" echo " 2. Scroll down to 'Artifacts' section" - echo " 3. Download 'robot-test-reports-html'" + echo " 3. Download 'robot-test-reports-html-no-api'" echo " 4. Extract and open report.html or log.html in your browser" echo "" echo "The HTML reports provide:" diff --git a/backends/advanced/src/advanced_omi_backend/services/memory/providers/mock_llm_provider.py b/backends/advanced/src/advanced_omi_backend/services/memory/providers/mock_llm_provider.py new file mode 100644 index 00000000..11ee893e --- /dev/null +++ b/backends/advanced/src/advanced_omi_backend/services/memory/providers/mock_llm_provider.py @@ -0,0 +1,130 @@ +""" +Mock LLM provider for testing without external API dependencies. + +This provider returns predefined responses for testing purposes, allowing +tests to run without OpenAI or other external LLM APIs. +""" + +import random +from typing import Any, Dict, List, Optional + +from ..base import LLMProviderBase + + +class MockLLMProvider(LLMProviderBase): + """ + Mock LLM provider for testing. + + Returns predefined memory extractions, embeddings, and action proposals. + Useful for testing API contracts and data flow without external APIs. + """ + + def __init__(self, config: Dict[str, Any] = None): + """Initialize the mock LLM provider. + + Args: + config: Optional configuration dictionary (ignored in mock) + """ + self._is_connected = False + self.embedding_dimension = 384 # Standard dimension for mock embeddings + + async def extract_memories(self, text: str, prompt: str) -> List[str]: + """ + Return predefined mock memories extracted from text. + + Args: + text: Input text to extract memories from (analyzed for mock generation) + prompt: System prompt (ignored in mock) + + Returns: + List of mock memory strings based on text content + """ + # Generate deterministic mock memories based on text content + # This simulates what a real LLM would extract + + if not text or len(text.strip()) == 0: + return [] + + # Simple heuristic: generate 1-3 mock memories based on text length + num_memories = min(3, max(1, len(text) // 200)) + + mock_memories = [ + "The user discussed testing without API dependencies.", + "Mock services are being used for test execution.", + "The conversation focused on technical implementation details.", + ] + + # Return subset based on text length + return mock_memories[:num_memories] + + async def generate_embeddings(self, texts: List[str]) -> List[List[float]]: + """ + Generate mock embedding vectors for the given texts. + + Args: + texts: List of text strings to embed + + Returns: + List of mock embedding vectors (deterministic based on text content) + """ + embeddings = [] + + for text in texts: + # Generate deterministic embeddings based on text hash + # This ensures same text always gets same embedding + seed = hash(text) % (2**32) + random.seed(seed) + + # Generate random normalized vector + embedding = [random.gauss(0, 0.3) for _ in range(self.embedding_dimension)] + + # Normalize to unit length (standard for embeddings) + magnitude = sum(x**2 for x in embedding) ** 0.5 + normalized_embedding = [x / magnitude for x in embedding] + + embeddings.append(normalized_embedding) + + return embeddings + + async def propose_memory_actions( + self, + retrieved_old_memory: List[Dict[str, str]] | List[str], + new_facts: List[str], + custom_prompt: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Return mock memory action proposals. + + Args: + retrieved_old_memory: List of existing memories (ignored in mock) + new_facts: List of new facts to process + custom_prompt: Optional custom prompt (ignored in mock) + + Returns: + Dictionary containing mock memory actions + """ + # Return simple ADD actions for all new facts + # This simulates the LLM deciding to add all new facts as memories + + if not new_facts: + return {"memory": []} + + actions = [] + for idx, fact in enumerate(new_facts): + actions.append({ + "id": str(idx), + "event": "ADD", + "text": fact, + "old_memory": None + }) + + return {"memory": actions} + + async def test_connection(self) -> bool: + """ + Test mock provider connection (always returns True). + + Returns: + True (mock provider is always available) + """ + return True diff --git a/backends/advanced/src/advanced_omi_backend/services/transcription/mock_provider.py b/backends/advanced/src/advanced_omi_backend/services/transcription/mock_provider.py new file mode 100644 index 00000000..f6a2d9c0 --- /dev/null +++ b/backends/advanced/src/advanced_omi_backend/services/transcription/mock_provider.py @@ -0,0 +1,82 @@ +""" +Mock transcription provider for testing without external API dependencies. + +This provider returns predefined transcripts for testing purposes, allowing +tests to run without Deepgram or other external transcription APIs. +""" + +from typing import Optional +from .base import BatchTranscriptionProvider + + +class MockTranscriptionProvider(BatchTranscriptionProvider): + """ + Mock transcription provider for testing. + + Returns predefined transcripts with word-level timestamps. + Useful for testing API contracts and data flow without external APIs. + """ + + def __init__(self): + """Initialize the mock transcription provider.""" + self._is_connected = False + + @property + def name(self) -> str: + """Return the provider name for logging.""" + return "mock" + + async def transcribe(self, audio_data: bytes, sample_rate: int, diarize: bool = False) -> dict: + """ + Return a predefined mock transcript. + + Args: + audio_data: Raw audio bytes (ignored in mock) + sample_rate: Audio sample rate (ignored in mock) + diarize: Whether to enable speaker diarization (ignored in mock) + + Returns: + Dictionary containing predefined transcript with words and segments + """ + # Calculate audio duration from bytes (assuming 16-bit PCM) + audio_duration = len(audio_data) / (sample_rate * 2) # 2 bytes per sample + + # Return a mock transcript with word-level timestamps + # This simulates a real transcription result + mock_transcript = "This is a mock transcription for testing purposes." + + # Generate mock words with timestamps + words = [ + {"word": "This", "start": 0.0, "end": 0.3, "confidence": 0.99, "speaker": 0}, + {"word": "is", "start": 0.3, "end": 0.5, "confidence": 0.99, "speaker": 0}, + {"word": "a", "start": 0.5, "end": 0.6, "confidence": 0.99, "speaker": 0}, + {"word": "mock", "start": 0.6, "end": 0.9, "confidence": 0.99, "speaker": 0}, + {"word": "transcription", "start": 0.9, "end": 1.5, "confidence": 0.98, "speaker": 0}, + {"word": "for", "start": 1.5, "end": 1.7, "confidence": 0.99, "speaker": 0}, + {"word": "testing", "start": 1.7, "end": 2.1, "confidence": 0.99, "speaker": 0}, + {"word": "purposes", "start": 2.1, "end": 2.6, "confidence": 0.97, "speaker": 0}, + ] + + # Mock segments (single speaker for simplicity) + segments = [ + { + "speaker": 0, + "start": 0.0, + "end": 2.6, + "text": mock_transcript + } + ] + + return { + "text": mock_transcript, + "words": words, + "segments": segments if diarize else [] + } + + async def connect(self, client_id: Optional[str] = None): + """Initialize the mock provider (no-op).""" + self._is_connected = True + + async def disconnect(self): + """Cleanup the mock provider (no-op).""" + self._is_connected = False diff --git a/tests/configs/mock-services.yml b/tests/configs/mock-services.yml new file mode 100644 index 00000000..f63f4a1b --- /dev/null +++ b/tests/configs/mock-services.yml @@ -0,0 +1,52 @@ +# Mock Services Configuration for Testing Without API Keys +# ======================================================== +# +# This configuration disables external API-dependent services +# (transcription, LLM, embeddings) while keeping core services +# (MongoDB, Redis, Qdrant) operational. +# +# Use this config for endpoint and infrastructure tests that don't +# require actual transcription or memory extraction. + +chat: + system_prompt: You are a helpful AI assistant with access to the user's personal + memories and conversation history. + +defaults: + # No default STT provider (transcription disabled) + # No default LLM provider (memory extraction disabled) + # No default embedding provider + vector_store: vs-qdrant # Keep Qdrant for memory storage tests + +memory: + extraction: + enabled: false # Disable memory extraction (no LLM needed) + prompt: '' + provider: chronicle # Use Chronicle provider (local, no API needed) + timeout_seconds: 1200 + +models: + # Qdrant vector store - no API key required, local service + - api_family: qdrant + description: Qdrant vector database (local) + model_params: + collection_name: omi_memories + host: ${QDRANT_BASE_URL:-qdrant} + port: ${QDRANT_PORT:-6333} + model_provider: qdrant + model_type: vector_store + model_url: http://${QDRANT_BASE_URL:-qdrant}:${QDRANT_PORT:-6333} + name: vs-qdrant + +# Speaker recognition disabled (no HF_TOKEN needed) +speaker_recognition: + enabled: false + timeout: 60 + +# Note: This configuration is designed for tests that: +# - Test API endpoints (CRUD operations, auth, permissions) +# - Test infrastructure (worker management, queue operations) +# - Test system health and readiness +# +# Tests requiring transcription or memory extraction should use +# deepgram-openai.yml or parakeet-ollama.yml configs instead. diff --git a/tests/integration/integration_test.robot b/tests/integration/integration_test.robot index 3d561616..e8ef6563 100644 --- a/tests/integration/integration_test.robot +++ b/tests/integration/integration_test.robot @@ -23,7 +23,7 @@ Test Setup Clear Test Databases *** Test Cases *** Full Pipeline Integration Test [Documentation] Complete end-to-end test of audio processing pipeline - [Tags] e2e + [Tags] e2e requires-api-keys [Timeout] 600s Log Starting Full Pipeline Integration Test INFO diff --git a/tests/run-no-api-tests.sh b/tests/run-no-api-tests.sh new file mode 100755 index 00000000..a802f43b --- /dev/null +++ b/tests/run-no-api-tests.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# Robot Framework Test Runner (No API Keys Required) +# Runs tests that don't require external API services (Deepgram, OpenAI) +# Excludes tests tagged with 'requires-api-keys' + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if we're in the right directory +if [ ! -f "Makefile" ] || [ ! -d "endpoints" ]; then + print_error "Please run this script from the tests/ directory" + exit 1 +fi + +# Set absolute paths for consistent directory references +TESTS_DIR="$(pwd)" +BACKEND_DIR="$(cd ../backends/advanced && pwd)" + +print_info "Robot Framework Test Runner (No API Keys)" +print_info "==========================================" +print_info "This runner executes tests that don't require external API services" +print_info "Tests tagged with 'requires-api-keys' are excluded" + +# Configuration +CLEANUP_CONTAINERS="${CLEANUP_CONTAINERS:-false}" +OUTPUTDIR="${OUTPUTDIR:-results-no-api}" + +# Use mock services config (no API keys needed) +export CONFIG_FILE="${CONFIG_FILE:-configs/mock-services.yml}" + +# Convert CONFIG_FILE to absolute path +if [[ ! "$CONFIG_FILE" = /* ]]; then + CONFIG_FILE="$(cd "$(dirname "$CONFIG_FILE")" && pwd)/$(basename "$CONFIG_FILE")" +fi + +print_info "Using config file: $CONFIG_FILE" +print_warning "Memory extraction and transcription are disabled in this mode" + +# Load environment variables if available (but don't require them) +if [ -f "setup/.env.test" ]; then + print_info "Loading environment variables from setup/.env.test..." + set -a + source setup/.env.test + set +a +fi + +# Create test environment file if it doesn't exist (without API keys) +if [ ! -f "setup/.env.test" ]; then + print_info "Creating test environment file..." + mkdir -p setup + cat > setup/.env.test << EOF +# API URLs +API_URL=http://localhost:8001 +BACKEND_URL=http://localhost:8001 +FRONTEND_URL=http://localhost:3001 + +# Test Admin Credentials +ADMIN_EMAIL=test-admin@example.com +ADMIN_PASSWORD=test-admin-password-123 + +# Test Configuration +TEST_TIMEOUT=120 +TEST_DEVICE_NAME=robot-test + +# Docker Compose Project Name +COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-advanced-backend-test} + +# Note: No API keys required for this test mode +# OPENAI_API_KEY and DEEPGRAM_API_KEY are not needed +EOF + print_success "Created setup/.env.test" +fi + +# Start test containers using dedicated startup script +FRESH_BUILD=true "$TESTS_DIR/setup-test-containers.sh" + +# Run Robot Framework tests via Makefile with tag exclusion +# Exclude tests that require API keys +print_info "Running Robot Framework tests (excluding requires-api-keys tag)..." +print_info "Output directory: $OUTPUTDIR" + +# Run tests with tag exclusion +if timeout 30m uv run --with-requirements test-requirements.txt \ + robot --exclude requires-api-keys \ + --outputdir "$OUTPUTDIR" \ + --loglevel INFO \ + --consolecolors on \ + --consolemarkers on \ + .; then + TEST_EXIT_CODE=0 +else + TEST_EXIT_CODE=$? +fi + +# Show service logs if tests failed +if [ $TEST_EXIT_CODE -ne 0 ]; then + print_info "Showing service logs..." + cd "$BACKEND_DIR" + echo "=== Backend Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 chronicle-backend-test + echo "" + echo "=== Worker Logs (last 50 lines) ===" + docker compose -f docker-compose-test.yml logs --tail=50 workers-test + cd "$TESTS_DIR" +fi + +# Display test results summary +if [ -f "$OUTPUTDIR/output.xml" ]; then + print_info "Test Results Summary:" + uv run python3 << 'PYTHON_SCRIPT' +import xml.etree.ElementTree as ET +import os + +output_file = os.getenv('OUTPUTDIR', 'results-no-api') + '/output.xml' +try: + tree = ET.parse(output_file) + root = tree.getroot() + + # Get overall stats + stats = root.find('.//total/stat') + if stats is not None: + passed = stats.get("pass", "0") + failed = stats.get("fail", "0") + print(f'✅ Passed: {passed}') + print(f'❌ Failed: {failed}') + print(f'📊 Total: {int(passed) + int(failed)}') + + # Show failed tests if any + if int(failed) > 0: + print('\n❌ Failed Tests:') + failed_tests = root.findall('.//test') + for test in failed_tests: + status = test.find('status') + if status is not None and status.get('status') == 'FAIL': + test_name = test.get('name', 'Unknown') + print(f' - {test_name}') +except Exception as e: + print(f'Error parsing results: {e}') +PYTHON_SCRIPT +fi + +# Cleanup containers if requested +if [ "$CLEANUP_CONTAINERS" = "true" ]; then + print_info "Cleaning up test containers..." + cd "$BACKEND_DIR" + docker compose -f docker-compose-test.yml down -v --remove-orphans + cd "$TESTS_DIR" + print_success "Cleanup completed" +fi + +# Final status +if [ $TEST_EXIT_CODE -eq 0 ]; then + print_success "All tests passed! ✅" +else + print_error "Some tests failed ❌" + exit $TEST_EXIT_CODE +fi diff --git a/tests/setup/test_manager_keywords.robot b/tests/setup/test_manager_keywords.robot index ed29f1f8..a1935dc4 100644 --- a/tests/setup/test_manager_keywords.robot +++ b/tests/setup/test_manager_keywords.robot @@ -122,6 +122,30 @@ Create Fixture Conversation ... Returns the conversation ID [Arguments] ${device_name}=fixture-device + # Check if a fixture already exists via API + ${conversations}= Get User Conversations + ${fixtures}= Evaluate [c for c in $conversations if c.get('is_fixture', False)] + ${fixture_count}= Get Length ${fixtures} + + # If fixture exists, reuse it + IF ${fixture_count} > 0 + ${conversation_id}= Set Variable ${fixtures}[0][conversation_id] + Log To Console \n✓ Reusing existing fixture conversation: ${conversation_id} + + # Verify it still has transcript (sanity check) + Dictionary Should Contain Key ${fixtures}[0] transcript + ${transcript}= Set Variable ${fixtures}[0][transcript] + Should Not Be Empty ${transcript} Fixture conversation has no transcript + ${transcript_len}= Get Length ${transcript} + Log To Console ✓ Fixture transcript length: ${transcript_len} chars + + # Set global variable for other tests to use + Set Global Variable ${FIXTURE_CONVERSATION_ID} ${conversation_id} + + RETURN ${conversation_id} + END + + # No fixture exists, create new one Log To Console \nCreating fixture conversation... # Upload test audio to fixtures folder diff --git a/tests/tags.md b/tests/tags.md index 6ddb6fba..8554a51c 100644 --- a/tests/tags.md +++ b/tests/tags.md @@ -4,7 +4,7 @@ This document defines the standard tags used across the Chronicle test suite. ## Simplified Tag Set -Chronicle uses a **minimal, focused tag set** for test organization. Only 11 tags are permitted. +Chronicle uses a **minimal, focused tag set** for test organization. Only 12 tags are permitted. ## Tag Format @@ -88,6 +88,15 @@ Chronicle uses a **minimal, focused tag set** for test organization. Only 11 tag - Full pipeline testing - Cross-service integration +### Special Tags + +**`requires-api-keys`** - Tests requiring external API services (cloud providers) +- Full E2E integration tests with transcription and LLM processing +- Memory extraction verification tests +- Transcript similarity verification tests +- Requires: DEEPGRAM_API_KEY and/or OPENAI_API_KEY environment variables +- These tests are excluded from PR runs by default (run only on dev/main branches) + ## Tag Usage Guidelines ### Single Tag per Test (Preferred) @@ -132,6 +141,7 @@ Use 2-3 tags only when testing interactions between components: 8. **Is it about health checks?** → `health` 9. **Is it end-to-end?** → `e2e` 10. **Is it infrastructure/config?** → `infra` +11. **Does it require external API keys?** → Add `requires-api-keys` tag ### Examples @@ -163,7 +173,7 @@ Use 2-3 tags only when testing interactions between components: ## Prohibited Tags -**DO NOT create or use any tags other than the 11 approved tags above.** +**DO NOT create or use any tags other than the 12 approved tags above.** Commonly misused tags that should NOT be used: - ❌ `positive`, `negative` - Test outcome is in the results, not tags @@ -226,10 +236,11 @@ Current distribution (approximate): - `health`: 9 tests - `audio-streaming`: 4 tests - `audio-upload`: 3 tests +- `requires-api-keys`: 1 test (integration_test.robot) - `audio-batch`: 0 tests (reserved for future use) --- -**Last Updated:** 2025-01-23 -**Total Approved Tags:** 11 +**Last Updated:** 2026-01-11 +**Total Approved Tags:** 12 **Enforcement:** Mandatory - no exceptions From 5bc99084a409ec9cb250ff0d87561db2f22c1f38 Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:21:30 +0000 Subject: [PATCH 2/4] Enhance testing documentation and workflows for API key separation - Updated CLAUDE.md to clarify test execution modes, emphasizing the separation of tests requiring API keys from those that do not. - Expanded the testing guidelines in TESTING_GUIDELINES.md to detail the organization of tests based on API dependencies, including tagging conventions and execution paths. - Improved mock-services.yml to include dummy configurations for LLM and embedding services, ensuring tests can run without actual API calls. - Added comprehensive documentation on GitHub workflows for different test scenarios, enhancing clarity for contributors and maintainers. --- CLAUDE.md | 92 ++++++++++++--- tests/TESTING_GUIDELINES.md | 180 ++++++++++++++++++++++++++++++ tests/configs/mock-services.yml | 31 ++++- tests/run-no-api-tests.sh | 6 +- tests/run-robot-tests.sh | 8 +- tests/setup-test-containers.sh | 8 +- tests/teardown-test-containers.sh | 14 ++- 7 files changed, 314 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 88c901be..1e55c229 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,25 +106,68 @@ The project includes simplified test scripts that mirror CI workflows: ``` #### Advanced Backend Integration Tests + +**Three Test Execution Modes:** + +1. **No-API Tests** (Fast, No External Dependencies) ```bash -cd backends/advanced +cd tests + +# Run tests without API keys (excludes requires-api-keys tag) +./run-no-api-tests.sh + +# ~70% of test suite +# Uses mock services config +# No DEEPGRAM_API_KEY or OPENAI_API_KEY required +# Fast feedback (~10-15 minutes) +``` + +2. **Full Tests with API Keys** (Comprehensive) +```bash +cd tests # Requires .env file with DEEPGRAM_API_KEY and OPENAI_API_KEY -cp .env.template .env # Configure API keys +cp setup/.env.test.template setup/.env.test # Configure API keys + +# Run full integration test suite (100% of tests) +./run-robot-tests.sh + +# Leave test containers running for debugging +CLEANUP_CONTAINERS=false ./run-robot-tests.sh +``` -# Run full integration test suite -./run-test.sh +3. **API-Only Tests** (Optional) +```bash +cd tests + +# Run only tests that require API keys +./run-api-tests.sh -# Leave test containers running for debugging (don't auto-cleanup) -CLEANUP_CONTAINERS=false ./run-test.sh +# ~30% of test suite +# Only E2E tests with transcription/memory extraction ``` +#### Test Separation by API Requirements + +Tests are separated into two categories: + +- **No API Keys Required** (~70%): Endpoint tests, infrastructure tests, basic integration + - Uses `configs/mock-services.yml` + - Runs on all PRs by default + - Fast CI feedback + +- **API Keys Required** (~30%): Full E2E tests with transcription and memory extraction + - Uses `configs/deepgram-openai.yml` + - Tagged with `requires-api-keys` + - Runs on dev/main branches or when PR labeled with `test-with-api-keys` + #### Test Configuration Flags -- **CLEANUP_CONTAINERS** (default: true): Automatically stop and remove test containers after test completion - - Set to `false` for debugging: `CLEANUP_CONTAINERS=false ./run-test.sh` -- **REBUILD** (default: true): Force rebuild containers with latest code changes -- **FRESH_RUN** (default: true): Start with clean database and fresh containers -- **TRANSCRIPTION_PROVIDER** (default: deepgram): Choose transcription provider (deepgram or parakeet) +- **CLEANUP_CONTAINERS** (default: false): Automatically stop and remove test containers after test completion + - Set to `true` for cleanup: `CLEANUP_CONTAINERS=true ./run-robot-tests.sh` +- **CONFIG_FILE**: Choose test configuration + - `configs/mock-services.yml` - No API keys (default for run-no-api-tests.sh) + - `configs/deepgram-openai.yml` - With API keys (default for run-robot-tests.sh) + - `configs/parakeet-ollama.yml` - Fully local (no external APIs) #### Test Environment Variables Tests use isolated test environment with overridden credentials: @@ -140,10 +183,33 @@ Tests use isolated test environment with overridden credentials: #### Test Script Features - **Environment Compatibility**: Works with both local .env files and CI environment variables - **Isolated Test Environment**: Separate ports and database prevent conflicts with running services -- **Automatic Cleanup**: Configurable via CLEANUP_CONTAINERS flag (default: true) +- **Automatic Cleanup**: Configurable via CLEANUP_CONTAINERS flag (default: false for faster re-runs) - **Colored Output**: Clear progress indicators and error reporting -- **Timeout Protection**: 15-minute timeout for advanced backend, 30-minute for speaker recognition +- **Timeout Protection**: 30-minute timeout for test execution - **Fresh Testing**: Clean database and containers for each test run +- **API Key Separation**: Ability to run tests with or without external API dependencies + +#### GitHub Workflows + +**Three workflows handle test execution:** + +1. **`robot-tests.yml`** - PR Tests (No API Keys) + - Triggers: All pull requests + - Execution: Excludes `requires-api-keys` tests (~70% of suite) + - No secrets required + - Fast feedback for contributors + +2. **`full-tests-with-api.yml`** - Dev/Main Tests (Full Suite) + - Triggers: Push to dev/main branches + - Execution: All tests including API-dependent (~100% of suite) + - Requires: DEEPGRAM_API_KEY, OPENAI_API_KEY + - Comprehensive validation before deployment + +3. **`pr-tests-with-api.yml`** - Label-Triggered PR Tests + - Triggers: PR with `test-with-api-keys` label + - Execution: Full test suite before merge + - Requires: DEEPGRAM_API_KEY, OPENAI_API_KEY + - Useful for testing API integration changes ### Mobile App Development ```bash diff --git a/tests/TESTING_GUIDELINES.md b/tests/TESTING_GUIDELINES.md index 6c07719a..b84da0a1 100644 --- a/tests/TESTING_GUIDELINES.md +++ b/tests/TESTING_GUIDELINES.md @@ -214,6 +214,186 @@ ${jobs}= Wait Until Keyword Succeeds 30s 2s - Use consistent variable naming across tests - Document required environment variables and their purposes +## API Key Separation and Test Organization + +### Overview + +Chronicle tests are separated into two execution paths based on external API dependencies: + +1. **No API Keys Required (~70% of tests)** - Run on all PRs by default +2. **API Keys Required (~30% of tests)** - Run on dev/main branches only + +This separation enables: +- Fast PR validation without external API dependencies +- External contributors can run full CI without secret access +- Reduced API costs (only charged on dev/main pushes) +- Comprehensive testing still happens on protected branches + +### The `requires-api-keys` Tag + +**Purpose**: Mark tests that require external API services (Deepgram, OpenAI, etc.) + +**Usage**: Add to test files that make external API calls for transcription or memory extraction: + +```robot +*** Test Cases *** +Full Pipeline Integration Test + [Documentation] Complete end-to-end test with transcription and memory extraction + [Tags] e2e requires-api-keys + [Timeout] 600s + + # This test will be excluded from PR runs + # It will run on dev/main branches with API keys +``` + +### When to Use `requires-api-keys` + +**Add this tag when tests:** +- Require actual transcription (Deepgram or other STT providers) +- Require memory extraction with LLM (OpenAI, Ollama with real inference) +- Verify transcript quality against ground truth +- Test end-to-end pipeline with real API integration + +**Do NOT add this tag when tests:** +- Test API endpoints (CRUD operations, permissions, etc.) +- Test infrastructure (worker management, queue operations) +- Test system health and readiness +- Can work with mock/stub services + +### Test Execution Modes + +**1. No-API Tests (PR runs)** +```bash +# Excludes tests tagged with requires-api-keys +cd tests +./run-no-api-tests.sh +``` +- Uses `configs/mock-services.yml` +- No external API calls +- Fast feedback (~10-15 minutes) +- Runs ~70% of test suite + +**2. Full Tests with API Keys (dev/main runs)** +```bash +# Runs all tests including API-dependent ones +cd tests +./run-robot-tests.sh +``` +- Uses `configs/deepgram-openai.yml` +- Requires DEEPGRAM_API_KEY and OPENAI_API_KEY +- Comprehensive validation (~20-30 minutes) +- Runs 100% of test suite + +**3. Label-Triggered PR Tests** +- Add label `test-with-api-keys` to PR +- Triggers full test suite before merge +- Useful for testing API integration changes + +### Mock Services Configuration + +For tests that don't require API keys, use the mock services config: + +**File**: `tests/configs/mock-services.yml` + +**Features**: +- Disables external transcription and LLM services +- Keeps core services operational (MongoDB, Redis, Qdrant) +- No API keys required +- Fast test execution + +**Use Cases**: +- Endpoint testing (auth, permissions, CRUD) +- Infrastructure testing (workers, queues) +- System health monitoring +- Local development without API keys + +### Writing Tests for API Separation + +**Good Example - Endpoint Test (No API Keys)**: +```robot +*** Test Cases *** +User Can Create and Delete Conversations + [Documentation] Test conversation CRUD without transcription + [Tags] conversation + + ${session}= Get Admin API Session + ${conversation}= Create Test Conversation ${session} + ${deleted}= Delete Conversation ${session} ${conversation}[id] + Should Be True ${deleted} +``` + +**Good Example - Integration Test (Requires API Keys)**: +```robot +*** Test Cases *** +Audio Upload Produces Quality Transcript + [Documentation] Verify transcription quality with ground truth + [Tags] e2e requires-api-keys + + ${conversation}= Upload Audio File ${TEST_AUDIO_FILE} + Verify Transcription Quality ${conversation} ${EXPECTED_TRANSCRIPT} + Verify Memory Extraction ${conversation} +``` + +### GitHub Workflows + +**Three workflows handle test execution:** + +1. **`robot-tests.yml`** (PR - No API Keys) + - Triggers: All pull requests + - Execution: Excludes `requires-api-keys` tests + - No secrets required + +2. **`full-tests-with-api.yml`** (Dev/Main - Full Suite) + - Triggers: Push to dev/main branches + - Execution: All tests including API-dependent + - Requires: DEEPGRAM_API_KEY, OPENAI_API_KEY + +3. **`pr-tests-with-api.yml`** (PR - Label Triggered) + - Triggers: PR with `test-with-api-keys` label + - Execution: Full test suite before merge + - Requires: DEEPGRAM_API_KEY, OPENAI_API_KEY + +### Tag Guidelines for API Separation + +**File-Level Tagging**: +- Tag entire test files that require API keys +- If ANY test in the file needs APIs, mark the whole file +- Simpler maintenance than per-test tagging + +**Multiple Tags**: +- Use tab-separated tags (see `tags.md`) +- Example: `[Tags] e2e requires-api-keys` +- Always include primary component tag (e2e, conversation, memory) + +**Tag Statistics**: +- `requires-api-keys`: ~1-2 test files (integration_test.robot) +- Most tests: No API requirements +- See `tests/tags.md` for complete tag list + +### Local Development + +**Running Tests Locally Without API Keys**: +```bash +cd tests +./run-no-api-tests.sh +``` +- Works without any API key configuration +- Fast feedback for most development +- Tests endpoint logic and infrastructure + +**Running Full Tests Locally**: +```bash +# Set API keys +export DEEPGRAM_API_KEY=xxx +export OPENAI_API_KEY=yyy + +cd tests +./run-robot-tests.sh +``` +- Validates full pipeline integration +- Tests transcription and memory extraction +- Use before pushing to dev/main + ## Future Additions As we develop more conventions and encounter new patterns, we will add them to this file: diff --git a/tests/configs/mock-services.yml b/tests/configs/mock-services.yml index f63f4a1b..50ae071b 100644 --- a/tests/configs/mock-services.yml +++ b/tests/configs/mock-services.yml @@ -14,8 +14,8 @@ chat: defaults: # No default STT provider (transcription disabled) - # No default LLM provider (memory extraction disabled) - # No default embedding provider + llm: mock-llm # Mock LLM (not actually used since extraction disabled) + embedding: mock-embed # Mock embeddings (not actually used) vector_store: vs-qdrant # Keep Qdrant for memory storage tests memory: @@ -26,6 +26,33 @@ memory: timeout_seconds: 1200 models: + # Dummy LLM - not actually used (memory extraction disabled) + # Using openai type with dummy key to satisfy config requirements + - api_family: openai + api_key: "dummy-key-not-used" + description: Dummy LLM for testing (not called) + model_name: gpt-4o-mini + model_output: json + model_params: + max_tokens: 2000 + temperature: 0.2 + model_provider: openai + model_type: llm + model_url: https://api.openai.com/v1 + name: mock-llm + + # Dummy embeddings - not actually used (memory extraction disabled) + - api_family: openai + api_key: "dummy-key-not-used" + description: Dummy embeddings for testing (not called) + embedding_dimensions: 384 + model_name: text-embedding-3-small + model_output: vector + model_provider: openai + model_type: embedding + model_url: https://api.openai.com/v1 + name: mock-embed + # Qdrant vector store - no API key required, local service - api_family: qdrant description: Qdrant vector database (local) diff --git a/tests/run-no-api-tests.sh b/tests/run-no-api-tests.sh index a802f43b..09fc1050 100755 --- a/tests/run-no-api-tests.sh +++ b/tests/run-no-api-tests.sh @@ -71,6 +71,10 @@ fi if [ ! -f "setup/.env.test" ]; then print_info "Creating test environment file..." mkdir -p setup + + # Set COMPOSE_PROJECT_NAME with fallback + COMPOSE_PROJECT_NAME_VALUE="${COMPOSE_PROJECT_NAME:-advanced-backend-test}" + cat > setup/.env.test << EOF # API URLs API_URL=http://localhost:8001 @@ -86,7 +90,7 @@ TEST_TIMEOUT=120 TEST_DEVICE_NAME=robot-test # Docker Compose Project Name -COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-advanced-backend-test} +COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME_VALUE} # Note: No API keys required for this test mode # OPENAI_API_KEY and DEEPGRAM_API_KEY are not needed diff --git a/tests/run-robot-tests.sh b/tests/run-robot-tests.sh index e7885d10..4c25fab2 100755 --- a/tests/run-robot-tests.sh +++ b/tests/run-robot-tests.sh @@ -121,6 +121,10 @@ export HF_TOKEN if [ ! -f "setup/.env.test" ]; then print_info "Creating test environment file..." mkdir -p setup + + # Set COMPOSE_PROJECT_NAME with fallback + COMPOSE_PROJECT_NAME_VALUE="${COMPOSE_PROJECT_NAME:-advanced-backend-test}" + cat > setup/.env.test << EOF # API URLs API_URL=http://localhost:8001 @@ -139,8 +143,8 @@ DEEPGRAM_API_KEY=${DEEPGRAM_API_KEY} TEST_TIMEOUT=120 TEST_DEVICE_NAME=robot-test -# Docker Compose Project Name (defaults to advanced-backend-test if not set) -COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-advanced-backend-test} +# Docker Compose Project Name +COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME_VALUE} EOF print_success "Created setup/.env.test" fi diff --git a/tests/setup-test-containers.sh b/tests/setup-test-containers.sh index 5d0bf5db..951db21c 100755 --- a/tests/setup-test-containers.sh +++ b/tests/setup-test-containers.sh @@ -20,7 +20,10 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; } SCRIPT_DIR="$(dirname "$0")" cd "$SCRIPT_DIR/../backends/advanced" || exit 1 -# Load environment variables for tests +# Set default COMPOSE_PROJECT_NAME (can be overridden by .env.test) +export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-advanced-backend-test}" + +# Load environment variables for tests (may override COMPOSE_PROJECT_NAME) if [ -f "$SCRIPT_DIR/setup/.env.test" ]; then print_info "Loading test environment..." set -a @@ -37,9 +40,6 @@ if [ -f "$SPEAKER_ENV" ] && [ -z "$HF_TOKEN" ]; then set +a fi -# Use unique project name -export COMPOSE_PROJECT_NAME="advanced-backend-test" - # Configuration FRESH_BUILD="${FRESH_BUILD:-false}" # Set to true for clean rebuild with volume removal diff --git a/tests/teardown-test-containers.sh b/tests/teardown-test-containers.sh index 4df129ee..d335e40e 100755 --- a/tests/teardown-test-containers.sh +++ b/tests/teardown-test-containers.sh @@ -16,10 +16,18 @@ print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } # Navigate to backend directory -cd "$(dirname "$0")/../backends/advanced" || exit 1 +SCRIPT_DIR="$(dirname "$0")" +cd "$SCRIPT_DIR/../backends/advanced" || exit 1 -# Use unique project name -export COMPOSE_PROJECT_NAME="advanced-backend-test" +# Set default COMPOSE_PROJECT_NAME (can be overridden by .env.test) +export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-advanced-backend-test}" + +# Load .env.test if available to get consistent project name +if [ -f "$SCRIPT_DIR/setup/.env.test" ]; then + set -a + source "$SCRIPT_DIR/setup/.env.test" + set +a +fi if [ "${REMOVE_VOLUMES:-false}" = "true" ]; then print_info "Stopping containers and removing volumes..." From f89a3de913d8c516b519e07966a0dd2acc192405 Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:53:54 +0000 Subject: [PATCH 3/4] Update test configurations and documentation for API key management - Modified `plugins.yml.template` to implement event subscriptions for the Home Assistant plugin, enhancing its event-driven capabilities. - Revised `README.md` to clarify test execution processes, emphasizing the distinction between tests requiring API keys and those that do not. - Updated `mock-services.yml` to streamline mock configurations, ensuring compatibility with the new testing workflows. - Added `requires-api-keys` tags to relevant test cases across various test files, improving organization and clarity regarding API dependencies. - Enhanced documentation for test scripts and configurations, providing clearer guidance for contributors on executing tests based on API key requirements. --- config/plugins.yml.template | 5 +- tests/README.md | 347 +++++------------- tests/configs/mock-services.yml | 104 ++---- tests/endpoints/audio_upload_tests.robot | 2 +- tests/endpoints/memory_tests.robot | 6 +- tests/endpoints/system_admin_tests.robot | 2 +- .../audio_streaming_integration_tests.robot | 2 + tests/integration/conversation_queue.robot | 1 + .../websocket_streaming_tests.robot | 2 + 9 files changed, 149 insertions(+), 322 deletions(-) diff --git a/config/plugins.yml.template b/config/plugins.yml.template index ef8cc63d..f4b6a64e 100644 --- a/config/plugins.yml.template +++ b/config/plugins.yml.template @@ -14,7 +14,10 @@ plugins: homeassistant: enabled: true - access_level: streaming_transcript # Execute on each streaming transcript chunk + subscriptions: # Subscribe to specific events (event-based architecture) + - transcript.streaming # Execute on each streaming transcript chunk + # - transcript.batch # Uncomment to also handle batch transcription + # - conversation.complete # Uncomment to handle completed conversations trigger: type: wake_word wake_words: # Support multiple wake words diff --git a/tests/README.md b/tests/README.md index a16a0281..12aef958 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,288 +1,137 @@ -# Chronicle API Tests +# Chronicle Test Suite -Comprehensive Robot Framework test suite for the Chronicle advanced backend API endpoints. +Quick reference guide for running Robot Framework tests locally. ## Quick Start -### Running Tests Locally - +### Run Tests Without API Keys (Fast) ```bash -# From the tests/ directory -./run-robot-tests.sh +cd tests +./run-no-api-tests.sh ``` +- **Runs**: ~70% of test suite (excludes `requires-api-keys` tag) +- **Config**: `configs/mock-services.yml` (no external APIs) +- **Time**: ~10-15 minutes +- **Use**: Daily development, PR validation -The script will: -1. Check for required API keys (DEEPGRAM_API_KEY, OPENAI_API_KEY) -2. Start test infrastructure (MongoDB, Redis, Qdrant) -3. Build and start the backend and workers -4. Run all Robot Framework tests via Makefile -5. Display results and cleanup - -**This mirrors the GitHub CI workflow for local development.** - -### Environment Setup - -The script will create `tests/setup/.env.test` automatically if it doesn't exist. You can also create it manually: - +### Run Full Test Suite (Comprehensive) ```bash -# API URLs -API_URL=http://localhost:8001 -BACKEND_URL=http://localhost:8001 -FRONTEND_URL=http://localhost:3001 +# Set API keys first +export DEEPGRAM_API_KEY=your-deepgram-key +export OPENAI_API_KEY=your-openai-key -# Test Admin Credentials -ADMIN_EMAIL=test-admin@example.com -ADMIN_PASSWORD=test-admin-password-123 - -# API Keys (required) -OPENAI_API_KEY=your-key-here -DEEPGRAM_API_KEY=your-key-here - -# Test Configuration -TEST_TIMEOUT=120 -TEST_DEVICE_NAME=robot-test +cd tests +./run-robot-tests.sh ``` +- **Runs**: 100% of test suite (all tests) +- **Config**: `configs/deepgram-openai.yml` (with external APIs) +- **Time**: ~20-30 minutes +- **Use**: Before pushing to dev/main, testing API integrations -### Configuration Options - -```bash -# Skip container cleanup (useful for debugging) -CLEANUP_CONTAINERS=false ./run-robot-tests.sh - -# Custom output directory -OUTPUTDIR=my-results ./run-robot-tests.sh - -# Leave containers running for debugging -CLEANUP_CONTAINERS=false ./run-robot-tests.sh -``` +## Test Categories -## Running Tests via Makefile +### No API Keys Required (~70%) +- **Endpoint Tests**: Auth, CRUD operations, permissions +- **Infrastructure Tests**: Worker management, queue operations +- **Health Tests**: System health and readiness checks +- **Basic Integration**: Non-transcription workflows -If you already have the backend running, you can use the Makefile directly: +### API Keys Required (~30%) +Tagged with `requires-api-keys`: +- **Audio Upload Tests**: File processing with transcription +- **Memory Tests**: LLM-based memory operations +- **Audio Streaming**: Real-time transcription tests +- **E2E Integration**: Full pipeline with transcription and memory -## Test Structure +## Configuration Files -### Test Files -- **`auth_tests.robot`** - Authentication and user management tests -- **`memory_tests.robot`** - Memory management and search tests -- **`conversation_tests.robot`** - Conversation management and versioning tests -- **`health_tests.robot`** - Health check and status endpoint tests -- **`chat_tests.robot`** - Chat service and session management tests -- **`client_queue_tests.robot`** - Client management and queue monitoring tests -- **`system_admin_tests.robot`** - System administration and configuration tests -- **`all_api_tests.robot`** - Master test suite runner +### `configs/mock-services.yml` +- No external API calls +- Dummy LLM/embedding models (satisfy config requirements) +- Used by: `run-no-api-tests.sh` +- Perfect for: Endpoint and infrastructure testing -### Resource Files -- **`resources/auth_keywords.robot`** - Authentication helper keywords -- **`resources/memory_keywords.robot`** - Memory management keywords -- **`resources/conversation_keywords.robot`** - Conversation management keywords -- **`resources/chat_keywords.robot`** - Chat service keywords -- **`resources/setup_resources.robot`** - Basic setup and health check keywords -- **`resources/login_resources.robot`** - Login-specific utilities +### `configs/deepgram-openai.yml` +- Real Deepgram transcription +- Real OpenAI LLM and embeddings +- Used by: `run-robot-tests.sh` +- Perfect for: Full E2E validation -### Configuration -- **`test_env.py`** - Environment configuration and test data -- **`.env`** - Environment variables (create from template) +### `configs/parakeet-ollama.yml` +- Local Parakeet ASR (offline transcription) +- Local Ollama LLM (offline processing) +- Perfect for: Fully offline testing -## Running Tests +## Environment Configuration -### Prerequisites -1. Chronicle backend running at `http://localhost:8001` (or set `API_URL` in `.env`) -2. Admin user credentials configured in `.env` -3. Robot Framework and RequestsLibrary installed +Tests use isolated test environment with separate ports and database: -### Environment Setup ```bash -# Copy environment template -cp .env.template .env - -# Edit .env with your configuration -API_URL=http://localhost:8001 -ADMIN_EMAIL=admin@example.com -ADMIN_PASSWORD=your-secure-admin-password +# Test Ports (avoid conflicts with dev services) +Backend: 8001 (vs 8000 prod) +MongoDB: 27018 (vs 27017 prod) +Qdrant: 6337/6338 (vs 6333/6334 prod) +WebUI: 3001 (vs 5173 prod) + +# Test Database +Database: test_db (separate from production) ``` -### Running Individual Test Suites +### Test Credentials ```bash -# Authentication and user tests -robot auth_tests.robot +# Admin account (created automatically) +ADMIN_EMAIL=test-admin@example.com +ADMIN_PASSWORD=test-admin-password-123 -# Memory management tests -robot memory_tests.robot +# Authentication secret (test-specific) +AUTH_SECRET_KEY=test-jwt-signing-key-for-integration-tests +``` -# Conversation management tests -robot conversation_tests.robot +## Test Scripts -# Health and status tests -robot health_tests.robot +### `run-no-api-tests.sh` +- Excludes tests tagged with `requires-api-keys` +- Uses `configs/mock-services.yml` +- No API key validation +- Fast feedback loop -# Chat service tests -robot chat_tests.robot +### `run-robot-tests.sh` +- Runs all tests (including API-dependent) +- Uses `configs/deepgram-openai.yml` +- Validates API keys before execution +- Comprehensive test coverage -# Client and queue tests -robot client_queue_tests.robot +### `run-api-tests.sh` (Optional) +- Runs only tests tagged with `requires-api-keys` +- Validates API integrations specifically +- ~30% of test suite -# System administration tests -robot system_admin_tests.robot -``` +## Quick Command Reference -### Running All Tests ```bash -# Run complete test suite -robot *.robot +# No-API tests (fast) +cd tests && ./run-no-api-tests.sh -# Run with specific tags -robot --include auth *.robot -robot --include positive *.robot -robot --include admin *.robot -``` +# Full tests (comprehensive) +export DEEPGRAM_API_KEY=xxx OPENAI_API_KEY=yyy +cd tests && ./run-robot-tests.sh -### Test Output -```bash -# Custom output directory -robot --outputdir results *.robot +# Run specific suites +make endpoints +make integration +make infra -# Verbose logging -robot --loglevel DEBUG *.robot +# View results +open results-no-api/report.html +open results/log.html -# Parallel execution -pabot --processes 4 *.robot +# Clean up +docker compose -f ../backends/advanced/docker-compose-test.yml down -v ``` -## Test Coverage - -### Authentication & Users (`/api/users`, `/auth`) -- ✅ Login with valid/invalid credentials -- ✅ Get current user information -- ✅ Create/update/delete users (admin only) -- ✅ User authorization and access control -- ✅ Admin privilege enforcement - -### Memory Management (`/api/memories`) -- ✅ Get user memories with pagination -- ✅ Search memories with similarity thresholds -- ✅ Get memories with transcripts -- ✅ Delete specific memories -- ✅ Admin memory access across users -- ✅ Unfiltered memory access for debugging - -### Conversation Management (`/api/conversations`) -- ✅ List and retrieve conversations -- ✅ Conversation version history -- ✅ Transcript reprocessing -- ✅ Memory reprocessing with version selection -- ✅ Version activation (transcript/memory) -- ✅ Conversation deletion and cleanup -- ✅ User data isolation - -### Health & Status (`/health`, `/readiness`) -- ✅ Main health check with service details -- ✅ Readiness check for orchestration -- ✅ Authentication service health -- ✅ Queue system health status -- ✅ Chat service health check -- ✅ System metrics (admin only) - -### Chat Service (`/api/chat`) -- ✅ Session creation and management -- ✅ Session title updates -- ✅ Message retrieval -- ✅ Chat statistics -- ✅ Memory extraction from sessions -- ✅ Session deletion and cleanup - -### Client & Queue Management -- ✅ Active client monitoring -- ✅ Queue job listing with pagination -- ✅ Queue statistics and health -- ✅ User job isolation -- ✅ Processing task monitoring (admin only) - -### System Administration -- ✅ Authentication configuration -- ✅ Diarization settings management -- ✅ Speaker configuration -- ✅ Memory configuration (YAML) -- ✅ Configuration validation and reload -- ✅ Bulk memory deletion - -## Test Categories - -### By Access Level -- **Public**: Health checks, auth config -- **User**: Memories, conversations, chat sessions -- **Admin**: User management, system config, metrics - -### By Test Type -- **Positive**: Valid operations and expected responses -- **Negative**: Invalid inputs, unauthorized access -- **Security**: Authentication, authorization, data isolation -- **Integration**: Cross-service functionality - -### By Component -- **Auth**: Authentication and authorization -- **Memory**: Memory storage and retrieval -- **Conversation**: Audio processing and transcription -- **Chat**: Interactive chat functionality -- **System**: Configuration and administration - -## Key Features Tested - -### Security -- JWT token authentication -- Role-based access control (admin vs user) -- Data isolation between users -- Unauthorized access prevention - -### Data Management -- CRUD operations for all entities -- Pagination and filtering -- Search functionality with thresholds -- Versioning and history tracking - -### System Integration -- Service health monitoring -- Configuration management -- Queue system monitoring -- Cross-service communication - -### Error Handling -- Invalid input validation -- Non-existent resource handling -- Permission denied scenarios -- Service unavailability graceful degradation - -## Maintenance - -### Adding New Tests -1. Create test file or add to existing suite -2. Use appropriate resource keywords -3. Follow naming conventions (`Test Name Test`) -4. Include proper tags and documentation -5. Add cleanup in teardown if needed - -### Updating Keywords -1. Modify resource files for reusable functionality -2. Keep keywords focused and single-purpose -3. Use proper argument handling -4. Include documentation strings - -### Environment Variables -Update `test_env.py` when adding new configuration options or test data. - -## Troubleshooting - -### Common Issues -- **401 Unauthorized**: Check admin credentials in `.env` -- **Connection Refused**: Ensure backend is running -- **Test Failures**: Check service health endpoints first -- **Timeout Errors**: Increase timeouts in test configuration - -### Debug Mode -```bash -# Run with detailed logging -robot --loglevel TRACE auth_tests.robot +## Additional Resources -# Stop on first failure -robot --exitonfailure *.robot -``` \ No newline at end of file +- **TESTING_GUIDELINES.md**: Comprehensive testing patterns and rules +- **tags.md**: Approved tag list (12 tags only) +- **setup/test_env.py**: Test environment configuration +- **setup/test_data.py**: Test data and fixtures diff --git a/tests/configs/mock-services.yml b/tests/configs/mock-services.yml index 50ae071b..34359bd4 100644 --- a/tests/configs/mock-services.yml +++ b/tests/configs/mock-services.yml @@ -1,79 +1,49 @@ -# Mock Services Configuration for Testing Without API Keys -# ======================================================== -# -# This configuration disables external API-dependent services -# (transcription, LLM, embeddings) while keeping core services -# (MongoDB, Redis, Qdrant) operational. -# -# Use this config for endpoint and infrastructure tests that don't -# require actual transcription or memory extraction. - chat: system_prompt: You are a helpful AI assistant with access to the user's personal memories and conversation history. - defaults: - # No default STT provider (transcription disabled) - llm: mock-llm # Mock LLM (not actually used since extraction disabled) - embedding: mock-embed # Mock embeddings (not actually used) - vector_store: vs-qdrant # Keep Qdrant for memory storage tests - + embedding: mock-embed + llm: mock-llm + vector_store: vs-qdrant memory: extraction: - enabled: false # Disable memory extraction (no LLM needed) + enabled: false prompt: '' - provider: chronicle # Use Chronicle provider (local, no API needed) + provider: chronicle timeout_seconds: 1200 - models: - # Dummy LLM - not actually used (memory extraction disabled) - # Using openai type with dummy key to satisfy config requirements - - api_family: openai - api_key: "dummy-key-not-used" - description: Dummy LLM for testing (not called) - model_name: gpt-4o-mini - model_output: json - model_params: - max_tokens: 2000 - temperature: 0.2 - model_provider: openai - model_type: llm - model_url: https://api.openai.com/v1 - name: mock-llm - - # Dummy embeddings - not actually used (memory extraction disabled) - - api_family: openai - api_key: "dummy-key-not-used" - description: Dummy embeddings for testing (not called) - embedding_dimensions: 384 - model_name: text-embedding-3-small - model_output: vector - model_provider: openai - model_type: embedding - model_url: https://api.openai.com/v1 - name: mock-embed - - # Qdrant vector store - no API key required, local service - - api_family: qdrant - description: Qdrant vector database (local) - model_params: - collection_name: omi_memories - host: ${QDRANT_BASE_URL:-qdrant} - port: ${QDRANT_PORT:-6333} - model_provider: qdrant - model_type: vector_store - model_url: http://${QDRANT_BASE_URL:-qdrant}:${QDRANT_PORT:-6333} - name: vs-qdrant - -# Speaker recognition disabled (no HF_TOKEN needed) +- api_family: openai + api_key: dummy-key-not-used + description: Dummy LLM for testing (not called) + model_name: gpt-4o-mini + model_output: json + model_params: + max_tokens: 2000 + temperature: 0.2 + model_provider: openai + model_type: llm + model_url: https://api.openai.com/v1 + name: mock-llm +- api_family: openai + api_key: dummy-key-not-used + description: Dummy embeddings for testing (not called) + embedding_dimensions: 384 + model_name: text-embedding-3-small + model_output: vector + model_provider: openai + model_type: embedding + model_url: https://api.openai.com/v1 + name: mock-embed +- api_family: qdrant + description: Qdrant vector database (local) + model_params: + collection_name: omi_memories + host: ${QDRANT_BASE_URL:-qdrant} + port: ${QDRANT_PORT:-6333} + model_provider: qdrant + model_type: vector_store + model_url: http://${QDRANT_BASE_URL:-qdrant}:${QDRANT_PORT:-6333} + name: vs-qdrant speaker_recognition: enabled: false timeout: 60 - -# Note: This configuration is designed for tests that: -# - Test API endpoints (CRUD operations, auth, permissions) -# - Test infrastructure (worker management, queue operations) -# - Test system health and readiness -# -# Tests requiring transcription or memory extraction should use -# deepgram-openai.yml or parakeet-ollama.yml configs instead. diff --git a/tests/endpoints/audio_upload_tests.robot b/tests/endpoints/audio_upload_tests.robot index bf2a0df5..efeade80 100644 --- a/tests/endpoints/audio_upload_tests.robot +++ b/tests/endpoints/audio_upload_tests.robot @@ -21,7 +21,7 @@ Suite Teardown Suite Teardown Test Setup Test Cleanup -Test Tags audio-upload +Test Tags audio-upload requires-api-keys *** Test Cases *** diff --git a/tests/endpoints/memory_tests.robot b/tests/endpoints/memory_tests.robot index 0672a2bd..c8d2af49 100644 --- a/tests/endpoints/memory_tests.robot +++ b/tests/endpoints/memory_tests.robot @@ -15,7 +15,7 @@ Test Setup Test Cleanup Get User Memories Test [Documentation] Test getting memories for authenticated user and verify trumpet flower memory exists if memories are present - [Tags] memory permissions + [Tags] memory permissions requires-api-keys ${response}= GET On Session api /api/memories @@ -59,7 +59,7 @@ Get User Memories Test Search Memories Test [Documentation] Test searching memories by query and verify trumpet flower memory exists - [Tags] memory + [Tags] memory requires-api-keys &{params}= Create Dictionary query=trumpet flower limit=20 score_threshold=0.4 ${response}= GET On Session api /api/memories/search params=${params} @@ -92,7 +92,7 @@ Search Memories Test Memory Pagination Test [Documentation] Test memory pagination with different limits - [Tags] memory + [Tags] memory requires-api-keys # Test with small limit &{params1}= Create Dictionary limit=5 diff --git a/tests/endpoints/system_admin_tests.robot b/tests/endpoints/system_admin_tests.robot index c8ce0c4c..eb33c039 100644 --- a/tests/endpoints/system_admin_tests.robot +++ b/tests/endpoints/system_admin_tests.robot @@ -140,7 +140,7 @@ Reload Memory Config Test Delete All User Memories Test [Documentation] Test deleting all memories for current user - [Tags] infra memory permissions + [Tags] infra memory permissions requires-api-keys ${response}= DELETE On Session api /api/admin/memory/delete-all Should Be Equal As Integers ${response.status_code} 200 diff --git a/tests/integration/audio_streaming_integration_tests.robot b/tests/integration/audio_streaming_integration_tests.robot index 3e70c718..7d085084 100644 --- a/tests/integration/audio_streaming_integration_tests.robot +++ b/tests/integration/audio_streaming_integration_tests.robot @@ -16,6 +16,8 @@ Suite Setup Suite Setup Suite Teardown Suite Teardown Test Setup Test Cleanup +Test Tags audio-streaming requires-api-keys + *** Variables *** diff --git a/tests/integration/conversation_queue.robot b/tests/integration/conversation_queue.robot index 3cda6639..2716bf75 100644 --- a/tests/integration/conversation_queue.robot +++ b/tests/integration/conversation_queue.robot @@ -14,6 +14,7 @@ Suite Setup Suite Setup Suite Teardown Suite Teardown Test Setup Clear Test Databases +Test Tags queue requires-api-keys *** Test Cases *** diff --git a/tests/integration/websocket_streaming_tests.robot b/tests/integration/websocket_streaming_tests.robot index cfc79799..2edec480 100644 --- a/tests/integration/websocket_streaming_tests.robot +++ b/tests/integration/websocket_streaming_tests.robot @@ -14,6 +14,8 @@ Suite Setup Suite Setup Suite Teardown Suite Teardown Test Setup Test Cleanup +Test Tags audio-streaming requires-api-keys + *** Variables *** From 8ca401c43281ed368ee5656a023586ed31279bd7 Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:04:26 +0000 Subject: [PATCH 4/4] Add optional service profile to Docker Compose test configuration --- backends/advanced/docker-compose-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backends/advanced/docker-compose-test.yml b/backends/advanced/docker-compose-test.yml index e01a75f6..25e7da88 100644 --- a/backends/advanced/docker-compose-test.yml +++ b/backends/advanced/docker-compose-test.yml @@ -153,6 +153,8 @@ services: retries: 5 start_period: 60s restart: unless-stopped + profiles: + - speaker # Optional service - only start when explicitly enabled workers-test: build: