using System.Buffers.Binary;
using System.Security.Cryptography;
using System.Text;
using SwBLL.OfflineRollout;
using Xunit;
namespace SwTests;
///
/// Covers RolloutPackageCrypto (Offline Rollout Package, tracker ยง53) โ real round-trip proof,
/// not mocked: generates a throwaway RSA keypair, encrypts via the real class, then decrypts
/// inline using the exact inverse of the documented container format (the same format
/// FrameworkBLL.Promotion.PromotionPackageCrypto's own DecryptPackageAsync implements โ this test
/// deliberately reimplements decrypt rather than referencing that class, since GB5Framework isn't
/// a dependency of SwTests and the whole point of this format being documented/versioned is that
/// an independent reader can decrypt it correctly from the spec alone).
///
public class RolloutPackageCryptoTests
{
[Fact]
public async Task EncryptPackageAsync_RealRoundTrip_DecryptsToOriginalPlaintext()
{
using var rsa = RSA.Create(2048);
var publicKeyPem = rsa.ExportSubjectPublicKeyInfoPem();
var plaintext = Encoding.UTF8.GetBytes("{\"PackageCode\":\"TEST_PKG\",\"Scripts\":[]}");
var crypto = new RolloutPackageCrypto();
var encrypted = await crypto.EncryptPackageAsync(plaintext, publicKeyPem, default);
var decrypted = DecryptForTest(encrypted, rsa);
Assert.Equal(plaintext, decrypted);
}
[Fact]
public async Task EncryptPackageAsync_EmptyPlaintext_Throws()
{
var crypto = new RolloutPackageCrypto();
await Assert.ThrowsAsync(() => crypto.EncryptPackageAsync(Array.Empty(), "PEM", default));
}
[Fact]
public async Task EncryptPackageAsync_MissingPublicKey_Throws()
{
var crypto = new RolloutPackageCrypto();
await Assert.ThrowsAsync(() => crypto.EncryptPackageAsync(new byte[] { 1 }, "", default));
}
// Inverse of RolloutPackageCrypto.EncryptPackageAsync's documented container format โ
// deliberately independent of the class under test, so this proves the *format* round-trips
// correctly, not just that encrypt/decrypt happen to agree with each other's internal state.
private static byte[] DecryptForTest(byte[] encryptedPackage, RSA rsa)
{
using var input = new MemoryStream(encryptedPackage);
using var reader = new BinaryReader(input);
var formatVersion = reader.ReadByte();
Assert.Equal(1, formatVersion);
var encryptedAesKeyLength = BinaryPrimitives.ReverseEndianness(reader.ReadInt32());
var encryptedAesKey = reader.ReadBytes(encryptedAesKeyLength);
var nonce = reader.ReadBytes(12);
var tag = reader.ReadBytes(16);
var ciphertext = reader.ReadBytes((int)(input.Length - input.Position));
var aesKey = rsa.Decrypt(encryptedAesKey, RSAEncryptionPadding.OaepSHA256);
var plaintext = new byte[ciphertext.Length];
using (var aes = new AesGcm(aesKey, 16))
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return plaintext;
}
}