Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5d561e9
feat: Enable AVM WAF implementation to align with updated AVM standar…
Pavan-Microsoft Sep 15, 2025
1d0b33d
feat: Replaced AVM modules with local modules to reduce the main.json…
Prajwal-Microsoft Sep 16, 2025
7c96b33
fix: Integrate UMI Across Infra, Secure PostgreSQL Connections, and U…
Pavan-Microsoft Sep 16, 2025
d36cf84
Merge branch 'waf-avm' of https://github.com/Azure-Samples/chat-with-…
Prajwal-Microsoft Sep 16, 2025
569e9ca
fix: Update OpenAI deployment SKU name and set default location for C…
Pavan-Microsoft Sep 16, 2025
ee431d5
feat: Add support for user-assigned identity in Azure OpenAI parameters
Pavan-Microsoft Sep 17, 2025
da2c4f7
feat: Enable system-assigned managed identity for Cognitive Services …
Sep 17, 2025
1c88d7e
fix: Created minified the version of workbk json to reduce size f mai…
Prajwal-Microsoft Sep 17, 2025
b94d47f
Merge pull request #1 from Azure-Samples/waf-avm
Prajwal-Microsoft Sep 17, 2025
3079f94
fix: Update principalId reference to use managed identity output in f…
Harmanpreet-Microsoft Sep 17, 2025
ffebb07
feat: Add support for restricting outbound network access and enablin…
Pavan-Microsoft Sep 17, 2025
bc3ec26
fix: Updates to networking, identity, and default settings in Cogniti…
Pavan-Microsoft Sep 17, 2025
afb5560
feat: Add system-assigned role assignments for Cognitive Services res…
Sep 17, 2025
f2559c3
fix: Removed private end points for Web, Admin & Function app
Prajwal-Microsoft Sep 17, 2025
b67aa9f
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 17, 2025
a320800
Implement code changes to enhance functionality and improve performance
Pavan-Microsoft Sep 17, 2025
b5c58af
fix: Refine Azure Cognitive Services Infra: Role Assignments and Stor…
Pavan-Microsoft Sep 17, 2025
ed5e62c
fix: Watnings fixed and added comments
Prajwal-Microsoft Sep 17, 2025
ea7e2bb
Merge branch 'waf-avm' of https://github.com/Azure-Samples/chat-with-…
Prajwal-Microsoft Sep 17, 2025
56d3469
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 18, 2025
a3cdbcb
fix: deployment script issue for postgres in private network
Prajwal-Microsoft Sep 18, 2025
b358930
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 18, 2025
329659c
feat: add support for system-assigned managed identity and role assig…
Pavan-Microsoft Sep 18, 2025
ee8edc4
fix: local debugging with integrated vectorization (#1903)
Pavan-Microsoft Sep 18, 2025
17ae566
fix: Refactored code and fix for deployment script
Prajwal-Microsoft Sep 18, 2025
4abd024
Merge branch 'waf-avm' of https://github.com/Azure-Samples/chat-with-…
Prajwal-Microsoft Sep 18, 2025
3247cf3
feat: Module refactroring for Database & identity
Prajwal-Microsoft Sep 18, 2025
8936f83
Fix: Fixed Postgres issue and commented bastion hist to decrease depl…
Prajwal-Microsoft Sep 18, 2025
2e1be1a
fix: Integrated vectorization for WAF
Prajwal-Microsoft Sep 18, 2025
e8ac87a
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 19, 2025
3456dde
feat: Disable private networking for OpenAI with integrated vectoriza…
Pavan-Microsoft Sep 19, 2025
02c8e7e
fix: Refactor OpenAI Networking for Integrated Vectorization Compatib…
Pavan-Microsoft Sep 19, 2025
aba8dea
fix: Refactored modules, descriptions for param & outputs
Prajwal-Microsoft Sep 19, 2025
84243fb
fix: Integrated Vectorization issue
Prajwal-Microsoft Sep 20, 2025
cde1750
fix: main.jsnon for integrated verctorization issue
Prajwal-Microsoft Sep 20, 2025
6ba6cc6
fix: Removed unwanted flag
Prajwal-Microsoft Sep 20, 2025
ca2fab9
fix: Updated default database type
Prajwal-Microsoft Sep 20, 2025
060df23
refactor: Removed old code & optimization to reduce deployment time
Prajwal-Microsoft Sep 20, 2025
30cd186
fix: File upload issue due to event grid
Prajwal-Microsoft Sep 21, 2025
70a0801
fix: Updated the delay time for postgresql
Prajwal-Microsoft Sep 21, 2025
f0b57c4
fix: Added Method to download the blob file via API
Prajwal-Microsoft Sep 21, 2025
3299a5f
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 22, 2025
31972e1
Merge remote-tracking branch 'upstream/dev' into waf-avm-fork-pk
Pavan-Microsoft Sep 22, 2025
17b61fb
fix: Update private networking dependency logic and adjust OpenAI par…
Pavan-Microsoft Sep 22, 2025
aa5bd5c
fix: removed \n
Prajwal-Microsoft Sep 22, 2025
997868a
fix: Update documentation and configuration for Teams extension and A…
Pavan-Microsoft Sep 22, 2025
ee98a03
fix: Remove default value for azureOpenAIStopSequence parameter
Pavan-Microsoft Sep 22, 2025
291224b
fix: Add deployment options and VM credentials configuration to LOCAL…
Pavan-Microsoft Sep 22, 2025
ae0ba01
fix: Update Azure parameters with new configurations for search and O…
Pavan-Microsoft Sep 22, 2025
d20f1c3
fix: Remove default values for azureSearchFilter and azureOpenAIStopS…
Pavan-Microsoft Sep 22, 2025
cb2e607
fix: Improve Deployment Docs, Single-Tenant Teams Extension, and Inge…
Pavan-Microsoft Sep 22, 2025
b2c6799
fix: Added Stop sequence for Open AI
Prajwal-Microsoft Sep 23, 2025
df5b326
Merge branch 'waf-avm' of https://github.com/Azure-Samples/chat-with-…
Prajwal-Microsoft Sep 23, 2025
e5141e5
refactor: Code refactoring
Prajwal-Microsoft Sep 23, 2025
5889528
fix: Revert the azure.yaml file
Prajwal-Microsoft Sep 23, 2025
24473dd
Merge remote-tracking branch 'upstream/waf-avm' into waf-avm-fork-pk
Pavan-Microsoft Sep 23, 2025
19da326
fix: Update role assignments in LOCAL_DEPLOYMENT.md for Azure services
Pavan-Microsoft Sep 23, 2025
f39cfab
fix: Update SSL parameter to boolean in Postgres connection and add i…
Pavan-Microsoft Sep 24, 2025
6c71290
fix: Update AzureWebJobsStorage parameter in local.settings.json.samp…
Pavan-Microsoft Sep 24, 2025
a69ee45
Merge remote-tracking branch 'upstream/dev' into waf-avm-fork-pk
Pavan-Microsoft Sep 24, 2025
8a68dc6
fix: Remove unused import 'urlparse' from create_app.py
Pavan-Microsoft Sep 24, 2025
0dfe330
fix: Refactor imports in create_app.py and add tests for file downloa…
Pavan-Microsoft Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions code/backend/batch/local.settings.json.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "",
"AzureWebJobsStorage__accountName": "",
"MyBindingConnection": "",
"AzureWebJobs.HttpExample.Disabled": "true"
},
Expand All @@ -11,4 +11,4 @@
"CORS": "*",
"CORSCredentials": false
}
}
}
3 changes: 2 additions & 1 deletion code/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
from os import path
import sys
import re
from urllib.parse import quote

