pulumi/sdk/dotnet/Pulumi/Testing/MockMonitor.cs
Pat Gavlin 683b4de2f0
Add .NET resource ref unit tests. (#6104)
- Add tests that serialize custom and component resources for targets
  that support resource references
- Add tests that serialize custom and component resources for downlevel
  targets
- Add tests that deserialize known custom and component resources
- Add tests that deserialize missing custom and component resources

These changes also fix a few bugs that were encountered during testing:
- Component resource construction was not supported
- Resources with missing packages could not be deserialized

In the latter case, a missing resource is deserialized as a generic
DependencyResource.

These changes also update the signature of IMocks.NewResourceAsync to
allow the returned ID to be null. This is technically a C# breaking change
with respect to nullability.

Contributes to #5943.

Co-authored-by: Mikhail Shilkov <github@mikhail.io>
2021-01-14 12:24:41 -08:00

171 lines
6.4 KiB
C#

// Copyright 2016-2020, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
using Pulumirpc;
namespace Pulumi.Testing
{
internal class MockMonitor : IMonitor
{
private readonly IMocks _mocks;
private readonly Serializer _serializer = new Serializer(excessiveDebugOutput: false);
private readonly Dictionary<string, object> _registeredResources = new Dictionary<string, object>();
public readonly List<Resource> Resources = new List<Resource>();
public MockMonitor(IMocks mocks)
{
_mocks = mocks;
}
public Task<SupportsFeatureResponse> SupportsFeatureAsync(SupportsFeatureRequest request)
{
var hasSupport = request.Id == "secrets" || request.Id == "resourceReferences";
return Task.FromResult(new SupportsFeatureResponse { HasSupport = hasSupport });
}
public async Task<InvokeResponse> InvokeAsync(InvokeRequest request)
{
var args = ToDictionary(request.Args);
if (request.Tok == "pulumi:pulumi:getResource")
{
var urn = (string)args["urn"];
object? registeredResource;
lock (_registeredResources)
{
if (!_registeredResources.TryGetValue(urn, out registeredResource))
{
throw new InvalidOperationException($"Unknown resource {urn}");
}
}
return new InvokeResponse { Return = await SerializeAsync(registeredResource).ConfigureAwait(false) };
}
var result = await _mocks.CallAsync(request.Tok, args, request.Provider)
.ConfigureAwait(false);
return new InvokeResponse { Return = await SerializeAsync(result).ConfigureAwait(false) };
}
public async Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request)
{
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name,
ToDictionary(request.Properties), request.Provider, request.Id).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);
lock (_registeredResources)
{
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add("urn", urn);
if (id != null)
{
builder.Add("id", id);
}
builder.Add("state", serializedState);
_registeredResources[urn] = builder.ToImmutable();
}
lock (this.Resources)
{
this.Resources.Add(resource);
}
return new ReadResourceResponse
{
Urn = urn,
Properties = Serializer.CreateStruct(serializedState),
};
}
public async Task<RegisterResourceResponse> RegisterResourceAsync(Resource resource, RegisterResourceRequest request)
{
lock (this.Resources)
{
this.Resources.Add(resource);
}
if (request.Type == Stack._rootPulumiStackTypeName)
{
return new RegisterResourceResponse
{
Urn = NewUrn(request.Parent, request.Type, request.Name),
Object = new Struct(),
};
}
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name, ToDictionary(request.Object),
request.Provider, request.ImportId).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);
lock (_registeredResources)
{
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add("urn", urn);
builder.Add("id", id ?? request.ImportId);
builder.Add("state", serializedState);
_registeredResources[urn] = builder.ToImmutable();
}
return new RegisterResourceResponse
{
Id = id ?? request.ImportId,
Urn = urn,
Object = Serializer.CreateStruct(serializedState),
};
}
public Task RegisterResourceOutputsAsync(RegisterResourceOutputsRequest request) => Task.CompletedTask;
private static string NewUrn(string parent, string type, string name)
{
if (!string.IsNullOrEmpty(parent))
{
var qualifiedType = parent.Split("::")[2];
var parentType = qualifiedType.Split("$").First();
type = parentType + "$" + type;
}
return "urn:pulumi:" + string.Join("::", new[] { Deployment.Instance.StackName, Deployment.Instance.ProjectName, type, name });
}
private static ImmutableDictionary<string, object> ToDictionary(Struct s)
{
var builder = ImmutableDictionary.CreateBuilder<string, object>();
foreach (var (key, value) in s.Fields)
{
var data = Deserializer.Deserialize(value);
if (data.IsKnown && data.Value != null)
{
builder.Add(key, data.Value);
}
}
return builder.ToImmutable();
}
private async Task<ImmutableDictionary<string, object>> SerializeToDictionary(object o)
{
if (o is IDictionary<string, object> d)
{
o = d.ToImmutableDictionary();
}
return await _serializer.SerializeAsync("", o, true).ConfigureAwait(false) as ImmutableDictionary<string, object>
?? throw new InvalidOperationException($"{o.GetType().FullName} is not a supported argument type");
}
private async Task<Struct> SerializeAsync(object o)
{
var dict = await SerializeToDictionary(o).ConfigureAwait(false);
return Serializer.CreateStruct(dict);
}
}
}