Port legacy Stubble template engine [#2]

This commit is contained in:
Chris Cheetham 2020-09-08 08:30:52 -04:00
Родитель 26fe75ea15
Коммит 750c45f345
98 изменённых файлов: 4229 добавлений и 1174 удалений

12
.config/dotnet-tools.json Normal file
Просмотреть файл

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "4.6.4",
"commands": [
"reportgenerator"
]
}
}
}

Просмотреть файл

@ -7,10 +7,10 @@ insert_final_newline = true
max_line_length = 120
charset = utf-8
[*.cs]
indent_size = 4
[*.{csproj,props,targets,DotSettings}]
indent_size = 2
[*.{xml,csproj,props}]
[*.cs]
indent_size = 4
[*.yaml]
@ -18,3 +18,6 @@ indent_size = 2
[*.ps1]
indent_size = 4
[*.json]
indent_size = 2

Просмотреть файл

@ -1,8 +1,12 @@
<Project>
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<Target Name="CleanBaseOutput" AfterTargets="Clean">
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
</Project>

Просмотреть файл

@ -6,7 +6,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.InitializrApi", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.InitializrApi.Models", "src\InitializrApi.Models\Steeltoe.InitializrApi.Models.csproj", "{ACE6FEE1-9CEA-4A95-9D76-C62A41947C8F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.InitializrApi.Test", "test\InitializrApi.Test\Steeltoe.InitializrApi.Test.csproj", "{EA849C8F-7118-45DB-BBF2-D36CE1FD43B4}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.InitializrApi.Test.Utils", "test\InitializrApi.Test.Utils\Steeltoe.InitializrApi.Test.Utils.csproj", "{EC08DC14-11D5-40D3-8908-BDB0D789F48D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.InitializrApi.Test.Unit", "test\InitializrApi.Test.Unit\Steeltoe.InitializrApi.Test.Unit.csproj", "{EA849C8F-7118-45DB-BBF2-D36CE1FD43B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.InitializrApi.Test.Integration", "test\InitializrApi.Test.Integration\Steeltoe.InitializrApi.Test.Integration.csproj", "{24A2AB13-F4C4-4C88-B67D-D280610240B8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F80B44AC-7383-40DA-B353-16163BF279F1}"
ProjectSection(SolutionItems) = preProject
@ -69,6 +73,30 @@ Global
{EA849C8F-7118-45DB-BBF2-D36CE1FD43B4}.Release|x64.Build.0 = Release|Any CPU
{EA849C8F-7118-45DB-BBF2-D36CE1FD43B4}.Release|x86.ActiveCfg = Release|Any CPU
{EA849C8F-7118-45DB-BBF2-D36CE1FD43B4}.Release|x86.Build.0 = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|x64.Build.0 = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Debug|x86.Build.0 = Debug|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|Any CPU.Build.0 = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|x64.ActiveCfg = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|x64.Build.0 = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|x86.ActiveCfg = Release|Any CPU
{24A2AB13-F4C4-4C88-B67D-D280610240B8}.Release|x86.Build.0 = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|x64.ActiveCfg = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|x64.Build.0 = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|x86.ActiveCfg = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Debug|x86.Build.0 = Debug|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|Any CPU.Build.0 = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|x64.ActiveCfg = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|x64.Build.0 = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|x86.ActiveCfg = Release|Any CPU
{EC08DC14-11D5-40D3-8908-BDB0D789F48D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -1,4 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Initializr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Steeltoe/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Initializr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Steeltoe/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Licensed to the .NET Foundation under one or more agreements.
The .NET Foundation licenses this file to you under the Apache 2.0 License.
See the LICENSE file in the project root for more information.
</s:String>
</wpf:ResourceDictionary>

Просмотреть файл

@ -1,4 +1,5 @@
# InitializrApi
# Steeltoe InitializrApi
Steeltoe Initializr API reference implementation
[![Build Status](https://dev.azure.com/SteeltoeOSS/Steeltoe/_apis/build/status/Initializr/SteeltoeOSS.InitializrApi?branchName=master)](https://dev.azure.com/SteeltoeOSS/Steeltoe/_build/latest?definitionId=31&branchName=master)

Просмотреть файл

@ -1,28 +1,24 @@
<Project>
<PropertyGroup>
<TargetFrameworkMonikerAssemblyAttributesPath>$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))</TargetFrameworkMonikerAssemblyAttributesPath>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworkMonikerAssemblyAttributesPath>$([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))</TargetFrameworkMonikerAssemblyAttributesPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedFiles Include="$(GeneratedAssemblyInfoFile)"/>
</ItemGroup>
<ItemGroup>
<EmbeddedFiles Include="$(GeneratedAssemblyInfoFile)" />
</ItemGroup>
<ItemGroup>
<SourceRoot Include="$(NuGetPackageRoot)" />
</ItemGroup>
<ItemGroup>
<SourceRoot Include="$(NuGetPackageRoot)" />
</ItemGroup>
<Target Name="CoverletGetPathMap"
DependsOnTargets="InitializeSourceRootMappedPaths"
Returns="@(_LocalTopLevelSourceRoot)"
Condition="'$(DeterministicSourcePaths)' == 'true'">
<ItemGroup>
<_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/>
</ItemGroup>
</Target>
<Target Name="CoverletGetPathMap"
DependsOnTargets="InitializeSourceRootMappedPaths"
Returns="@(_LocalTopLevelSourceRoot)"
Condition="'$(DeterministicSourcePaths)' == 'true'">
<ItemGroup>
<_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''" />
</ItemGroup>
</Target>
</Project>
<!--
vim: ft=xml
-->

Просмотреть файл

@ -1,41 +1,37 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<Authors>Steeltoe,VMware</Authors>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<Authors>Steeltoe,VMware</Authors>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- SA1101: Prefix local calls with this -->
<!-- SA1309: Field '...' should not begin with an underscore -->
<NoWarn>SA1101;SA1309</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- SA1101: Prefix local calls with this -->
<!-- SA1309: Field '...' should not begin with an underscore -->
<NoWarn>SA1101;SA1309</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">0.7.0</Version>
<Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(BUILD_BUILDNUMBER)</Version>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">0.7.0</Version>
<Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(BUILD_BUILDNUMBER)</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\stylecop.json" />
</ItemGroup>
</Project>
<!--
vim: ft=xml
-->

Просмотреть файл

@ -5,10 +5,14 @@
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of project configuration metadata used by Initializr UIs and clients.
/// A model of metadata used to describe the configuration of a project.
/// </summary>
public sealed class ProjectMetadata
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the project name.
/// </summary>
@ -19,30 +23,35 @@ namespace Steeltoe.InitializrApi.Models
/// </summary>
public Text Description { get; set; }
/// <summary>
/// Gets or sets the project namespace.
/// </summary>
public Text Namespace { get; set; }
/// <summary>
/// Gets or sets the Steeltoe versions.
/// </summary>
public SingleSelectList SteeltoeVersion { get; set; }
/// <summary>
/// Gets or sets the DotNet framework versions.
/// Gets or sets the DotNet frameworks.
/// </summary>
public SingleSelectList DotNetVersion { get; set; }
public SingleSelectList DotNetFramework { get; set; }
/// <summary>
/// Gets or sets the DotNet template types.
/// </summary>
public SingleSelectList Type { get; set; }
public SingleSelectList DotNetTemplate { get; set; }
/// <summary>
/// Gets or sets the DotNet languages.
/// Gets or sets the languages.
/// </summary>
public SingleSelectList Language { get; set; }
/// <summary>
/// Gets or sets the project bundle archive format.
/// Gets or sets the project archive mime type.
/// </summary>
public SingleSelectList Format { get; set; }
public SingleSelectList ArchiveMimeType { get; set; }
/// <summary>
/// Gets or sets the project dependencies.
@ -70,8 +79,6 @@ namespace Steeltoe.InitializrApi.Models
/// </summary>
public abstract class Item
{
private string _name;
/// <summary>
/// Gets or sets the item ID.
/// </summary>
@ -80,11 +87,7 @@ namespace Steeltoe.InitializrApi.Models
/// <summary>
/// Gets or sets the item name.
/// </summary>
public string Name
{
get => _name ?? Id;
set => _name = value;
}
public string Name { get; set; }
}
/// <summary>
@ -158,6 +161,11 @@ namespace Steeltoe.InitializrApi.Models
/// </summary>
public class GroupList : ItemList
{
/// <summary>
/// Gets or sets the default dependencies.
/// </summary>
public string Default { get; set; }
/// <summary>
/// Gets or sets the groups.
/// </summary>

Просмотреть файл

@ -0,0 +1,138 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Text;
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of the project generation spec.
/// </summary>
public sealed class ProjectSpec
{
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private string _archiveMimeType;
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the project name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the project description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the project namespace.
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// Gets or sets the Steeltoe version.
/// </summary>
public string SteeltoeVersion { get; set; }
/// <summary>
/// Gets or sets the DotNet framework ID.
/// </summary>
public string DotNetFramework { get; set; }
/// <summary>
/// Gets or sets the DotNet template ID.
/// </summary>
public string DotNetTemplate { get; set; }
/// <summary>
/// Gets or sets the language ID.
/// </summary>
public string Language { get; set; }
/// <summary>
/// Gets or sets the project archive mime type.
/// </summary>
public string ArchiveMimeType
{
get => _archiveMimeType;
set => _archiveMimeType = value is null ? null : (value.Contains('/') ? value : $"application/{value}");
}
/// <summary>
/// Gets or sets the project dependencies.
/// </summary>
public string Dependencies { get; set; }
/// <summary>
/// Returns a user-friendly representation of the current object.
/// </summary>
/// <returns>Returns a user-friendly string that represents the current object.</returns>
public override string ToString()
{
var buf = new StringBuilder();
var delim = string.Empty;
buf.Append('[');
if (Name != null)
{
buf.Append(delim).Append("name=").Append(Name);
delim = ",";
}
if (Description != null)
{
buf.Append(delim).Append("description=").Append(Description);
delim = ",";
}
if (Namespace != null)
{
buf.Append(delim).Append("namespace=").Append(Namespace);
delim = ",";
}
if (SteeltoeVersion != null)
{
buf.Append(delim).Append("steeltoeVersion=").Append(SteeltoeVersion);
delim = ",";
}
if (DotNetFramework != null)
{
buf.Append(delim).Append("dotNetFramework=").Append(DotNetFramework);
delim = ",";
}
if (DotNetTemplate != null)
{
buf.Append(delim).Append("dotNetTemplate=").Append(DotNetTemplate);
delim = ",";
}
if (Language != null)
{
buf.Append(delim).Append("language=").Append(Language);
delim = ",";
}
if (ArchiveMimeType != null)
{
buf.Append(delim).Append("archiveMimeType=").Append(ArchiveMimeType);
}
if (Dependencies != null)
{
buf.Append(delim).Append("dependencies=").Append(Dependencies);
}
buf.Append(']');
return buf.ToString();
}
}
}

Просмотреть файл

@ -1,42 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of the specification used to generate a project.
/// </summary>
public sealed class ProjectSpecification
{
/// <summary>
/// Gets or sets the project name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the project description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the Steeltoe version.
/// </summary>
public string SteeltoeVersion { get; set; }
/// <summary>
/// Gets or sets the DotNet target framework.
/// </summary>
public string DotnetTargetFrameworkId { get; set; }
/// <summary>
/// Gets or sets the DotNet template ID.
/// </summary>
public string DotnetTemplateId { get; set; }
/// <summary>
/// Gets or sets the DotNet language.
/// </summary>
public string DotnetLanguageId { get; set; }
}
}

Просмотреть файл

@ -1,39 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of project template configuration metadata used by InitializrApi.
/// </summary>
public class ProjectTemplateConfiguration
{
/// <summary>
/// Gets or sets the project template URI.
/// </summary>
public Uri Uri { get; set; }
/// <summary>
/// Gets or sets the project template Steeltoe version range.
/// </summary>
public string SteeltoeVersionRange { get; set; }
/// <summary>
/// Gets or sets the project template DotNet version range.
/// </summary>
public string DotNetVersionRange { get; set; }
/// <summary>
/// Gets or sets the project template DotNet template type.
/// </summary>
public string Type { get; set; }
/// <summary>
/// Gets or sets the project template language.
/// </summary>
public string Language { get; set; }
}
}

Просмотреть файл

