Skip to content

Commit bc633bf

Browse files
Adding End-to-End Test and Streamlining Existing Tests (#247)
* Combined updates --------- Co-authored-by: Matt Dotson <[email protected]>
1 parent 2257f39 commit bc633bf

22 files changed

+2127
-86
lines changed

.devcontainer/devcontainer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
"terraform-linters.tflint-vscode",
3737
"microsoft-IsvExpTools.powerplatform-vscode",
3838
"ms-vscode.azurecli",
39-
"bierner.markdown-mermaid"
39+
"bierner.markdown-mermaid",
40+
"ms-dotnettools.csharp",
41+
"ms-dotnettools.vscode-dotnet-runtime"
4042
// Include other VSCode extensions if needed
4143
// Right click on an extension inside VSCode to add directly to devcontainer.json, or copy the extension ID
4244
],

.devcontainer/postCreate.sh

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,19 @@ tflint --init
1212
echo "Installing PowerApps CLI..."
1313
dotnet tool install --global Microsoft.PowerApps.CLI.Tool --version 1.43.6
1414

15-
echo "Post-create setup completed successfully!"
15+
# Restore .NET packages including Microsoft.Agents.CopilotStudio.Client
16+
echo "Restoring .NET packages..."
17+
if [ -f "Directory.Build.props" ]; then
18+
dotnet restore
19+
echo ".NET packages restored successfully!"
20+
else
21+
# Fallback to individual project restore
22+
if [ -f "tests/Copilot/CopilotTests.csproj" ]; then
23+
dotnet restore tests/Copilot/CopilotTests.csproj
24+
echo "Copilot project packages restored successfully!"
25+
else
26+
echo "No .NET projects found, skipping package restore"
27+
fi
28+
fi
29+
30+
echo "Post-create setup completed successfully!"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,7 @@ power_platform_deployment_settings
493493
*_env/
494494
*_venv/
495495
*.venv/
496+
497+
# Ignore environment files
498+
.env
499+
.env.*

Directory.Build.props

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
8+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
9+
<WarningsNotAsErrors>NU1503</WarningsNotAsErrors>
10+
</PropertyGroup>
11+
12+
<PropertyGroup>
13+
<!-- Common package versions -->
14+
<MicrosoftAgentsCopilotStudioClientVersion>1.0.0</MicrosoftAgentsCopilotStudioClientVersion>
15+
<MicrosoftNETTestSdkVersion>17.8.0</MicrosoftNETTestSdkVersion>
16+
<XunitVersion>2.4.2</XunitVersion>
17+
<XunitRunnerVersion>2.4.5</XunitRunnerVersion>
18+
<CoverletVersion>6.0.0</CoverletVersion>
19+
</PropertyGroup>
20+
21+
</Project>
22+

