From 83487f01836a57573d4fac4470a251e0b073aec7 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 24 Apr 2026 12:25:58 +0200 Subject: [PATCH] Add Proton Pass zip import unit tests (#1959) --- .../AliasVault.UnitTests.csproj | 1 + .../TestData/Exports/protonpass.zip | Bin 0 -> 3863 bytes .../Utilities/ImportExportTests.cs | 97 ++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 apps/server/Tests/AliasVault.UnitTests/TestData/Exports/protonpass.zip diff --git a/apps/server/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj b/apps/server/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj index df9d56a53..8e7728493 100644 --- a/apps/server/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj +++ b/apps/server/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj @@ -74,6 +74,7 @@ + diff --git a/apps/server/Tests/AliasVault.UnitTests/TestData/Exports/protonpass.zip b/apps/server/Tests/AliasVault.UnitTests/TestData/Exports/protonpass.zip new file mode 100644 index 0000000000000000000000000000000000000000..8659e675fab0bd7eeb006f936dd3b600a2cf823a GIT binary patch literal 3863 zcmd5<+iv1U7-qXY>{V~7-miSIS0S-6J`gEtF<`LG!GuFF-3kr%00XwivBww^?L{A= zQXiz(z3TJy89GB?lkFzkRhmixA%4yH|K~q^{`}dtUVfR+y?T|){rdK|x4Ao?@2PFa znBefW4U*)<0|Xo|6C7T1X#!)z<4!r(27FT}l!l8s_B-`$TOCJ@zJn*5hSSSuby)>E zZnWL{U~{E)R#RuKgGQ&~2!E@z&VQp8jI$4Y+>89UY zL)B}z2<7X-V%tPbtKhq-R>|lgG5II9NyV@y-SB}qa^ad zmifBa$1!)xA92WK7xV9^MPNXa{^@#W?Rvz|l`7w>otLdOxitOGgrB3PAgQTR!S%i` z8yL6XDh`)Kni##*n}%KA)GmhG@?gTt%OEYfW4+gox1LpVBPU9_lIFAYkGNz3Vz@ix zf9nygn9iM*8$bdM_P%X>n2^`e4Mqf-PlyTYCbd`}1}S0~QIu%`%0S_0d*Ft|Ku6H` zSd)G};>b2)9zLFj#)rvveVMoXHmEXAyIOAa*}1) zaR#Re^*nZQaKf?pm_&mZQ7>wMlq~3VZ0)2+VjRw=c=JO7-58QHyLnOUX`ERA@%Ab230{R7Dc(h^WqT~ zU=PijJF8S>#S&38w4Po=me1_ zm*Z5Q7o~Mz4f-BBuU*=e%CcF{wq|eHnvL7r1{howN@1;2$(QBz7S+*6miVAzj-2WI z`Ji;yZaSxXAqvz2lmPrBL=OROA^PwY zNp@CJ>KWriyUOe0z-ZJwb+B4V+e@v9+UH~w-w^jRBBjDZ1BZo~2;|cA$vBA({6JCc z)IB$Sz(92QlPtkvFs3LxEmNrK{m^0SkAV*8Z>w2n9k-I@+^$A--WH4Vaw$^c##kIq z@?9+$@MV!m7+~K_8>@I+Uk*XI#?>k5A~>${1T`!vwhTXCZ|JTN)vlfgQ?LnSl<0A~ z8|FjkDX4;?%Dnt!==qO;c~ZOxJbgyoLp6dx*B^tI_Oxy4k_90c_EHOG$l8>eR;OO; zXbY!Y>t++wX^-Z0rN3So!|JRr+Ld9ohR$`ZA9O&vR7BS^anL`H+-eBr@-z&S^0Rod zRMLdtV0NQt-alK~R0X-a9Xi)LLTCm=#hcE&Cxgv_aBfe#D~YJmfg2D+{(Wlb-?)hJ z5EC!^Iv#%1eM-70|CBC@!WTqdrjq7EfZeV?24HDbIu~&qo0_K;3}Z647gt2=WuYQD zP`#8FlU{$Bm$HCw&8i_XZN2QQja4h_i}~7mFk9=^)ui6WD|2nNf?9JiY&yyF5ZE^W z+i8~f@VZ?)NRbav1{9#EQi00NhvoJ@;KBd&`bp + /// Test case for importing credentials from a Proton Pass .zip export. + /// The fixture contains a single "Personal" vault (promoted to root) with 6 items: + /// 4 logins (one with a TOTP URI, one with URLs, one with no password), 1 alias, 1 note. + /// + /// Async task. + [Test] + public async Task ImportCredentialsFromProtonPassZip() + { + // Arrange + var zipBytes = await ResourceReaderUtility.ReadEmbeddedResourceBytesAsync("AliasVault.UnitTests.TestData.Exports.protonpass.zip"); + + // Act + var importer = new ProtonPassZipImporter(); + var importedCredentials = await importer.ImportFromArchiveAsync(zipBytes); + + // Assert + Assert.That(importedCredentials, Has.Count.EqualTo(6)); + + // Login with TOTP URI. + var loginWithTotp = importedCredentials.First(c => c.ServiceName == "Test proton 1"); + Assert.Multiple(() => + { + Assert.That(loginWithTotp.ItemType, Is.EqualTo(ImportedItemType.Login)); + Assert.That(loginWithTotp.Username, Is.EqualTo("user1")); + Assert.That(loginWithTotp.Password, Is.EqualTo("pass1")); + Assert.That(loginWithTotp.ServiceUrls, Has.Count.EqualTo(1)); + Assert.That(loginWithTotp.ServiceUrls![0], Is.EqualTo("https://www.website.com/")); + Assert.That(loginWithTotp.TwoFactorSecret, Does.StartWith("otpauth://totp/")); + Assert.That(loginWithTotp.FolderPath, Is.Null); // "Personal" vault promoted to root + Assert.That(loginWithTotp.Email, Is.Null); + }); + + var expectedCreatedAt = DateTimeOffset.FromUnixTimeSeconds(1744362003).UtcDateTime; + Assert.That(loginWithTotp.CreatedAt, Is.EqualTo(expectedCreatedAt)); + + // Alias item — email comes from the envelope's aliasEmail field. + var aliasItem = importedCredentials.First(c => c.ServiceName == "Test alias"); + Assert.Multiple(() => + { + Assert.That(aliasItem.ItemType, Is.EqualTo(ImportedItemType.Login)); + Assert.That(aliasItem.Email, Is.EqualTo("testalias.gating981@passinbox.com")); + Assert.That(aliasItem.Username, Is.EqualTo("testalias.gating981@passinbox.com")); + Assert.That(aliasItem.Password, Is.Null); + Assert.That(aliasItem.FolderPath, Is.Null); + }); + + // Login without URLs. + var loginNoUrls = importedCredentials.First(c => c.ServiceName == "Test proton2"); + Assert.Multiple(() => + { + Assert.That(loginNoUrls.Username, Is.EqualTo("testuser2")); + Assert.That(loginNoUrls.Password, Is.EqualTo("testpassword2")); + Assert.That(loginNoUrls.ServiceUrls, Is.Null); + Assert.That(loginNoUrls.TwoFactorSecret, Is.Null); + }); + + // Login without a password. + var loginNoPass = importedCredentials.First(c => c.ServiceName == "testwithoutpass"); + Assert.Multiple(() => + { + Assert.That(loginNoPass.Username, Is.EqualTo("testuser")); + Assert.That(loginNoPass.Password, Is.Null); + }); + + // Login with URLs and a note. + var loginWithNote = importedCredentials.First(c => c.ServiceName == "Customfields"); + Assert.Multiple(() => + { + Assert.That(loginWithNote.Username, Is.EqualTo("usernamecustom")); + Assert.That(loginWithNote.Password, Is.EqualTo("passwordecustom")); + Assert.That(loginWithNote.Notes, Is.EqualTo("Notecustom")); + Assert.That(loginWithNote.ServiceUrls, Has.Count.EqualTo(1)); + Assert.That(loginWithNote.ServiceUrls![0], Is.EqualTo("http://example.com/")); + }); + + // Secure note — note text lives in metadata.note. + var noteItem = importedCredentials.First(c => c.ServiceName == "Customnote"); + Assert.Multiple(() => + { + Assert.That(noteItem.ItemType, Is.EqualTo(ImportedItemType.Note)); + Assert.That(noteItem.Notes, Is.EqualTo("Customnotecontent")); + }); + + // Conversion to Item extracts the TOTP secret from the URI. + var convertedItems = BaseImporter.ConvertToItem(importedCredentials); + Assert.That(convertedItems, Has.Count.EqualTo(6)); + + var convertedLogin = convertedItems.First(i => i.Name == "Test proton 1"); + Assert.Multiple(() => + { + Assert.That(convertedLogin.ItemType, Is.EqualTo(ItemType.Login)); + Assert.That(convertedLogin.TotpCodes, Has.Count.EqualTo(1)); + Assert.That(convertedLogin.TotpCodes.First().SecretKey, Is.EqualTo("PLW4SB3PQ7MKVXY2MXF4NEXS6Y")); + }); + } }