Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions MIGRATION-legacy-toolkit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Migrating From The Legacy .NET Framework Toolkit

This repository is the modernized ByteForge Toolkit line. It is being brought
back to feature parity with the legacy `.NET Framework 4.8` toolkit at:

`C:\Users\pauls\source\TelecomInc\MainProjects\ByteForge.Toolkit`

The goal is drop-in compatibility where practical, while preserving the modern
project's existing folder and namespace style.

## Layout And Namespace Differences

Some legacy folders were renamed during the modernization. Keep using the modern
namespaces in new code.

| Legacy location | Modern location | Modern namespace |
| --- | --- | --- |
| `CLI` | `Toolkit.Modern/CommandLine` | `ByteForge.Toolkit.CommandLine` |
| `Utils` | `Toolkit.Modern/Utilities` | `ByteForge.Toolkit.Utilities` |
| `Converters` | `Toolkit.Modern/Utilities` or `Toolkit.Modern/Data/Database` | Existing modern namespace for the type |
| `Attributes` | `Toolkit.Modern/Data/Attributes` for data mapping attributes | `ByteForge.Toolkit.Data` |
| `Configuration` | `Toolkit.Modern/Configuration` | `ByteForge.Toolkit.Configuration` |

Legacy assembly metadata attributes are an exception: they remain in
`ByteForge.Toolkit` so assembly info files can continue to use the short
attribute names with `using ByteForge.Toolkit`.

## DBAccess2

`DBAccess2` was started in the legacy toolkit but was abandoned before becoming
functional. It is intentionally not ported to the modern toolkit.

Compatibility notes:

- `DBAccess` remains the supported database access implementation.
- `DatabaseAccessFactory` exists for callers that used the legacy factory shape.
- The `useDBAccess2` factory flag is accepted for source compatibility but is
ignored.
- `CreateModern(DatabaseOptions)` remains only as an obsolete compatibility shim
and returns `DBAccess`.
- Do not migrate callers to `DBAccess2`; migrate them to `DBAccess` or
`DatabaseAccessFactory.Create(...)`.

## Current Compatibility Additions

The modern toolkit includes these legacy-parity additions:

- Configuration documentation support types such as
`ConfigDocumentationCatalog`, `ConfigSectionSchema`, and
`ConfigOptionsProviderAttribute`.
- Logging context helpers such as `LogContext`, `LogSecretMasker`, and
`LogRoutingContext`.
- `IDatabaseAccess` plus cancellation-token overloads on `DBAccess`.
- `ReportTimestampFormatter` under `ByteForge.Toolkit.Utilities`.
- Assembly metadata attributes under `ByteForge.Toolkit`.
21 changes: 21 additions & 0 deletions Toolkit.Modern.Tests/Unit/AssemblyMetadataAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using AwesomeAssertions;

