Skip to content

Commit fa57893

Browse files
Nirjan Chapagainnchapagain001
authored andcommitted
Adding ut
1 parent 340cfc6 commit fa57893

2 files changed

Lines changed: 288 additions & 0 deletions

File tree

src/VirtualClient/VirtualClient.Core.UnitTests/KeyVaultManagerTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,25 @@ public void SetupDefaultBehaviors()
4141
.Setup(c => c.GetSecretAsync("mysecret", null, It.IsAny<CancellationToken>()))
4242
.ReturnsAsync(Response.FromValue(secret, Mock.Of<Response>()));
4343

44+
this.secretClientMock
45+
.Setup(c => c.GetSecretAsync(It.Is<string>(x => x == "mysecret"), It.IsAny<string>(), It.IsAny<CancellationToken>()))
46+
.ReturnsAsync(Response.FromValue(secret, Mock.Of<Response>()));
47+
48+
var pfxCertificate = this.GenerateTestCertificateWithPrivateKey();
49+
var pfxBytes = pfxCertificate.Export(X509ContentType.Pfx, "");
50+
var pfxBase64 = Convert.ToBase64String(pfxBytes);
51+
var certSecret = SecretModelFactory.KeyVaultSecret(
52+
properties: SecretModelFactory.SecretProperties(
53+
name: "mycert",
54+
version: "v3",
55+
vaultUri: new Uri("https://myvault.vault.azure.net/"),
56+
id: new Uri("https://myvault.vault.azure.net/secrets/mycert/v3")),
57+
pfxBase64);
58+
59+
this.secretClientMock
60+
.Setup(c => c.GetSecretAsync(It.Is<string>(s => s == "mycert"), It.IsAny<string>(), It.IsAny<CancellationToken>()))
61+
.ReturnsAsync(Response.FromValue(certSecret, Mock.Of<Response>()));
62+
4463
// Mock the key
4564
this.keyClientMock = new Mock<KeyClient>(MockBehavior.Strict, new Uri("https://myvault.vault.azure.net/"), new MockTokenCredential());
4665
var key = KeyModelFactory.KeyVaultKey(properties: KeyModelFactory.KeyProperties(
@@ -124,10 +143,14 @@ public async Task KeyVaultManagerReturnsExpectedCertificate(bool retrieveWithPri
124143
if (retrieveWithPrivateKey)
125144
{
126145
Assert.IsTrue(result.HasPrivateKey);
146+
Assert.IsNotNull(result.Export(X509ContentType.Pfx, string.Empty)); // Verifies cert is exportable with private key
147+
this.secretClientMock.Verify(c => c.GetSecretAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
127148
}
128149
else
129150
{
130151
Assert.IsFalse(result.HasPrivateKey);
152+
Assert.IsNotNull(result.Export(X509ContentType.Cert, string.Empty));
153+
this.certificateClientMock.Verify(c => c.GetCertificateAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
131154
}
132155
}
133156

src/VirtualClient/VirtualClient.Dependencies.UnitTests/CertificateInstallationTests.cs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,271 @@ public void GetKeyVaultManager_ThrowsWhenNoKeyVaultManagerOrAccessTokenProvided(
518518
}
519519
}
520520

521+
[Test]
522+
[TestCase(true, "testCert.pfx")]
523+
[TestCase(false, "testCert.cer")]
524+
public async Task ExecuteAsync_SavesCertificateToDownloadDirectory(bool withPrivateKey, string expectedFileName)
525+
{
526+
this.mockFixture.Setup(PlatformID.Win32NT);
527+
string downloadDir = "/tmp/certificates";
528+
string expectedPath = this.mockFixture.Combine(downloadDir, expectedFileName);
529+
530+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
531+
{
532+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
533+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
534+
{ nameof(CertificateInstallation.CertificateDownloadDir), downloadDir },
535+
{ nameof(CertificateInstallation.WithPrivateKey), withPrivateKey }
536+
};
537+
538+
this.mockFixture.File.Setup(f => f.Exists(expectedPath)).Returns(false);
539+
this.mockFixture.File.Setup(f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
540+
.Returns(Task.CompletedTask);
541+
542+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
543+
{
544+
this.mockFixture.KeyVaultManager
545+
.Setup(m => m.GetCertificateAsync(
546+
It.IsAny<string>(),
547+
It.IsAny<CancellationToken>(),
548+
It.IsAny<string>(),
549+
It.IsAny<bool>(),
550+
It.IsAny<IAsyncPolicy>()))
551+
.ReturnsAsync(this.testCertificate);
552+
553+
component.OnInstallCertificateOnWindows = (cert, token) => Task.CompletedTask;
554+
555+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
556+
}
557+
558+
// Verify the file was written with the correct path
559+
this.mockFixture.File.Verify(
560+
f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()),
561+
Times.Once);
562+
}
563+
564+
[Test]
565+
public async Task ExecuteAsync_SavesPrivateCertificateAsPfxFormat()
566+
{
567+
this.mockFixture.Setup(PlatformID.Win32NT);
568+
this.SetupPrivateCertificate();
569+
570+
string downloadDir = "/tmp/certificates";
571+
string expectedPath = this.mockFixture.Combine(downloadDir, "testCert.pfx");
572+
byte[] capturedBytes = null;
573+
574+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
575+
{
576+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
577+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
578+
{ nameof(CertificateInstallation.CertificateDownloadDir), downloadDir },
579+
{ nameof(CertificateInstallation.WithPrivateKey), true }
580+
};
581+
582+
this.mockFixture.File.Setup(f => f.Exists(expectedPath)).Returns(false);
583+
this.mockFixture.File.Setup(f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
584+
.Callback<string, byte[], CancellationToken>((path, bytes, token) => capturedBytes = bytes)
585+
.Returns(Task.CompletedTask);
586+
587+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
588+
{
589+
this.mockFixture.KeyVaultManager
590+
.Setup(m => m.GetCertificateAsync(
591+
It.IsAny<string>(),
592+
It.IsAny<CancellationToken>(),
593+
It.IsAny<string>(),
594+
It.IsAny<bool>(),
595+
It.IsAny<IAsyncPolicy>()))
596+
.ReturnsAsync(this.testCertificate);
597+
598+
component.OnInstallCertificateOnWindows = (cert, token) => Task.CompletedTask;
599+
600+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
601+
}
602+
603+
// Verify the bytes captured are in PFX format
604+
Assert.IsNotNull(capturedBytes);
605+
byte[] expectedBytes = this.testCertificate.Export(X509ContentType.Pfx, string.Empty);
606+
Assert.AreEqual(expectedBytes.Length, capturedBytes.Length);
607+
}
608+
609+
[Test]
610+
public async Task ExecuteAsync_SavesPublicCertificateAsCerFormat()
611+
{
612+
this.mockFixture.Setup(PlatformID.Win32NT);
613+
string downloadDir = "/tmp/certificates";
614+
string expectedPath = this.mockFixture.Combine(downloadDir, "testCert.cer");
615+
byte[] capturedBytes = null;
616+
617+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
618+
{
619+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
620+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
621+
{ nameof(CertificateInstallation.CertificateDownloadDir), downloadDir },
622+
{ nameof(CertificateInstallation.WithPrivateKey), false }
623+
};
624+
625+
this.mockFixture.File.Setup(f => f.Exists(expectedPath)).Returns(false);
626+
this.mockFixture.File.Setup(f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
627+
.Callback<string, byte[], CancellationToken>((path, bytes, token) => capturedBytes = bytes)
628+
.Returns(Task.CompletedTask);
629+
630+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
631+
{
632+
this.mockFixture.KeyVaultManager
633+
.Setup(m => m.GetCertificateAsync(
634+
It.IsAny<string>(),
635+
It.IsAny<CancellationToken>(),
636+
It.IsAny<string>(),
637+
It.IsAny<bool>(),
638+
It.IsAny<IAsyncPolicy>()))
639+
.ReturnsAsync(this.testCertificate);
640+
641+
component.OnInstallCertificateOnWindows = (cert, token) => Task.CompletedTask;
642+
643+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
644+
}
645+
646+
// Verify the bytes captured are in Cert format
647+
Assert.IsNotNull(capturedBytes);
648+
byte[] expectedBytes = this.testCertificate.Export(X509ContentType.Cert, string.Empty);
649+
CollectionAssert.AreEqual(expectedBytes, capturedBytes);
650+
}
651+
652+
[Test]
653+
public async Task ExecuteAsync_DeletesExistingCertificateFileBeforeWriting()
654+
{
655+
this.mockFixture.Setup(PlatformID.Win32NT);
656+
string downloadDir = "/tmp/certificates";
657+
string expectedPath = this.mockFixture.Combine(downloadDir, "testCert.pfx");
658+
659+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
660+
{
661+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
662+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
663+
{ nameof(CertificateInstallation.CertificateDownloadDir), downloadDir },
664+
{ nameof(CertificateInstallation.WithPrivateKey), true }
665+
};
666+
667+
// Setup file exists to return true
668+
this.mockFixture.File.Setup(f => f.Exists(expectedPath)).Returns(true);
669+
this.mockFixture.File.Setup(f => f.Delete(expectedPath));
670+
this.mockFixture.File.Setup(f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
671+
.Returns(Task.CompletedTask);
672+
673+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
674+
{
675+
this.mockFixture.KeyVaultManager
676+
.Setup(m => m.GetCertificateAsync(
677+
It.IsAny<string>(),
678+
It.IsAny<CancellationToken>(),
679+
It.IsAny<string>(),
680+
It.IsAny<bool>(),
681+
It.IsAny<IAsyncPolicy>()))
682+
.ReturnsAsync(this.testCertificate);
683+
684+
component.OnInstallCertificateOnWindows = (cert, token) => Task.CompletedTask;
685+
686+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
687+
}
688+
689+
// Verify the existing file was deleted before writing
690+
this.mockFixture.File.Verify(f => f.Delete(expectedPath), Times.Once);
691+
this.mockFixture.File.Verify(
692+
f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()),
693+
Times.Once);
694+
}
695+
696+
[Test]
697+
public async Task ExecuteAsync_DoesNotSaveCertificateWhenDownloadDirNotProvided()
698+
{
699+
this.mockFixture.Setup(PlatformID.Win32NT);
700+
701+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
702+
{
703+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
704+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
705+
{ nameof(CertificateInstallation.WithPrivateKey), true }
706+
// CertificateDownloadDir is not provided
707+
};
708+
709+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
710+
{
711+
this.mockFixture.KeyVaultManager
712+
.Setup(m => m.GetCertificateAsync(
713+
It.IsAny<string>(),
714+
It.IsAny<CancellationToken>(),
715+
It.IsAny<string>(),
716+
It.IsAny<bool>(),
717+
It.IsAny<IAsyncPolicy>()))
718+
.ReturnsAsync(this.testCertificate);
719+
720+
component.OnInstallCertificateOnWindows = (cert, token) => Task.CompletedTask;
721+
722+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
723+
}
724+
725+
// Verify no file operations were performed
726+
this.mockFixture.File.Verify(f => f.Exists(It.IsAny<string>()), Times.Never);
727+
this.mockFixture.File.Verify(f => f.Delete(It.IsAny<string>()), Times.Never);
728+
this.mockFixture.File.Verify(
729+
f => f.WriteAllBytesAsync(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<CancellationToken>()),
730+
Times.Never);
731+
}
732+
733+
[Test]
734+
public async Task ExecuteAsync_SavesCertificateOnUnixPlatform()
735+
{
736+
this.mockFixture.Setup(PlatformID.Unix);
737+
string downloadDir = "/tmp/certificates";
738+
string expectedPath = this.mockFixture.Combine(downloadDir, "testCert.pfx");
739+
740+
this.mockFixture.Parameters = new Dictionary<string, IConvertible>()
741+
{
742+
{ nameof(CertificateInstallation.CertificateName), "testCert" },
743+
{ nameof(CertificateInstallation.KeyVaultUri), "https://testvault.vault.azure.net/" },
744+
{ nameof(CertificateInstallation.CertificateDownloadDir), downloadDir },
745+
{ nameof(CertificateInstallation.WithPrivateKey), true }
746+
};
747+
748+
this.mockFixture.File.Setup(f => f.Exists(expectedPath)).Returns(false);
749+
this.mockFixture.File.Setup(f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()))
750+
.Returns(Task.CompletedTask);
751+
752+
using (TestCertificateInstallation component = new TestCertificateInstallation(this.mockFixture.Dependencies, this.mockFixture.Parameters))
753+
{
754+
this.mockFixture.KeyVaultManager
755+
.Setup(m => m.GetCertificateAsync(
756+
It.IsAny<string>(),
757+
It.IsAny<CancellationToken>(),
758+
It.IsAny<string>(),
759+
It.IsAny<bool>(),
760+
It.IsAny<IAsyncPolicy>()))
761+
.ReturnsAsync(this.testCertificate);
762+
763+
component.OnInstallCertificateOnUnix = (cert, token) => Task.CompletedTask;
764+
765+
await component.ExecuteAsync(EventContext.None, CancellationToken.None);
766+
}
767+
768+
// Verify certificate was saved to download directory even on Unix
769+
this.mockFixture.File.Verify(
770+
f => f.WriteAllBytesAsync(expectedPath, It.IsAny<byte[]>(), It.IsAny<CancellationToken>()),
771+
Times.Once);
772+
}
773+
774+
private void SetupPrivateCertificate()
775+
{
776+
var distinguishedName = new X500DistinguishedName("CN=TestCert");
777+
778+
using var rsa = RSA.Create(2048);
779+
var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
780+
781+
this.testCertificate = request.CreateSelfSigned(
782+
DateTimeOffset.UtcNow.AddDays(-1),
783+
DateTimeOffset.UtcNow.AddYears(1));
784+
}
785+
521786
private class TestCertificateInstallation : CertificateInstallation
522787
{
523788
public TestCertificateInstallation(IServiceCollection dependencies, IDictionary<string, IConvertible> parameters)

0 commit comments

Comments
 (0)