release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
namespace StellaOps.Plugin.Samples.HelloWorld.Tests;
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.Plugin.Abstractions;
|
||||
using StellaOps.Plugin.Abstractions.Health;
|
||||
using StellaOps.Plugin.Testing;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for HelloWorldPlugin demonstrating the Plugin.Testing library usage.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class HelloWorldPluginTests : PluginTestBase<HelloWorldPlugin>
|
||||
{
|
||||
protected override Dictionary<string, object> GetConfiguration() => new()
|
||||
{
|
||||
["Greeting"] = "Hello",
|
||||
["LogGreetings"] = true
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Info_ReturnsCorrectPluginInfo()
|
||||
{
|
||||
// Assert - Plugin is already initialized by base class
|
||||
Plugin.Info.Id.Should().Be("org.stellaops.samples.helloworld");
|
||||
Plugin.Info.Name.Should().Be("Hello World Sample");
|
||||
Plugin.Info.Version.Should().Be("1.0.0");
|
||||
Plugin.Info.Vendor.Should().Be("Stella Ops");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Capabilities_ReturnsNone()
|
||||
{
|
||||
// Assert - this is a sample plugin with no special capabilities
|
||||
Plugin.Capabilities.Should().Be(PluginCapabilities.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void State_AfterInitialization_IsActive()
|
||||
{
|
||||
// Assert - base class initializes the plugin
|
||||
Plugin.State.Should().Be(StellaOps.Plugin.Abstractions.Lifecycle.PluginLifecycleState.Active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Greet_WithName_ReturnsGreeting()
|
||||
{
|
||||
// Act
|
||||
var result = Plugin.Greet("World");
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Hello, World!");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthCheckAsync_WhenInitialized_ReturnsHealthy()
|
||||
{
|
||||
// Act
|
||||
var health = await Plugin.HealthCheckAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
health.Status.Should().Be(HealthStatus.Healthy);
|
||||
health.Details.Should().ContainKey("greeting");
|
||||
health.Details!["greeting"].Should().Be("Hello");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Greet_LogsMessage()
|
||||
{
|
||||
// Act
|
||||
Plugin.Greet("Test");
|
||||
|
||||
// Assert
|
||||
Logger.HasLoggedContaining("Generated greeting").Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that require custom configuration.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class HelloWorldPluginCustomConfigTests : PluginTestBase<HelloWorldPlugin>
|
||||
{
|
||||
protected override Dictionary<string, object> GetConfiguration() => new()
|
||||
{
|
||||
["Greeting"] = "Howdy",
|
||||
["LogGreetings"] = false
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Greet_WithCustomGreeting_UsesConfiguredGreeting()
|
||||
{
|
||||
// Act
|
||||
var result = Plugin.Greet("Partner");
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Howdy, Partner!");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Greet_WhenLoggingDisabled_DoesNotLog()
|
||||
{
|
||||
// Arrange
|
||||
Logger.Clear();
|
||||
|
||||
// Act
|
||||
Plugin.Greet("Test");
|
||||
|
||||
// Assert - should not have logged the greeting
|
||||
Logger.HasLoggedContaining("Generated greeting").Should().BeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests with deterministic time.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class HelloWorldPluginTimeTests : PluginTestBase<HelloWorldPlugin>
|
||||
{
|
||||
private static readonly DateTimeOffset FixedStartTime = new(2025, 1, 15, 10, 30, 0, TimeSpan.Zero);
|
||||
|
||||
protected override void ConfigureHost(PluginTestHostOptions options)
|
||||
{
|
||||
// Set a fixed start time for deterministic testing
|
||||
options.StartTime = FixedStartTime;
|
||||
}
|
||||
|
||||
protected override Dictionary<string, object> GetConfiguration() => new()
|
||||
{
|
||||
["Greeting"] = "Hello"
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task HealthCheckAsync_ReturnsInitializedTime()
|
||||
{
|
||||
// Act
|
||||
var health = await Plugin.HealthCheckAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
health.Status.Should().Be(HealthStatus.Healthy);
|
||||
health.Details.Should().ContainKey("initialized_at");
|
||||
// The initialized time should contain our fixed date
|
||||
health.Details!["initialized_at"].ToString().Should().Contain("2025-01-15");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeTimeProvider_CanAdvanceTime()
|
||||
{
|
||||
// Arrange
|
||||
var initialTime = FakeTimeProvider?.GetUtcNow();
|
||||
|
||||
// Act
|
||||
FakeTimeProvider?.AdvanceMinutes(5);
|
||||
|
||||
// Assert
|
||||
var newTime = FakeTimeProvider?.GetUtcNow();
|
||||
newTime.Should().Be(initialTime?.AddMinutes(5));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for uninitialized plugin behavior.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class HelloWorldPluginUninitializedTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HealthCheckAsync_WhenNotInitialized_ReturnsUnhealthy()
|
||||
{
|
||||
// Arrange - create plugin without initializing
|
||||
var plugin = new HelloWorldPlugin();
|
||||
|
||||
// Act
|
||||
var health = await plugin.HealthCheckAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
health.Status.Should().Be(HealthStatus.Unhealthy);
|
||||
health.Message.Should().Contain("not initialized");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<!-- Opt out of Concelier test infrastructure -->
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Test packages are added by Directory.Build.props for *.Tests projects -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Plugin.Samples.HelloWorld\StellaOps.Plugin.Samples.HelloWorld.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Plugin.Testing\StellaOps.Plugin.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,122 @@
|
||||
namespace StellaOps.Plugin.Samples.HelloWorld;
|
||||
|
||||
using StellaOps.Plugin.Abstractions;
|
||||
using StellaOps.Plugin.Abstractions.Context;
|
||||
using StellaOps.Plugin.Abstractions.Health;
|
||||
using StellaOps.Plugin.Sdk;
|
||||
|
||||
/// <summary>
|
||||
/// Sample HelloWorld plugin demonstrating SDK usage patterns.
|
||||
/// This plugin shows how to:
|
||||
/// - Use PluginBase for simplified development
|
||||
/// - Access configuration values
|
||||
/// - Use structured logging
|
||||
/// - Handle lifecycle events
|
||||
/// - Report health status
|
||||
/// </summary>
|
||||
public sealed class HelloWorldPlugin : PluginBase
|
||||
{
|
||||
private HelloWorldOptions? _options;
|
||||
private DateTimeOffset _initializedAt;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PluginInfo Info { get; } = new PluginInfoBuilder()
|
||||
.WithId("org.stellaops.samples.helloworld")
|
||||
.WithName("Hello World Sample")
|
||||
.WithVersion("1.0.0")
|
||||
.WithVendor("Stella Ops")
|
||||
.WithDescription("A sample plugin demonstrating SDK usage patterns")
|
||||
.WithLicense("AGPL-3.0-or-later")
|
||||
.Build();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PluginCapabilities Capabilities => PluginCapabilities.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializeAsync(IPluginContext context, CancellationToken ct)
|
||||
{
|
||||
// Load and validate configuration
|
||||
_options = Configuration.Bind<HelloWorldOptions>();
|
||||
|
||||
Logger.Info("HelloWorld plugin initializing with greeting: {Greeting}", _options.Greeting);
|
||||
|
||||
// Demonstrate async initialization (e.g., loading resources)
|
||||
await Task.Delay(10, ct);
|
||||
|
||||
_initializedAt = TimeProvider.GetUtcNow();
|
||||
Logger.Debug("HelloWorld plugin initialized successfully at {Time}", _initializedAt);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ValueTask OnDisposeAsync()
|
||||
{
|
||||
Logger.Debug("HelloWorld plugin disposed");
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<HealthCheckResult> HealthCheckAsync(CancellationToken ct)
|
||||
{
|
||||
// Example health check logic
|
||||
if (_options is null)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Unhealthy("Plugin not initialized"));
|
||||
}
|
||||
|
||||
return Task.FromResult(
|
||||
HealthCheckResult.Healthy()
|
||||
.WithDetails(new Dictionary<string, object>
|
||||
{
|
||||
["greeting"] = _options.Greeting,
|
||||
["initialized_at"] = _initializedAt.ToString("O")
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates the greeting functionality.
|
||||
/// </summary>
|
||||
/// <param name="name">Name to greet.</param>
|
||||
/// <returns>The greeting message.</returns>
|
||||
public string Greet(string name)
|
||||
{
|
||||
var greeting = _options?.Greeting ?? "Hello";
|
||||
var message = $"{greeting}, {name}!";
|
||||
|
||||
if (_options?.LogGreetings == true)
|
||||
{
|
||||
Logger.Info("Generated greeting for {Name}: {Message}", name, message);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for HelloWorld plugin.
|
||||
/// </summary>
|
||||
public sealed class HelloWorldOptions : PluginOptionsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the greeting phrase.
|
||||
/// </summary>
|
||||
[PluginConfig(Key = "Greeting", DefaultValue = "Hello", Description = "The greeting phrase to use")]
|
||||
public string Greeting { get; set; } = "Hello";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to log greetings.
|
||||
/// </summary>
|
||||
[PluginConfig(Key = "LogGreetings", DefaultValue = true, Description = "Whether to log each greeting")]
|
||||
public bool LogGreetings { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> Validate(
|
||||
System.ComponentModel.DataAnnotations.ValidationContext validationContext)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Greeting))
|
||||
{
|
||||
yield return new System.ComponentModel.DataAnnotations.ValidationResult(
|
||||
"Greeting cannot be empty",
|
||||
new[] { nameof(Greeting) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Description>Sample HelloWorld plugin demonstrating SDK usage</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Plugin.Sdk\StellaOps.Plugin.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user