Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/cognitivelanguage/azure-ai-language-questionanswering-authoring",
"Tag": "python/cognitivelanguage/azure-ai-language-questionanswering-authoring_401205f3a4"
"Tag": "python/cognitivelanguage/azure-ai-language-questionanswering-authoring_5b3f5f3704"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Question Answering Authoring Tests

This folder contains live tests for the Question Answering Authoring client.

## Test strategy

These tests use a **session-scoped unique project** created in `conftest.py`:

- Create a unique project name per session
- Run authoring operations (create/deploy/import/export/update)
- Delete the project in teardown (best-effort cleanup)

This keeps tests isolated and reduces flakiness from shared state.

## Live / playback behavior

Set the following environment variables before running live tests:

- `AZURE_TEST_RUN_LIVE=true`
- `AZURE_QUESTIONANSWERING_ENDPOINT`
- `AZURE_QUESTIONANSWERING_KEY`
- `AZURE_QUESTIONANSWERING_PROJECT` (used as project name prefix)

If `AZURE_TEST_RUN_LIVE` is not `true`, live-only tests are skipped.

When `AZURE_TEST_RUN_LIVE=false`, tests run in playback mode using sanitized environment values and recorded interactions.

## Install dev dependencies

Run from package directory:

```powershell
cd sdk/cognitivelanguage/azure-ai-language-questionanswering-authoring
pip install -r dev_requirements.txt
```

## Run tests

```powershell
pytest tests
```

Run a single test example:

```powershell
pytest -q tests/test_update_knowledge_sources.py::TestSourcesQnasSynonyms::test_add_qna_with_explicitlytaggedheading -q
```
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import sys
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path

package_root = Path(__file__).resolve().parent.parent
if str(package_root) not in sys.path:
sys.path.insert(0, str(package_root))

import pytest
from devtools_testutils.sanitizers import (
add_header_regex_sanitizer,
add_general_regex_sanitizer,
add_oauth_response_sanitizer,
)
from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from azure.ai.language.questionanswering.authoring import QuestionAnsweringAuthoringClient

ENV_ENDPOINT = "AZURE_QUESTIONANSWERING_ENDPOINT"
ENV_KEY = "AZURE_QUESTIONANSWERING_KEY"
ENV_PROJECT = "AZURE_QUESTIONANSWERING_PROJECT"
ENV_TEST_RUN_LIVE = "AZURE_TEST_RUN_LIVE"

TEST_ENDPOINT = "https://test-resource.cognitiveservices.azure.com/"
TEST_KEY = "0000000000000000"
Expand All @@ -22,14 +37,66 @@ def add_sanitizers(test_proxy, environment_variables): # type: ignore[name-defi
ENV_PROJECT: TEST_PROJECT,
}
)
# Normalize dynamic live project names in recordings to a stable playback value.
add_general_regex_sanitizer(
regex=r"test-project-\d{14}-[0-9a-f]{8}",
value=TEST_PROJECT,
)
add_oauth_response_sanitizer()
add_header_regex_sanitizer(key="Set-Cookie", value="[set-cookie;]")


@pytest.fixture(scope="session")
def qna_authoring_creds(environment_variables): # type: ignore[name-defined]
def qna_authoring_creds_base(environment_variables): # type: ignore[name-defined]
is_live = (os.environ.get(ENV_TEST_RUN_LIVE) or "").strip().lower() == "true"
if is_live:
endpoint = os.environ.get(ENV_ENDPOINT)
key = os.environ.get(ENV_KEY)
base_project = os.environ.get(ENV_PROJECT)
if not endpoint or not key:
pytest.skip("Missing AZURE_QUESTIONANSWERING_ENDPOINT/KEY environment variables")
else:
endpoint = environment_variables.get(ENV_ENDPOINT)
key = environment_variables.get(ENV_KEY)
base_project = environment_variables.get(ENV_PROJECT)

yield {
"endpoint": environment_variables.get(ENV_ENDPOINT),
"key": environment_variables.get(ENV_KEY),
"project": environment_variables.get(ENV_PROJECT),
"endpoint": endpoint,
"key": key,
"project": base_project,
}


