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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ Tests use **MSTest.Sdk** targeting net10.0 only. The test project (`Essentials.T
- `CacheProviderTests.cs` - Tests cache operations including expiration, and that a legitimately cached `null` is honoured rather than treated as a miss: `Get`/`GetAsync` return it instead of throwing, and `GetOrAdd`/`GetOrAddAsync` return it without re-invoking the factory. `TryGet`'s `bool` is the sole authority on presence — a null check on the value is not a proxy for it. It also pins that an expired entry's removal leaves a value set concurrently under the same key in place (a key type that runs the `Set` between `TryGet`'s read and its remove makes that interleaving deterministic), and that a `TimeSpan.MaxValue` or `TimeSpan.MinValue` time-to-live saturates rather than overflowing
- `CommandExecutorTests.cs` - Tests command execution, including the synchronous path, cancellation before and during a run (and that both paths kill a cancelled command rather than leaving it to finish, issue #53), a working directory that does not exist, that the synchronous and asynchronous paths capture the same bytes for a command whose output ends without a terminator, and that `ExecuteAndGetOutput` throws unwrapped. `ICommandExecutor`'s own synchronous defaults are reached through a test double that declares only the asynchronous members, since `NativeCommandExecutor` replaces them
- `EncodingProviderTests.cs` - Tests Base64 and Hex encoding
- `ObfuscationProviderTests.cs` - Tests all obfuscation providers via round-trip (obfuscate → deobfuscate)
- `ObfuscationProviderTests.cs` - Tests all obfuscation providers via round-trip (obfuscate → deobfuscate), and that the Composite runs its stages in order to obfuscate and in reverse to deobfuscate, on the span, stream and async paths, using an order-sensitive `[Base64, Reverse]` pair, since a pipeline run backwards in both directions would still round-trip
- `FileSystemProviderTests.cs` - Tests filesystem operations
- `LoggingProviderTests.cs` - Tests the console logging provider through `AddEssentials()`
- `FileLoggingProviderTests.cs` - Tests the rolling file logger against `InMemoryFileSystemProvider`: line format and escaping, rolling and retention, concurrency, dropped writes, DI
Expand Down
93 changes: 93 additions & 0 deletions Essentials.Tests/ObfuscationProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ktsu.Essentials;
using ktsu.Essentials.ObfuscationProviders.Base64;
using ktsu.Essentials.ObfuscationProviders.Composite;
using ktsu.Essentials.ObfuscationProviders.Hex;
using ktsu.Essentials.ObfuscationProviders.Reverse;
using ktsu.Essentials.ObfuscationProviders.Xor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -256,4 +258,95 @@

CollectionAssert.AreEqual(original, provider.Deobfuscate(provider.Obfuscate(original)));
}

// Base64-encoding then reversing gives different bytes from reversing then encoding, so the pair
// is sensitive to stage order, which a round-trip test alone cannot see: a composite that ran
// both directions backwards would still undo itself.
private static readonly IObfuscationProvider FirstStage = new Base64ObfuscationProvider();
private static readonly IObfuscationProvider SecondStage = new ReverseObfuscationProvider();

private static readonly byte[] OrderSample = Encoding.UTF8.GetBytes("composite stages run in order");

private static IObfuscationProvider OrderedComposite()
=> new CompositeObfuscationProvider([FirstStage, SecondStage]);

private static byte[] InOrder(byte[] data) => SecondStage.Obfuscate(FirstStage.Obfuscate(data));

private static byte[] OutOfOrder(byte[] data) => FirstStage.Obfuscate(SecondStage.Obfuscate(data));

private static byte[] ReverseOrder(byte[] data) => FirstStage.Deobfuscate(SecondStage.Deobfuscate(data));

[TestMethod]
public void Obfuscation_Composite_ChosenStagesAreOrderSensitive()
=> CollectionAssert.AreNotEqual(InOrder(OrderSample), OutOfOrder(OrderSample), "The test pair must give different bytes in each order");

Check warning on line 281 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreNotSequenceEqual' instead of 'CollectionAssert.AreNotEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQq_&open=AaGhfDdmi9dlr5MsDQq_&pullRequest=175

[TestMethod]
public void Obfuscation_Composite_Span_AppliesStagesInOrder()
{
IObfuscationProvider provider = OrderedComposite();
byte[] destination = new byte[provider.GetMaxObfuscatedLength(OrderSample.Length)];

Assert.IsTrue(provider.TryObfuscate(OrderSample, destination, out int bytesWritten));

CollectionAssert.AreEqual(InOrder(OrderSample), destination.AsSpan(0, bytesWritten).ToArray());

Check warning on line 291 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrA&open=AaGhfDdmi9dlr5MsDQrA&pullRequest=175
}

[TestMethod]
public void Obfuscation_Composite_Span_DeobfuscatesStagesInReverseOrder()
{
IObfuscationProvider provider = OrderedComposite();
byte[] obfuscated = InOrder(OrderSample);
byte[] destination = new byte[provider.GetMaxDeobfuscatedLength(obfuscated.Length)];

Assert.IsTrue(provider.TryDeobfuscate(obfuscated, destination, out int bytesWritten));

CollectionAssert.AreEqual(ReverseOrder(obfuscated), destination.AsSpan(0, bytesWritten).ToArray());

Check warning on line 303 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrB&open=AaGhfDdmi9dlr5MsDQrB&pullRequest=175
CollectionAssert.AreEqual(OrderSample, destination.AsSpan(0, bytesWritten).ToArray());

Check warning on line 304 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrC&open=AaGhfDdmi9dlr5MsDQrC&pullRequest=175
}

[TestMethod]
public void Obfuscation_Composite_Stream_AppliesStagesInOrder()
{
IObfuscationProvider provider = OrderedComposite();
using MemoryStream input = new(OrderSample);
using MemoryStream output = new();

Assert.IsTrue(provider.TryObfuscate(input, output));

CollectionAssert.AreEqual(InOrder(OrderSample), output.ToArray());

Check warning on line 316 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrD&open=AaGhfDdmi9dlr5MsDQrD&pullRequest=175
}

[TestMethod]
public void Obfuscation_Composite_Stream_DeobfuscatesStagesInReverseOrder()
{
IObfuscationProvider provider = OrderedComposite();
byte[] obfuscated = InOrder(OrderSample);
using MemoryStream input = new(obfuscated);
using MemoryStream output = new();

Assert.IsTrue(provider.TryDeobfuscate(input, output));

CollectionAssert.AreEqual(ReverseOrder(obfuscated), output.ToArray());

Check warning on line 329 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrE&open=AaGhfDdmi9dlr5MsDQrE&pullRequest=175
}

[TestMethod]
public async Task Obfuscation_Composite_Async_AppliesStagesInOrder()
{
IObfuscationProvider provider = OrderedComposite();

byte[] obfuscated = await provider.ObfuscateAsync(OrderSample, TestContext.CancellationToken).ConfigureAwait(false);

CollectionAssert.AreEqual(InOrder(OrderSample), obfuscated);

Check warning on line 339 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrF&open=AaGhfDdmi9dlr5MsDQrF&pullRequest=175
}

[TestMethod]
public async Task Obfuscation_Composite_Async_DeobfuscatesStagesInReverseOrder()
{
IObfuscationProvider provider = OrderedComposite();
byte[] obfuscated = InOrder(OrderSample);

byte[] restored = await provider.DeobfuscateAsync(obfuscated, TestContext.CancellationToken).ConfigureAwait(false);

CollectionAssert.AreEqual(ReverseOrder(obfuscated), restored);

Check warning on line 350 in Essentials.Tests/ObfuscationProviderTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Essentials&issues=AaGhfDdmi9dlr5MsDQrG&open=AaGhfDdmi9dlr5MsDQrG&pullRequest=175
}
}
Loading