Added CLI test project and tests for 'm4g init' command
This commit is contained in:
parent
4f322e56c7
commit
f676f236b1
19
.dockerignore
Normal file
19
.dockerignore
Normal file
@ -0,0 +1,19 @@
|
||||
# directories
|
||||
**/bin/
|
||||
**/obj/
|
||||
**/out/
|
||||
.git
|
||||
.idea
|
||||
docs
|
||||
.gitignore
|
||||
.dockerignore
|
||||
MycroForge.sln.DotSettings.user
|
||||
nuget.docker-compose.yml
|
||||
README.md
|
||||
|
||||
# files
|
||||
Dockerfile*
|
||||
**/*.md
|
||||
|
||||
#MycroForge.PluginTemplate
|
||||
#MycroForge.PluginTemplate.Package
|
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
|
||||
|
||||
# Copy the files to the /tool directory.
|
||||
WORKDIR /tool
|
||||
COPY . .
|
||||
# Manually add a reference to MycroForge.Core in the MycroForge.PluginTemplate project,
|
||||
# otherwise it will try to fetch it from a nuget repository.
|
||||
RUN dotnet add MycroForge.PluginTemplate reference MycroForge.Core
|
||||
|
||||
# Install the tools required for testing
|
||||
RUN apt update -y && \
|
||||
apt upgrade -y && \
|
||||
apt install -y git && \
|
||||
apt install -y bash && \
|
||||
apt install -y python3 && \
|
||||
apt install -y python3-pip && \
|
||||
apt install -y python3-venv
|
||||
|
||||
# Publish the CLI as a global tool and add the .dotnet/tools folder the the PATH variable.
|
||||
WORKDIR /tool/MycroForge.CLI
|
||||
RUN ./scripts/publish-tool.sh
|
||||
ENV PATH="$PATH:/root/.dotnet/tools"
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
ENV PATH="$PATH:/root/.dotnet/tools"
|
||||
|
||||
CMD ["sleep", "infinity"]
|
@ -0,0 +1,11 @@
|
||||
using DotNet.Testcontainers.Containers;
|
||||
|
||||
namespace MycroForge.CLI.Tests.Extensions;
|
||||
|
||||
internal static class ContainerInterfaceExtensions
|
||||
{
|
||||
public static Task<byte[]> ReadFileFromRootAsync(this IContainer container, string file)
|
||||
{
|
||||
return container.ReadFileAsync($"/test/todo/{file}");
|
||||
}
|
||||
}
|
1
MycroForge.CLI.Tests/GlobalUsings.cs
Normal file
1
MycroForge.CLI.Tests/GlobalUsings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using NUnit.Framework;
|
222
MycroForge.CLI.Tests/InitCommandTests.cs
Normal file
222
MycroForge.CLI.Tests/InitCommandTests.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using DotNet.Testcontainers.Images;
|
||||
using MycroForge.CLI.Tests.Extensions;
|
||||
using MycroForge.Core;
|
||||
|
||||
namespace MycroForge.CLI.Tests;
|
||||
|
||||
public class InitCommandTests
|
||||
{
|
||||
private IFutureDockerImage Image = null!;
|
||||
private IContainer Container = null!;
|
||||
|
||||
private async Task CreateImage()
|
||||
{
|
||||
Image = new ImageFromDockerfileBuilder()
|
||||
.WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), string.Empty)
|
||||
.WithDockerfile("Dockerfile")
|
||||
.WithCleanUp(true)
|
||||
.Build();
|
||||
|
||||
await Image.CreateAsync();
|
||||
}
|
||||
|
||||
private async Task CreateContainer()
|
||||
{
|
||||
Container = new ContainerBuilder()
|
||||
.WithImage(Image)
|
||||
.WithCleanUp(true)
|
||||
.Build();
|
||||
|
||||
await Container.StartAsync();
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupOnce()
|
||||
{
|
||||
await CreateImage();
|
||||
|
||||
await CreateContainer();
|
||||
|
||||
await Container.ExecAsync(["m4g", "init", "todo"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_m4g_json()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("m4g.json");
|
||||
using var stream = new MemoryStream(bytes);
|
||||
var config = await JsonSerializer.DeserializeAsync<ProjectConfig>(stream, DefaultJsonSerializerOptions.Default);
|
||||
|
||||
Assert.That(config, Is.Not.Null);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(config!.Api, Is.Not.Null);
|
||||
Assert.That(config.Api.Port, Is.EqualTo(8000));
|
||||
Assert.That(config.Db, Is.Not.Null);
|
||||
Assert.That(config.Db.DbhPort, Is.EqualTo(5050));
|
||||
Assert.That(config.Db.DbuPort, Is.EqualTo(5051));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_main_py()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("main.py");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("from fastapi import FastAPI"));
|
||||
Assert.That(lines, Does.Contain("from api.routers import hello"));
|
||||
Assert.That(lines, Does.Contain("app = FastAPI()"));
|
||||
Assert.That(lines, Does.Contain("app.include_router(prefix=\"/hello\", router=hello.router)"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_gitignore()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync(".gitignore");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain(".venv"));
|
||||
Assert.That(lines, Does.Contain("__pycache__/"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_alembic_ini()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("alembic.ini");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("[alembic]"));
|
||||
Assert.That(lines, Does.Contain("sqlalchemy.url = driver://user:pass@localhost/dbname"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_db_docker_compose_yml()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("db.docker-compose.yml");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain(" todo_mariadb:"));
|
||||
Assert.That(lines, Does.Contain(" container_name: 'todo_mariadb'"));
|
||||
Assert.That(lines, Does.Contain(" - '${DBH_PORT}:3306'"));
|
||||
Assert.That(lines, Does.Contain(" MYSQL_DATABASE: 'todo'"));
|
||||
Assert.That(lines, Does.Contain(" - 'todo_mariadb:/var/lib/mysql'"));
|
||||
Assert.That(lines, Does.Contain(" todo_phpmyadmin:"));
|
||||
Assert.That(lines, Does.Contain(" container_name: todo_phpmyadmin"));
|
||||
Assert.That(lines, Does.Contain(" PMA_HOST: todo_mariadb"));
|
||||
Assert.That(lines, Does.Contain(" - todo_mariadb"));
|
||||
Assert.That(lines, Does.Contain(" todo_mariadb:"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_api__router_hello_py()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("api/routers/hello.py");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("from fastapi import APIRouter"));
|
||||
Assert.That(lines, Does.Contain("from fastapi.responses import JSONResponse"));
|
||||
Assert.That(lines, Does.Contain("from fastapi.encoders import jsonable_encoder"));
|
||||
Assert.That(lines, Does.Contain("router = APIRouter()"));
|
||||
Assert.That(lines, Does.Contain("@router.get(\"/{name}\")"));
|
||||
Assert.That(lines, Does.Contain("async def hello(name: str):"));
|
||||
Assert.That(lines, Does.Contain("\treturn JSONResponse(status_code=200, content=jsonable_encoder({'greeting': f\"Hello, {name}!\"}))"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_db__engine__async_session_py()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("db/engine/async_session.py");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession"));
|
||||
Assert.That(lines, Does.Contain("from db.settings import DbSettings"));
|
||||
Assert.That(lines, Does.Contain("async_engine: AsyncEngine = create_async_engine(DbSettings.get_connectionstring())"));
|
||||
Assert.That(lines, Does.Contain("def async_session() -> AsyncSession:"));
|
||||
Assert.That(lines, Does.Contain("\treturn AsyncSession(async_engine, expire_on_commit=False)"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_db__entities__entity_base_py()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("db/entities/entity_base.py");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("from sqlalchemy.orm import DeclarativeBase"));
|
||||
Assert.That(lines, Does.Contain("class EntityBase(DeclarativeBase):"));
|
||||
Assert.That(lines, Does.Contain("\tpass"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Creates_db__settings_py()
|
||||
{
|
||||
var bytes = await Container.ReadFileFromRootAsync("db/settings.py");
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
var lines = text.Split('\n');
|
||||
|
||||
TestContext.WriteLine(text);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(text, Is.Not.Empty);
|
||||
Assert.That(lines, Does.Contain("class DbSettings:"));
|
||||
Assert.That(lines, Does.Contain("\tdef get_connectionstring() -> str:"));
|
||||
Assert.That(lines, Does.Contain("\t\tconnectionstring = \"mysql+asyncmy://root:password@localhost:5050/todo\""));
|
||||
Assert.That(lines, Does.Contain("\t\treturn connectionstring"));
|
||||
});
|
||||
}
|
||||
}
|
25
MycroForge.CLI.Tests/MycroForge.CLI.Tests.csproj
Normal file
25
MycroForge.CLI.Tests/MycroForge.CLI.Tests.csproj
Normal file
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="Testcontainers" Version="3.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MycroForge.Core\MycroForge.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
dotnet pack -v d
|
||||
|
||||
|
17
MycroForge.Core/DefaultJsonSerializerOptions.cs
Normal file
17
MycroForge.Core/DefaultJsonSerializerOptions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MycroForge.Core;
|
||||
|
||||
public static class DefaultJsonSerializerOptions
|
||||
{
|
||||
public static readonly JsonSerializerOptions Default = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerOptions CamelCasePrettyPrint = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
}
|
@ -4,15 +4,12 @@ namespace MycroForge.Core.Extensions;
|
||||
|
||||
public static class ObjectStreamExtensions
|
||||
{
|
||||
public static async Task<string> SerializeAsync(
|
||||
this object @object,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null
|
||||
)
|
||||
public static async Task<string> SerializeAsync(this object @object, JsonSerializerOptions? options = null)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using var reader = new StreamReader(stream);
|
||||
var options = jsonSerializerOptions ?? Serialization.DefaultJsonSerializerOptions.Default;
|
||||
|
||||
options ??= DefaultJsonSerializerOptions.Default;
|
||||
|
||||
await JsonSerializer.SerializeAsync(stream, @object, options);
|
||||
stream.Position = 0;
|
||||
return await reader.ReadToEndAsync();
|
||||
|
@ -21,8 +21,8 @@ public class ProjectContext
|
||||
}
|
||||
|
||||
var config = await JsonSerializer.DeserializeAsync<ProjectConfig>(
|
||||
File.OpenRead(ConfigPath),
|
||||
Serialization.DefaultJsonSerializerOptions.CamelCasePrettyPrint
|
||||
File.OpenRead(ConfigPath),
|
||||
DefaultJsonSerializerOptions.CamelCasePrettyPrint
|
||||
);
|
||||
|
||||
if (config is null)
|
||||
@ -116,7 +116,7 @@ public class ProjectContext
|
||||
|
||||
public async Task SaveConfig(ProjectConfig config)
|
||||
{
|
||||
var json = await config.SerializeAsync(Serialization.DefaultJsonSerializerOptions.CamelCasePrettyPrint);
|
||||
var json = await config.SerializeAsync(DefaultJsonSerializerOptions.CamelCasePrettyPrint);
|
||||
await File.WriteAllTextAsync(ConfigPath, json);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MycroForge.Core;
|
||||
|
||||
public static class Serialization
|
||||
{
|
||||
public static class DefaultJsonSerializerOptions
|
||||
{
|
||||
public static readonly JsonSerializerOptions Default = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerOptions CamelCasePrettyPrint = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate",
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate.Package", "MycroForge.PluginTemplate.Package\MycroForge.PluginTemplate.Package.csproj", "{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.CLI.Tests", "MycroForge.CLI.Tests\MycroForge.CLI.Tests.csproj", "{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -30,5 +32,9 @@ Global
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
Loading…
Reference in New Issue
Block a user