import requests
from openai import AzureOpenAI, Stream, APIStatusError
from openai.types.chat import ChatCompletionChunk
from flask import Flask, Response, request, Request, jsonify
from dotenv import load_dotenv
from urllib.parse import quote, urlparse
from backend.batch.utilities.helpers.env_helper import EnvHelper
from backend.batch.utilities.helpers.azure_search_helper import AzureSearchHelper
from backend.batch.utilities.helpers.orchestrator_helper import Orchestrator
Expand Down
2 changes: 1 addition & 1 deletion code/tests/chat_history/test_postgresdbservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def test_connect(mock_credential, mock_connect, postgres_client, mock_conn
database="test_db",
password="mock_token",
port=5432,
ssl="require",
ssl=True,
)
assert postgres_client.conn == mock_connection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def test_integrated_vectorization_datasouce_created(
"container": {
"name": f"{app_config.get_from_json('AZURE_BLOB_STORAGE_INFO','containerName')}"
},
"identity": {
"@odata.type": "#Microsoft.Azure.Search.DataUserAssignedIdentity",
"userAssignedIdentity": ""
},
"dataDeletionDetectionPolicy": {
"@odata.type": "#Microsoft.Azure.Search.NativeBlobSoftDeleteDeletionDetectionPolicy"
},
Expand Down Expand Up @@ -367,6 +371,10 @@ def test_integrated_vectorization_skillset_created(
"resourceUri": f"https://localhost:{httpserver.port}/",
"deploymentId": f"{app_config.get_from_json('AZURE_OPENAI_EMBEDDING_MODEL_INFO','model')}",
"apiKey": f"{app_config.get('AZURE_OPENAI_API_KEY')}",
"authIdentity": {
"@odata.type": "#Microsoft.Azure.Search.DataUserAssignedIdentity",
"userAssignedIdentity": ""
},
},
],
"indexProjections": {
Expand Down
226 changes: 226 additions & 0 deletions code/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from unittest.mock import AsyncMock, MagicMock, Mock, patch

from azure.core.exceptions import ClientAuthenticationError, ResourceNotFoundError, ServiceRequestError
from openai import RateLimitError, BadRequestError, InternalServerError
import pytest
from flask.testing import FlaskClient
Expand Down Expand Up @@ -923,3 +924,228 @@ def test_conversation_azure_byod_returns_correct_response_when_streaming_without
data
== '{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"role": "assistant", "content": "mock content"}]}]}\n'
)


