1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-03-13 13:49:37 +01:00

[PM-16690] Bitwarden CSV Import - collections not created (#13636)

This commit is contained in:
Vijay Oommen 2025-03-07 16:58:43 -06:00 committed by GitHub
parent 264ceaa82a
commit f1b69ad65d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 0 deletions

View File

@ -0,0 +1,94 @@
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { BitwardenCsvImporter } from "./bitwarden-csv-importer";
describe("BitwardenCsvImporter", () => {
let importer: BitwardenCsvImporter;
beforeEach(() => {
importer = new BitwardenCsvImporter();
importer.organizationId = "orgId" as OrganizationId;
});
it("should return an empty result if data is null", async () => {
const result = await importer.parse("");
expect(result.success).toBe(false);
expect(result.ciphers.length).toBe(0);
});
it("should parse CSV data correctly", async () => {
const data =
`collections,type,name,notes,fields,reprompt,login_uri,login_username,login_password,login_totp` +
`\ncollection1/collection2,login,testlogin,testnotes,,0,https://example.com,testusername,testpassword,`;
const result = await importer.parse(data);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.name).toBe("testlogin");
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.notes).toBe("testnotes");
expect(cipher.reprompt).toBe(CipherRepromptType.None);
expect(cipher.login).toBeDefined();
expect(cipher.login.username).toBe("testusername");
expect(cipher.login.password).toBe("testpassword");
expect(cipher.login.uris[0].uri).toBe("https://example.com");
expect(result.collections.length).toBe(2);
expect(result.collections[0].name).toBe("collection1/collection2");
expect(result.collections[1].name).toBe("collection1");
});
it("should handle secure notes correctly", async () => {
const data = `name,type,notes` + `\nTest Note,note,Some secure notes`;
const result = await importer.parse(data);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.name).toBe("Test Note");
expect(cipher.type).toBe(CipherType.SecureNote);
expect(cipher.notes).toBe("Some secure notes");
expect(cipher.secureNote).toBeDefined();
expect(cipher.secureNote.type).toBe(SecureNoteType.Generic);
});
it("should handle missing fields gracefully", async () => {
const data =
`name,login_username,login_password,login_uri` +
`\nTest Login,username,password,http://example.com`;
const result = await importer.parse(data);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.name).toBe("Test Login");
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.login.username).toBe("username");
expect(cipher.login.password).toBe("password");
expect(cipher.login.uris[0].uri).toBe("http://example.com");
});
it("should handle collections correctly", async () => {
const data = `name,collections` + `\nTest Login,collection1/collection2`;
const result = await importer.parse(data);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
expect(result.collections.length).toBe(2);
expect(result.collections[0].name).toBe("collection1/collection2");
expect(result.collections[1].name).toBe("collection1");
});
});

View File

@ -43,6 +43,17 @@ export class BitwardenCsvImporter extends BaseImporter implements Importer {
} }
result.collectionRelationships.push([result.ciphers.length, collectionIndex]); result.collectionRelationships.push([result.ciphers.length, collectionIndex]);
// if the collection name is a/b/c/d, we need to create a/b/c and a/b and a
const parts = col.split("/");
for (let i = parts.length - 1; i > 0; i--) {
const parentCollectionName = parts.slice(0, i).join("/") as string;
if (result.collections.find((c) => c.name === parentCollectionName) == null) {
const parentCollection = new CollectionView();
parentCollection.name = parentCollectionName;
result.collections.push(parentCollection);
}
}
}); });
} else if (!this.organization) { } else if (!this.organization) {
this.processFolder(result, value.folder); this.processFolder(result, value.folder);