diff --git a/src/Aspire.Hosting.Azure.Provisioning/BicepValueProxy.cs b/src/Aspire.Hosting.Azure.Provisioning/BicepValueProxy.cs index 0e18b1d98de..3352f87ad61 100644 --- a/src/Aspire.Hosting.Azure.Provisioning/BicepValueProxy.cs +++ b/src/Aspire.Hosting.Azure.Provisioning/BicepValueProxy.cs @@ -180,7 +180,12 @@ private void EnsureLiteralType() private void EnsureLiteralType(Type targetType) { - if (_value.Kind != BicepValueKind.Literal && _valueType == typeof(object)) + // Bicep parameters are strings even when the target SDK property is an enum. + // Only expressions can cross that boundary; a string literal still needs a typed enum. + if (_value.Kind != BicepValueKind.Literal && + (_valueType == typeof(object) || + (_value.Kind == BicepValueKind.Expression && _valueType == typeof(string) && + (Nullable.GetUnderlyingType(targetType) ?? targetType).IsEnum))) { return; } diff --git a/tests/Aspire.Hosting.Azure.Provisioning.Tests/BicepValueProxyTests.cs b/tests/Aspire.Hosting.Azure.Provisioning.Tests/BicepValueProxyTests.cs index 7f8abfe64cd..3dd3edfc45d 100644 --- a/tests/Aspire.Hosting.Azure.Provisioning.Tests/BicepValueProxyTests.cs +++ b/tests/Aspire.Hosting.Azure.Provisioning.Tests/BicepValueProxyTests.cs @@ -66,6 +66,83 @@ public void AssignToPreservesExpression() Assert.Equal("resourceName", target.ToBicepExpression().ToString()); } + [Fact] + public void AssignToAllowsStringParameterExpressionForEnum() + { + var builder = DistributedApplication.CreateBuilder(); + var parameter = builder.AddParameter("sku", "Standard"); + var proxy = CreateInfrastructure().Create().Parameter(parameter, "sku"); + var target = new BicepValue(ProvisioningValueType.Boolean); + + proxy.AssignTo(target); + + Assert.Equal(BicepValueKind.Expression, ((IBicepValue)target).Kind); + Assert.Equal("sku", target.ToBicepExpression().ToString()); + } + + [Fact] + public void ConvertAllowsStringExpressionForEnumAndPreservesSecurity() + { + var proxy = BicepValueProxy.Create( + new BicepValue(new IdentifierExpression("sku")), + isSecure: true); + + var converted = BicepValueProxy.Convert(proxy); + + Assert.Equal(BicepValueKind.Expression, ((IBicepValue)converted).Kind); + Assert.True(((IBicepValue)converted).IsSecure); + Assert.Equal("sku", converted.ToBicepExpression().ToString()); + } + + [Fact] + public void AssignToRejectsStringLiteralForEnumWithoutChangingTarget() + { + var proxy = BicepValueProxy.Create(new BicepValue("Standard")); + var target = new BicepValue(ProvisioningValueType.Boolean); + + var exception = Assert.Throws(() => proxy.AssignTo(target)); + + Assert.Equal( + "A literal of type String cannot be assigned to a BicepValue.", + exception.Message); + Assert.Equal(ProvisioningValueType.Boolean, target.Value); + } + + [Fact] + public void UnsetStringProxyIsRejectedForEnumWithoutChangingTarget() + { + var source = new BicepValue("Standard"); + source.ClearValue(); + var proxy = BicepValueProxy.Create(source); + var target = new BicepValue(ProvisioningValueType.Boolean); + + Assert.Equal(BicepValueKind.Unset, proxy.Kind); + + var exception = Assert.Throws(() => proxy.AssignTo(target)); + + Assert.Equal( + "A value of type String cannot be assigned to a BicepValue.", + exception.Message); + Assert.Equal(BicepValueKind.Literal, ((IBicepValue)target).Kind); + Assert.Equal(ProvisioningValueType.Boolean, target.Value); + Assert.Throws(() => BicepValueProxy.Convert(proxy)); + } + + [Fact] + public void ObjectExpressionProxyIsAcceptedForEnum() + { + var proxy = BicepValueProxy.CreateExpression(new IdentifierExpression("sku"), isSecure: false); + var target = new BicepValue(ProvisioningValueType.Boolean); + + proxy.AssignTo(target); + var converted = BicepValueProxy.Convert(proxy); + + Assert.Equal(BicepValueKind.Expression, ((IBicepValue)target).Kind); + Assert.Equal("sku", target.ToBicepExpression().ToString()); + Assert.Equal(BicepValueKind.Expression, ((IBicepValue)converted).Kind); + Assert.Equal("sku", converted.ToBicepExpression().ToString()); + } + [Fact] public void AssignToPreservesSecureMetadata() {