@pytest.fixture(scope="session")
def authoring_project_name(qna_authoring_creds_base):
"""Return a unique project name for this test session.

We derive from AZURE_QUESTIONANSWERING_PROJECT if present, but always append a unique suffix
to avoid cross-run interference. Cleanup is best-effort.
"""
is_live = (os.environ.get(ENV_TEST_RUN_LIVE) or "").strip().lower() == "true"
if not is_live:
yield qna_authoring_creds_base["project"]
return

base = os.environ.get(ENV_PROJECT) or "qa"
suffix = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") + "-" + uuid.uuid4().hex[:8]
project_name = f"{base}-{suffix}"
yield project_name

endpoint = qna_authoring_creds_base["endpoint"]
key = qna_authoring_creds_base["key"]
if not endpoint or not key:
return
client = QuestionAnsweringAuthoringClient(endpoint, AzureKeyCredential(key))
try:
delete_poller = client.begin_delete_project(project_name=project_name)
delete_poller.result()
except (ResourceNotFoundError, HttpResponseError):
pass


@pytest.fixture(scope="session")
def qna_authoring_creds(qna_authoring_creds_base, authoring_project_name):
qna_authoring_creds_base["project"] = authoring_project_name
yield qna_authoring_creds_base
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class AuthoringTestHelper:
"""Utility helper for creating and exporting authoring test projects."""

_SURFACE_BOOK_GUIDE_URL = "https://download.microsoft.com/download/7/B/1/7B10C82E-F520-4080-8516-5CF0D803EEE0/surface-book-user-guide-EN.pdf"
_SURFACE_BOOK_GUIDE_DISPLAY_NAME = "surface-book-user-guide-EN"

@staticmethod
def create_test_project(
client,
Expand Down Expand Up @@ -39,9 +42,12 @@ def add_sources(client, project_name, **kwargs):
{
"op": "add",
"value": {
"displayName": "Isaac Newton Bio",
"sourceUri": "https://wikipedia.org/wiki/Isaac_Newton",
"displayName": AuthoringTestHelper._SURFACE_BOOK_GUIDE_DISPLAY_NAME,
"source": AuthoringTestHelper._SURFACE_BOOK_GUIDE_URL,
"sourceUri": AuthoringTestHelper._SURFACE_BOOK_GUIDE_URL,
"sourceKind": "url",
"contentStructureKind": "unstructured",
"refresh": False,
},
}
],
Expand All @@ -63,6 +69,9 @@ def export_project(client, project_name, delete_project=True, **kwargs): # pylin
class AuthoringAsyncTestHelper:
"""Async utility helper for creating and exporting authoring test projects."""

_SURFACE_BOOK_GUIDE_URL = AuthoringTestHelper._SURFACE_BOOK_GUIDE_URL
_SURFACE_BOOK_GUIDE_DISPLAY_NAME = AuthoringTestHelper._SURFACE_BOOK_GUIDE_DISPLAY_NAME