namespace ByteForge.Toolkit.Tests.Unit
{
[TestClass]
[TestCategory("Unit")]
public class AssemblyMetadataAttributeTests
{
/// <summary>
/// Verifies that assembly developer metadata attributes preserve constructor values.
/// </summary>
[TestMethod]
public void AssemblyDeveloperAttributes_ShouldExposeConfiguredValues()
{
new AssemblyDeveloperAttribute("Paulo Santos").Name.Should().Be("Paulo Santos");
new AssemblyDeveloperCompanyAttribute("ByteForge, LLC.").Name.Should().Be("ByteForge, LLC.");
new AssemblyCompanyUrlAttribute("https://byteforge.example").Url.Should().Be("https://byteforge.example");
new AssemblyDeveloperCompanyUrlAttribute("https://dev.example").Url.Should().Be("https://dev.example");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using AwesomeAssertions;
using ByteForge.Toolkit.Configuration;
using System.Reflection;

namespace ByteForge.Toolkit.Tests.Unit.Configuration
{
[TestClass]
[TestCategory("Unit")]
[TestCategory("Configuration")]
public class ConfigOptionsProviderAttributeTests
{
/// <summary>
/// Verifies that provider attributes create the configured provider instance.
/// </summary>
[TestMethod]
public void CreateProvider_WithValidProviderType_ShouldCreateProvider()
{
var attribute = new ConfigOptionsProviderAttribute(typeof(TestOptionsProvider));

var provider = attribute.CreateProvider();

provider.Should().BeOfType<TestOptionsProvider>();
}

/// <summary>
/// Verifies that provider attributes reject non-provider types.
/// </summary>
[TestMethod]
public void Constructor_WithInvalidProviderType_ShouldThrowArgumentException()
{
Action action = () => new ConfigOptionsProviderAttribute(typeof(string));

action.Should().Throw<ArgumentException>()
.WithParameterName("providerType");
}

/// <summary>
/// Verifies that provider attributes reject null provider types.
/// </summary>
[TestMethod]
public void Constructor_WithNullProviderType_ShouldThrowArgumentNullException()
{
Action action = () => new ConfigOptionsProviderAttribute(null);

action.Should().Throw<ArgumentNullException>()
.WithParameterName("providerType");
}

private sealed class TestOptionsProvider : IConfigOptionsProvider
{
public IReadOnlyCollection<ConfigItemOption> GetOptions(PropertyInfo property)
{
return Array.Empty<ConfigItemOption>();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using AwesomeAssertions;
using ByteForge.Toolkit.Configuration;
using ByteForge.Toolkit.Tests.Helpers;
using System.ComponentModel;

namespace ByteForge.Toolkit.Tests.Unit.Configuration
{
/// <summary>
/// Tests Toolkit INI documentation preservation behavior.
/// </summary>
[TestClass]
public class ConfigurationDocumentationTests
{
/// <summary>
/// Cleans up temporary files after each test.
/// </summary>
[TestCleanup]
public void TestCleanup()
{
TestConfigurationHelper.CleanupTempFiles();
}

/// <summary>
/// Verifies that documentation attached to default-valued keys is removed with those keys during save.
/// </summary>
[TestMethod]
public void Save_WithDocumentedDefaultValues_ShouldNotLeaveDetachedDocBlocks()
{
var path = TestConfigurationHelper.CreateTempConfigFile(@"[Docs]
;;;
; Doc 1
Property1=DefaultValue
;;;
; Doc 2
Property2=DefaultValue
;;;
; Doc 3
Property3=DefaultValue
OtherProperty=Non-default-value");
IConfigurationManager config = new ByteForge.Toolkit.Configuration.Configuration();

config.Initialize(path);
config.GetSection<DefaultDocumentedConfig>("Docs");
config.Save();

var saved = File.ReadAllText(path);
saved.Should().NotContain("Doc 1");
saved.Should().NotContain("Doc 2");
saved.Should().NotContain("Doc 3");
saved.Should().NotContain("Property1=");
saved.Should().NotContain("Property2=");
saved.Should().NotContain("Property3=");
saved.Should().Contain("OtherProperty=Non-default-value");
}

/// <summary>
/// Test configuration section with default-valued documented properties.
/// </summary>
public class DefaultDocumentedConfig
{
/// <summary>
/// Gets or sets the first default-valued property.
/// </summary>
[DefaultValue("DefaultValue")]
public string? Property1 { get; set; }

/// <summary>
/// Gets or sets the second default-valued property.
/// </summary>
[DefaultValue("DefaultValue")]
public string? Property2 { get; set; }

/// <summary>
/// Gets or sets the third default-valued property.
/// </summary>
[DefaultValue("DefaultValue")]
public string? Property3 { get; set; }

/// <summary>
/// Gets or sets the non-default property that must not inherit detached documentation.
/// </summary>
[DefaultValue("DefaultValue")]
public string? OtherProperty { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using AwesomeAssertions;
using ByteForge.Toolkit.Data;
using ByteForge.Toolkit.Tests.Helpers;

namespace ByteForge.Toolkit.Tests.Unit.Data.Database
{
[TestClass]
[TestCategory("Unit")]
[TestCategory("Data")]
public class DatabaseAccessFactoryTests
{
/// <summary>
/// Verifies that the factory returns DBAccess even when the legacy DBAccess2 flag is requested.
/// </summary>
[TestMethod]
public void Create_WithUseDBAccess2Flag_ShouldReturnDBAccess()
{
var options = DatabaseTestHelper.CreateTestDatabaseOptions();

var result = DatabaseAccessFactory.Create(options, useDBAccess2: true);

result.Should().BeOfType<DBAccess>();
result.Options.Should().BeSameAs(options);
}

/// <summary>
/// Verifies that the obsolete modern factory shape remains a DBAccess compatibility shim.
/// </summary>
[TestMethod]
public void CreateModern_ShouldReturnDBAccessCompatibilityShim()
{
var options = DatabaseTestHelper.CreateTestDatabaseOptions();

#pragma warning disable CS0618
var result = DatabaseAccessFactory.CreateModern(options);
#pragma warning restore CS0618

result.Should().BeOfType<DBAccess>();
result.Options.Should().BeSameAs(options);
}

/// <summary>
/// Verifies that null options are rejected consistently.
/// </summary>
[TestMethod]
public void Create_WithNullOptions_ShouldThrowArgumentNullException()
{
Action action = () => DatabaseAccessFactory.Create((DatabaseOptions)null);

action.Should().Throw<ArgumentNullException>()
.WithParameterName("options");
}

/// <summary>
/// Verifies that DBAccess can be consumed through the compatibility interface.
/// </summary>
[TestMethod]
public void DBAccess_ShouldImplementIDatabaseAccess()
{
var options = DatabaseTestHelper.CreateTestDatabaseOptions();

var result = new DBAccess(options);

result.Should().BeAssignableTo<IDatabaseAccess>();
((IDatabaseAccess)result).ConnectionString.Should().Be(options.GetConnectionString());
}

/// <summary>
/// Verifies that cancellation-token compatibility overloads honor pre-canceled tokens.
/// </summary>
[TestMethod]
public void CompatibilityAsyncOverloads_WithCanceledToken_ShouldThrowOperationCanceledException()
{
var access = (IDatabaseAccess)new DBAccess(DatabaseTestHelper.CreateTestDatabaseOptions());
using var source = new CancellationTokenSource();
source.Cancel();

Action action = () => access.ExecuteQueryAsync("SELECT 1", source.Token).GetAwaiter().GetResult();

action.Should().Throw<OperationCanceledException>();
}
}
}
Loading
Loading