class TestGetFile:
"""Test the get_file endpoint for downloading files from blob storage."""

@patch("create_app.AzureBlobStorageClient")
def test_get_file_success(self, mock_blob_client_class, client):
"""Test successful file download with proper headers."""
# given
filename = "test_document.pdf"
file_content = b"Mock file content for PDF document"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = True
mock_blob_client.download_file.return_value = file_content

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 200
assert response.data == file_content
assert response.headers["Content-Type"] == "application/pdf"
assert response.headers["Content-Disposition"] == f'inline; filename="{filename}"'
assert response.headers["Content-Length"] == str(len(file_content))
assert response.headers["Cache-Control"] == "public, max-age=3600"
assert response.headers["X-Content-Type-Options"] == "nosniff"
assert response.headers["X-Frame-Options"] == "DENY"
assert response.headers["Content-Security-Policy"] == "default-src 'none'"

# Verify blob client was initialized with correct container
mock_blob_client_class.assert_called_once_with(container_name="documents")
mock_blob_client.file_exists.assert_called_once_with(filename)
mock_blob_client.download_file.assert_called_once_with(filename)

@patch("create_app.AzureBlobStorageClient")
def test_get_file_with_unknown_mime_type(self, mock_blob_client_class, client):
"""Test file download with unknown file extension."""
# given
filename = "test_file.unknownext"
file_content = b"Mock file content"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = True
mock_blob_client.download_file.return_value = file_content

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 200
assert response.headers["Content-Type"] == "application/octet-stream"

@patch("create_app.AzureBlobStorageClient")
def test_get_file_large_file_warning(self, mock_blob_client_class, client):
"""Test that large files are handled properly with logging."""
# given
filename = "large_document.pdf"
file_content = b"x" * (11 * 1024 * 1024) # 11MB file

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = True
mock_blob_client.download_file.return_value = file_content

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 200
assert len(response.data) == len(file_content)

def test_get_file_empty_filename(self, client):
"""Test error response when filename is empty."""
# when
response = client.get("/api/files/")

# then
# This should result in a 404 as the route won't match
assert response.status_code == 404

def test_get_file_invalid_filename_too_long(self, client):
"""Test error response for filenames that are too long."""
# given
filename = "a" * 256 # 256 characters, exceeds 255 limit

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 400
assert response.json == {"error": "Filename too long"}

@patch("create_app.AzureBlobStorageClient")
def test_get_file_not_exists_in_storage(self, mock_blob_client_class, client):
"""Test error response when file doesn't exist in blob storage."""
# given
filename = "nonexistent.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = False

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 404
assert response.json == {"error": "File not found"}
mock_blob_client.file_exists.assert_called_once_with(filename)
mock_blob_client.download_file.assert_not_called()

@patch("create_app.AzureBlobStorageClient")
def test_get_file_client_authentication_error(self, mock_blob_client_class, client):
"""Test handling of Azure ClientAuthenticationError."""
# given
filename = "test.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.side_effect = ClientAuthenticationError("Auth failed")

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 401
assert response.json == {"error": "Authentication failed"}

@patch("create_app.AzureBlobStorageClient")
def test_get_file_resource_not_found_error(self, mock_blob_client_class, client):
"""Test handling of Azure ResourceNotFoundError."""
# given
filename = "test.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.side_effect = ResourceNotFoundError("Resource not found")

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 404
assert response.json == {"error": "File not found"}

