|
17 | 17 | using Microsoft.Extensions.Configuration;
|
18 | 18 | using Microsoft.Extensions.Hosting;
|
19 | 19 | using Aspire.TestUtilities;
|
| 20 | +using Aspire.Hosting.ApplicationModel; |
20 | 21 |
|
21 | 22 | namespace Aspire.Hosting.Azure.Tests;
|
22 | 23 |
|
@@ -396,6 +397,85 @@ public async Task DeployAsync_WithMultipleComputeEnvironments_Works()
|
396 | 397 | cmd.Arguments.StartsWith("push acaregistry.azurecr.io/"));
|
397 | 398 | }
|
398 | 399 |
|
| 400 | + [Fact] |
| 401 | + public async Task DeployAsync_WithAzureFunctionsProject_Works() |
| 402 | + { |
| 403 | + // Arrange |
| 404 | + var mockProcessRunner = new MockProcessRunner(); |
| 405 | + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, publisher: "default", isDeploy: true); |
| 406 | + var deploymentOutputs = new Dictionary<string, object> |
| 407 | + { |
| 408 | + // ACA Environment outputs (needed for containerAppEnv) |
| 409 | + ["env_AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" }, |
| 410 | + ["env_AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" }, |
| 411 | + ["env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" }, |
| 412 | + ["env_AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" }, |
| 413 | + ["env_AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }, |
| 414 | + |
| 415 | + // Storage outputs for funcstorage |
| 416 | + ["funcstorage_blobEndpoint"] = new { type = "String", value = "https://testfuncstorage.blob.core.windows.net/" }, |
| 417 | + ["funcstorage_queueEndpoint"] = new { type = "String", value = "https://testfuncstorage.queue.core.windows.net/" }, |
| 418 | + ["funcstorage_tableEndpoint"] = new { type = "String", value = "https://testfuncstorage.table.core.windows.net/" }, |
| 419 | + |
| 420 | + // Storage outputs for hoststorage |
| 421 | + ["hoststorage_blobEndpoint"] = new { type = "String", value = "https://testhoststorage.blob.core.windows.net/" }, |
| 422 | + ["hoststorage_queueEndpoint"] = new { type = "String", value = "https://testhoststorage.queue.core.windows.net/" }, |
| 423 | + ["hoststorage_tableEndpoint"] = new { type = "String", value = "https://testhoststorage.table.core.windows.net/" }, |
| 424 | + |
| 425 | + ["funcapp_identity_id"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" }, |
| 426 | + ["funcapp_identity_clientId"] = new { type = "String", value = "test-client-id" } |
| 427 | + }; |
| 428 | + var armClientProvider = new TestArmClientProvider(deploymentOutputs); |
| 429 | + ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner); |
| 430 | + |
| 431 | + var containerAppEnv = builder.AddAzureContainerAppEnvironment("env"); |
| 432 | + var azureEnv = builder.AddAzureEnvironment(); |
| 433 | + |
| 434 | + // Add Azure Storage for the Functions project |
| 435 | + var storage = builder.AddAzureStorage("funcstorage"); |
| 436 | + var hostStorage = builder.AddAzureStorage("hoststorage"); |
| 437 | + var blobs = storage.AddBlobs("blobs"); |
| 438 | + var funcApp = builder.AddAzureFunctionsProject<TestFunctionsProject>("funcapp") |
| 439 | + .WithReference(blobs) |
| 440 | + .WithHostStorage(hostStorage); |
| 441 | + |
| 442 | + // Act |
| 443 | + using var app = builder.Build(); |
| 444 | + await app.StartAsync(); |
| 445 | + await app.WaitForShutdownAsync(); |
| 446 | + |
| 447 | + // Assert that container environment outputs are propagated |
| 448 | + Assert.Equal("testregistry", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_NAME"]); |
| 449 | + Assert.Equal("testregistry.azurecr.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_ENDPOINT"]); |
| 450 | + Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"]); |
| 451 | + Assert.Equal("test.westus.azurecontainerapps.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]); |
| 452 | + Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]); |
| 453 | + |
| 454 | + // Assert that funcapp outputs are propagated |
| 455 | + var funcAppDeployment = Assert.IsType<AzureProvisioningResource>(funcApp.Resource.GetDeploymentTargetAnnotation()?.DeploymentTarget); |
| 456 | + Assert.NotNull(funcAppDeployment); |
| 457 | + Assert.Equal(await ((BicepOutputReference)funcAppDeployment.Parameters["env_outputs_azure_container_apps_environment_default_domain"]!).GetValueAsync(), containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]); |
| 458 | + Assert.Equal(await ((BicepOutputReference)funcAppDeployment.Parameters["env_outputs_azure_container_apps_environment_id"]!).GetValueAsync(), containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]); |
| 459 | + Assert.Equal("https://testfuncstorage.blob.core.windows.net/", await ((BicepOutputReference)funcAppDeployment.Parameters["funcstorage_outputs_blobendpoint"]!).GetValueAsync()); |
| 460 | + Assert.Equal("https://testhoststorage.blob.core.windows.net/", await ((BicepOutputReference)funcAppDeployment.Parameters["hoststorage_outputs_blobendpoint"]!).GetValueAsync()); |
| 461 | + |
| 462 | + // Assert - Verify ACR login command was called since Functions image needs to be built and pushed |
| 463 | + Assert.Contains(mockProcessRunner.ExecutedCommands, |
| 464 | + cmd => cmd.ExecutablePath.Contains("az") && |
| 465 | + cmd.Arguments == "acr login --name testregistry"); |
| 466 | + |
| 467 | + // Assert - Verify Docker tag and push called for Azure Functions project |
| 468 | + Assert.Contains(mockProcessRunner.ExecutedCommands, |
| 469 | + cmd => cmd.ExecutablePath == "docker" && |
| 470 | + cmd.Arguments != null && |
| 471 | + cmd.Arguments.StartsWith("tag funcapp testregistry.azurecr.io/")); |
| 472 | + |
| 473 | + Assert.Contains(mockProcessRunner.ExecutedCommands, |
| 474 | + cmd => cmd.ExecutablePath == "docker" && |
| 475 | + cmd.Arguments != null && |
| 476 | + cmd.Arguments.StartsWith("push testregistry.azurecr.io/")); |
| 477 | + } |
| 478 | + |
399 | 479 | private static void ConfigureTestServices(IDistributedApplicationTestingBuilder builder,
|
400 | 480 | IInteractionService? interactionService = null,
|
401 | 481 | IBicepProvisioner? bicepProvisioner = null,
|
@@ -453,4 +533,21 @@ private sealed class Project : IProjectMetadata
|
453 | 533 | {
|
454 | 534 | public string ProjectPath => "project";
|
455 | 535 | }
|
| 536 | + |
| 537 | + private sealed class TestFunctionsProject : IProjectMetadata |
| 538 | + { |
| 539 | + public string ProjectPath => "functions-project"; |
| 540 | + |
| 541 | + public LaunchSettings LaunchSettings => new() |
| 542 | + { |
| 543 | + Profiles = new Dictionary<string, LaunchProfile> |
| 544 | + { |
| 545 | + ["funcapp"] = new() |
| 546 | + { |
| 547 | + CommandLineArgs = "--port 7071", |
| 548 | + LaunchBrowser = false, |
| 549 | + } |
| 550 | + } |
| 551 | + }; |
| 552 | + } |
456 | 553 | }
|
0 commit comments