File: RazorServicesTest.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.CodeAnalysis.Remote.Razor.UnitTests\Microsoft.CodeAnalysis.Remote.Razor.UnitTests.csproj (Microsoft.CodeAnalysis.Remote.Razor.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Remote.Razor;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.CodeAnalysis.Razor.Remote;
 
public class RazorServicesTest(ITestOutputHelper testOutputHelper) : ToolingTestBase(testOutputHelper)
{
    private const string Prefix = "IRemote";
    private const string Suffix = "Service";
 
    private static readonly XmlDocument s_servicesFile = LoadServicesFile();
 
    [Theory]
    [MemberData(nameof(MessagePackServices))]
    public void MessagePackServicesAreListedProperly(Type serviceType, Type? callbackType)
    {
        VerifyService(serviceType, callbackType);
    }
 
    [Theory]
    [MemberData(nameof(JsonServices))]
    public void JsonServicesAreListedProperly(Type serviceType, Type? callbackType)
    {
        Assert.True(typeof(IRemoteJsonService).IsAssignableFrom(serviceType));
        VerifyService(serviceType, callbackType);
    }
 
    [Theory]
    [MemberData(nameof(JsonServices))]
    public void JsonServicesHaveTheRightParameters(Type serviceType, Type? _)
    {
        Assert.True(typeof(IRemoteJsonService).IsAssignableFrom(serviceType));
 
        foreach (var method in serviceType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
        {
            if (method.Name != "RunServiceAsync" &&
                method.GetParameters() is [{ ParameterType: { } parameterType }, ..])
            {
                if (typeof(RazorPinnedSolutionInfoWrapper).IsAssignableFrom(parameterType))
                {
                    Assert.Fail($"Method {method.Name} in a Json service has a pinned solution info wrapper parameter that isn't Json serializable");
                }
            }
        }
    }
 
    [Fact]
    public void RazorServicesContainsAllServices()
    {
        var services = new HashSet<string>(RazorServices.TestAccessor.MessagePackServices.Select(s => s.Item1.Name));
        services.UnionWith(RazorServices.TestAccessor.JsonServices.Select(s => s.Item1.Name));
        var serviceNodes = s_servicesFile.SelectNodes("/Project/ItemGroup/ServiceHubService");
        Assert.NotNull(serviceNodes);
        foreach (XmlNode serviceNode in serviceNodes)
        {
            Assert.NotNull(serviceNode);
            Assert.NotNull(serviceNode.Attributes);
 
            var serviceEntry = serviceNode.Attributes["Include"]!.Value;
            var factoryName = serviceNode.Attributes["ClassName"]!.Value;
 
            var factoryType = typeof(ServiceArgs).Assembly.GetType(factoryName);
            AssertEx.NotNull(factoryType, $"Could not load type for factory '{factoryName}'");
 
            var interfaceType = factoryType.BaseType!.GetGenericArguments()[0];
            Assert.True(services.Contains(interfaceType.Name), $"Service '{interfaceType.Name}' is not listed in RazorServices");
        }
    }
 
    public static IEnumerable<object?[]> MessagePackServices()
    {
        foreach (var service in RazorServices.TestAccessor.MessagePackServices)
        {
            yield return [service.Item1, service.Item2];
        }
    }
 
    public static IEnumerable<object?[]> JsonServices()
    {
        foreach (var service in RazorServices.TestAccessor.JsonServices)
        {
            yield return [service.Item1, service.Item2];
        }
    }
 
    private static XmlDocument LoadServicesFile()
    {
        var document = new XmlDocument();
        document.Load(Path.Combine(TestProject.GetRepoRoot(), "eng", "targets", "RazorServices.props"));
        return document;
    }
 
    private static void VerifyService(Type serviceType, Type? callbackType)
    {
        Assert.Null(callbackType);
 
        var serviceName = serviceType.Name;
        Assert.StartsWith(Prefix, serviceName);
        Assert.EndsWith(Suffix, serviceName);
 
        var shortName = serviceName.Substring(Prefix.Length, serviceName.Length - Prefix.Length - Suffix.Length);
        var servicePropsEntry = $"Microsoft.VisualStudio.Razor.{shortName}";
 
        var serviceNode = s_servicesFile.SelectSingleNode($"/Project/ItemGroup/ServiceHubService[@Include='{servicePropsEntry}']");
        AssertEx.NotNull(serviceNode, $"Expected entry in Services.props for {servicePropsEntry}");
 
        var serviceImplName = $"Microsoft.CodeAnalysis.Remote.Razor.Remote{shortName}Service";
        var factoryName = serviceNode.Attributes!["ClassName"]!.Value;
        Assert.Equal($"{serviceImplName}+Factory", factoryName);
 
        var serviceImplType = typeof(ServiceArgs).Assembly.GetType(serviceImplName);
        Assert.NotNull(serviceImplType);
 
        var factoryType = typeof(ServiceArgs).Assembly.GetType(factoryName);
        Assert.NotNull(factoryType);
 
        Assert.True(serviceType.IsAssignableFrom(serviceImplType));
 
        var interfaceType = factoryType.BaseType!.GetGenericArguments()[0];
        Assert.Equal(serviceType, interfaceType);
    }
}