README.md

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ network security.
1818
- [Deploying](#deploying)
1919
- [Using the bot](#using-the-bot)
2020
- [Clean up](#clean-up)
21+
- [Testing](#testing)
22+
- [Copilot Studio Agent Tests](#copilot-studio-agent-test)
23+
- [AI Search Tests (Optional)](#ai-search-test-optional)
2124
- [Advanced scenarios](#advanced-scenarios)
2225
- [GitHub self-hosted runners](#github-self-hosted-runners)
2326
- [Resource Configuration Notes](#resource-configuration-notes)
@@ -215,7 +218,105 @@ To clean up all the resources created by this sample:
215218
216219
All the Azure and Power Platform resources will be deleted.
217220
218-
## Advanced scenarios
221+
## Testing
222+
223+
This solution includes tests that validate both Copilot Studio and Azure AI Search components after deployment.
224+
225+
### Copilot Studio Agent Test
226+
227+
Located in `tests/Copilot/`, this test validates:
228+
229+
- **Conversation Flow**: End-to-end conversation test with the deployed agent
230+
- **Integration**: Validation that Copilot Studio can successfully query Azure AI Search
231+
232+
Currently, [the Copilot Studio Client in the Agent SDK does not support the use of Service Principals for authentication](https://github.com/microsoft/Agents/blob/main/samples/basic/copilotstudio-client/dotnet/README.md#create-an-application-registration-in-entra-id---service-principal-login), and testing requires a cloud-native app registration as well as a test account with MFA turned off. The test user account must have access to the Power Platform environment containing the agent as well as access to the agent itself.
233+
234+
#### Running Tests After Local Deployment Execution
235+
236+
After a successful local deployment execution, the local .env file contains most of the information needed to run the end-to-end Copilot Studio test. Alternatively, any test input can be set directly through environment variables.
237+
238+
Run the commands below to execute the test after a deployment.
239+
240+
```bash
241+
# Navigate to the test directory
242+
cd tests/Copilot
243+
244+
export POWER_PLATFORM_USERNAME="[email protected]"
245+
export POWER_PLATFORM_PASSWORD="passhere"
246+
export TEST_CLIENT_ID="native-app-guid-here"
247+
248+
# Run tests using azd environment outputs (recommended)
249+
dotnet test --logger "console;verbosity=detailed"
250+
```
251+
252+
#### Running Tests with Manual Environment Variable Configuration
253+
254+
If you prefer to set environment variables manually or need to override specific values, you can configure all required variables explicitly:
255+
256+
```bash
257+
# Navigate to the test directory
258+
cd tests/Copilot
259+
260+
# Power Platform authentication
261+
export POWER_PLATFORM_USERNAME="[email protected]"
262+
export POWER_PLATFORM_PASSWORD="your-test-password"
263+
export POWER_PLATFORM_TENANT_ID="your-tenant-id"
264+
export POWER_PLATFORM_ENVIRONMENT_ID="your-environment-id"
265+
266+
# Native client application ID
267+
export TEST_CLIENT_ID="your-native-app-client-id"
268+
269+
# Copilot Studio configuration
270+
export COPILOT_STUDIO_ENDPOINT="https://api.copilotstudio.microsoft.com"
271+
export COPILOT_STUDIO_AGENT_ID="crfXX_agentName"
272+
273+
# Run the test
274+
dotnet test --logger "console;verbosity=detailed"
275+
```
276+
277+
**Important Notes:**
278+
- The test account must have **MFA disabled** for automated authentication
279+
- The user must have access to the Power Platform environment and the Copilot Studio agent
280+
- Environment variables take precedence over values from azd .env files
281+
282+
### AI Search Test (Optional)
283+
284+
Located in `tests/AISearch/`, this test validates:
285+
286+
- **Resource Existence**: Verify all search resources (index, datasource, skillset, indexer) exist
287+
- **Configuration Validation**: Check resource configurations match expected settings
288+
- **Content Verification**: Validate index contains expected documents and supports search
289+
- **Pipeline Integration**: End-to-end validation of the complete search pipeline
290+
291+
Because the Copilot agent end-to-end test includes indirect validation of the AI Search functionality, this test does not need to be run unless direct validation and troubleshooting of the AI Search resources is required.
292+
293+
#### Prerequisites for AI Search Tests
294+
295+
Before running AI Search tests, you must complete the following configuration:
296+
297+
1. **Make AI Search Endpoint Public**: Unless the test is run on the same virtual network as the AI Search resource, the AI Search service must be updated to be accessible to the test script. Configure network access in the Azure portal:
298+
- Navigate to your AI Search service
299+
- Go to **Networking** → **Firewalls and virtual networks**
300+
- Select **All networks** or add the test runner's IP to **Selected IP addresses**
301+
302+
2. **Assign RBAC Roles**: The user or service principal running the tests must have the following roles:
303+
- Navigate to your AI Search service in the Azure portal
304+
- Go to **Access control (IAM)****Add role assignment**
305+
- Select **Search Index Data Contributor** role and assign to the user or service principal that will execute the tests
306+
- Add another role assignment for **Search Service Contributor** role to the same user or service principal
307+
308+
#### Running AI Search Tests Locally
309+
310+
```bash
311+
# Ensure you're authenticated and have an azd environment deployed
312+
az login
313+
314+
# Run the test script
315+
cd tests/AISearch
316+
./run-tests.sh
317+
```
318+
319+
The tests automatically discover configuration from your azd environment outputs.
219320

220321
### GitHub self-hosted runners
221322

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Decision Log 005: Agent SDK Authentication Strategy for Testing
2+
3+
**Date:** 2025-07-30
4+
**Status:** Approved
5+
6+
## Context
7+
8+
Our end-to-end testing strategy requires automated testing of Copilot Studio agents to validate the end-to-end flow of responses from the Azure resources to the Copilot Studio agent. We evaluated multiple approaches for implementing these tests, considering authentication requirements, platform support, and operational complexity.
9+
10+
The key testing approaches considered were:
11+
- **Agent SDK with Service Principal Authentication**: Programmatic access using client credentials
12+
- **Agent SDK with User-Based Authentication**: Username/password authentication with real user accounts
13+
- **Copilot Studio Built-in Evaluation Tools**: Native evaluation capabilities within the Copilot Studio platform
14+
15+
Our solution requires reliable, automated testing that can be integrated into CI/CD pipelines while providing comprehensive validation of the AI search integration functionality.
16+
17+
## Decision
18+
19+
We will use **Agent SDK with User-Based Authentication** as our primary testing approach for Copilot Studio agent validation.
20+
21+
## Rationale
22+
23+
1. **Broadest Platform Support**: Agent SDK provides comprehensive support across Microsoft's AI technologies, including Copilot Studio, Foundry, and other conversational AI tools. This gives us maximum flexibility and ensures our testing approach aligns with Microsoft's recommended practices.
24+
25+
2. **Current Authentication Limitations**: Agent SDK does not currently support service principal authentication for Copilot Studio scenarios. While this limits our automation options, user-based authentication is the only viable approach for programmatic testing at this time.
26+
27+
3. **Proven Functional Capability**: We have successfully validated that user-based authentication works reliably for our testing scenarios, providing the functionality needed to execute comprehensive end-to-end tests.
28+
29+
4. **Native SDK Benefits**: Using the official Agent SDK ensures we stay aligned with Microsoft's evolving API surface and receive support for new features as they become available.
30+
31+
## Trade-offs and Considerations
32+
33+
### Accepted Trade-offs
34+
35+
- **MFA Requirement**: User-based authentication requires test accounts with Multi-Factor Authentication (MFA) disabled or configured with app passwords, which introduces security considerations that must be managed through proper test account governance.
36+
37+
- **Credential Management**: User credentials require more careful handling compared to service principals, necessitating additional security measures in our testing infrastructure.
38+
39+
- **Account Lifecycle**: Test user accounts require ongoing management and may be subject to organizational password policies and account lifecycle requirements.
40+
41+
### Alternative Approaches Considered
42+
43+
1. **Copilot Studio Built-in Evaluation Tools**
44+
- **Status**: Currently in preview
45+
- **Benefits**: Lower effort setup, designed specifically for low-code users, integrated with Copilot Studio platform
46+
- **Limitations**: Not generally available, limited programmatic control, unclear CI/CD integration capabilities
47+
- **Future Consideration**: This will be reevaluated when these tools reach general availability
48+
49+
2. **Service Principal Authentication**
50+
- **Status**: Not supported by Agent SDK for Copilot Studio
51+
- **Benefits**: Better security model, easier credential management, standard for enterprise automation
52+
- **Limitations**: Currently not available for our use case
53+
- **Future Consideration**: Will be adopted immediately when Agent SDK adds this capability
54+
55+
3. **Test Channels**
56+
- **Status**: Deprioritized in favor of preserving a secure base agent
57+
- **Limitations**: Testing against a specific channel adds a layer of abstraction that could obscure issues in the Copilot itself. The go-to test channel (DirectLine) is unsecure, and the default agent should not include an unsecure channel by default.
58+
- **Future Consideration**: Tests against channel-specific functionality may be added in the future if a particular channel proves to be heavily utilized.
59+
60+
4. **Python-Based Tests**
61+
- **Status**: Pending on SDK updates
62+
- **Limitations**: At test creation time, the Python SDK did not include the full testing feature set and documentation that was available in the .NET tools.
63+
- **Future Consideration**: The tests could be converted to Python-based tests when the Python SDK reaches feature parity.
64+
65+
## Implementation Requirements
66+
67+
1. **Test Account Management**: Establish dedicated test accounts with appropriate security configurations
68+
2. **Credential Security**: Implement secure credential storage and handling practices in CI/CD pipelines
69+
3. **Error Handling**: Robust authentication error handling and retry logic for transient failures
70+
4. **Monitoring**: Track authentication success/failure rates and account health
71+
72+
## Future Considerations
73+
74+
- **Service Principal Support**: Monitor Agent SDK releases for service principal authentication support and migrate when available
75+
- **Copilot Studio Evaluation Tools**: Evaluate built-in tools when they reach general availability and assess integration with our testing strategy
76+
- **Hybrid Approach**: Consider combining multiple testing approaches as capabilities mature to provide comprehensive coverage
77+
78+
## Related Decisions
79+
80+
- This decision supports the overall testing strategy established in our CI/CD pipeline design
81+
- Security considerations align with our credential management policies outlined in infrastructure security practices
82+

global.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"sdk": {
3+
"version": "8.0.412"
4+
}
5+
}

infra/main.search_configuration.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ resource "azapi_resource" "configure_search_index" {
176176
echo "=== Step 3: Configuring search index ==="
177177
python index_utils.py \
178178
--aisearch_name ${azurerm_search_service.ai_search.name} \
179-
--base_index_name "default" \
179+
--base_index_name "${var.ai_search_base_index_name}" \
180180
--openai_api_base ${module.azure_open_ai.endpoint} \
181181
--subscription_id ${data.azurerm_client_config.current.subscription_id} \
182182
--resource_group_name ${local.resource_group_name} \

infra/modules/copilot_studio/power_platform_users.tf

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,32 @@ data "powerplatform_security_roles" "all_roles" {
1414
}
1515

1616
locals {
17-
security_role_id = { for item in data.powerplatform_security_roles.all_roles.security_roles : item.name => item.role_id }
18-
security_role_ids = [for name in var.pp_environment_user_security_role : local.security_role_id[name]]
17+
# Create a map of security role names to IDs
18+
security_role_id = { for item in data.powerplatform_security_roles.all_roles.security_roles : item.name => item.role_id }
19+
20+
# Filter to only include roles that actually exist in the environment
21+
available_roles = [for name in var.pp_environment_user_security_role : name if contains(keys(local.security_role_id), name)]
22+
23+
# Get the role IDs for the available roles
24+
security_role_ids = [for name in local.available_roles : local.security_role_id[name]]
25+
26+
# Calculate missing roles for debugging
27+
missing_roles = [for name in var.pp_environment_user_security_role : name if !contains(keys(local.security_role_id), name)]
28+
}
29+
30+
# Validation check for missing security roles
31+
check "security_roles_exist" {
32+
assert {
33+
condition = length(local.missing_roles) == 0
34+
error_message = "The following security roles were requested but do not exist in the Power Platform environment: ${join(", ", local.missing_roles)}. Available roles are: ${join(", ", keys(local.security_role_id))}"
35+
}
1936
}
2037

2138
# Add non-dataverse user to Power Platform environment
2239
resource "powerplatform_user" "new_non_dataverse_user" {
2340
for_each = length(var.resource_share_user) > 0 ? var.resource_share_user : []
2441
environment_id = local.power_platform_environment_id
25-
security_roles = local.security_role_ids # Using the same roles from the all_roles data source
42+
security_roles = local.security_role_ids
2643
aad_id = each.value
27-
disable_delete = false
44+
// disable_delete = false
2845
}

infra/outputs.tf

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ output "ai_search_resource_name" {
33
value = azurerm_search_service.ai_search.name
44
}
55

6+
output "ai_search_endpoint" {
7+
description = "The endpoint URL of the AI Search service"
8+
value = "https://${azurerm_search_service.ai_search.name}.search.windows.net"
9+
}
10+
11+
output "ai_search_base_index_name" {
12+
description = "The base name used for AI Search resources"
13+
value = var.ai_search_base_index_name
14+
}
15+
616
output "app_insights_instrumentation_key" {
717
sensitive = true
818
value = var.include_app_insights ? azurerm_application_insights.insights[0].instrumentation_key : null
@@ -57,3 +67,23 @@ output "secondary_azure_region" {
5767
description = "The secondary Azure region for deployment"
5868
value = local.secondary_azure_region
5969
}
70+
71+
output "copilot_studio_endpoint" {
72+
description = "The endpoint URL for Copilot Studio API"
73+
value = "https://api.copilotstudio.microsoft.com"
74+
}
75+
76+
output "copilot_studio_agent_id" {
77+
description = "The ID of the deployed Copilot Studio agent (placeholder - to be extracted from deployment)"
78+
value = "crf6d_aiSearchConnectionExample" # This should match the bot name from the solution
79+
}
80+
81+
output "azure_tenant_id" {
82+
description = "The Azure tenant ID"
83+
value = data.azurerm_client_config.current.tenant_id
84+
}
85+
86+
output "azure_subscription_id" {
87+
description = "The Azure subscription ID"
88+
value = data.azurerm_client_config.current.subscription_id
89+
}

0 commit comments

Comments
 (0)