@patch("create_app.AzureBlobStorageClient")
def test_get_file_service_request_error(self, mock_blob_client_class, client):
"""Test handling of Azure ServiceRequestError."""
# given
filename = "test.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.side_effect = ServiceRequestError("Service unavailable")

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 503
assert response.json == {"error": "Storage service unavailable"}

@patch("create_app.AzureBlobStorageClient")
def test_get_file_unexpected_exception(self, mock_blob_client_class, client):
"""Test handling of unexpected exceptions."""
# given
filename = "test.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.side_effect = Exception("Unexpected error")

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 500
assert response.json == {"error": "Internal server error"}

@patch("create_app.AzureBlobStorageClient")
def test_get_file_download_exception(self, mock_blob_client_class, client):
"""Test handling of exceptions during file download."""
# given
filename = "test.pdf"

mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = True
mock_blob_client.download_file.side_effect = Exception("Download failed")

# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 500
assert response.json == {"error": "Internal server error"}

def test_get_file_valid_filenames(self, client):
"""Test that valid filenames with allowed characters pass validation."""
# Mock the blob client to avoid actual Azure calls
with patch("create_app.AzureBlobStorageClient") as mock_blob_client_class:
mock_blob_client = MagicMock()
mock_blob_client_class.return_value = mock_blob_client
mock_blob_client.file_exists.return_value = True
mock_blob_client.download_file.return_value = b"test content"

valid_filenames = [
"document.pdf",
"file_name.txt",
"file-name.docx",
"file name.xlsx",
"test123.json",
"a.b",
"very_long_but_valid_filename_with_underscores.pdf"
]

for filename in valid_filenames:
# when
response = client.get(f"/api/files/{filename}")

# then
assert response.status_code == 200, f"Failed for filename: {filename}"
4 changes: 2 additions & 2 deletions code/tests/utilities/helpers/test_azure_postgres_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_create_search_client_success(self, mock_connect, mock_credential):
"https://ossrdbms-aad.database.windows.net/.default"
)
mock_connect.assert_called_once_with(
"host=mock_host user=mock_user dbname=mock_database password=mock-access-token"
"host=mock_host user=mock_user dbname=mock_database password=mock-access-token sslmode=require"
)

@patch("backend.batch.utilities.helpers.azure_postgres_helper.psycopg2.connect")
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_get_vector_store_success(self, mock_cursor, mock_connect, mock_credenti
# Assert
self.assertEqual(results, mock_results)
mock_connect.assert_called_once_with(
"host=mock_host user=mock_user dbname=mock_database password=mock-access-token"
"host=mock_host user=mock_user dbname=mock_database password=mock-access-token sslmode=require"
)

@patch("backend.batch.utilities.helpers.azure_postgres_helper.get_azure_credential")
Expand Down
3 changes: 2 additions & 1 deletion code/tests/utilities/helpers/test_azure_search_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def env_helper_mock():
env_helper.AZURE_SEARCH_CONVERSATIONS_LOG_INDEX = (
AZURE_SEARCH_CONVERSATIONS_LOG_INDEX
)
env_helper.MANAGED_IDENTITY_CLIENT_ID = "mock-client-id"

env_helper.USE_ADVANCED_IMAGE_PROCESSING = USE_ADVANCED_IMAGE_PROCESSING
env_helper.is_auth_type_keys.return_value = True
Expand Down Expand Up @@ -156,7 +157,7 @@ def test_creates_search_clients_with_rabc(
AzureSearchHelper()

# then
default_azure_credential_mock.assert_called_once_with()
default_azure_credential_mock.assert_called_once_with("mock-client-id")
search_client_mock.assert_called_once_with(
endpoint=AZURE_SEARCH_SERVICE,
index_name=AZURE_SEARCH_INDEX,
Expand Down
1 change: 1 addition & 0 deletions code/tests/utilities/helpers/test_secret_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_get_secret_returns_value_from_secret_client_when_use_key_vault_is_true(
secret_name = "MY_SECRET"
expected_value = ""
monkeypatch.setenv("USE_KEY_VAULT", "true")
monkeypatch.setenv("AZURE_KEY_VAULT_ENDPOINT", "https://test-vault.vault.azure.net/")
secret_client.return_value.get_secret.return_value.value = expected_value
secret_helper = SecretHelper()

Expand Down
Loading