@staticmethod
async def create_test_project(
client,
Expand Down Expand Up @@ -99,9 +108,12 @@ async def add_sources(client, project_name, **kwargs):
{
"op": "add",
"value": {
"displayName": "Isaac Newton Bio",
"sourceUri": "https://wikipedia.org/wiki/Isaac_Newton",
"displayName": AuthoringAsyncTestHelper._SURFACE_BOOK_GUIDE_DISPLAY_NAME,
"source": AuthoringAsyncTestHelper._SURFACE_BOOK_GUIDE_URL,
"sourceUri": AuthoringAsyncTestHelper._SURFACE_BOOK_GUIDE_URL,
"sourceKind": "url",
"contentStructureKind": "unstructured",
"refresh": False,
},
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from testcase import QuestionAnsweringAuthoringTestCase

from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import ResourceExistsError
from azure.ai.language.questionanswering.authoring import QuestionAnsweringAuthoringClient


Expand All @@ -23,30 +24,36 @@ def test_create_project(self, recorded_test, qna_authoring_creds): # type: igno
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
client.create_project( # pylint: disable=no-value-for-parameter
project_name=project_name,
options={
"description": "Biography of Sir Isaac Newton",
"language": "en",
"multilingualResource": True,
"settings": {"defaultAnswer": "no answer"},
},
)
project_name = qna_authoring_creds["project"]
try:
client.create_project( # pylint: disable=no-value-for-parameter
project_name=project_name,
options={
"description": "Biography of Sir Isaac Newton",
"language": "en",
"multilingualResource": True,
"settings": {"defaultAnswer": "no answer"},
},
)
except ResourceExistsError:
pass
found = any(p.get("projectName") == project_name for p in client.list_projects())
assert found

def test_deploy_project(self, recorded_test, qna_authoring_creds): # type: ignore[name-defined] # pylint: disable=unused-argument
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
AuthoringTestHelper.create_test_project(
client,
project_name=project_name,
is_deployable=True,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
project_name = qna_authoring_creds["project"]
try:
AuthoringTestHelper.create_test_project(
client,
project_name=project_name,
is_deployable=True,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
except ResourceExistsError:
pass
deployment_poller = client.begin_deploy_project(
project_name=project_name,
deployment_name="production",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from testcase import QuestionAnsweringAuthoringTestCase

from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import ResourceExistsError
from azure.ai.language.questionanswering.authoring.aio import QuestionAnsweringAuthoringClient


Expand All @@ -13,17 +14,20 @@ async def test_create_project(self, recorded_test, qna_authoring_creds): # type
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
project_name = qna_authoring_creds["project"]
async with client:
await client.create_project( # pylint: disable=no-value-for-parameter
project_name=project_name,
options={
"description": "Biography of Sir Isaac Newton",
"language": "en",
"multilingualResource": True,
"settings": {"defaultAnswer": "no answer"},
},
)
try:
await client.create_project( # pylint: disable=no-value-for-parameter
project_name=project_name,
options={
"description": "Biography of Sir Isaac Newton",
"language": "en",
"multilingualResource": True,
"settings": {"defaultAnswer": "no answer"},
},
)
except ResourceExistsError:
pass
found = False
async for p in client.list_projects():
if p.get("projectName") == project_name:
Expand All @@ -35,14 +39,17 @@ async def test_deploy_project(self, recorded_test, qna_authoring_creds): # type
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
project_name = qna_authoring_creds["project"]
async with client:
await AuthoringAsyncTestHelper.create_test_project(
client,
project_name=project_name,
is_deployable=True,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
try:
await AuthoringAsyncTestHelper.create_test_project(
client,
project_name=project_name,
is_deployable=True,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
except ResourceExistsError:
pass
deployment_poller = await client.begin_deploy_project(
project_name=project_name,
deployment_name="production",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from testcase import QuestionAnsweringAuthoringTestCase

from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import ResourceExistsError
from azure.ai.language.questionanswering.authoring import QuestionAnsweringAuthoringClient, models as _models


Expand All @@ -11,10 +12,13 @@ def test_export_project(self, recorded_test, qna_authoring_creds): # type: igno
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
AuthoringTestHelper.create_test_project(
client, project_name=project_name, polling_interval=0 if self.is_playback else None # pylint: disable=using-constant-test
)
project_name = qna_authoring_creds["project"]
try:
AuthoringTestHelper.create_test_project(
client, project_name=project_name, polling_interval=0 if self.is_playback else None # pylint: disable=using-constant-test
)
except ResourceExistsError:
pass
export_poller = client.begin_export(
project_name=project_name,
file_format="json",
Expand All @@ -27,15 +31,18 @@ def test_import_project(self, recorded_test, qna_authoring_creds): # type: igno
client = QuestionAnsweringAuthoringClient(
qna_authoring_creds["endpoint"], AzureKeyCredential(qna_authoring_creds["key"])
)
project_name = "IsaacNewton"
project_name = qna_authoring_creds["project"]
# Create project without deleting it; we just need it present for import.
AuthoringTestHelper.create_test_project(
client,
project_name=project_name,
get_export_url=False,
delete_old_project=False,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
try:
AuthoringTestHelper.create_test_project(
client,
project_name=project_name,
get_export_url=False,
delete_old_project=False,
polling_interval=0 if self.is_playback else None, # pylint: disable=using-constant-test
)
except ResourceExistsError:
pass
# Wait briefly until project is visible (eventual consistency safeguard)
visible = any(p.get("projectName") == project_name for p in client.list_projects())
if not visible:
Expand Down
Loading
Loading