chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from azure.ai.contentunderstanding.models import AnalysisResult
|
||||
|
||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _load_fixture(name: str) -> dict[str, Any]:
|
||||
return json.loads((FIXTURES_DIR / name).read_text()) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pdf_fixture_raw() -> dict[str, Any]:
|
||||
return _load_fixture("analyze_pdf_result.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pdf_analysis_result(pdf_fixture_raw: dict[str, Any]) -> AnalysisResult:
|
||||
return AnalysisResult(pdf_fixture_raw)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_fixture_raw() -> dict[str, Any]:
|
||||
return _load_fixture("analyze_audio_result.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def audio_analysis_result(audio_fixture_raw: dict[str, Any]) -> AnalysisResult:
|
||||
return AnalysisResult(audio_fixture_raw)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def invoice_fixture_raw() -> dict[str, Any]:
|
||||
return _load_fixture("analyze_invoice_result.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def invoice_analysis_result(invoice_fixture_raw: dict[str, Any]) -> AnalysisResult:
|
||||
return AnalysisResult(invoice_fixture_raw)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def video_fixture_raw() -> dict[str, Any]:
|
||||
return _load_fixture("analyze_video_result.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def video_analysis_result(video_fixture_raw: dict[str, Any]) -> AnalysisResult:
|
||||
return AnalysisResult(video_fixture_raw)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image_fixture_raw() -> dict[str, Any]:
|
||||
return _load_fixture("analyze_image_result.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image_analysis_result(image_fixture_raw: dict[str, Any]) -> AnalysisResult:
|
||||
return AnalysisResult(image_fixture_raw)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_cu_client() -> AsyncMock:
|
||||
"""Create a mock ContentUnderstandingClient."""
|
||||
client = AsyncMock()
|
||||
client.close = AsyncMock()
|
||||
return client
|
||||
|
||||
|
||||
def make_mock_poller(result: AnalysisResult) -> AsyncMock:
|
||||
"""Create a mock poller that returns the given result immediately."""
|
||||
poller = AsyncMock()
|
||||
poller.result = AsyncMock(return_value=result)
|
||||
poller.continuation_token = MagicMock(return_value="mock_continuation_token")
|
||||
poller.done = MagicMock(return_value=True)
|
||||
return poller
|
||||
|
||||
|
||||
def make_slow_poller(result: AnalysisResult, delay: float = 10.0) -> MagicMock:
|
||||
"""Create a mock poller that simulates a timeout then eventually returns."""
|
||||
poller = MagicMock()
|
||||
|
||||
async def slow_result() -> AnalysisResult:
|
||||
await asyncio.sleep(delay)
|
||||
return result
|
||||
|
||||
poller.result = slow_result
|
||||
poller.continuation_token = MagicMock(return_value="mock_slow_continuation_token")
|
||||
poller.done = MagicMock(return_value=False)
|
||||
return poller
|
||||
|
||||
|
||||
def make_failing_poller(error: Exception) -> AsyncMock:
|
||||
"""Create a mock poller that raises an exception."""
|
||||
poller = AsyncMock()
|
||||
poller.result = AsyncMock(side_effect=error)
|
||||
return poller
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "synthetic-audio-001",
|
||||
"status": "Succeeded",
|
||||
"analyzer_id": "prebuilt-audioSearch",
|
||||
"api_version": "2025-05-01-preview",
|
||||
"created_at": "2026-03-21T10:05:00Z",
|
||||
"contents": [
|
||||
{
|
||||
"markdown": "## Call Center Recording\n\n**Duration:** 2 minutes 15 seconds\n**Speakers:** 2\n\n### Transcript\n\n**Speaker 1 (Agent):** Thank you for calling Contoso support. My name is Sarah. How can I help you today?\n\n**Speaker 2 (Customer):** Hi Sarah, I'm calling about my recent order number ORD-5678. It was supposed to arrive yesterday but I haven't received it.\n\n**Speaker 1 (Agent):** I'm sorry to hear that. Let me look up your order. Can you confirm your name and email address?\n\n**Speaker 2 (Customer):** Sure, it's John Smith, john.smith@example.com.\n\n**Speaker 1 (Agent):** Thank you, John. I can see your order was shipped on March 18th. It looks like there was a delay with the carrier. The updated delivery estimate is March 22nd.\n\n**Speaker 2 (Customer):** That's helpful, thank you. Is there anything I can do to track it?\n\n**Speaker 1 (Agent):** Yes, I'll send you a tracking link to your email right away. Is there anything else I can help with?\n\n**Speaker 2 (Customer):** No, that's all. Thanks for your help.\n\n**Speaker 1 (Agent):** You're welcome! Have a great day.",
|
||||
"fields": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
+857
@@ -0,0 +1,857 @@
|
||||
{
|
||||
"analyzerId": "prebuilt-documentSearch",
|
||||
"apiVersion": "2025-11-01",
|
||||
"createdAt": "2026-03-21T22:44:21Z",
|
||||
"stringEncoding": "codePoint",
|
||||
"warnings": [],
|
||||
"contents": [
|
||||
{
|
||||
"path": "input1",
|
||||
"markdown": "# Contoso Q1 2025 Financial Summary\n\nTotal revenue for Q1 2025 was $42.7 million, an increase of 18% over Q1 2024.\nOperating expenses were $31.2 million. Net profit was $11.5 million. The largest\nrevenue segment was Cloud Services at $19.3 million, followed by Professional\nServices at $14.8 million and Product Licensing at $8.6 million. Headcount at end of\nQ1 was 1,247 employees across 8 offices worldwide.\n",
|
||||
"fields": {
|
||||
"Summary": {
|
||||
"type": "string",
|
||||
"valueString": "The document provides a financial summary for Contoso in Q1 2025, reporting total revenue of $42.7 million, an 18% increase from Q1 2024. Operating expenses were $31.2 million, resulting in a net profit of $11.5 million. The largest revenue segment was Cloud Services with $19.3 million, followed by Professional Services at $14.8 million and Product Licensing at $8.6 million. The company had 1,247 employees across 8 offices worldwide at the end of Q1.",
|
||||
"spans": [
|
||||
{
|
||||
"offset": 37,
|
||||
"length": 77
|
||||
},
|
||||
{
|
||||
"offset": 115,
|
||||
"length": 80
|
||||
},
|
||||
{
|
||||
"offset": 196,
|
||||
"length": 77
|
||||
},
|
||||
{
|
||||
"offset": 274,
|
||||
"length": 84
|
||||
},
|
||||
{
|
||||
"offset": 359,
|
||||
"length": 50
|
||||
}
|
||||
],
|
||||
"confidence": 0.592,
|
||||
"source": "D(1,212.0000,334.0000,1394.0000,334.0000,1394.0000,374.0000,212.0000,374.0000);D(1,213.0000,379.0000,1398.0000,379.0000,1398.0000,422.0000,213.0000,422.0000);D(1,212.0000,423.0000,1389.0000,423.0000,1389.0000,464.0000,212.0000,464.0000);D(1,213.0000,468.0000,1453.0000,468.0000,1453.0000,510.0000,213.0000,510.0000);D(1,213.0000,512.0000,1000.0000,512.0000,1000.0000,554.0000,213.0000,554.0000)"
|
||||
}
|
||||
},
|
||||
"kind": "document",
|
||||
"startPageNumber": 1,
|
||||
"endPageNumber": 1,
|
||||
"unit": "pixel",
|
||||
"pages": [
|
||||
{
|
||||
"pageNumber": 1,
|
||||
"angle": -0.0242,
|
||||
"width": 1700,
|
||||
"height": 2200,
|
||||
"spans": [
|
||||
{
|
||||
"offset": 0,
|
||||
"length": 410
|
||||
}
|
||||
],
|
||||
"words": [
|
||||
{
|
||||
"content": "Contoso",
|
||||
"span": {
|
||||
"offset": 2,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.99,
|
||||
"source": "D(1,214,222,401,222,401,274,214,273)"
|
||||
},
|
||||
{
|
||||
"content": "Q1",
|
||||
"span": {
|
||||
"offset": 10,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.957,
|
||||
"source": "D(1,414,222,473,222,473,275,414,274)"
|
||||
},
|
||||
{
|
||||
"content": "2025",
|
||||
"span": {
|
||||
"offset": 13,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.929,
|
||||
"source": "D(1,494,222,607,222,607,276,494,275)"
|
||||
},
|
||||
{
|
||||
"content": "Financial",
|
||||
"span": {
|
||||
"offset": 18,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.975,
|
||||
"source": "D(1,624,222,819,223,819,277,624,276)"
|
||||
},
|
||||
{
|
||||
"content": "Summary",
|
||||
"span": {
|
||||
"offset": 28,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.991,
|
||||
"source": "D(1,836,223,1050,225,1050,279,836,277)"
|
||||
},
|
||||
{
|
||||
"content": "Total",
|
||||
"span": {
|
||||
"offset": 37,
|
||||
"length": 5
|
||||
},
|
||||
"confidence": 0.996,
|
||||
"source": "D(1,212,335,287,334,288,374,212,373)"
|
||||
},
|
||||
{
|
||||
"content": "revenue",
|
||||
"span": {
|
||||
"offset": 43,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.994,
|
||||
"source": "D(1,299,334,417,334,418,374,299,374)"
|
||||
},
|
||||
{
|
||||
"content": "for",
|
||||
"span": {
|
||||
"offset": 51,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.994,
|
||||
"source": "D(1,427,334,467,334,467,374,427,374)"
|
||||
},
|
||||
{
|
||||
"content": "Q1",
|
||||
"span": {
|
||||
"offset": 55,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.944,
|
||||
"source": "D(1,475,334,515,334,515,374,475,374)"
|
||||
},
|
||||
{
|
||||
"content": "2025",
|
||||
"span": {
|
||||
"offset": 58,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.876,
|
||||
"source": "D(1,528,334,604,334,604,374,529,374)"
|
||||
},
|
||||
{
|
||||
"content": "was",
|
||||
"span": {
|
||||
"offset": 63,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.991,
|
||||
"source": "D(1,613,334,672,334,672,374,613,374)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 67,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.999,
|
||||
"source": "D(1,681,334,698,334,698,374,681,374)"
|
||||
},
|
||||
{
|
||||
"content": "42.7",
|
||||
"span": {
|
||||
"offset": 68,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.946,
|
||||
"source": "D(1,700,334,765,334,765,374,700,374)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 73,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.977,
|
||||
"source": "D(1,775,334,867,334,867,374,776,374)"
|
||||
},
|
||||
{
|
||||
"content": ",",
|
||||
"span": {
|
||||
"offset": 80,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,870,334,877,334,877,374,870,374)"
|
||||
},
|
||||
{
|
||||
"content": "an",
|
||||
"span": {
|
||||
"offset": 82,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,888,334,922,334,922,374,888,374)"
|
||||
},
|
||||
{
|
||||
"content": "increase",
|
||||
"span": {
|
||||
"offset": 85,
|
||||
"length": 8
|
||||
},
|
||||
"confidence": 0.991,
|
||||
"source": "D(1,934,334,1058,335,1059,374,934,374)"
|
||||
},
|
||||
{
|
||||
"content": "of",
|
||||
"span": {
|
||||
"offset": 94,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.982,
|
||||
"source": "D(1,1069,335,1098,335,1098,374,1069,374)"
|
||||
},
|
||||
{
|
||||
"content": "18",
|
||||
"span": {
|
||||
"offset": 97,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.963,
|
||||
"source": "D(1,1108,335,1142,335,1142,374,1108,374)"
|
||||
},
|
||||
{
|
||||
"content": "%",
|
||||
"span": {
|
||||
"offset": 99,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,1143,335,1171,335,1171,374,1143,374)"
|
||||
},
|
||||
{
|
||||
"content": "over",
|
||||
"span": {
|
||||
"offset": 101,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.946,
|
||||
"source": "D(1,1181,335,1248,335,1248,374,1181,374)"
|
||||
},
|
||||
{
|
||||
"content": "Q1",
|
||||
"span": {
|
||||
"offset": 106,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.875,
|
||||
"source": "D(1,1256,335,1295,335,1295,374,1256,374)"
|
||||
},
|
||||
{
|
||||
"content": "2024",
|
||||
"span": {
|
||||
"offset": 109,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.683,
|
||||
"source": "D(1,1310,335,1384,335,1384,374,1310,374)"
|
||||
},
|
||||
{
|
||||
"content": ".",
|
||||
"span": {
|
||||
"offset": 113,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.991,
|
||||
"source": "D(1,1385,335,1394,335,1394,374,1385,374)"
|
||||
},
|
||||
{
|
||||
"content": "Operating",
|
||||
"span": {
|
||||
"offset": 115,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.996,
|
||||
"source": "D(1,213,380,358,380,358,422,213,422)"
|
||||
},
|
||||
{
|
||||
"content": "expenses",
|
||||
"span": {
|
||||
"offset": 125,
|
||||
"length": 8
|
||||
},
|
||||
"confidence": 0.997,
|
||||
"source": "D(1,369,380,513,379,513,421,369,421)"
|
||||
},
|
||||
{
|
||||
"content": "were",
|
||||
"span": {
|
||||
"offset": 134,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,521,379,595,379,595,421,521,421)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 139,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.999,
|
||||
"source": "D(1,603,379,620,379,620,421,603,421)"
|
||||
},
|
||||
{
|
||||
"content": "31.2",
|
||||
"span": {
|
||||
"offset": 140,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.938,
|
||||
"source": "D(1,623,379,686,379,686,421,623,421)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 145,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.913,
|
||||
"source": "D(1,696,379,790,379,790,421,696,421)"
|
||||
},
|
||||
{
|
||||
"content": ".",
|
||||
"span": {
|
||||
"offset": 152,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.975,
|
||||
"source": "D(1,793,379,800,379,800,421,793,421)"
|
||||
},
|
||||
{
|
||||
"content": "Net",
|
||||
"span": {
|
||||
"offset": 154,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.976,
|
||||
"source": "D(1,811,379,862,379,862,420,811,421)"
|
||||
},
|
||||
{
|
||||
"content": "profit",
|
||||
"span": {
|
||||
"offset": 158,
|
||||
"length": 6
|
||||
},
|
||||
"confidence": 0.993,
|
||||
"source": "D(1,871,379,947,379,947,420,871,420)"
|
||||
},
|
||||
{
|
||||
"content": "was",
|
||||
"span": {
|
||||
"offset": 165,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.997,
|
||||
"source": "D(1,954,379,1012,379,1012,420,953,420)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 169,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,1021,379,1039,379,1039,420,1021,420)"
|
||||
},
|
||||
{
|
||||
"content": "11.5",
|
||||
"span": {
|
||||
"offset": 170,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.954,
|
||||
"source": "D(1,1043,379,1106,379,1106,421,1043,420)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 175,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.837,
|
||||
"source": "D(1,1118,379,1208,379,1208,421,1118,421)"
|
||||
},
|
||||
{
|
||||
"content": ".",
|
||||
"span": {
|
||||
"offset": 182,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.978,
|
||||
"source": "D(1,1210,379,1217,379,1217,421,1210,421)"
|
||||
},
|
||||
{
|
||||
"content": "The",
|
||||
"span": {
|
||||
"offset": 184,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.949,
|
||||
"source": "D(1,1228,379,1285,379,1285,421,1228,421)"
|
||||
},
|
||||
{
|
||||
"content": "largest",
|
||||
"span": {
|
||||
"offset": 188,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.978,
|
||||
"source": "D(1,1295,379,1398,379,1398,421,1295,421)"
|
||||
},
|
||||
{
|
||||
"content": "revenue",
|
||||
"span": {
|
||||
"offset": 196,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.995,
|
||||
"source": "D(1,212,425,334,425,334,464,212,464)"
|
||||
},
|
||||
{
|
||||
"content": "segment",
|
||||
"span": {
|
||||
"offset": 204,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.996,
|
||||
"source": "D(1,344,425,472,424,472,464,344,464)"
|
||||
},
|
||||
{
|
||||
"content": "was",
|
||||
"span": {
|
||||
"offset": 212,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,480,424,541,424,541,464,480,464)"
|
||||
},
|
||||
{
|
||||
"content": "Cloud",
|
||||
"span": {
|
||||
"offset": 216,
|
||||
"length": 5
|
||||
},
|
||||
"confidence": 0.997,
|
||||
"source": "D(1,550,424,636,424,637,464,551,464)"
|
||||
},
|
||||
{
|
||||
"content": "Services",
|
||||
"span": {
|
||||
"offset": 222,
|
||||
"length": 8
|
||||
},
|
||||
"confidence": 0.995,
|
||||
"source": "D(1,647,424,774,424,774,464,647,464)"
|
||||
},
|
||||
{
|
||||
"content": "at",
|
||||
"span": {
|
||||
"offset": 231,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.996,
|
||||
"source": "D(1,784,424,812,424,812,464,784,464)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 234,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,820,424,837,424,837,464,820,464)"
|
||||
},
|
||||
{
|
||||
"content": "19.3",
|
||||
"span": {
|
||||
"offset": 235,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.879,
|
||||
"source": "D(1,840,424,903,423,903,463,840,464)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 240,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.876,
|
||||
"source": "D(1,915,423,1006,423,1006,463,915,463)"
|
||||
},
|
||||
{
|
||||
"content": ",",
|
||||
"span": {
|
||||
"offset": 247,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.999,
|
||||
"source": "D(1,1008,423,1015,423,1015,463,1008,463)"
|
||||
},
|
||||
{
|
||||
"content": "followed",
|
||||
"span": {
|
||||
"offset": 249,
|
||||
"length": 8
|
||||
},
|
||||
"confidence": 0.978,
|
||||
"source": "D(1,1026,423,1148,424,1148,463,1026,463)"
|
||||
},
|
||||
{
|
||||
"content": "by",
|
||||
"span": {
|
||||
"offset": 258,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.986,
|
||||
"source": "D(1,1160,424,1194,424,1194,463,1160,463)"
|
||||
},
|
||||
{
|
||||
"content": "Professional",
|
||||
"span": {
|
||||
"offset": 261,
|
||||
"length": 12
|
||||
},
|
||||
"confidence": 0.965,
|
||||
"source": "D(1,1204,424,1389,424,1389,463,1204,463)"
|
||||
},
|
||||
{
|
||||
"content": "Services",
|
||||
"span": {
|
||||
"offset": 274,
|
||||
"length": 8
|
||||
},
|
||||
"confidence": 0.991,
|
||||
"source": "D(1,213,469,341,469,341,510,213,510)"
|
||||
},
|
||||
{
|
||||
"content": "at",
|
||||
"span": {
|
||||
"offset": 283,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.997,
|
||||
"source": "D(1,352,469,380,469,380,510,352,510)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 286,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,388,469,405,469,405,510,388,510)"
|
||||
},
|
||||
{
|
||||
"content": "14.8",
|
||||
"span": {
|
||||
"offset": 287,
|
||||
"length": 4
|
||||
},
|
||||
"confidence": 0.973,
|
||||
"source": "D(1,410,469,472,469,472,510,410,510)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 292,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.987,
|
||||
"source": "D(1,483,469,575,469,575,510,483,510)"
|
||||
},
|
||||
{
|
||||
"content": "and",
|
||||
"span": {
|
||||
"offset": 300,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.999,
|
||||
"source": "D(1,585,469,638,469,638,510,585,510)"
|
||||
},
|
||||
{
|
||||
"content": "Product",
|
||||
"span": {
|
||||
"offset": 304,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.995,
|
||||
"source": "D(1,652,469,765,469,765,510,652,510)"
|
||||
},
|
||||
{
|
||||
"content": "Licensing",
|
||||
"span": {
|
||||
"offset": 312,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.993,
|
||||
"source": "D(1,777,469,914,469,914,510,777,510)"
|
||||
},
|
||||
{
|
||||
"content": "at",
|
||||
"span": {
|
||||
"offset": 322,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,925,469,953,469,953,510,925,510)"
|
||||
},
|
||||
{
|
||||
"content": "$",
|
||||
"span": {
|
||||
"offset": 325,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.998,
|
||||
"source": "D(1,961,469,978,469,978,510,961,510)"
|
||||
},
|
||||
{
|
||||
"content": "8.6",
|
||||
"span": {
|
||||
"offset": 326,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.958,
|
||||
"source": "D(1,980,469,1025,469,1025,510,980,510)"
|
||||
},
|
||||
{
|
||||
"content": "million",
|
||||
"span": {
|
||||
"offset": 330,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.908,
|
||||
"source": "D(1,1036,469,1128,468,1128,510,1036,510)"
|
||||
},
|
||||
{
|
||||
"content": ".",
|
||||
"span": {
|
||||
"offset": 337,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.987,
|
||||
"source": "D(1,1130,468,1137,468,1137,510,1130,510)"
|
||||
},
|
||||
{
|
||||
"content": "Headcount",
|
||||
"span": {
|
||||
"offset": 339,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.934,
|
||||
"source": "D(1,1150,468,1310,468,1310,510,1150,510)"
|
||||
},
|
||||
{
|
||||
"content": "at",
|
||||
"span": {
|
||||
"offset": 349,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.993,
|
||||
"source": "D(1,1318,468,1348,468,1348,510,1318,510)"
|
||||
},
|
||||
{
|
||||
"content": "end",
|
||||
"span": {
|
||||
"offset": 352,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.947,
|
||||
"source": "D(1,1355,468,1410,468,1410,510,1355,510)"
|
||||
},
|
||||
{
|
||||
"content": "of",
|
||||
"span": {
|
||||
"offset": 356,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.974,
|
||||
"source": "D(1,1419,468,1453,468,1453,509,1419,509)"
|
||||
},
|
||||
{
|
||||
"content": "Q1",
|
||||
"span": {
|
||||
"offset": 359,
|
||||
"length": 2
|
||||
},
|
||||
"confidence": 0.931,
|
||||
"source": "D(1,213,512,252,512,252,554,213,554)"
|
||||
},
|
||||
{
|
||||
"content": "was",
|
||||
"span": {
|
||||
"offset": 362,
|
||||
"length": 3
|
||||
},
|
||||
"confidence": 0.847,
|
||||
"source": "D(1,267,512,326,512,326,554,267,554)"
|
||||
},
|
||||
{
|
||||
"content": "1,247",
|
||||
"span": {
|
||||
"offset": 366,
|
||||
"length": 5
|
||||
},
|
||||
"confidence": 0.523,
|
||||
"source": "D(1,338,512,419,512,419,554,338,554)"
|
||||
},
|
||||
{
|
||||
"content": "employees",
|
||||
"span": {
|
||||
"offset": 372,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.972,
|
||||
"source": "D(1,429,513,591,512,591,554,429,554)"
|
||||
},
|
||||
{
|
||||
"content": "across",
|
||||
"span": {
|
||||
"offset": 382,
|
||||
"length": 6
|
||||
},
|
||||
"confidence": 0.972,
|
||||
"source": "D(1,601,512,697,512,697,554,601,554)"
|
||||
},
|
||||
{
|
||||
"content": "8",
|
||||
"span": {
|
||||
"offset": 389,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.946,
|
||||
"source": "D(1,708,512,725,512,725,553,708,554)"
|
||||
},
|
||||
{
|
||||
"content": "offices",
|
||||
"span": {
|
||||
"offset": 391,
|
||||
"length": 7
|
||||
},
|
||||
"confidence": 0.95,
|
||||
"source": "D(1,736,512,831,512,831,553,736,553)"
|
||||
},
|
||||
{
|
||||
"content": "worldwide",
|
||||
"span": {
|
||||
"offset": 399,
|
||||
"length": 9
|
||||
},
|
||||
"confidence": 0.988,
|
||||
"source": "D(1,840,512,989,512,989,552,840,553)"
|
||||
},
|
||||
{
|
||||
"content": ".",
|
||||
"span": {
|
||||
"offset": 408,
|
||||
"length": 1
|
||||
},
|
||||
"confidence": 0.996,
|
||||
"source": "D(1,991,512,1000,512,1000,552,991,552)"
|
||||
}
|
||||
],
|
||||
"lines": [
|
||||
{
|
||||
"content": "Contoso Q1 2025 Financial Summary",
|
||||
"source": "D(1,214,221,1050,225,1050,279,213,273)",
|
||||
"span": {
|
||||
"offset": 2,
|
||||
"length": 33
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Total revenue for Q1 2025 was $42.7 million, an increase of 18% over Q1 2024.",
|
||||
"source": "D(1,212,334,1394,335,1394,374,212,374)",
|
||||
"span": {
|
||||
"offset": 37,
|
||||
"length": 77
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Operating expenses were $31.2 million. Net profit was $11.5 million. The largest",
|
||||
"source": "D(1,213,379,1398,378,1398,421,213,422)",
|
||||
"span": {
|
||||
"offset": 115,
|
||||
"length": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "revenue segment was Cloud Services at $19.3 million, followed by Professional",
|
||||
"source": "D(1,212,424,1389,423,1389,463,212,464)",
|
||||
"span": {
|
||||
"offset": 196,
|
||||
"length": 77
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Services at $14.8 million and Product Licensing at $8.6 million. Headcount at end of",
|
||||
"source": "D(1,213,469,1453,468,1453,510,213,511)",
|
||||
"span": {
|
||||
"offset": 274,
|
||||
"length": 84
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Q1 was 1,247 employees across 8 offices worldwide.",
|
||||
"source": "D(1,213,512,1000,512,1000,554,213,554)",
|
||||
"span": {
|
||||
"offset": 359,
|
||||
"length": 50
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"paragraphs": [
|
||||
{
|
||||
"role": "title",
|
||||
"content": "Contoso Q1 2025 Financial Summary",
|
||||
"source": "D(1,214,219,1050,225,1050,279,213,273)",
|
||||
"span": {
|
||||
"offset": 0,
|
||||
"length": 35
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Total revenue for Q1 2025 was $42.7 million, an increase of 18% over Q1 2024. Operating expenses were $31.2 million. Net profit was $11.5 million. The largest revenue segment was Cloud Services at $19.3 million, followed by Professional Services at $14.8 million and Product Licensing at $8.6 million. Headcount at end of Q1 was 1,247 employees across 8 offices worldwide.",
|
||||
"source": "D(1,212,334,1453,333,1454,553,212,554)",
|
||||
"span": {
|
||||
"offset": 37,
|
||||
"length": 372
|
||||
}
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"span": {
|
||||
"offset": 0,
|
||||
"length": 409
|
||||
},
|
||||
"elements": [
|
||||
"/paragraphs/0",
|
||||
"/paragraphs/1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"analyzerId": "prebuilt-documentSearch",
|
||||
"mimeType": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"analyzerId": "prebuilt-invoice",
|
||||
"apiVersion": "2025-11-01",
|
||||
"createdAt": "2026-03-21T22:44:33Z",
|
||||
"stringEncoding": "codePoint",
|
||||
"warnings": [],
|
||||
"contents": [
|
||||
{
|
||||
"markdown": "# Master Services Agreement\n\nClient: Alpine Industries Inc.\n\nContract Reference: MSA-2025-ALP-00847\n\nEffective Date: January 15, 2025\nPrepared for: Robert Chen, Chief Executive Officer, Alpine Industries Inc.\n\nAddress: 742 Evergreen Blvd, Denver, CO 80203\n\nThis Master Services Agreement (the 'Agreement') is entered into by and between Alpine Industries\nInc. (the 'Client') and TechServe Global Partners (the 'Provider'). This agreement governs the provision\nof managed technology services as descri",
|
||||
"fields": {
|
||||
"VendorName": {
|
||||
"type": "string",
|
||||
"valueString": "TechServe Global Partners",
|
||||
"confidence": 0.71
|
||||
},
|
||||
"DueDate": {
|
||||
"type": "date",
|
||||
"valueDate": "2025-02-15",
|
||||
"confidence": 0.793
|
||||
},
|
||||
"InvoiceDate": {
|
||||
"type": "date",
|
||||
"valueDate": "2025-01-15",
|
||||
"confidence": 0.693
|
||||
},
|
||||
"InvoiceId": {
|
||||
"type": "string",
|
||||
"valueString": "INV-100",
|
||||
"confidence": 0.489
|
||||
},
|
||||
"AmountDue": {
|
||||
"type": "object",
|
||||
"valueObject": {
|
||||
"Amount": {
|
||||
"type": "number",
|
||||
"valueNumber": 610,
|
||||
"confidence": 0.758
|
||||
},
|
||||
"CurrencyCode": {
|
||||
"type": "string",
|
||||
"valueString": "USD"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SubtotalAmount": {
|
||||
"type": "object",
|
||||
"valueObject": {
|
||||
"Amount": {
|
||||
"type": "number",
|
||||
"valueNumber": 100,
|
||||
"confidence": 0.902
|
||||
},
|
||||
"CurrencyCode": {
|
||||
"type": "string",
|
||||
"valueString": "USD"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LineItems": {
|
||||
"type": "array",
|
||||
"valueArray": [
|
||||
{
|
||||
"type": "object",
|
||||
"valueObject": {
|
||||
"Description": {
|
||||
"type": "string",
|
||||
"valueString": "Consulting Services",
|
||||
"confidence": 0.664
|
||||
},
|
||||
"Quantity": {
|
||||
"type": "number",
|
||||
"valueNumber": 2,
|
||||
"confidence": 0.957
|
||||
},
|
||||
"UnitPrice": {
|
||||
"type": "object",
|
||||
"valueObject": {
|
||||
"Amount": {
|
||||
"type": "number",
|
||||
"valueNumber": 30,
|
||||
"confidence": 0.956
|
||||
},
|
||||
"CurrencyCode": {
|
||||
"type": "string",
|
||||
"valueString": "USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"valueObject": {
|
||||
"Description": {
|
||||
"type": "string",
|
||||
"valueString": "Document Fee",
|
||||
"confidence": 0.712
|
||||
},
|
||||
"Quantity": {
|
||||
"type": "number",
|
||||
"valueNumber": 3,
|
||||
"confidence": 0.939
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "document",
|
||||
"startPageNumber": 1,
|
||||
"endPageNumber": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"analyzerId": "prebuilt-documentSearch",
|
||||
"apiVersion": "2025-11-01",
|
||||
"createdAt": "2026-03-21T22:44:09Z",
|
||||
"contents": [
|
||||
{
|
||||
"path": "input1",
|
||||
"markdown": "# Contoso Q1 2025 Financial Summary\n\nTotal revenue for Q1 2025 was $42.7 million, an increase of 18% over Q1 2024.\nOperating expenses were $31.2 million. Net profit was $11.5 million. The largest\nrevenue segment was Cloud Services at $19.3 million, followed by Professional\nServices at $14.8 million and Product Licensing at $8.6 million. Headcount at end of\nQ1 was 1,247 employees across 8 offices worldwide.\n\n<!-- PageBreak -->\n\n\n# Contoso Q2 2025 Financial Summary\n\nTotal revenue for Q2 2025 was $48.1 million, an increase of 22% over Q2 2024.\nOperating expenses were $33.9 million. Net profit was $14.2 million. Cloud Services\ngrew to $22.5 million, Professional Services was $15.7 million, and Product Licensing\nwas $9.9 million. The company opened a new office in Tokyo, bringing the total to 9\noffices. Headcount grew to 1,389 employees.\n\n<!-- PageBreak -->\n\n\n## Contoso Product Roadmap 2025\n\nThree major product launches are planned for 2025: (1) Contoso CloudVault - an\nenterprise document storage solution, launching August 2025, with an expected price\nof $29.99/user/month. (2) Contoso DataPulse - a real-time analytics dashboard,\nlaunching October 2025. (3) Contoso SecureLink - a zero-trust networking product,\nlaunching December 2025. Total R&D; budget for 2025 is $18.4 million.\n\n<!-- PageBreak -->\n\n\n# Contoso Employee Satisfaction Survey Results\n\nThe annual employee satisfaction survey was completed in March 2025 with a 87%\nresponse rate. Overall satisfaction score was 4.2 out of 5.0. Work-life balance scored\n3.8/5.0. Career growth opportunities scored 3.9/5.0. Compensation satisfaction\nscored 3.6/5.0. The top requested improvement was 'more flexible remote work\noptions' cited by 62% of respondents. Employee retention rate for the trailing 12\nmonths was 91%.\n\n<!-- PageBreak -->\n\n\n## Contoso Partnership Announcements\n\nContoso announced three strategic partnerships in H1 2025: (1) A joint venture with\nMeridian Technologies for AI-powered document processing, valued at $5.2 million\nover 3 years. (2) A distribution agreement with Pacific Rim Solutions covering 12\ncountries in Asia-Pacific. (3) A technology integration partnership with NovaBridge\nSystems for unified identity management. The Chief Partnership Officer, Helena\nNakagawa, stated the partnerships are expected to generate an additional $15 million\nin revenue by 2027.\n",
|
||||
"fields": {
|
||||
"Summary": {
|
||||
"type": "string",
|
||||
"valueString": "The document provides a comprehensive overview of Contoso's key business metrics and initiatives for 2025, including financial performance for Q1 and Q2 with revenue, expenses, and profit details; a product roadmap with three major launches and R&D budget; employee satisfaction survey results highlighting scores and retention; and strategic partnership announcements expected to boost future revenue.",
|
||||
"confidence": 0.46
|
||||
}
|
||||
},
|
||||
"kind": "document",
|
||||
"startPageNumber": 1,
|
||||
"endPageNumber": 5,
|
||||
"mimeType": "application/pdf",
|
||||
"analyzerId": "prebuilt-documentSearch"
|
||||
}
|
||||
]
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"id": "synthetic-video-001",
|
||||
"status": "Succeeded",
|
||||
"analyzer_id": "prebuilt-videoSearch",
|
||||
"api_version": "2025-05-01-preview",
|
||||
"created_at": "2026-03-21T10:15:00Z",
|
||||
"contents": [
|
||||
{
|
||||
"kind": "audioVisual",
|
||||
"startTimeMs": 1000,
|
||||
"endTimeMs": 14000,
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"markdown": "# Video: 00:01.000 => 00:14.000\n\nTranscript\n```\nWEBVTT\n\n00:01.000 --> 00:05.000\n<Speaker 1>Welcome to the Contoso Product Demo.\n\n00:05.000 --> 00:14.000\n<Speaker 1>Today we'll be showcasing our latest cloud infrastructure management tool.\n```",
|
||||
"fields": {
|
||||
"Summary": {
|
||||
"type": "string",
|
||||
"valueString": "Introduction to the Contoso Product Demo showcasing the latest cloud infrastructure management tool."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "audioVisual",
|
||||
"startTimeMs": 15000,
|
||||
"endTimeMs": 35000,
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"markdown": "# Video: 00:15.000 => 00:35.000\n\nTranscript\n```\nWEBVTT\n\n00:15.000 --> 00:25.000\n<Speaker 1>As you can see on the dashboard, the system provides real-time monitoring of all deployed resources.\n\n00:25.000 --> 00:35.000\n<Speaker 1>Key features include automated scaling, cost optimization, and security compliance monitoring.\n```",
|
||||
"fields": {
|
||||
"Summary": {
|
||||
"type": "string",
|
||||
"valueString": "Dashboard walkthrough covering real-time monitoring, automated scaling, cost optimization, and security compliance."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "audioVisual",
|
||||
"startTimeMs": 36000,
|
||||
"endTimeMs": 42000,
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"markdown": "# Video: 00:36.000 => 00:42.000\n\nTranscript\n```\nWEBVTT\n\n00:36.000 --> 00:42.000\n<Speaker 1>Visit contoso.com/cloud-manager to learn more and start your free trial.\n```",
|
||||
"fields": {
|
||||
"Summary": {
|
||||
"type": "string",
|
||||
"valueString": "Call to action directing viewers to contoso.com/cloud-manager for more information and a free trial."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,318 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Integration tests for ContentUnderstandingContextProvider.
|
||||
|
||||
These tests require a live Azure Content Understanding endpoint.
|
||||
Set AZURE_CONTENTUNDERSTANDING_ENDPOINT to enable them.
|
||||
|
||||
To generate fixtures for unit tests, run these tests with --update-fixtures flag
|
||||
and the resulting JSON files will be written to tests/cu/fixtures/.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
skip_if_cu_integration_tests_disabled = pytest.mark.skipif(
|
||||
not os.environ.get("AZURE_CONTENTUNDERSTANDING_ENDPOINT"),
|
||||
reason="CU integration tests disabled (AZURE_CONTENTUNDERSTANDING_ENDPOINT not set)",
|
||||
)
|
||||
|
||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||
|
||||
# Shared sample asset — same PDF used by samples and integration tests
|
||||
INVOICE_PDF_PATH = Path(__file__).resolve().parents[2] / "samples" / "shared" / "sample_assets" / "invoice.pdf"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_cu_integration_tests_disabled
|
||||
async def test_analyze_pdf_binary() -> None:
|
||||
"""Analyze a PDF via binary upload and optionally capture fixture."""
|
||||
from azure.ai.contentunderstanding.aio import ContentUnderstandingClient
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
endpoint = os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"]
|
||||
analyzer_id = os.environ.get("AZURE_CONTENTUNDERSTANDING_ANALYZER_ID", "prebuilt-documentSearch")
|
||||
|
||||
pdf_path = INVOICE_PDF_PATH
|
||||
assert pdf_path.exists(), f"Test fixture not found: {pdf_path}"
|
||||
pdf_bytes = pdf_path.read_bytes()
|
||||
|
||||
async with DefaultAzureCredential() as credential, ContentUnderstandingClient(endpoint, credential) as client: # pyrefly: ignore[bad-argument-type]
|
||||
poller = await client.begin_analyze_binary(
|
||||
analyzer_id,
|
||||
binary_input=pdf_bytes,
|
||||
content_type="application/pdf",
|
||||
string_encoding="utf-8",
|
||||
)
|
||||
result = await poller.result()
|
||||
|
||||
assert result.contents
|
||||
assert result.contents[0].markdown
|
||||
assert len(result.contents[0].markdown) > 10
|
||||
assert "CONTOSO LTD." in result.contents[0].markdown
|
||||
|
||||
# Optionally capture fixture
|
||||
if os.environ.get("CU_UPDATE_FIXTURES"):
|
||||
FIXTURES_DIR.mkdir(exist_ok=True)
|
||||
fixture_path = FIXTURES_DIR / "analyze_pdf_result.json"
|
||||
fixture_path.write_text(json.dumps(result.as_dict(), indent=2, default=str))
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_cu_integration_tests_disabled
|
||||
async def test_before_run_e2e() -> None:
|
||||
"""End-to-end test: Content.from_data → before_run → state populated."""
|
||||
from agent_framework import Content, Message, SessionContext
|
||||
from agent_framework._sessions import AgentSession
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from agent_framework_azure_contentunderstanding import ContentUnderstandingContextProvider
|
||||
|
||||
endpoint = os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"]
|
||||
|
||||
pdf_path = INVOICE_PDF_PATH
|
||||
assert pdf_path.exists(), f"Test fixture not found: {pdf_path}"
|
||||
pdf_bytes = pdf_path.read_bytes()
|
||||
|
||||
async with DefaultAzureCredential() as credential:
|
||||
cu = ContentUnderstandingContextProvider(
|
||||
endpoint=endpoint,
|
||||
credential=credential, # pyrefly: ignore[bad-argument-type]
|
||||
max_wait=None, # wait until analysis completes (no background deferral)
|
||||
)
|
||||
async with cu:
|
||||
msg = Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text("What's in this document?"),
|
||||
Content.from_data(
|
||||
pdf_bytes,
|
||||
"application/pdf",
|
||||
additional_properties={"filename": "invoice.pdf"},
|
||||
),
|
||||
],
|
||||
)
|
||||
context = SessionContext(input_messages=[msg])
|
||||
state: dict[str, object] = {}
|
||||
session = AgentSession()
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
await cu.before_run(agent=MagicMock(), session=session, context=context, state=state)
|
||||
|
||||
docs = cast("dict[str, Any]", state.get("documents", {}))
|
||||
assert isinstance(docs, dict)
|
||||
assert "invoice.pdf" in docs
|
||||
doc_entry = docs["invoice.pdf"]
|
||||
assert doc_entry["status"] == "ready"
|
||||
# ``result`` is now the rendered string from ``to_llm_input``.
|
||||
rendered = doc_entry["result"]
|
||||
assert isinstance(rendered, str)
|
||||
assert len(rendered) > 10
|
||||
assert "source: invoice.pdf" in rendered
|
||||
assert "CONTOSO LTD." in rendered
|
||||
|
||||
|
||||
# Raw GitHub URL for a public invoice PDF from the CU samples repo
|
||||
_INVOICE_PDF_URL = (
|
||||
"https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/invoice.pdf"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_cu_integration_tests_disabled
|
||||
async def test_before_run_uri_content() -> None:
|
||||
"""End-to-end test: Content.from_uri with an external URL → before_run → state populated.
|
||||
|
||||
Verifies that CU can analyze a file referenced by URL (not base64 data).
|
||||
Uses a public invoice PDF from the Azure CU samples repository.
|
||||
"""
|
||||
from agent_framework import Content, Message, SessionContext
|
||||
from agent_framework._sessions import AgentSession
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from agent_framework_azure_contentunderstanding import ContentUnderstandingContextProvider
|
||||
|
||||
endpoint = os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"]
|
||||
|
||||
async with DefaultAzureCredential() as credential:
|
||||
cu = ContentUnderstandingContextProvider(
|
||||
endpoint=endpoint,
|
||||
credential=credential, # pyrefly: ignore[bad-argument-type]
|
||||
max_wait=None, # wait until analysis completes (no background deferral)
|
||||
)
|
||||
async with cu:
|
||||
msg = Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text("What's on this invoice?"),
|
||||
Content.from_uri(
|
||||
uri=_INVOICE_PDF_URL,
|
||||
media_type="application/pdf",
|
||||
additional_properties={"filename": "invoice.pdf"},
|
||||
),
|
||||
],
|
||||
)
|
||||
context = SessionContext(input_messages=[msg])
|
||||
state: dict[str, object] = {}
|
||||
session = AgentSession()
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
await cu.before_run(agent=MagicMock(), session=session, context=context, state=state)
|
||||
|
||||
docs = cast("dict[str, Any]", state.get("documents", {}))
|
||||
assert isinstance(docs, dict)
|
||||
assert "invoice.pdf" in docs
|
||||
|
||||
doc_entry = docs["invoice.pdf"]
|
||||
assert doc_entry["status"] == "ready"
|
||||
rendered = doc_entry["result"]
|
||||
assert isinstance(rendered, str)
|
||||
assert len(rendered) > 10
|
||||
assert "source: invoice.pdf" in rendered
|
||||
assert "CONTOSO LTD." in rendered
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_cu_integration_tests_disabled
|
||||
async def test_before_run_data_uri_content() -> None:
|
||||
"""End-to-end test: Content.from_uri with a base64 data URI → before_run → state populated.
|
||||
|
||||
Verifies that CU can analyze a file embedded as a data URI (data:application/pdf;base64,...).
|
||||
This tests the data URI path: from_uri with "data:" prefix → type="data" → begin_analyze_binary.
|
||||
"""
|
||||
import base64
|
||||
|
||||
from agent_framework import Content, Message, SessionContext
|
||||
from agent_framework._sessions import AgentSession
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from agent_framework_azure_contentunderstanding import ContentUnderstandingContextProvider
|
||||
|
||||
endpoint = os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"]
|
||||
|
||||
pdf_path = INVOICE_PDF_PATH
|
||||
assert pdf_path.exists(), f"Test fixture not found: {pdf_path}"
|
||||
pdf_bytes = pdf_path.read_bytes()
|
||||
b64 = base64.b64encode(pdf_bytes).decode("ascii")
|
||||
data_uri = f"data:application/pdf;base64,{b64}"
|
||||
|
||||
async with DefaultAzureCredential() as credential:
|
||||
cu = ContentUnderstandingContextProvider(
|
||||
endpoint=endpoint,
|
||||
credential=credential, # pyrefly: ignore[bad-argument-type]
|
||||
max_wait=None, # wait until analysis completes
|
||||
)
|
||||
async with cu:
|
||||
msg = Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text("What's on this invoice?"),
|
||||
Content.from_uri(
|
||||
uri=data_uri,
|
||||
media_type="application/pdf",
|
||||
additional_properties={"filename": "invoice_b64.pdf"},
|
||||
),
|
||||
],
|
||||
)
|
||||
context = SessionContext(input_messages=[msg])
|
||||
state: dict[str, object] = {}
|
||||
session = AgentSession()
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
await cu.before_run(agent=MagicMock(), session=session, context=context, state=state)
|
||||
|
||||
docs = cast("dict[str, Any]", state.get("documents", {}))
|
||||
assert isinstance(docs, dict)
|
||||
assert "invoice_b64.pdf" in docs
|
||||
|
||||
doc_entry = docs["invoice_b64.pdf"]
|
||||
assert doc_entry["status"] == "ready"
|
||||
rendered = doc_entry["result"]
|
||||
assert isinstance(rendered, str)
|
||||
assert len(rendered) > 10
|
||||
assert "source: invoice_b64.pdf" in rendered
|
||||
assert "CONTOSO LTD." in rendered
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
@pytest.mark.integration
|
||||
@skip_if_cu_integration_tests_disabled
|
||||
async def test_before_run_background_analysis() -> None:
|
||||
"""End-to-end test: max_wait timeout → background analysis → resolved on next turn.
|
||||
|
||||
Uses a short max_wait (0.5s) so CU analysis is deferred to background.
|
||||
Then waits for analysis to complete and calls before_run again to verify
|
||||
the background task resolves and the document becomes ready.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from agent_framework import Content, Message, SessionContext
|
||||
from agent_framework._sessions import AgentSession
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
from agent_framework_azure_contentunderstanding import ContentUnderstandingContextProvider
|
||||
|
||||
endpoint = os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"]
|
||||
|
||||
async with DefaultAzureCredential() as credential:
|
||||
cu = ContentUnderstandingContextProvider(
|
||||
endpoint=endpoint,
|
||||
credential=credential, # pyrefly: ignore[bad-argument-type]
|
||||
max_wait=0.5, # short timeout to force background deferral
|
||||
)
|
||||
async with cu:
|
||||
# Turn 1: upload file — should time out and defer to background
|
||||
msg = Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text("What's on this invoice?"),
|
||||
Content.from_uri(
|
||||
uri=_INVOICE_PDF_URL,
|
||||
media_type="application/pdf",
|
||||
additional_properties={"filename": "invoice.pdf"},
|
||||
),
|
||||
],
|
||||
)
|
||||
context = SessionContext(input_messages=[msg])
|
||||
state: dict[str, object] = {}
|
||||
session = AgentSession()
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
await cu.before_run(agent=MagicMock(), session=session, context=context, state=state)
|
||||
|
||||
docs = cast("dict[str, Any]", state.get("documents", {}))
|
||||
assert isinstance(docs, dict)
|
||||
assert "invoice.pdf" in docs
|
||||
assert docs["invoice.pdf"]["status"] == "analyzing", (
|
||||
f"Expected 'analyzing' but got '{docs['invoice.pdf']['status']}' — "
|
||||
"CU responded too fast for the 0.5s timeout"
|
||||
)
|
||||
assert docs["invoice.pdf"]["result"] is None
|
||||
|
||||
# Wait for background analysis to complete
|
||||
await asyncio.sleep(30)
|
||||
|
||||
# Turn 2: no new files — should resolve the background task
|
||||
msg2 = Message(role="user", contents=[Content.from_text("Is it ready?")])
|
||||
context2 = SessionContext(input_messages=[msg2])
|
||||
|
||||
await cu.before_run(agent=MagicMock(), session=session, context=context2, state=state)
|
||||
|
||||
assert docs["invoice.pdf"]["status"] == "ready"
|
||||
rendered = docs["invoice.pdf"]["result"]
|
||||
assert isinstance(rendered, str)
|
||||
assert "CONTOSO LTD." in rendered
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from agent_framework_azure_contentunderstanding._models import (
|
||||
DocumentEntry,
|
||||
DocumentStatus,
|
||||
FileSearchConfig,
|
||||
)
|
||||
|
||||
|
||||
class TestDocumentEntry:
|
||||
def test_construction(self) -> None:
|
||||
entry: DocumentEntry = {
|
||||
"status": DocumentStatus.READY,
|
||||
"filename": "invoice.pdf",
|
||||
"media_type": "application/pdf",
|
||||
"analyzer_id": "prebuilt-documentSearch",
|
||||
"analyzed_at": "2026-01-01T00:00:00+00:00",
|
||||
"analysis_duration_s": 1.23,
|
||||
"upload_duration_s": None,
|
||||
"result": "---\nsource: invoice.pdf\n---\n# Title",
|
||||
"error": None,
|
||||
}
|
||||
assert entry["status"] == DocumentStatus.READY
|
||||
assert entry["filename"] == "invoice.pdf"
|
||||
assert entry["analyzer_id"] == "prebuilt-documentSearch"
|
||||
assert entry["analysis_duration_s"] == 1.23
|
||||
assert entry["upload_duration_s"] is None
|
||||
assert isinstance(entry["result"], str)
|
||||
|
||||
def test_failed_entry(self) -> None:
|
||||
entry: DocumentEntry = {
|
||||
"status": DocumentStatus.FAILED,
|
||||
"filename": "bad.pdf",
|
||||
"media_type": "application/pdf",
|
||||
"analyzer_id": "prebuilt-documentSearch",
|
||||
"analyzed_at": "2026-01-01T00:00:00+00:00",
|
||||
"analysis_duration_s": 0.5,
|
||||
"upload_duration_s": None,
|
||||
"result": None,
|
||||
"error": "Service unavailable",
|
||||
}
|
||||
assert entry["status"] == DocumentStatus.FAILED
|
||||
assert entry["error"] == "Service unavailable"
|
||||
assert entry["result"] is None
|
||||
|
||||
|
||||
class TestFileSearchConfig:
|
||||
def test_required_fields(self) -> None:
|
||||
backend = AsyncMock()
|
||||
tool = {"type": "file_search", "vector_store_ids": ["vs_123"]}
|
||||
config = FileSearchConfig(backend=backend, vector_store_id="vs_123", file_search_tool=tool)
|
||||
assert config.backend is backend
|
||||
assert config.vector_store_id == "vs_123"
|
||||
assert config.file_search_tool is tool
|
||||
|
||||
def test_from_openai_factory(self) -> None:
|
||||
from agent_framework_azure_contentunderstanding._file_search import OpenAIFileSearchBackend
|
||||
|
||||
client = AsyncMock()
|
||||
tool = {"type": "file_search", "vector_store_ids": ["vs_abc"]}
|
||||
config = FileSearchConfig.from_openai(client, vector_store_id="vs_abc", file_search_tool=tool)
|
||||
assert isinstance(config.backend, OpenAIFileSearchBackend)
|
||||
assert config.vector_store_id == "vs_abc"
|
||||
assert config.file_search_tool is tool
|
||||
Reference in New Issue
Block a user