@ -1,7 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
using Steeltoe.InitializrApi.Services;
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Archivers
{
/// <summary>
/// An in-memory <see cref="IArchiverRegistry"/> implementation.
/// </summary>
public class ArchiverRegistry : InitializrApiServiceBase, IArchiverRegistry
{
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly Dictionary<string, IArchiver> _archivers = new Dictionary<string, IArchiver>();
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="ArchiverRegistry"/> class.
/// </summary>
/// <param name="logger">Injected logger.</param>
public ArchiverRegistry(ILogger<ArchiverRegistry> logger)
: base(logger)
{
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <inheritdoc />
public void Initialize()
{
Logger.LogInformation("Initializing archiver registry.");
_archivers.Clear();
Register(new ZipArchiver());
}
/// <inheritdoc />
public void Register(IArchiver archiver)
{
Logger.LogInformation(
"Registering archive mime type: {{MimeType}} [{{Archiver}}]",
archiver.GetMimeType(),
archiver.GetType());
_archivers.Add(archiver.GetMimeType(), archiver);
}
/// <inheritdoc />
public IArchiver Lookup(string mimeType)
{
_archivers.TryGetValue(mimeType, out var archiver);
return archiver;
}
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace Steeltoe.InitializrApi.Archivers
{
/// <summary>
/// An <see cref="IArchiver"/> implementation using the ZIP archive file format.
/// </summary>
public class ZipArchiver : IArchiver
{
/* ----------------------------------------------------------------- *
* constants *
* ----------------------------------------------------------------- */
private const string MimeType = "application/zip";
private const string FileExtension = ".zip";
/* ----------------------------------------------------------------- *
* Fix UNIX permissions in Zip archive extraction *
* Owner *
* Group *
* Other *
* r w r r *
* ----------------------------------------------------------------- */
private const int UnixPermissions = 0b_0000_0001_1010_0100_0000_0000_0000_0000;
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly CompressionLevel _compression;
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="ZipArchiver"/> class.
/// </summary>
/// <param name="compression">Compression level default <see cref="CompressionLevel.Fastest"/>.</param>
public ZipArchiver(CompressionLevel compression = CompressionLevel.Fastest)
{
_compression = compression;
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <inheritdoc/>
public byte[] ToBytes(IEnumerable<FileEntry> fileEntries)
{
var buffer = new MemoryStream();
var archive = new ZipArchive(buffer, ZipArchiveMode.Create, true);
foreach (var fileEntry in fileEntries)
{
var zipEntry = archive.CreateEntry(fileEntry.Path, _compression);
if (fileEntry.Text == null)
{
continue;
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
zipEntry.ExternalAttributes = UnixPermissions;
}
using var textStream = new MemoryStream(Encoding.UTF8.GetBytes(fileEntry.Text));
using var zipStream = zipEntry.Open();
textStream.CopyTo(zipStream);
}
archive.Dispose();
buffer.Seek(0, SeekOrigin.Begin);
return buffer.ToArray();
}
/// <inheritdoc/>
public string GetMimeType()
{
return MimeType;
}
/// <inheritdoc/>
public string GetFileExtension()
{
return FileExtension;
}
}
}

Просмотреть файл

@ -1,36 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Options;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System.Threading.Tasks;
namespace Steeltoe.InitializrApi.Configuration
{
/// <summary>
/// An <see cref="IConfigurationRepository"/> using a <a href="https://cloud.spring.io/spring-cloud-config/reference/html/#_spring_cloud_config_server">Spring Cloud Config Server</a> backend.
/// </summary>
public class ConfigServerConfigurationRepository : IConfigurationRepository
{
private readonly InitializrApiConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigServerConfigurationRepository"/> class.
/// </summary>
/// <param name="configuration">Injected configuration from Config Server.</param>
public ConfigServerConfigurationRepository(IOptions<InitializrApiConfiguration> configuration)
{
_configuration = configuration.Value;
}
/// <inheritdoc/>
public Task<InitializrApiConfiguration> GetConfiguration()
{
var result = new TaskCompletionSource<InitializrApiConfiguration>();
result.SetResult(_configuration);
return result.Task;
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
namespace Steeltoe.InitializrApi.Configuration
{
/// <summary>
/// An <see cref="IInitializrConfigService"/> using a <a href="https://cloud.spring.io/spring-cloud-config/reference/html/#_spring_cloud_config_server">Spring Cloud Config Server</a> backend.
/// </summary>
public class InitializrConfigService : InitializrApiServiceBase, IInitializrConfigService
{
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly InitializrConfig _config;
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="InitializrConfigService"/> class.
/// </summary>
/// <param name="configuration">Injected configuration from Config Server.</param>
/// <param name="logger">Injected logger.</param>
public InitializrConfigService(
IOptions<InitializrConfig> configuration,
ILogger<InitializrConfigService> logger)
: base(logger)
{
_config = configuration.Value;
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <inheritdoc />
public void Initialize()
{
Logger.LogInformation("Initializing Initializr configuration.");
if (_config.ProjectMetadata is null)
{
Logger.LogError("Project metadata missing.");
}
if (_config.ProjectTemplates is null)
{
Logger.LogError("Project templates configuration missing.");
}
}
/// <inheritdoc/>
public InitializrConfig GetInitializrConfig()
{
return _config;
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Steeltoe.InitializrApi.Controllers
{
@ -11,8 +12,25 @@ namespace Steeltoe.InitializrApi.Controllers
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class AboutController : ControllerBase
public class AboutController : InitializrApiControllerBase
{
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="AboutController"/> class.
/// </summary>
/// <param name="logger">Injected logger.</param>
public AboutController(ILogger<AboutController> logger)
: base(logger)
{
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <summary>
/// Implements <c>GET</c>.
/// </summary>

Просмотреть файл

@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
namespace Steeltoe.InitializrApi.Controllers
{
/// <summary>
/// Server configuration endpoint.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ConfigController : InitializrApiControllerBase
{
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly InitializrConfig _config;
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="ConfigController"/> class.
/// </summary>
/// <param name="configService">Injected configuration repository.</param>
/// <param name="logger">Injected logger.</param>
public ConfigController(
IInitializrConfigService configService,
ILogger<ConfigController> logger)
: base(logger)
{
_config = configService.GetInitializrConfig();
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <summary>
/// Implements <c>GET</c> index.
/// </summary>
/// <returns>Returns a <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains the Initializr configuration.</returns>
[HttpGet]
public IActionResult GetInitializrConfiguration()
{
return Ok(_config);
}
/// <summary>
/// Implements <c>GET projectMetadata</c>.
/// </summary>
/// <returns>Returns a <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains Initializr project metadata.</returns>
[HttpGet]
[Route("projectMetadata")]
public IActionResult GetProjectMetadata()
{
return Ok(_config.ProjectMetadata);
}
/// <summary>
/// Implements <c>GET projectTemplates</c>.
/// </summary>
/// <returns>Return a <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains InitializrApi project templates configuration.</returns>
[HttpGet]
[Route("projectTemplates")]
public IActionResult GetProjectTemplatesConfig()
{
return Ok(_config.ProjectTemplates);
}
}
}

Просмотреть файл

@ -1,64 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Steeltoe.InitializrApi.Services;
using System.Threading.Tasks;
namespace Steeltoe.InitializrApi.Controllers
{
/// <summary>
/// Server configuration endpoint.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ConfigurationController : ControllerBase
{
private readonly IConfigurationRepository _configurationRepository;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationController"/> class.
/// </summary>
/// <param name="configurationRepository">Injected configuration repository.</param>
public ConfigurationController(IConfigurationRepository configurationRepository)
{
_configurationRepository = configurationRepository;
}
/// <summary>
/// Implements <c>GET</c> index.
/// </summary>
/// <returns>A task containing the <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains the server configuration.</returns>
[HttpGet]
public async Task<IActionResult> GetConfiguration()
{
var config = await _configurationRepository.GetConfiguration();
return Ok(config);
}
/// <summary>
/// Implements <c>GET metadata</c>.
/// </summary>
/// <returns>A task containing the <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains project metadata configuration.</returns>
[HttpGet]
[Route("metadata")]
public async Task<IActionResult> GetMetadata()
{
var config = await _configurationRepository.GetConfiguration();
return Ok(config.Metadata);
}
/// <summary>
/// Implements <c>GET templates</c>.
/// </summary>
/// <returns>A task containing the <c>GET</c> result which, if is <see cref="OkObjectResult"/>, contains templates configuration.</returns>
[HttpGet]
[Route("templates")]
public async Task<IActionResult> GetTemplates()
{
var config = await _configurationRepository.GetConfiguration();
return Ok(config.Templates);
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Steeltoe.InitializrApi.Controllers
{
/// <summary>
/// Base class for InitializrApi controllers.
/// </summary>
public abstract class InitializrApiControllerBase : ControllerBase
{
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="InitializrApiControllerBase"/> class.
/// </summary>
/// <param name="logger">Injected logger.</param>
protected InitializrApiControllerBase(ILogger logger)
{
Logger = logger;
}
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets the logger.
/// </summary>
protected ILogger Logger { get; }
}
}

Просмотреть файл

@ -2,14 +2,13 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System.IO;
using System.Net;
using System.Net.Mime;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Controllers
{
@ -18,41 +17,117 @@ namespace Steeltoe.InitializrApi.Controllers
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ProjectController : ControllerBase
public class ProjectController : InitializrApiControllerBase
{
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly InitializrConfig _config;
private readonly IProjectGenerator _projectGenerator;
private readonly ILogger<ProjectController> _logger;
private readonly IArchiverRegistry _archiverRegistry;
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="ProjectController"/> class.
/// </summary>
/// <param name="configService">Injected Initializr configuration service.</param>
/// <param name="projectGenerator">Injected project generator.</param>
/// <param name="archiverRegistry">Injected archiver registry.</param>
/// <param name="logger">Injected logger.</param>
public ProjectController(IProjectGenerator projectGenerator, ILogger<ProjectController> logger)
public ProjectController(
IInitializrConfigService configService,
IProjectGenerator projectGenerator,
IArchiverRegistry archiverRegistry,
ILogger<ProjectController> logger)
: base(logger)
{
_config = configService.GetInitializrConfig();
_projectGenerator = projectGenerator;
_logger = logger;
_archiverRegistry = archiverRegistry;
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <summary>
/// Implements <c>GET</c>.
/// Generated project is bundled in a ZIP archive.
/// </summary>
/// <returns>A task containing the <c>GET</c> result which, if is <see cref="FileContentResult"/>, contains a project bundle archive stream.</returns>
[HttpGet]
public async Task<ActionResult> Get([FromQuery] ProjectSpecification spec)
public ActionResult GetProjectArchive([FromQuery] ProjectSpec spec)
{
_logger.LogDebug($"Project specification: {spec}");
var stream = await _projectGenerator.GenerateProject(spec);
byte[] bytes;
await using (var buf = new MemoryStream())
var defaults = _config.ProjectMetadata;
var normalizedSpec = new ProjectSpec()
{
await stream.CopyToAsync(buf);
bytes = buf.ToArray();
Name = spec.Name ?? defaults?.Name?.Default,
Description = spec.Description ?? defaults?.Description?.Default,
Namespace = spec.Namespace ?? defaults?.Namespace?.Default,
SteeltoeVersion = spec.SteeltoeVersion ?? defaults?.SteeltoeVersion?.Default,
DotNetFramework = spec.DotNetFramework ?? defaults?.DotNetFramework?.Default,
DotNetTemplate = spec.DotNetTemplate ?? defaults?.DotNetTemplate?.Default,
Language = spec.Language ?? defaults?.Language?.Default,
ArchiveMimeType = spec.ArchiveMimeType ?? defaults?.ArchiveMimeType?.Default,
Dependencies = spec.Dependencies ?? defaults?.Dependencies?.Default,
};
if (normalizedSpec.ArchiveMimeType is null)
{
return StatusCode(
StatusCodes.Status500InternalServerError,
"Default archive mime type not configured.");
}
return File(bytes, MediaTypeNames.Application.Zip, $"{spec.Name}.zip");
var archiver = _archiverRegistry.Lookup(normalizedSpec.ArchiveMimeType);
if (archiver is null)
{
return NotFound($"Archive mime type '{normalizedSpec.ArchiveMimeType}' not found.");
}
if (normalizedSpec.Dependencies != null && _config.ProjectMetadata?.Dependencies?.Values != null)
{
var caseSensitiveDeps = new List<string>();
foreach (var group in _config.ProjectMetadata.Dependencies.Values)
{
foreach (var dep in group.Values)
{
caseSensitiveDeps.Add(dep.Id);
}
}
var deps = normalizedSpec.Dependencies.Split(',');
for (int i = 0; i < deps.Length; ++i)
{
foreach (var caseSensitiveDep in caseSensitiveDeps)
{
if (caseSensitiveDep.Equals(deps[i], StringComparison.OrdinalIgnoreCase))
{
deps[i] = caseSensitiveDep;
}
}
}
normalizedSpec.Dependencies = string.Join(',', deps);
}
Logger.LogDebug("Project specification: {ProjectSpec}", normalizedSpec);
var project = _projectGenerator.GenerateProject(normalizedSpec);
if (project is null)
{
return NotFound($"No such project for spec: {normalizedSpec}");
}
var archiveBytes = archiver.ToBytes(project.FileEntries);
return File(
archiveBytes,
normalizedSpec.ArchiveMimeType,
$"{normalizedSpec.Name}{archiver.GetFileExtension()}");
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal abstract class BooleanOperatorToken : OperatorToken
{
}
}

Просмотреть файл

@ -0,0 +1,115 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Expressions
{
/// <summary>
/// Parser for expressions.
/// </summary>
/// BNF:
/// expression := operand | operand operator operand
/// operator := booleanOperator
/// booleanOperator := orOperator
/// orOperator := "||"
/// operand := parameter
/// parameter := alpha | alpha alphanumeric
/// alpha := "a-Z"
/// digit := "0-9"
/// alphanumeric := alpha | digit
public class Expression<T>
{
private readonly IEnumerator<Token> _tokens;
private readonly Type _type;
/// <summary>
/// Initializes a new instance of the <see cref="Expression{T}"/> class.
/// </summary>
/// <param name="expression">The expression in string form.</param>
public Expression(string expression)
{
_type = typeof(T);
_tokens = new Tokenizer().Scan(expression).GetEnumerator();
}
/// <summary>
/// Evaluates the expression.
/// </summary>
/// <param name="parameters"> Expression parameters.</param>
/// <returns>The evaluation.</returns>
public T Evaluate(Dictionary<string, object> parameters)
{
if (_type == typeof(bool))
{
return (T)(object)EvaluateBooleanExpression(parameters);
}
return default(T);
}
private bool EvaluateBooleanExpression(Dictionary<string, object> parameters)
{
bool result = false;
while (_tokens.MoveNext())
{
switch (_tokens.Current)
{
case ParameterToken parameter:
result = GetBooleanValue(parameter.Name, parameters);
break;
default:
throw new ExpressionException($"Expected a parameter: {_tokens.Current}");
}
if (!_tokens.MoveNext())
{
break;
}
do
{
if (!(_tokens.Current is OrOperatorToken))
{
throw new ExpressionException($"Expected an or operator: {_tokens.Current}");
}
if (!_tokens.MoveNext())
{
throw new ExpressionException($"Expected a parameter.");
}
switch (_tokens.Current)
{
case ParameterToken parameter:
result = result || GetBooleanValue(parameter.Name, parameters);
break;
default:
throw new ExpressionException($"Expected a parameter: {_tokens.Current}");
}
}
while (_tokens.MoveNext());
}
return result;
}
private bool GetBooleanValue(string name, Dictionary<string, object> parameters)
{
if (parameters.ContainsKey(name))
{
var value = parameters[name];
if (value is bool)
{
return (bool)value;
}
}
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
namespace Steeltoe.InitializrApi.Expressions
{
/// <summary>
/// An exception representing an error in an expression.
/// </summary>
public class ExpressionException : ArgumentException
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionException"/> class.
/// </summary>
/// <param name="message">The exception message.</param>
public ExpressionException(string message)
: base(message)
{
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal abstract class OperandToken : Token
{
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal abstract class OperatorToken : Token
{
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal class OrOperatorToken : BooleanOperatorToken
{
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal class ParameterToken : OperandToken
{
internal ParameterToken(string name)
{
Name = name;
}
internal string Name { get; }
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Expressions
{
internal abstract class Token
{
}
}

Просмотреть файл

@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Steeltoe.InitializrApi.Expressions
{
internal class Tokenizer
{
private const int EndOfString = -1;
internal IEnumerable<Token> Scan(string expression)
{
var reader = new StringReader(expression);
var tokens = new List<Token>();
while (reader.Peek() != EndOfString)
{
var ch = (char)reader.Peek();
if (char.IsWhiteSpace(ch))
{
reader.Read();
continue;
}
if (char.IsLetter(ch))
{
tokens.Add(ParseParameterToken(reader));
}
else if (ch.Equals('|'))
{
tokens.Add(ParseOrOperatorToken(reader));
}
else
{
throw new ExpressionException($"unexpected character: '{ch}'");
}
}
return tokens;
}
private ParameterToken ParseParameterToken(StringReader reader)
{
var ch = (char)reader.Peek();
if (!char.IsLetter(ch))
{
throw new ExpressionException($"expected a letter: '{ch}'");
}
var buf = new StringBuilder();
while (char.IsLetterOrDigit((char)reader.Peek()))
{
ch = (char)reader.Read();
buf.Append(ch);
}
return new ParameterToken(buf.ToString());
}
private OrOperatorToken ParseOrOperatorToken(StringReader reader)
{
if (!((char)reader.Read()).Equals('|'))
{
throw new ExpressionException("expected character: '|'");
}
if (!((char)reader.Read()).Equals('|'))
{
throw new ExpressionException("expected character: '|'");
}
return new OrOperatorToken();
}
}
}

Просмотреть файл

@ -2,24 +2,116 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
using Steeltoe.InitializrApi.Expressions;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System.IO;
using System.Threading.Tasks;
using Stubble.Core;
using Stubble.Core.Builders;
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Generators
{
/// <summary>
/// An <see cref="IProjectGenerator"/> implementation using <a gref="https://github.com/StubbleOrg/Stubble">Stubble</a>, a Mustache template engine implemented in C#.
/// </summary>
public class StubbleProjectGenerator : IProjectGenerator
public class StubbleProjectGenerator : InitializrApiServiceBase, IProjectGenerator
{
/// <inheritdoc/>
public Task<Stream> GenerateProject(ProjectSpecification specification)
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly IProjectTemplateRegistry _projectTemplateRegistry;
private readonly StubbleVisitorRenderer _renderer;
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="StubbleProjectGenerator"/> class.
/// </summary>
/// <param name="projectTemplateRegistry">Injected template registry.</param>
/// <param name="logger">Injected logger.</param>
public StubbleProjectGenerator(
IProjectTemplateRegistry projectTemplateRegistry,
ILogger<StubbleProjectGenerator> logger)
: base(logger)
{
var result = new TaskCompletionSource<Stream>();
result.SetResult(new MemoryStream());
return result.Task;
_projectTemplateRegistry = projectTemplateRegistry;
_renderer = new StubbleBuilder()
.Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); })
.Build();
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <inheritdoc/>
public Project GenerateProject(ProjectSpec spec)
{
var template = _projectTemplateRegistry.Lookup(spec);
if (template is null)
{
return null;
}
var parameters = new Dictionary<string, object>
{
["Name"] = spec.Name,
["Description"] = spec.Description,
["Namespace"] = spec.Namespace,
["SteeltoeVersion"] = spec.SteeltoeVersion,
["DotNetFramework"] = spec.DotNetFramework,
["DotNetTemplate"] = spec.DotNetTemplate,
["Language"] = spec.Language,
};
if (spec.Dependencies != null)
{
foreach (var dependency in spec.Dependencies?.Split(','))
{
parameters[dependency] = true;
}
}
if (template.Parameters != null)
{
foreach (var templateParameter in template.Parameters)
{
parameters[templateParameter.Name] =
new Expression<bool>(templateParameter.Expression).Evaluate(parameters);
}
}
var project = new Project();
foreach (var fileEntry in template.Manifest)
{
if (fileEntry.Dependencies != null)
{
var dependencySatisfied = false;
foreach (var fileEntryDependency in fileEntry.Dependencies.Split(','))
{
if (parameters.ContainsKey(fileEntryDependency))
{
dependencySatisfied = true;
break;
}
}
if (!dependencySatisfied)
{
continue;
}
}
var path = fileEntry.Rename is null ? fileEntry.Path : _renderer.Render(fileEntry.Rename, parameters);
var text = fileEntry.Text is null ? null : _renderer.Render(fileEntry.Text, parameters);
project.FileEntries.Add(new FileEntry { Path = path, Text = text });
}
return project;
}
}
}

Просмотреть файл

@ -7,8 +7,12 @@ namespace Steeltoe.InitializrApi.Models
/// <summary>
/// Application information, such as version.
/// </summary>
public class About
public sealed class About
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the application name.
/// </summary>

Просмотреть файл

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model a file entry or a directory entry.
/// A file entry must have a path that does not end in '/' and text that is not null.
/// A directory entry must have a path that ends in '/' and text that is null.
/// </summary>
public sealed class FileEntry
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the file path.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets the file text.
/// </summary>
public string Text { get; set; }
/// <summary>
/// Gets or sets the name to be renamed.
/// </summary>
public string Rename { get; set; }
/// <summary>
/// Gets or sets the associated dependencies.
/// </summary>
public string Dependencies { get; set; }
}
}

Просмотреть файл

@ -7,16 +7,20 @@ namespace Steeltoe.InitializrApi.Models
/// <summary>
/// A model of an InitializrApi server configuration.
/// </summary>
public sealed class InitializrApiConfiguration
public sealed class InitializrConfig
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the project metadata.
/// </summary>
public ProjectMetadata Metadata { get; set; }
public ProjectMetadata ProjectMetadata { get; set; }
/// <summary>
/// Gets or sets the project template configurations.
/// Gets or sets the project templates configuration.
/// </summary>
public ProjectTemplateConfiguration[] Templates { get; set; }
public ProjectTemplateConfiguration[] ProjectTemplates { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of a project.
/// </summary>
public sealed class Project
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the spec used to create the project.
/// </summary>
public ProjectSpec Spec { get; set; }
/// <summary>
/// Gets or sets the file entries.
/// </summary>
public List<FileEntry> FileEntries { get; set; } = new List<FileEntry>();
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Text;
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of project spec constraints.
/// </summary>
public sealed class ProjectSpecConstraints
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the Steeltoe version range.
/// </summary>
public string SteeltoeVersionRange { get; set; }
/// <summary>
/// Gets or sets the DotNet framework range.
/// </summary>
public string DotNetFrameworkRange { get; set; }
/// <summary>
/// Gets or sets the DotNet template.
/// </summary>
public string DotNetTemplate { get; set; }
/// <summary>
/// Gets or sets the language.
/// </summary>
public string Language { get; set; }
/// <summary>
/// Returns a user-friendly representation of the current object.
/// </summary>
/// <returns>Returns a user-friendly string that represents the current object.</returns>
public override string ToString()
{
var buf = new StringBuilder();
buf.Append('[');
buf.Append("steeltoeVersionRange=");
buf.Append(SteeltoeVersionRange);
buf.Append(",dotNetFrameworkRange=");
buf.Append(DotNetFrameworkRange);
buf.Append(",dotNetTemplate=");
buf.Append(DotNetTemplate);
buf.Append(",language=");
buf.Append(Language);
buf.Append(']');
return buf.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of a project template manifest an an IZR template bundle.
/// </summary>
public sealed class ProjectTemplate
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the constraints.
/// </summary>
public ProjectSpecConstraints Constraints { get; set; }
/// <summary>
/// Gets or sets the files.
/// </summary>
public FileEntry[] Manifest { get; set; }
/// <summary>
/// Gets or sets the parameter expressions.
/// </summary>
public ParameterExpression[] Parameters { get; set; }
/// <summary>
/// A model of a parameter expression.
/// </summary>
public class ParameterExpression
{
/// <summary>
/// Gets or sets the parameter name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the parameter expression.
/// </summary>
public string Expression { get; set; }
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
namespace Steeltoe.InitializrApi.Models
{
/// <summary>
/// A model of project template configuration.
/// </summary>
public sealed class ProjectTemplateConfiguration
{
/* ----------------------------------------------------------------- *
* properties *
* ----------------------------------------------------------------- */
/// <summary>
/// Gets or sets the project template URI.
/// </summary>
public Uri Uri { get; set; }
}
}

Просмотреть файл

@ -3,9 +3,11 @@
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Steeltoe.Extensions.Configuration.ConfigServer;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@ -45,10 +47,13 @@ namespace Steeltoe.InitializrApi
/// <summary>
/// Program entrypoint.
/// </summary>
public static int Main(string[] args)
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
return 0;
var host = CreateHostBuilder(args).Build();
host.Services.GetRequiredService<IInitializrConfigService>().Initialize();
host.Services.GetRequiredService<IProjectTemplateRegistry>().Initialize();
host.Services.GetRequiredService<IArchiverRegistry>().Initialize();
host.Run();
}
/// <summary>

Просмотреть файл

@ -20,7 +20,7 @@
"Development": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:19200",
"applicationUrl": "http://0.0.0.0:19200",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -28,7 +28,7 @@
"Staging": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "https://localhost:19201;http://localhost:19200",
"applicationUrl": "https://0.0.0.0:19201;http://0.0.0.0:19200",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}

Просмотреть файл

@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Steeltoe.InitializrApi.Models;
using System.Collections.Generic;
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for archiver implementations.
/// </summary>
public interface IArchiver
{
/// <summary>
/// Returns the archive as a byte array.
/// </summary>
/// <param name="fileEntries">File entries to be archived.</param>
/// <returns>A new stream containing the archive.</returns>
byte[] ToBytes(IEnumerable<FileEntry> fileEntries);
/// <summary>
/// Gets the MIME type for the archive format.
/// </summary>
/// <returns>The MIME type.</returns>
string GetMimeType();
/// <summary>
/// Gets the file extension for the archive.
/// </summary>
/// <returns>The file extension.</returns>
string GetFileExtension();
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for a archiver registry implementations.
/// </summary>
public interface IArchiverRegistry : IRegistry<IArchiver, string>
{
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for services that can be initialized and reinitialized.
/// </summary>
public interface IInitializeable
{
/// <summary>
/// Perform initialization.
/// Called when started or reconfigured.
/// </summary>
void Initialize();
}
}

Просмотреть файл

@ -3,19 +3,18 @@
// See the LICENSE file in the project root for more information.
using Steeltoe.InitializrApi.Models;
using System.Threading.Tasks;
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for project configuration repository implementations.
/// Contract for Initializr configuration service implementations.
/// </summary>
public interface IConfigurationRepository
public interface IInitializrConfigService : IInitializeable
{
/// <summary>
/// Gets the project generation configuration.
/// Gets the Initializr configuration.
/// </summary>
/// <returns>A task containing server configuration.</returns>
public Task<InitializrApiConfiguration> GetConfiguration();
/// <returns>Returns an Initializr configuration.</returns>
public InitializrConfig GetInitializrConfig();
}
}

Просмотреть файл

@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using Steeltoe.InitializrApi.Models;
using System.IO;
using System.Threading.Tasks;
namespace Steeltoe.InitializrApi.Services
{
@ -14,10 +12,10 @@ namespace Steeltoe.InitializrApi.Services
public interface IProjectGenerator
{
/// <summary>
/// Generates a project bundle as a ZIP archive byte stream.
/// Generates a project based on the spec.
/// </summary>
/// <param name="specification">Project specification.</param>
/// <returns>A task containing generated project bundle.</returns>
public Task<Stream> GenerateProject(ProjectSpecification specification);
/// <param name="spec">Project specification.</param>
/// <returns>The generated project, or null if not able to generate project per spec.</returns>
Project GenerateProject(ProjectSpec spec);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Steeltoe.InitializrApi.Models;
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for project template registry implementations.
/// </summary>
public interface IProjectTemplateRegistry : IRegistry<ProjectTemplate, ProjectSpec>
{
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Contract for registries.
/// </summary>
/// <typeparam name="TValue">The value to be registered.</typeparam>
/// <typeparam name="TLookup">Key used to lookup a value.</typeparam>
public interface IRegistry<TValue, TLookup> : IInitializeable
{
/// <summary>
/// Registers the value.
/// </summary>
/// <param name="value">The value to register.</param>
void Register(TValue value);
/// <summary>
/// Look for a value that satisfies the key.
/// </summary>
/// <param name="lookup">The lookup key of the value to get.</param>
/// <returns>A value associated with the lookup key, or <c>null</c> if no value found.</returns>
TValue Lookup(TLookup lookup);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
namespace Steeltoe.InitializrApi.Services
{
/// <summary>
/// Base class for InitializrApi services.
/// </summary>
public abstract class InitializrApiServiceBase
{
/// <summary>
/// Initializes a new instance of the <see cref="InitializrApiServiceBase"/> class.
/// </summary>
/// <param name="logger">Injected logger.</param>
protected InitializrApiServiceBase(ILogger logger)
{
Logger = logger;
}
/// <summary>
/// Gets the logger.
/// </summary>
protected ILogger Logger { get; }
}
}

Просмотреть файл

@ -9,12 +9,13 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Steeltoe.Extensions.Configuration.ConfigServer;
using Steeltoe.InitializrApi.Archivers;
using Steeltoe.InitializrApi.Configuration;
using Steeltoe.InitializrApi.Generators;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Steeltoe.InitializrApi.Templates;
using System.Diagnostics.CodeAnalysis;
using System.Transactions;
namespace Steeltoe.InitializrApi
{
@ -39,17 +40,23 @@ namespace Steeltoe.InitializrApi
public IConfiguration Configuration { get; }
/// <summary>
/// Called by the runtime. Adds <see cref="IConfigurationRepository"/> and <see cref="IProjectGenerator"/> services.
/// Called by the runtime. Adds <see cref="IInitializrConfigService"/> and <see cref="IProjectGenerator"/> services.
/// </summary>
/// <param name="services">Injected services.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.ConfigureConfigServerClientOptions(Configuration);
services.Configure<InitializrApiConfiguration>(Configuration);
services.AddSingleton<IConfigurationRepository, ConfigServerConfigurationRepository>();
services.AddSingleton<IProjectGenerator, StubbleProjectGenerator>();
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true);
services.Configure<InitializrConfig>(Configuration);
services.AddSingleton<IInitializrConfigService, InitializrConfigService>();
services.AddSingleton<IProjectTemplateRegistry, ProjectTemplateRegistry>();
services.AddSingleton<IArchiverRegistry, ArchiverRegistry>();
services.AddTransient<IProjectGenerator, StubbleProjectGenerator>();
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreNullValues = true;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
}
/// <summary>
@ -61,7 +68,7 @@ namespace Steeltoe.InitializrApi
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
var about = Program.About;
logger.LogInformation($"{about.Name}, version {about.Version} [{about.Commit}]");
logger.LogInformation("{Program}, version {Version} [{Commit}]", about.Name, about.Version, about.Commit);
if (env.IsDevelopment())
{

Просмотреть файл

@ -1,20 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Steeltoe.Extensions.Configuration.ConfigServerCore" Version="3.0.0-m2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Steeltoe.Extensions.Configuration.ConfigServerCore" Version="3.0.0-m2" />
<PackageReference Include="Stubble.Core" Version="1.9.3" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\InitializrApi.Models\Steeltoe.InitializrApi.Models.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\InitializrApi.Models\Steeltoe.InitializrApi.Models.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\DeterministicBuild.targets">
<Link>DeterministicBuild.targets</Link>
</Content>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="..\DeterministicBuild.targets">
<Link>DeterministicBuild.targets</Link>
</Content>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,214 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace Steeltoe.InitializrApi.Templates
{
/// <summary>
/// A simple <see cref="IProjectTemplateRegistry"/> implementation.
/// </summary>
public class ProjectTemplateRegistry : InitializrApiServiceBase, IProjectTemplateRegistry
{
/* ----------------------------------------------------------------- *
* constants *
* ----------------------------------------------------------------- */
private const string Metadata = ".IZR/metadata.yaml";
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private readonly IInitializrConfigService _configService;
private readonly List<(ProjectSpecConstraints Constraints, ProjectTemplate Template)> _templates =
new List<(ProjectSpecConstraints Constraints, ProjectTemplate Template)>();
/* ----------------------------------------------------------------- *
* constructors *
* ----------------------------------------------------------------- */
/// <summary>
/// Initializes a new instance of the <see cref="ProjectTemplateRegistry"/> class.
/// </summary>
/// <param name="configService">Injected Initializr configuration service.</param>
/// <param name="logger">Injected logger.</param>
public ProjectTemplateRegistry(
IInitializrConfigService configService,
ILogger<ProjectTemplateRegistry> logger)
: base(logger)
{
_configService = configService;
}
/* ----------------------------------------------------------------- *
* methods *
* ----------------------------------------------------------------- */
/// <inheritdoc />
public void Initialize()
{
Logger.LogInformation("Initializing project template registry.");
_templates.Clear();
var config = _configService.GetInitializrConfig();
if (config.ProjectTemplates is null)
{
Logger.LogError("Templates not configured.");
return;
}
foreach (var templateConfig in config.ProjectTemplates)
{
Register(templateConfig.Uri);
}
}
/// <inheritdoc/>
public void Register(ProjectTemplate projectTemplate)
{
Logger.LogInformation(
$"Registering project template: {projectTemplate.Constraints} ({projectTemplate.Description})");
_templates.Add((projectTemplate.Constraints, projectTemplate));
}
/// <inheritdoc/>
public ProjectTemplate Lookup(ProjectSpec spec)
{
foreach (var template in _templates)
{
var constraints = template.Constraints;
if ((constraints.SteeltoeVersionRange is null
|| constraints.SteeltoeVersionRange.Equals(spec.SteeltoeVersion))
&& (constraints.DotNetFrameworkRange is null
|| constraints.DotNetFrameworkRange.Equals(spec.DotNetFramework))
&& (constraints.DotNetTemplate is null
|| constraints.DotNetTemplate.Equals(spec.DotNetTemplate))
&& (constraints.Language is null
|| constraints.Language.Equals(spec.Language)))
{
return template.Template;
}
}
return null;
}
private void Register(Uri uri)
{
Logger.LogInformation("Fetching project template: {Uri}", uri);
try
{
var request = WebRequest.Create(uri);
using var response = request.GetResponse();
using var stream = response.GetResponseStream();
if (stream is null)
{
Logger.LogError("URI returned null stream: {TemplateUri}", uri);
return;
}
using var archive = new ZipArchive(stream);
ProjectTemplate projectTemplate;
try
{
projectTemplate = ParseZipEntry<ProjectTemplate>(archive.GetEntry(Metadata));
if (projectTemplate is null)
{
Logger.LogError($"Project template metadata missing ('{Metadata}'): {{TemplateUri}}", uri);
return;
}
if (projectTemplate.Constraints is null)
{
Logger.LogError("Project template constraints missing: {TemplateUri}", uri);
return;
}
if (projectTemplate.Manifest is null)
{
Logger.LogError("Project template manifest missing: {TemplateUri}", uri);
return;
}
}
catch (YamlException e)
{
Logger.LogError(e, "Project template metadata malformed ('{Path}'): {TemplateUri}", Metadata, uri);
return;
}
foreach (var fileEntry in projectTemplate.Manifest)
{
var path = fileEntry.Path;
var archiveEntry = archive.GetEntry(path);
if (archiveEntry is null)
{
Logger.LogError("Project template missing file: '{File}' {TemplateUri}", path, uri);
return;
}
if (path.EndsWith('/'))
{
continue;
}
using var reader = new StreamReader(archiveEntry.Open());
fileEntry.Text = reader.ReadToEnd();
}
Register(projectTemplate);
}
catch (InvalidDataException)
{
Logger.LogError("URI not a Zip archive: {TemplateUri}", uri);
}
catch (Exception e)
{
while (e.InnerException != null)
{
e = e.InnerException;
}
if (e is DirectoryNotFoundException || e is FileNotFoundException)
{
Logger.LogError("URI not found: {TemplateUri}", uri);
}
else
{
Logger.LogError(
e,
"Unexpected error [{Exception}[{Error}]]: {TemplateUri}",
e.GetType(),
e.Message,
uri);
}
}
}
private T ParseZipEntry<T>(ZipArchiveEntry entry)
{
if (entry is null)
{
return default(T);
}
using var reader = new StreamReader(entry.Open());
var text = reader.ReadToEnd();
Logger.LogDebug("{Path}:\n{Text}", entry.FullName, text);
return new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build().Deserialize<T>(text);
}
}
}

4
test/.gitignore поставляемый
Просмотреть файл

@ -1,2 +1,2 @@
# CodeCoverage
coverage.opencover.xml
# code coverage
coverage*.*

Просмотреть файл

@ -1,24 +1,21 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="coverlet.msbuild" Version="2.9.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="FluentAssertions.Json" Version="5.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="coverlet.msbuild" Version="2.9.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="FluentAssertions.Json" Version="5.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
</Project>
<!--
vim: ft=xml
-->

Просмотреть файл

@ -0,0 +1,108 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Integration
{
public class HttpTests : IClassFixture<WebApplicationFactory<Startup>>
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public async Task Get_Unknown_Path_Should_Return_404_Not_Found()
{
// Act
var response = await HttpClient.GetAsync("no such path");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Theory]
[InlineData(AboutEndpoint)]
[InlineData(ConfigurationEndpoint)]
[InlineData(ProjectEndpoint)]
public async Task Post_Should_Return_405_Method_Not_Allowed(string path)
{
// Act
var response = await HttpClient.PostAsync(path, new StringContent(""));
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Theory]
[InlineData(AboutEndpoint)]
[InlineData(ConfigurationEndpoint)]
[InlineData(ProjectEndpoint)]
public async Task Put_Should_Return_405_Method_Not_Allowed(string path)
{
// Act
var response = await HttpClient.PutAsync(path, new StringContent(""));
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Theory]
[InlineData(AboutEndpoint)]
[InlineData(ConfigurationEndpoint)]
[InlineData(ProjectEndpoint)]
public async Task Patch_Should_Return_405_Method_Not_Allowed(string path)
{
// Act
var response = await HttpClient.PatchAsync(path, new StringContent(""));
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Theory]
[InlineData(AboutEndpoint)]
[InlineData(ConfigurationEndpoint)]
[InlineData(ProjectEndpoint)]
public async Task Delete_Should_Return_405_Method_Not_Allowed(string path)
{
// Act
var response = await HttpClient.DeleteAsync(path);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
/* ----------------------------------------------------------------- *
* constants *
* ----------------------------------------------------------------- */
private const string AboutEndpoint = "/api/about";
private const string ConfigurationEndpoint = "/api/config";
private const string ProjectEndpoint = "/api/project";
/* ----------------------------------------------------------------- *
* fields *
* ----------------------------------------------------------------- */
private HttpClient HttpClient { get; }
/* ----------------------------------------------------------------- *
* constructors
* ----------------------------------------------------------------- */
public HttpTests(WebApplicationFactory<Startup> fixture)
{
HttpClient = fixture.CreateClient();
}
}
}

Просмотреть файл

@ -5,12 +5,16 @@
using FluentAssertions;
using Xunit;
namespace Steeltoe.InitializrApi.Test
namespace Steeltoe.InitializrApi.Test.Integration
{
public class ProgramTest
public class ProgramTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void About()
public void About_Should_Contain_About_Details()
{
var about = Program.About;
about.Name.Should().Be("Steeltoe.InitializrApi");
@ -22,5 +26,8 @@ namespace Steeltoe.InitializrApi.Test
about.Commit.Length.Should().Be(40); // Git SHA string length
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\InitializrApi\Steeltoe.InitializrApi.csproj" />
<ProjectReference Include="..\InitializrApi.Test.Utils\Steeltoe.InitializrApi.Test.Utils.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Archivers;
using Steeltoe.InitializrApi.Services;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Archivers
{
public class ArchiverRegistryTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Zip_Format_Should_Return_ZipArchiver()
{
// Arrange
var registry = new ArchiverRegistry(new NullLogger<ArchiverRegistry>());
registry.Initialize();
// Act
var archiver = registry.Lookup("application/zip");
// Assert
archiver.Should().NotBeNull();
archiver.Should().BeOfType<ZipArchiver>();
}
[Fact]
public void Initialize_Should_Reset_State()
{
// Arrange
var registry = new ArchiverRegistry(new NullLogger<ArchiverRegistry>());
registry.Initialize();
var myArchiver = new Mock<IArchiver>();
myArchiver.Setup(a => a.GetMimeType()).Returns("mytype");
registry.Register(myArchiver.Object);
// Act
var archiver = registry.Lookup("mytype");
// Assert
archiver.Should().NotBeNull();
// Act
registry.Initialize();
archiver = registry.Lookup("mytype");
// Assert
archiver.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void Unknown_Format_Should_Return_Null()
{
// Arrange
var registry = new ArchiverRegistry(new NullLogger<ArchiverRegistry>());
registry.Initialize();
// Act
var archiver = registry.Lookup("unknown");
// Assert
archiver.Should().BeNull();
}
}
}

Просмотреть файл

@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.IO.Compression;
using FluentAssertions;
using Steeltoe.InitializrApi.Archivers;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Archivers
{
public class ZipArchiverTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void ToStream_Should_Create_Zip_Archive()
{
// Arrange
var archiver = new ZipArchiver();
var files = new FileEntry[0];
// Act
var buf = archiver.ToBytes(files);
// Assert
new ZipArchive(new MemoryStream(buf)).Should().BeOfType<ZipArchive>();
}
[Fact]
public void ToStream_Should_Archive_File_Contents()
{
// Arrange
var archiver = new ZipArchiver();
var files = new[]
{
new FileEntry { Path = "d1/f1", Text = "f1 stuff" },
};
// Act
var buf = archiver.ToBytes(files);
// Assert
var zip = new ZipArchive(new MemoryStream(buf));
using var entries = zip.Entries.GetEnumerator();
entries.MoveNext().Should().BeTrue();
var entry = entries.Current;
Assert.NotNull(entry);
entry.Name.Should().Be("f1");
entry.FullName.Should().Be("d1/f1");
using var reader = new StreamReader(entry.Open());
reader.ReadToEnd().Should().Be("f1 stuff");
entries.MoveNext().Should().BeFalse();
}
[Fact]
public void ToStream_Should_Archive_Directories()
{
// Arrange
var archiver = new ZipArchiver();
var files = new[]
{
new FileEntry { Path = "d1/d2" },
};
// Act
var buf = archiver.ToBytes(files);
// Assert
var zip = new ZipArchive(new MemoryStream(buf));
using var entries = zip.Entries.GetEnumerator();
entries.MoveNext().Should().BeTrue();
var entry = entries.Current;
Assert.NotNull(entry);
entry.Name.Should().Be("d2");
entry.FullName.Should().Be("d1/d2");
using var reader = new StreamReader(entry.Open());
reader.ReadToEnd().Should().BeEmpty();
entries.MoveNext().Should().BeFalse();
}
[Fact]
public void GetMimeType_Should_Be_application_zip()
{
// Arrange
var archiver = new ZipArchiver();
// Act
var mimeType = archiver.GetMimeType();
// Assert
mimeType.Should().Be("application/zip");
}
[Fact]
public void GetFileExtension_Should_Be_zip()
{
// Arrange
var archiver = new ZipArchiver();
// Act
var ext = archiver.GetFileExtension();
// Assert
ext.Should().Be(".zip");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Steeltoe.InitializrApi.Configuration;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Test.Utils;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Configuration
{
public class InitializrConfigServiceTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void Null_Project_Metadata_Should_Log_An_Error()
{
// Arrange
var cfg = new InitializrConfig
{
ProjectTemplates = new ProjectTemplateConfiguration[0],
};
var configOptions = new Mock<IOptions<InitializrConfig>>();
configOptions.Setup(opts => opts.Value).Returns(cfg);
var logger = new Mock<ILogger<InitializrConfigService>>();
var service = new InitializrConfigService(configOptions.Object, logger.Object);
// Act
service.Initialize();
// Assert
logger.VerifyLog(LogLevel.Error, "Project metadata missing.");
}
[Fact]
public void Null_Project_Templates_Configuration_Should_Log_An_Error()
{
// Arrange
var cfg = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata(),
};
cfg.ProjectMetadata = new ProjectMetadata();
var configOptions = new Mock<IOptions<InitializrConfig>>();
configOptions.Setup(opts => opts.Value).Returns(cfg);
var logger = new Mock<ILogger<InitializrConfigService>>();
var service = new InitializrConfigService(configOptions.Object, logger.Object);
// Act
service.Initialize();
// Assert
logger.VerifyLog(LogLevel.Error, "Project templates configuration missing.");
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Controllers
{
public class AboutControllerTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void GetAbout_Should_Return_About()
{
// Arrange
var controller = new AboutController(new NullLogger<AboutController>());
// Act
var result = controller.GetAbout();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeOfType<About>();
var about = Assert.IsType<About>(indexResult.Value);
about.Name.Should().Be("Steeltoe.InitializrApi");
about.Vendor.Should().Be("SteeltoeOSS/VMware");
about.Url.Should().Be("https://github.com/SteeltoeOSS/InitializrApi/");
about.Version.Should().StartWith("0.7.0");
about.Commit.Should().NotBeNullOrWhiteSpace();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Controllers
{
public class ConfigControllerTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void GetInitializrApiConfiguration_Should_Return_InitializrApiConfiguration()
{
// Arrange
var config = new InitializrConfig();
var configService = new Mock<IInitializrConfigService>();
configService.Setup(repo => repo.GetInitializrConfig()).Returns(config);
var controller = new ConfigController(configService.Object, new NullLogger<ConfigController>());
// Act
var result = controller.GetInitializrConfiguration();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config);
}
[Fact]
public void GetProjectMetadata_Should_Return_ProjectMetadata()
{
// Arrange
var config = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata(),
};
var configService = new Mock<IInitializrConfigService>();
configService.Setup(repo => repo.GetInitializrConfig()).Returns(config);
var controller = new ConfigController(configService.Object, new NullLogger<ConfigController>());
// Act
var result = controller.GetProjectMetadata();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config.ProjectMetadata);
}
[Fact]
public void GetProjectTemplateConfigurations_Should_Return_ProjectTemplateConfigurations()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new ProjectTemplateConfiguration[0],
};
var configService = new Mock<IInitializrConfigService>();
configService.Setup(repo => repo.GetInitializrConfig()).Returns(config);
var controller = new ConfigController(configService.Object, new NullLogger<ConfigController>());
// Act
var result = controller.GetProjectTemplatesConfig();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config.ProjectTemplates);
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,311 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Archivers;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Controllers
{
public class ProjectControllerTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Zip_Archive_Format_Should_Return_Zip_Archive()
{
// Arrange
var archiverRegistry = new Mock<IArchiverRegistry>();
archiverRegistry.Setup(reg => reg.Lookup(It.Is<string>(s => s.Equals("application/zip"))))
.Returns(new ZipArchiver());
var controller = new ProjectControllerBuilder().WithArchiverRegistry(archiverRegistry.Object).Build();
var spec = new ProjectSpec { ArchiveMimeType = "application/zip" };
// Act
var unknown = controller.GetProjectArchive(spec);
// Assert
var result = Assert.IsType<FileContentResult>(unknown);
result.ContentType.Should().Be("application/zip");
result.FileDownloadName.Should().EndWith(".zip");
var stream = new MemoryStream(result.FileContents);
new ZipArchive(stream).Should().BeOfType<ZipArchive>();
}
[Fact]
public void Configuration_Should_Specify_Defaults()
{
// Arrange
var config = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata
{
Name = new ProjectMetadata.Text { Default = "my name" },
Description = new ProjectMetadata.Text { Default = "my description" },
Namespace = new ProjectMetadata.Text { Default = "my namespace" },
SteeltoeVersion = new ProjectMetadata.SingleSelectList { Default = "my steeltoe version" },
DotNetFramework = new ProjectMetadata.SingleSelectList { Default = "my dotnet framework" },
DotNetTemplate = new ProjectMetadata.SingleSelectList { Default = "my dotnet template" },
Language = new ProjectMetadata.SingleSelectList { Default = "my language" },
ArchiveMimeType = new ProjectMetadata.SingleSelectList { Default = "application/myarchive" },
Dependencies = new ProjectMetadata.GroupList { Default = "dep1,dep2" },
},
};
var controller = new ProjectControllerBuilder()
.WithInitializrConfiguration(config)
.Build();
// Act
var unknown = controller.GetProjectArchive(new ProjectSpec());
// Assert
var result = Assert.IsType<FileContentResult>(unknown);
using var reader = new StreamReader(new MemoryStream(result.FileContents));
reader.ReadLine().Should().Be("name=my name");
reader.ReadLine().Should().Be("description=my description");
reader.ReadLine().Should().Be("namespace=my namespace");
reader.ReadLine().Should().Be("steeltoe version=my steeltoe version");
reader.ReadLine().Should().Be("dotnet framework=my dotnet framework");
reader.ReadLine().Should().Be("dotnet template=my dotnet template");
reader.ReadLine().Should().Be("language=my language");
reader.ReadLine().Should().Be("archive mime type=application/myarchive");
reader.ReadLine().Should().Be("dependencies=dep1,dep2");
reader.ReadLine().Should().BeNull();
}
[Fact]
public void Dependencies_Should_Be_Case_Corrected()
{
// Arrange
var config = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata
{
Dependencies = new ProjectMetadata.GroupList
{
Values = new []
{
new ProjectMetadata.Group
{
Values = new []
{
new ProjectMetadata.GroupItem
{
Id = "CamelCaseDep",
},
},
},
},
},
},
};
var spec = new ProjectSpec
{
ArchiveMimeType = "application/myarchive",
Dependencies = "unknowndep,camelcasedep",
};
var controller = new ProjectControllerBuilder()
.WithInitializrConfiguration(config)
.Build();
// Act
var unknown = controller.GetProjectArchive(spec);
// Assert
var result = Assert.IsType<FileContentResult>(unknown);
using var reader = new StreamReader(new MemoryStream(result.FileContents));
var body = reader.ReadToEnd();
body.Should().Contain("dependencies=unknowndep,CamelCaseDep");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void No_Template_Found_Should_Return_404_Page_Not_Found()
{
// Arrange
var controller = new ProjectControllerBuilder().Build();
var spec = new ProjectSpec { Name = "nosuchtemplate" };
// Act
var unknown = controller.GetProjectArchive(spec);
// Assert
var result = Assert.IsType<NotFoundObjectResult>(unknown);
result.Value.ToString().Should()
.Be("No such project for spec: [name=nosuchtemplate,archiveMimeType=application/myarchive]");
}
[Fact]
public void Unknown_Archive_Format_Should_Return_404_Page_Not_Found()
{
// Arrange
var controller = new ProjectControllerBuilder().Build();
var spec = new ProjectSpec { ArchiveMimeType = "application/nosuchformat" };
// Act
var unknown = controller.GetProjectArchive(spec);
// Assert
var result = Assert.IsType<NotFoundObjectResult>(unknown);
result.Value.ToString().Should().Be("Archive mime type 'application/nosuchformat' not found.");
}
[Fact]
public void Null_Archive_Format_Should_Return_500_Internal_Server_Error()
{
// Arrange
var spec = new ProjectSpec();
var config = new InitializrConfig { ProjectTemplates = new ProjectTemplateConfiguration[0] };
var controller = new ProjectControllerBuilder().WithInitializrConfiguration(config).Build();
// Act
var unknown = controller.GetProjectArchive(spec);
// Assert
var result = Assert.IsType<ObjectResult>(unknown);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
result.Value.Should().Be("Default archive mime type not configured.");
}
/* ----------------------------------------------------------------- *
* test helpers *
* ----------------------------------------------------------------- */
class ProjectControllerBuilder
{
private InitializrConfig _config;
private IProjectGenerator _generator;
private IArchiverRegistry _registry;
internal ProjectController Build()
{
if (_config is null)
{
_config = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata
{
ArchiveMimeType = new ProjectMetadata.SingleSelectList
{
Default = "application/myarchive",
},
},
ProjectTemplates = new ProjectTemplateConfiguration[0],
};
}
if (_generator is null)
{
_generator = new TestProjectGenerator();
}
if (_registry is null)
{
var mock = new Mock<IArchiverRegistry>();
mock.Setup(reg => reg.Lookup(It.Is<string>(s => s.Equals("application/myarchive"))))
.Returns(new TestArchiver());
_registry = mock.Object;
}
var configurationService = new Mock<IInitializrConfigService>();
configurationService.Setup(svc => svc.GetInitializrConfig()).Returns(_config);
var logger = new NullLogger<ProjectController>();
var projectController =
new ProjectController(configurationService.Object, _generator, _registry, logger)
{
ControllerContext =
{
HttpContext = new DefaultHttpContext()
}
};
return projectController;
}
internal ProjectControllerBuilder WithInitializrConfiguration(InitializrConfig config)
{
_config = config;
return this;
}
internal ProjectControllerBuilder WithArchiverRegistry(IArchiverRegistry registry)
{
_registry = registry;
return this;
}
}
private class TestProjectGenerator : IProjectGenerator
{
public Project GenerateProject(ProjectSpec spec)
{
if (spec.Name != null && spec.Name.Equals("nosuchtemplate"))
{
return null;
}
var project = new Project();
project.FileEntries.Add(new FileEntry { Path = "name", Text = spec.Name ?? "<na>" });
project.FileEntries.Add(new FileEntry { Path = "description", Text = spec.Description ?? "<na>" });
project.FileEntries.Add(new FileEntry { Path = "namespace", Text = spec.Namespace ?? "<na>" });
project.FileEntries.Add(new FileEntry
{ Path = "steeltoe version", Text = spec.SteeltoeVersion ?? "<na>" });
project.FileEntries.Add(new FileEntry
{ Path = "dotnet framework", Text = spec.DotNetFramework ?? "<na>" });
project.FileEntries.Add(
new FileEntry { Path = "dotnet template", Text = spec.DotNetTemplate ?? "<na>" });
project.FileEntries.Add(new FileEntry { Path = "language", Text = spec.Language ?? "<na>" });
project.FileEntries.Add(new FileEntry
{ Path = "archive mime type", Text = spec.ArchiveMimeType ?? "<na>" });
project.FileEntries.Add(new FileEntry { Path = "dependencies", Text = spec.Dependencies ?? "<na>" });
return project;
}
}
private class TestArchiver : IArchiver
{
public byte[] ToBytes(IEnumerable<FileEntry> fileEntries)
{
var buf = new StringBuilder();
foreach (var fileEntry in fileEntries)
{
buf.Append(fileEntry.Path)
.Append('=')
.Append(fileEntry.Text)
.Append(Environment.NewLine);
}
return Encoding.UTF8.GetBytes(buf.ToString());
}
public string GetMimeType()
{
return "application/myarchive";
}
public string GetFileExtension()
{
return ".myext";
}
}
}
}

Просмотреть файл

@ -0,0 +1,161 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using FluentAssertions;
using Steeltoe.InitializrApi.Expressions;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Expressions
{
public class ExpressionParserTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void True_Parameter_Should_Evaluate_To_True()
{
// Arrange
var expression = new Expression<bool>("tDep");
var parameters = new Dictionary<string, object>
{
{ "tDep", true },
};
// Act
var result = expression.Evaluate(parameters);
// Assert
result.Should().Be(true);
}
[Fact]
public void False_Parameter_Should_Evaluate_To_False()
{
// Arrange
var expression = new Expression<bool>("fDep");
var parameters = new Dictionary<string, object>
{
{ "fDep", false },
};
// Act
var result = expression.Evaluate(parameters);
// Assert
result.Should().Be(false);
}
[Fact]
public void Null_Parameter_Should_Evaluate_To_False()
{
// Arrange
var expression = new Expression<bool>("nDep");
var parameters = new Dictionary<string, object>();
// Act
var result = expression.Evaluate(parameters);
// Assert
result.Should().Be(false);
}
[Fact]
public void Or_Operand_Should_Should_Evaluate_Per_Boolean_Rules()
{
// Arrange
var expressionTt = new Expression<bool>("t1Dep || t2Dep");
var expressionTf = new Expression<bool>("t1Dep || f1Dep");
var expressionTn = new Expression<bool>("t1Dep || n1Dep");
var expressionFt = new Expression<bool>("f1Dep || t1Dep");
var expressionFf = new Expression<bool>("f1Dep || f2Dep");
var expressionFn = new Expression<bool>("f1Dep || n1Dep");
var expressionNt = new Expression<bool>("n1Dep || t1Dep");
var expressionNf = new Expression<bool>("n1Dep || f1Dep");
var expressionNn = new Expression<bool>("n1Dep || n2Dep");
var parameters = new Dictionary<string, object>
{
{ "t1Dep", true },
{ "t2Dep", true },
{ "f1Dep", false },
{ "f2Dep", false },
};
// Act
var resultTt = expressionTt.Evaluate(parameters);
var resultTf = expressionTf.Evaluate(parameters);
var resultTn = expressionTn.Evaluate(parameters);
var resultFt = expressionFt.Evaluate(parameters);
var resultFf = expressionFf.Evaluate(parameters);
var resultFn = expressionFn.Evaluate(parameters);
var resultNt = expressionNt.Evaluate(parameters);
var resultNf = expressionNf.Evaluate(parameters);
var resultNn = expressionNn.Evaluate(parameters);
// Assert
resultTt.Should().Be(true);
resultTf.Should().Be(true);
resultTn.Should().Be(true);
resultFt.Should().Be(true);
resultFf.Should().Be(false);
resultFn.Should().Be(false);
resultNt.Should().Be(true);
resultNf.Should().Be(false);
resultNn.Should().Be(false);
}
[Fact]
public void Complex_Or_Operand_Should_Should_Evaluate_Per_Boolean_Rules()
{
// Arrange
var expressionFff = new Expression<bool>("f1Dep || f2Dep || f3Dep");
var expressionNnn = new Expression<bool>("n1Dep || n2Dep || n3Dep");
var expressionTtt = new Expression<bool>("t1Dep || t2Dep || t3Dep");
var expressionTfn = new Expression<bool>("t1Dep || f1Dep || n1Dep");
var parameters = new Dictionary<string, object>
{
{ "t1Dep", true },
{ "t2Dep", true },
{ "t3Dep", true },
{ "f1Dep", false },
{ "f2Dep", false },
{ "f3Dep", false },
};
// Act
var resultFff = expressionFff.Evaluate(parameters);
var resultNnn = expressionNnn.Evaluate(parameters);
var resultTtt = expressionTtt.Evaluate(parameters);
var resultTfn = expressionTfn.Evaluate(parameters);
// Assert
resultFff.Should().Be(false);
resultNnn.Should().Be(false);
resultTtt.Should().Be(true);
resultTfn.Should().Be(true);
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void Unexpected_Character_Should_Throw_Exception()
{
// Arrange
// Act
Action act = () =>
{
var _ = new Expression<object>("@");
};
// Assert
act.Should().Throw<ExpressionException>().WithMessage("Unexpected character: '@'");
}
}
}

Просмотреть файл

@ -0,0 +1,230 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Generators;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Generators
{
public class StubbleProjectGeneratorTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void GenerateProject_Should_Generate_A_Project()
{
// Arrange
var spec = new ProjectSpec();
var template = new ProjectTemplate { Manifest = new FileEntry[0] };
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.Should().NotBeNull();
}
[Fact]
public void GenerateProject_Should_Render_Files()
{
// Arrange
var spec = new ProjectSpec
{
Name = "my name",
Description = "my description",
Namespace = "my namespace",
SteeltoeVersion = "my steeltoe version",
DotNetFramework = "my dotnet framework",
DotNetTemplate = "my dotnet template",
Language = "my language",
};
var template = new ProjectTemplate
{
Manifest = new[]
{
new FileEntry { Path = "name", Text = "{{Name}}" },
new FileEntry { Path = "description", Text = "{{Description}}" },
new FileEntry { Path = "namespace", Text = "{{Namespace}}" },
new FileEntry { Path = "steeltoe version", Text = "{{SteeltoeVersion}}" },
new FileEntry { Path = "dotnet framework", Text = "{{DotNetFramework}}" },
new FileEntry { Path = "dotnet template", Text = "{{DotNetTemplate}}" },
new FileEntry { Path = "language", Text = "{{Language}}" },
},
};
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.Should().NotBeNull();
var i = 0;
project.FileEntries[i++].Text.Should().Be("my name");
project.FileEntries[i++].Text.Should().Be("my description");
project.FileEntries[i++].Text.Should().Be("my namespace");
project.FileEntries[i++].Text.Should().Be("my steeltoe version");
project.FileEntries[i++].Text.Should().Be("my dotnet framework");
project.FileEntries[i++].Text.Should().Be("my dotnet template");
project.FileEntries[i++].Text.Should().Be("my language");
}
[Fact]
public void GenerateProject_Should_Render_Directories()
{
// Arrange
var spec = new ProjectSpec();
var template = new ProjectTemplate
{
Manifest = new[]
{
new FileEntry { Path = "d1/" },
new FileEntry { Path = "d1/d2/" },
},
};
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.Should().NotBeNull();
project.FileEntries[0].Path.Should().Be("d1/");
project.FileEntries[0].Text.Should().BeNull();
project.FileEntries[1].Path.Should().Be("d1/d2/");
project.FileEntries[1].Text.Should().BeNull();
}
[Fact]
public void GenerateProject_Should_Rename_Files()
{
// Arrange
var spec = new ProjectSpec { Name = "newName" };
var template = new ProjectTemplate
{
Manifest = new[]
{
new FileEntry { Path = "oldName", Rename = "{{Name}}" },
},
};
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.Should().NotBeNull();
project.FileEntries[0].Path.Should().Be("newName");
}
[Fact]
public void GenerateProject_Should_Omit_Files_If_Dependency_Not_Met()
{
// Arrange
var spec = new ProjectSpec { Dependencies = "dep0,dep1" };
var template = new ProjectTemplate
{
Manifest = new[]
{
new FileEntry { Path = "f0" },
new FileEntry { Path = "f1", Dependencies = "dep0" },
new FileEntry { Path = "f2", Dependencies = "dep1" },
new FileEntry { Path = "f3", Dependencies = "dep0,dep1" },
new FileEntry { Path = "f4", Dependencies = "dep2" },
new FileEntry { Path = "f5", Dependencies = "dep0,dep2" },
},
};
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.FileEntries.Count.Should().Be(5);
var i = 0;
project.FileEntries[i++].Path.Should().Be("f0");
project.FileEntries[i++].Path.Should().Be("f1");
project.FileEntries[i++].Path.Should().Be("f2");
project.FileEntries[i++].Path.Should().Be("f3");
project.FileEntries[i++].Path.Should().Be("f5");
}
[Fact]
public void GenerateProject_Should_Evaluate_Parameter_Or_Expressions()
{
// Arrange
var spec = new ProjectSpec { Dependencies = "dep1,dep2" };
var template = new ProjectTemplate
{
Manifest = new[]
{
new FileEntry { Path = "f", Text = "{{#or}}text{{/or}}" },
},
Parameters = new[]
{
new ProjectTemplate.ParameterExpression()
{
Name = "or",
Expression = "dep1||dep2",
},
},
};
var generator = new StubbleProjectGeneratorBuilder().WithProjectTemplate(template).Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.FileEntries[0].Text.Should().Be("text");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void No_Template_Should_Return_Null_Project()
{
// Arrange
var spec = new ProjectSpec();
var generator = new StubbleProjectGeneratorBuilder().Build();
// Act
var project = generator.GenerateProject(spec);
// Assert
project.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* test helpers *
* ----------------------------------------------------------------- */
class StubbleProjectGeneratorBuilder
{
private Mock<IProjectTemplateRegistry> _registry = new Mock<IProjectTemplateRegistry>();
private ILogger<StubbleProjectGenerator> _logger = new NullLogger<StubbleProjectGenerator>();
internal StubbleProjectGenerator Build()
{
return new StubbleProjectGenerator(_registry.Object, _logger);
}
internal StubbleProjectGeneratorBuilder WithProjectTemplate(ProjectTemplate template)
{
_registry.Setup(reg => reg.Lookup(It.IsAny<ProjectSpec>())).Returns(template);
return this;
}
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class AboutTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var about = new About();
// Act
// Assert
about.Name.Should().BeNull();
about.Vendor.Should().BeNull();
about.Url.Should().BeNull();
about.Version.Should().BeNull();
about.Commit.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class FileEntryTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var entry = new FileEntry();
// Act
// Assert
entry.Path.Should().BeNull();
entry.Text.Should().BeNull();
entry.Rename.Should().BeNull();
entry.Dependencies.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class InitializrConfigTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var config = new InitializrConfig();
// Act
// Assert
config.ProjectMetadata.Should().BeNull();
config.ProjectTemplates.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectMetadataTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var metadata = new ProjectMetadata();
// Act
// Assert
metadata.Name.Should().BeNull();
metadata.Description.Should().BeNull();
metadata.Namespace.Should().BeNull();
metadata.SteeltoeVersion.Should().BeNull();
metadata.DotNetFramework.Should().BeNull();
metadata.DotNetTemplate.Should().BeNull();
metadata.Language.Should().BeNull();
metadata.ArchiveMimeType.Should().BeNull();
metadata.Dependencies.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectSpecConstraintsTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var constraints = new ProjectSpecConstraints();
// Act
// Assert
constraints.SteeltoeVersionRange.Should().BeNull();
constraints.DotNetFrameworkRange.Should().BeNull();
constraints.DotNetTemplate.Should().BeNull();
constraints.Language.Should().BeNull();
}
[Fact]
public void ToString_Should_Be_User_Friendly()
{
// Arrange
var constraints = new ProjectSpecConstraints();
// Act
var s = constraints.ToString();
// Assert
s.Should().Be("[steeltoeVersionRange=,dotNetFrameworkRange=,dotNetTemplate=,language=]");
// Arrange
constraints.SteeltoeVersionRange = "myversion";
// Act
s = constraints.ToString();
// Assert
s.Should().Be("[steeltoeVersionRange=myversion,dotNetFrameworkRange=,dotNetTemplate=,language=]");
// Arrange
constraints.DotNetFrameworkRange = "myframework";
// Act
s = constraints.ToString();
// Assert
s.Should().Be("[steeltoeVersionRange=myversion,dotNetFrameworkRange=myframework,dotNetTemplate=,language=]");
// Arrange
constraints.DotNetTemplate = "mytemplate";
// Act
s = constraints.ToString();
// Assert
s.Should().Be("[steeltoeVersionRange=myversion,dotNetFrameworkRange=myframework,dotNetTemplate=mytemplate,language=]");
// Arrange
constraints.Language = "mylanguage";
// Act
s = constraints.ToString();
// Assert
s.Should().Be("[steeltoeVersionRange=myversion,dotNetFrameworkRange=myframework,dotNetTemplate=mytemplate,language=mylanguage]");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,142 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectSpecTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var spec = new ProjectSpec();
// Act
// Assert
spec.Name.Should().BeNull();
spec.Description.Should().BeNull();
spec.Namespace.Should().BeNull();
spec.SteeltoeVersion.Should().BeNull();
spec.DotNetFramework.Should().BeNull();
spec.DotNetTemplate.Should().BeNull();
spec.Language.Should().BeNull();
spec.ArchiveMimeType.Should().BeNull();
}
[Fact]
public void Application_Should_Be_Assumed()
{
// Arrange
var spec = new ProjectSpec { ArchiveMimeType = "foo" };
// Act
// Assert
spec.ArchiveMimeType.Should().Be("application/foo");
}
[Fact]
public void ToString_Should_Be_User_Friendly()
{
// Arrange
var spec = new ProjectSpec();
// Act
var s = spec.ToString();
// Assert
s.Should().Be("[]");
// Arrange
spec.ArchiveMimeType = "mymimetype";
// Act
s = spec.ToString();
// Assert
s.Should().Be("[archiveMimeType=application/mymimetype]");
// Arrange
spec.Language = "mylanguage";
// Act
s = spec.ToString();
// Assert
s.Should().Be("[language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.DotNetTemplate = "mytemplate";
// Act
s = spec.ToString();
// Assert
s.Should().Be("[dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.DotNetFramework = "myframework";
// Act
s = spec.ToString();
// Assert
s.Should().Be(
"[dotNetFramework=myframework,dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.SteeltoeVersion = "mysteeltoeversion";
// Act
s = spec.ToString();
// Assert
s.Should().Be(
"[steeltoeVersion=mysteeltoeversion,dotNetFramework=myframework,dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.Namespace = "mynamespace";
// Act
s = spec.ToString();
// Assert
s.Should().Be(
"[namespace=mynamespace,steeltoeVersion=mysteeltoeversion,dotNetFramework=myframework,dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.Description = "mydesc";
// Act
s = spec.ToString();
// Assert
s.Should().Be(
"[description=mydesc,namespace=mynamespace,steeltoeVersion=mysteeltoeversion,dotNetFramework=myframework,dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
// Arrange
spec.Name = "myname";
// Act
s = spec.ToString();
// Assert
s.Should().Be(
"[name=myname,description=mydesc,namespace=mynamespace,steeltoeVersion=mysteeltoeversion,dotNetFramework=myframework,dotNetTemplate=mytemplate,language=mylanguage,archiveMimeType=application/mymimetype]");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectTemplateConfigurationTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var config = new ProjectTemplateConfiguration();
// Act
// Assert
config.Uri.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectTemplateTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var template = new ProjectTemplate();
// Act
// Assert
template.Description.Should().BeNull();
template.Constraints.Should().BeNull();
template.Manifest.Should().BeNull();
template.Parameters.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Models
{
public class ProjectTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Properties_Should_Be_Defined()
{
// Arrange
var project = new Project();
// Act
// Assert
project.Spec.Should().BeNull();
project.FileEntries.Should().NotBeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
}
}

Просмотреть файл

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\InitializrApi\Steeltoe.InitializrApi.csproj" />
<ProjectReference Include="..\InitializrApi.Test.Utils\Steeltoe.InitializrApi.Test.Utils.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,464 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Net;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Steeltoe.InitializrApi.Templates;
using Steeltoe.InitializrApi.Test.Utils;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Unit.Templates
{
public class ProjectTemplateRegistryTests
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void Files_In_Project_Template_Should_Match_Manifest()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr"),
},
},
};
var registry = new TemplateRegistryBuilder().WithConfig(config).Build();
// Act
var template = registry.Lookup(new ProjectSpec());
// Assert
Assert.NotNull(template);
var fileEntries = template.Manifest.ToList();
fileEntries.Count().Should().Be(3);
fileEntries[0].Path.Should().Be("f1");
fileEntries[0].Text.Should().Be("my file f1");
fileEntries[0].Rename.Should().BeNull();
fileEntries[1].Path.Should().Be("d1/");
fileEntries[1].Text.Should().BeNull();
fileEntries[1].Rename.Should().BeNull();
fileEntries[2].Path.Should().Be("r1");
fileEntries[2].Text.Should().Be("my file r1->n1");
fileEntries[2].Rename.Should().Be("n1");
}
[Fact]
public void Lookup_Should_Find_Match()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri(
"pt:///izr?description=pt1&steeltoeVersionRange=st1&dotNetFrameworkRange=df1&dotNetTemplate=dt1&language=l1"),
},
new ProjectTemplateConfiguration
{
Uri = new Uri(
"pt:///izr?description=pt2&steeltoeVersionRange=st2&dotNetFrameworkRange=df1&dotNetTemplate=dt1&language=l1"),
},
new ProjectTemplateConfiguration
{
Uri = new Uri(
"pt:///izr?description=pt3&steeltoeVersionRange=st2&dotNetFrameworkRange=df2&dotNetTemplate=dt1&language=l1"),
},
new ProjectTemplateConfiguration
{
Uri = new Uri(
"pt:///izr?description=pt4&steeltoeVersionRange=st2&dotNetFrameworkRange=df2&dotNetTemplate=dt2&language=l1"),
},
new ProjectTemplateConfiguration
{
Uri = new Uri(
"pt:///izr?description=pt5&steeltoeVersionRange=st2&dotNetFrameworkRange=df2&dotNetTemplate=dt2&language=l2"),
},
},
};
var registry = new TemplateRegistryBuilder().WithConfig(config).Build();
// Act
var template = registry.Lookup(new ProjectSpec
{ SteeltoeVersion = "st1", DotNetFramework = "df1", DotNetTemplate = "dt1", Language = "l1" });
// Assert
Assert.NotNull(template);
template.Description.Should().Be("pt1");
// Act
template = registry.Lookup(new ProjectSpec
{ SteeltoeVersion = "st2", DotNetFramework = "df1", DotNetTemplate = "dt1", Language = "l1" });
// Assert
Assert.NotNull(template);
template.Description.Should().Be("pt2");
// Act
template = registry.Lookup(new ProjectSpec
{ SteeltoeVersion = "st2", DotNetFramework = "df2", DotNetTemplate = "dt1", Language = "l1" });
// Assert
Assert.NotNull(template);
template.Description.Should().Be("pt3");
// Act
template = registry.Lookup(new ProjectSpec
{ SteeltoeVersion = "st2", DotNetFramework = "df2", DotNetTemplate = "dt2", Language = "l1" });
// Assert
Assert.NotNull(template);
template.Description.Should().Be("pt4");
// Act
template = registry.Lookup(new ProjectSpec
{ SteeltoeVersion = "st2", DotNetFramework = "df2", DotNetTemplate = "dt2", Language = "l2" });
// Assert
Assert.NotNull(template);
template.Description.Should().Be("pt5");
}
[Fact]
public void Initialize_Should_Reset_State()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr"),
},
},
};
var registry = new TemplateRegistryBuilder().WithConfig(config).Build();
// Act
var template = registry.Lookup(new ProjectSpec());
// Assert
template.Should().NotBeNull();
// Arrange
config.ProjectTemplates = new ProjectTemplateConfiguration[0];
// Act
registry.Initialize();
template = registry.Lookup(new ProjectSpec());
// Assert
template.Should().BeNull();
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
[Fact]
public void Project_Templates_Not_Configured_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig();
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "Templates not configured.");
}
[Fact]
public void Uri_File_Not_Found_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///no/such/file"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "URI not found: pt:///no/such/file");
}
[Fact]
public void Uri_Directory_Not_Found_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///no/such/dir/"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "URI not found: pt:///no/such/dir/");
}
[Fact]
public void Uri_Null_Stream_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///stream/null"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "URI returned null stream: pt:///stream/null");
}
[Fact]
public void Uri_Non_Zip_Stream_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///stream/empty"),
}
},
};
var configService = new Mock<IInitializrConfigService>();
configService.Setup(svc => svc.GetInitializrConfig()).Returns(config);
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "URI not a Zip archive: pt:///stream/empty");
}
[Fact]
public void Uri_Unexpected_Error_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///error"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "Unexpected error [System.Exception[some error message]]: pt:///error");
}
[Fact]
public void Missing_Metadata_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr?metadata=no"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error,
"Project template metadata missing ('.IZR/metadata.yaml'): pt:///izr?metadata=no");
}
[Fact]
public void Malformed_Metadata_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr?metadata=malformed"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error,
"Project template metadata malformed ('.IZR/metadata.yaml'): pt:///izr?metadata=malformed");
}
[Fact]
public void Missing_Constraints_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr?constraints=no"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "Project template constraints missing: pt:///izr?constraints=no");
}
[Fact]
public void Missing_Manifest_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr?manifest=no"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "Project template manifest missing: pt:///izr?manifest=no");
}
[Fact]
public void Missing_File_Should_Log_An_Error()
{
// Arrange
var config = new InitializrConfig
{
ProjectTemplates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("pt:///izr?missingfile=yes"),
}
},
};
var logger = new Mock<ILogger<ProjectTemplateRegistry>>();
// Act
new TemplateRegistryBuilder().WithConfig(config).WithLogger(logger.Object).Build();
// Assert
logger.VerifyLog(LogLevel.Error, "Project template missing file: 'f1' pt:///izr?missingfile=yes");
}
/* ----------------------------------------------------------------- *
* test helpers *
* ----------------------------------------------------------------- */
static ProjectTemplateRegistryTests()
{
WebRequest.RegisterPrefix("pt", new MockProjectTemplateWebScheme());
}
class TemplateRegistryBuilder
{
private InitializrConfig _config = new InitializrConfig
{
ProjectMetadata = new ProjectMetadata(),
ProjectTemplates = new ProjectTemplateConfiguration[0],
};
private ILogger<ProjectTemplateRegistry> _logger = new NullLogger<ProjectTemplateRegistry>();
internal ProjectTemplateRegistry Build()
{
var configService = new Mock<IInitializrConfigService>();
configService.Setup(svc => svc.GetInitializrConfig()).Returns(_config);
var registry = new ProjectTemplateRegistry(configService.Object, _logger);
registry.Initialize();
return registry;
}
internal TemplateRegistryBuilder WithConfig(InitializrConfig config)
{
_config = config;
return this;
}
internal TemplateRegistryBuilder WithLogger(ILogger<ProjectTemplateRegistry> logger)
{
_logger = logger;
return this;
}
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
// Borrowed from https://gist.github.com/changhuixu/c59b18ad7523562eabfa2016546d3b26
using System;
using Microsoft.Extensions.Logging;
using Moq;
namespace Steeltoe.InitializrApi.Test.Utils
{
public static class MockLoggerExtensions
{
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, string message,
string failMessage = null)
{
loggerMock.VerifyLog(level, message, Times.Once(), failMessage);
}
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, string message, Times times,
string failMessage = null)
{
loggerMock.Verify(l => l.Log(level, It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, _) => o.ToString() == message), It.IsAny<Exception>(),
It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)),
times, failMessage);
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Net;
namespace Steeltoe.InitializrApi.Test.Utils
{
public class MockProjectTemplateWebRequest : WebRequest
{
private Uri _uri;
public MockProjectTemplateWebRequest(Uri uri)
{
_uri = uri;
}
public override WebResponse GetResponse()
{
return new MockProjectTemplateWebResponse(_uri);
}
}
}

Просмотреть файл

@ -0,0 +1,160 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
namespace Steeltoe.InitializrApi.Test.Utils
{
public class MockProjectTemplateWebResponse : WebResponse
{
private Uri _uri;
public MockProjectTemplateWebResponse(Uri uri)
{
_uri = uri;
}
public override Stream GetResponseStream()
{
switch (_uri.AbsolutePath)
{
case "/error":
return Error();
case "/izr":
return IzrSteam();
case "/no/such/file":
return NoSuchFile();
case "/no/such/dir/":
return NoSuchDirectory();
case "/stream/empty":
return EmptyStream();
case "/stream/null":
return NullSteam();
case "/zip":
return ZipSteam();
default:
throw new UriFormatException($"Don't know how to create test stream: {_uri}");
}
}
private Stream IzrSteam()
{
var buf = new MemoryStream();
var archive = new ZipArchive(buf, ZipArchiveMode.Create, true);
var queryParams = new Dictionary<string, string>()
{
{ "constraints", "yes" },
{ "description", "project template description" },
{ "dotNetFrameworkRange", "" },
{ "dotNetTemplate", "" },
{ "language", "" },
{ "manifest", "yes" },
{ "metadata", "yes" },
{ "missingfile", "" },
{ "steeltoeVersionRange", "" },
};
if (_uri.Query.StartsWith('?'))
{
foreach (var nameValuePair in _uri.Query.Substring(1).Split('&'))
{
var queryParam = nameValuePair.Split('=', 2);
queryParams[queryParam[0]] = queryParam[1];
}
}
if (!queryParams["metadata"].Equals("no"))
{
var metadata = new StringBuilder();
if (queryParams["metadata"].Equals("malformed"))
{
metadata.Append("malformed yaml");
}
else
{
metadata.Append("description: ").Append(queryParams["description"]).Append(Environment.NewLine);
if (!queryParams["constraints"].Equals("no"))
{
metadata.Append("constraints:").Append(Environment.NewLine);
metadata.Append($" steeltoeVersionRange: {queryParams["steeltoeVersionRange"]}")
.Append(Environment.NewLine);
metadata.Append($" dotNetFrameworkRange: {queryParams["dotNetFrameworkRange"]}")
.Append(Environment.NewLine);
metadata.Append($" dotNetTemplate: {queryParams["dotNetTemplate"]}")
.Append(Environment.NewLine);
metadata.Append($" language: {queryParams["language"]}").Append(Environment.NewLine);
}
// manifest
if (!queryParams["manifest"].Equals("no"))
{
metadata.Append("manifest:").Append(Environment.NewLine);
metadata.Append("- path: f1").Append(Environment.NewLine);
metadata.Append("- path: d1/").Append(Environment.NewLine);
metadata.Append("- path: r1").Append(Environment.NewLine);
metadata.Append(" rename: n1").Append(Environment.NewLine);
}
}
WriteEntryText(archive.CreateEntry(".IZR/metadata.yaml"), metadata.ToString());
}
if (!queryParams["missingfile"].Equals("yes"))
{
WriteEntryText(archive.CreateEntry("f1"), "my file f1");
}
archive.CreateEntry("d1/");
WriteEntryText(archive.CreateEntry("r1"), "my file r1->n1");
archive.Dispose();
return buf;
}
private static void WriteEntryText(ZipArchiveEntry entry, string text)
{
using var stream = entry.Open();
using var writer = new StreamWriter(stream);
writer.Write(text);
}
private Stream ZipSteam()
{
var buf = new MemoryStream();
var archive = new ZipArchive(buf, ZipArchiveMode.Create, true);
archive.Dispose();
return buf;
}
private Stream EmptyStream()
{
return new MemoryStream();
}
private Stream NullSteam()
{
return null;
}
private Stream NoSuchFile()
{
throw new WebException("URI not found", new FileNotFoundException(_uri.ToString()));
}
private Stream NoSuchDirectory()
{
throw new WebException("URI not found", new DirectoryNotFoundException(_uri.ToString()));
}
private Stream Error()
{
throw new Exception($"some error message");
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Net;
namespace Steeltoe.InitializrApi.Test.Utils
{
public class MockProjectTemplateWebScheme : IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
return new MockProjectTemplateWebRequest(uri);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
using System.Text.Json;
namespace Steeltoe.InitializrApi.Test.Utils
{
public static class Serializer
{
private static JsonSerializerOptions Options { get; } = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
public static T DeserializeJson<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, Options);
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Utils
{
public class SerializerTest
{
/* ----------------------------------------------------------------- *
* positive tests *
* ----------------------------------------------------------------- */
[Fact]
public void DeserializeJson_Should_Return_Deserialized_Object()
{
// Arrange
var json = @"{""DummyProperty"": ""dummy""}";
// Act
var dummy = Serializer.DeserializeJson<DummyObject>(json);
// Assert
dummy.Should().NotBeNull();
dummy.DummyProperty.Should().Be("dummy");
}
/* ----------------------------------------------------------------- *
* negative tests *
* ----------------------------------------------------------------- */
/* ----------------------------------------------------------------- *
* test helpers *
* ----------------------------------------------------------------- */
public class DummyObject
{
public string DummyProperty { get; set; }
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
</Project>

Просмотреть файл

@ -5,7 +5,7 @@
using System;
using System.IO;
namespace Steeltoe.InitializrApi.Test.TestUtils
namespace Steeltoe.InitializrApi.Test.Utils
{
public sealed class TempFile : IDisposable
{

Просмотреть файл

@ -1,38 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using Steeltoe.Extensions.Configuration.ConfigServer;
using Steeltoe.InitializrApi.Configuration;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Configuration
{
public class ConfigServerConfigurationRepositoryTest
{
[Fact]
public async Task ConfigurationShouldNotBeNull()
{
// Arrange
var expected = new InitializrApiConfiguration();
var mockConfigOptions = new Mock<IOptions<InitializrApiConfiguration>>();
mockConfigOptions.Setup(opts => opts.Value).Returns(expected);
var mockSettings = new ConfigServerClientSettingsOptions {Uri = "a uri"};
var mockSettingsOptions = new Mock<IOptions<ConfigServerClientSettingsOptions>>();
mockSettingsOptions.Setup(settings => settings.Value).Returns(mockSettings);
var repo = new ConfigServerConfigurationRepository(mockConfigOptions.Object);
// Act
var actual = await repo.GetConfiguration();
// Assert
Assert.IsType<InitializrApiConfiguration>(actual);
actual.Should().Be(expected);
}
}
}

Просмотреть файл

@ -1,92 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Steeltoe.InitializrApi.Test.TestUtils;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Controllers
{
public class AboutControllerTest
{
private const string Endpoint = "/api/about";
[Fact]
public void GetAbout()
{
// Arrange
var controller = new AboutController();
// Act
var result = controller.GetAbout();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeOfType<About>();
}
[Fact]
public async Task PostNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PostAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PutNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PutAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PatchNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PatchAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task DeleteNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
// Act
var response = await client.DeleteAsync(Endpoint);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
}
}

Просмотреть файл

@ -1,135 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Steeltoe.InitializrApi.Test.TestUtils;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Controllers
{
public class ConfigurationControllerTest
{
private const string Endpoint = "/api/configuration";
[Fact]
public async Task GetConfiguration()
{
// Arrange
var config = new InitializrApiConfiguration();
var mockRepo = new Mock<IConfigurationRepository>();
mockRepo.Setup(repo => repo.GetConfiguration()).ReturnsAsync(config);
var controller = new ConfigurationController(mockRepo.Object);
// Act
var result = await controller.GetConfiguration();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config);
}
[Fact]
public async Task GetMetadata()
{
// Arrange
var config = new InitializrApiConfiguration
{
Metadata = new ProjectMetadata(),
};
var mockRepo = new Mock<IConfigurationRepository>();
mockRepo.Setup(repo => repo.GetConfiguration()).ReturnsAsync(config);
var controller = new ConfigurationController(mockRepo.Object);
// Act
var result = await controller.GetMetadata();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config.Metadata);
}
[Fact]
public async Task GetTemplates()
{
// Arrange
var config = new InitializrApiConfiguration
{
Templates = new ProjectTemplateConfiguration[0],
};
var mockRepo = new Mock<IConfigurationRepository>();
mockRepo.Setup(repo => repo.GetConfiguration()).ReturnsAsync(config);
var controller = new ConfigurationController(mockRepo.Object);
// Act
var result = await controller.GetTemplates();
// Assert
var indexResult = Assert.IsType<OkObjectResult>(result);
indexResult.Value.Should().BeSameAs(config.Templates);
}
[Fact]
public async Task PostNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PostAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PutNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PutAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PatchNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PatchAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task DeleteNotSupported()
{
// Arrange
var client = new HttpClientBuilder().Build();
// Act
var response = await client.DeleteAsync(Endpoint);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
}
}

Просмотреть файл

@ -1,119 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Models;
using Steeltoe.InitializrApi.Services;
using Steeltoe.InitializrApi.Test.TestUtils;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Controllers
{
public class ProjectControllerTest
{
private const string Endpoint = "/api/project";
[Fact]
public async Task GetReturnsProjectArchive()
{
// Arrange
var controller = new ProjectControllerBuilder().Build();
var spec = new ProjectSpecification
{
Name = "someProject",
};
// Act
var unknown = await controller.Get(spec);
// Assert
var result = Assert.IsType<FileContentResult>(unknown);
result.ContentType.Should().Be("application/zip");
result.FileDownloadName.Should().Be("someProject.zip");
}
[Fact]
public async Task PostReturnsMethodNotAllowed()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PostAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PutReturnsMethodNotAllowed()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PutAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task PatchReturnsMethodNotAllowed()
{
// Arrange
var client = new HttpClientBuilder().Build();
var content = new Mock<HttpContent>();
// Act
var response = await client.PatchAsync(Endpoint, content.Object);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
[Fact]
public async Task DeleteReturnsMethodNotAllowed()
{
// Arrange
var client = new HttpClientBuilder().Build();
// Act
var response = await client.DeleteAsync(Endpoint);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
}
class ProjectControllerBuilder
{
private readonly ProjectController _projectController;
internal ProjectControllerBuilder()
{
var mockGenerator = new Mock<IProjectGenerator>();
mockGenerator.Setup(g => g.GenerateProject(It.IsAny<ProjectSpecification>()))
.ReturnsAsync(new MemoryStream());
_projectController = new ProjectController(mockGenerator.Object, new NullLogger<ProjectController>());
}
internal ProjectController Build()
{
_projectController.ControllerContext.HttpContext = new DefaultHttpContext();
return _projectController;
}
}
}
}

Просмотреть файл

@ -1,29 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using FluentAssertions;
using Steeltoe.InitializrApi.Generators;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Generators
{
public class StubbleProjectGeneratorTest
{
[Fact]
public async Task GenerateProject()
{
// Arrange
var spec = new ProjectSpecification();
var generator = new StubbleProjectGenerator();
// Act
var stream = await generator.GenerateProject(spec);
// Assert
stream.Should().NotBeNull();
}
}
}

Просмотреть файл

@ -1,24 +0,0 @@
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using Steeltoe.InitializrApi.Test.TestUtils;
using Xunit;
namespace Steeltoe.InitializrApi.Test
{
public class HttpTest
{
[Fact]
public async Task NotFound()
{
// Arrange
var client = new HttpClientBuilder().Build();
// Act
var response = await client.GetAsync("no such path");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
}
}

Просмотреть файл

@ -1,31 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Models
{
public class AboutTest
{
[Fact]
public void Properties()
{
var about = new About
{
Name = "some name",
Vendor = "some vendor",
Url = "some url",
Version = "some version",
Commit = "some commit",
};
about.Name.Should().Be("some name");
about.Vendor.Should().Be("some vendor");
about.Url.Should().Be("some url");
about.Version.Should().Be("some version");
about.Commit.Should().Be("some commit");
}
}
}

Просмотреть файл

@ -1,38 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Models
{
public class InitializrApiConfigurationTest
{
[Fact]
public void Properties()
{
var config = new InitializrApiConfiguration
{
Metadata = new ProjectMetadata
{
Name = new ProjectMetadata.Text
{
Default = "a metadata name",
},
},
Templates = new[]
{
new ProjectTemplateConfiguration
{
Uri = new Uri("file:///a uri"),
},
},
};
config.Metadata.Name.Default.Should().Be("a metadata name");
config.Templates[0].Uri.Should().Be("file:///a uri");
}
}
}

Просмотреть файл

@ -1,147 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Models
{
public class ProjectMetadataTest
{
[Fact]
public void Properties()
{
var metadata = new ProjectMetadata
{
Name = new ProjectMetadata.Text
{
Type = "a name type",
Default = "a name default",
},
Description = new ProjectMetadata.Text
{
Type = "a description type",
Default = "a description default",
},
SteeltoeVersion = new ProjectMetadata.SingleSelectList
{
Type = "a steeltoe version type",
Default = "a steeltoe version default",
Values = new[]
{
new ProjectMetadata.SelectItem
{
Id = "a steeltoe version id",
Name = "a steeltoe version name",
},
}
},
DotNetVersion = new ProjectMetadata.SingleSelectList
{
Type = "a dotnet version type",
Default = "a dotnet version default",
Values = new[]
{
new ProjectMetadata.SelectItem
{
Id = "a dotnet version id",
Name = "a dotnet version name",
},
}
},
Type = new ProjectMetadata.SingleSelectList
{
Type = "a dotnet template type type",
Default = "a dotnet template type default",
Values = new[]
{
new ProjectMetadata.SelectItem
{
Id = "a dotnet template type id",
Name = "a dotnet template type name",
},
}
},
Language = new ProjectMetadata.SingleSelectList
{
Type = "a language type",
Default = "a language default",
Values = new[]
{
new ProjectMetadata.SelectItem
{
Id = "a language id",
Name = "a language name",
},
}
},
Format = new ProjectMetadata.SingleSelectList
{
Type = "a project bundle archive format type",
Default = "a project bundle archive format default",
Values = new[]
{
new ProjectMetadata.SelectItem
{
Id = "a project bundle archive format id",
Name = "a project bundle archive format name",
},
}
},
Dependencies = new ProjectMetadata.GroupList
{
Type = "a dependencies type",
Values = new[]
{
new ProjectMetadata.Group
{
Name = "a dependency group name",
Values = new[]
{
new ProjectMetadata.GroupItem
{
Id = "a dependency id",
Name = "a dependency name",
Description = "a dependency description",
SteeltoeVersionRange = "a dependency steeltoe version range",
},
},
},
},
},
};
metadata.Name.Type.Should().Be("a name type");
metadata.Name.Default.Should().Be("a name default");
metadata.Description.Type.Should().Be("a description type");
metadata.Description.Default.Should().Be("a description default");
metadata.SteeltoeVersion.Type.Should().Be("a steeltoe version type");
metadata.SteeltoeVersion.Default.Should().Be("a steeltoe version default");
metadata.SteeltoeVersion.Values[0].Id.Should().Be("a steeltoe version id");
metadata.SteeltoeVersion.Values[0].Name.Should().Be("a steeltoe version name");
metadata.DotNetVersion.Type.Should().Be("a dotnet version type");
metadata.DotNetVersion.Default.Should().Be("a dotnet version default");
metadata.DotNetVersion.Values[0].Id.Should().Be("a dotnet version id");
metadata.DotNetVersion.Values[0].Name.Should().Be("a dotnet version name");
metadata.Type.Type.Should().Be("a dotnet template type type");
metadata.Type.Default.Should().Be("a dotnet template type default");
metadata.Type.Values[0].Id.Should().Be("a dotnet template type id");
metadata.Type.Values[0].Name.Should().Be("a dotnet template type name");
metadata.Language.Type.Should().Be("a language type");
metadata.Language.Default.Should().Be("a language default");
metadata.Language.Values[0].Id.Should().Be("a language id");
metadata.Language.Values[0].Name.Should().Be("a language name");
metadata.Format.Type.Should().Be("a project bundle archive format type");
metadata.Format.Default.Should().Be("a project bundle archive format default");
metadata.Format.Values[0].Id.Should().Be("a project bundle archive format id");
metadata.Format.Values[0].Name.Should().Be("a project bundle archive format name");
metadata.Dependencies.Type.Should().Be("a dependencies type");
metadata.Dependencies.Values[0].Name.Should().Be("a dependency group name");
metadata.Dependencies.Values[0].Values[0].Id.Should().Be("a dependency id");
metadata.Dependencies.Values[0].Values[0].Name.Should().Be("a dependency name");
metadata.Dependencies.Values[0].Values[0].Description.Should().Be("a dependency description");
metadata.Dependencies.Values[0].Values[0].SteeltoeVersionRange.Should().Be("a dependency steeltoe version range");
}
}
}

Просмотреть файл

@ -1,33 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Models
{
public class ProjectSpecificationTest
{
[Fact]
public void Properties()
{
var spec = new ProjectSpecification
{
Name = "some name",
Description = "some description",
SteeltoeVersion = "some Steeltoe release ID",
DotnetTargetFrameworkId = "some DotNet framework ID",
DotnetLanguageId = "some DotNet language ID",
DotnetTemplateId = "some DotNet template ID",
};
spec.Name.Should().Be("some name");
spec.Description.Should().Be("some description");
spec.SteeltoeVersion.Should().Be("some Steeltoe release ID");
spec.DotnetTargetFrameworkId.Should().Be("some DotNet framework ID");
spec.DotnetLanguageId.Should().Be("some DotNet language ID");
spec.DotnetTemplateId.Should().Be("some DotNet template ID");
}
}
}

Просмотреть файл

@ -1,32 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using FluentAssertions;
using Steeltoe.InitializrApi.Models;
using Xunit;
namespace Steeltoe.InitializrApi.Test.Models
{
public class ProjectTemplateConfigurationTest
{
[Fact]
public void Properties()
{
var config = new ProjectTemplateConfiguration
{
Uri = new Uri("file:///a uri"),
SteeltoeVersionRange = "a steeltoe version range",
DotNetVersionRange = "a dotnet version range",
Type = "a dotnet template type",
Language = "a language",
};
config.Uri.Should().Be("file:///a uri");
config.SteeltoeVersionRange.Should().Be("a steeltoe version range");
config.DotNetVersionRange.Should().Be("a dotnet version range");
config.Type.Should().Be("a dotnet template type");
config.Language.Should().Be("a language");
}
}
}

Просмотреть файл

@ -1,54 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Steeltoe.InitializrApi.Controllers;
using Steeltoe.InitializrApi.Services;
using Xunit;
namespace Steeltoe.InitializrApi.Test
{
public class StartupTest
{
[Fact]
public void ConfigurationControllerRegistered()
{
// Arrange
var cfg = new ConfigurationBuilder().Build();
var startup = new Startup(cfg);
IServiceCollection svcs = new ServiceCollection();
svcs.AddSingleton<ILogger<IConfigurationRepository>>(new NullLogger<IConfigurationRepository>());
startup.ConfigureServices(svcs);
// Act
svcs.AddTransient<ConfigurationController>();
// Assert
var controller = svcs.BuildServiceProvider().GetService<ConfigurationController>();
controller.Should().NotBeNull();
}
[Fact]
public void ProjectControllerRegisterer()
{
// Arrange
var cfg = new ConfigurationBuilder().Build();
var startup = new Startup(cfg);
IServiceCollection svcs = new ServiceCollection();
svcs.AddSingleton<ILogger<ProjectController>>(new NullLogger<ProjectController>());
startup.ConfigureServices(svcs);
// Act
svcs.AddTransient<ProjectController>();
// Assert
var controller = svcs.BuildServiceProvider().GetService<ProjectController>();
controller.Should().NotBeNull();
}
}
}

Просмотреть файл

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\src\InitializrApi\Steeltoe.InitializrApi.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Directory.Build.props">
<Link>Directory.Build.props</Link>
</Content>
</ItemGroup>
</Project>

Просмотреть файл

@ -1,31 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
namespace Steeltoe.InitializrApi.Test.TestUtils
{
public class HttpClientBuilder
{
private static readonly IHost Host;
static HttpClientBuilder()
{
var hostBuilder = new HostBuilder().ConfigureWebHost(webHost =>
{
webHost.UseTestServer();
webHost.UseStartup<Startup>();
});
Host = hostBuilder.Start();
}
public HttpClient Build()
{
return Host.GetTestClient();
}
}
}