1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-24 08:09:59 +02:00

Passing test for observables

This commit is contained in:
Thomas Rittson 2024-10-24 11:30:08 +10:00
parent 1cdcba203f
commit 69e8f4fa6e
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
2 changed files with 121 additions and 30 deletions

View File

@ -1,4 +1,4 @@
import { mock } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, of } from "rxjs"; import { firstValueFrom, of } from "rxjs";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -6,6 +6,7 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { import {
FakeStateProvider, FakeStateProvider,
@ -16,7 +17,7 @@ import {
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key"; import { OrgKey } from "@bitwarden/common/types/key";
import { CollectionData } from "../models"; import { CollectionData, CollectionView } from "../models";
import { import {
DefaultCollectionvNextService, DefaultCollectionvNextService,
@ -46,31 +47,40 @@ describe("DefaultCollectionService", () => {
}); });
// Arrange cryptoService - orgKeys and mock decryption // Arrange cryptoService - orgKeys and mock decryption
const cryptoService = mockCryptoService(); const [cryptoService, encryptService] = mockCryptoService();
const orgKey1 = makeSymmetricCryptoKey<OrgKey>(64, 1);
const orgKey2 = makeSymmetricCryptoKey<OrgKey>(64, 2);
cryptoService.orgKeys$.mockReturnValue( cryptoService.orgKeys$.mockReturnValue(
of({ of({
[org1]: makeSymmetricCryptoKey<OrgKey>(), [org1]: orgKey1,
[org2]: makeSymmetricCryptoKey<OrgKey>(), [org2]: orgKey2,
}), }),
); );
const collectionService = new DefaultCollectionvNextService( const collectionService = new DefaultCollectionvNextService(
cryptoService, cryptoService,
mock<EncryptService>(), encryptService,
mockI18nService(), mockI18nService(),
fakeStateProvider, fakeStateProvider,
); );
// Assert emitted values
const result = await firstValueFrom(collectionService.decryptedCollections$(of(userId))); const result = await firstValueFrom(collectionService.decryptedCollections$(of(userId)));
expect(result.length).toBe(2); // expect(result.length).toBe(2);
expect(result[0]).toMatchObject({ expect(result).toContainEqual(collectionViewFactory(collection1));
id: collection1.id, expect(result).toContainEqual(collectionViewFactory(collection2));
name: "DECRYPTED_STRING",
}); // Assert that the correct org keys were used for each encrypted string
expect(result[1]).toMatchObject({ const collection1NameData = new EncString(collection1.name).data;
id: collection2.id, const collection2NameData = new EncString(collection2.name).data;
name: "DECRYPTED_STRING", expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(
}); expect.objectContaining({ data: collection1NameData }),
orgKey1,
);
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(
expect.objectContaining({ data: collection2NameData }),
orgKey2,
);
}); });
it("handles null collection state", async () => { it("handles null collection state", async () => {
@ -81,10 +91,10 @@ describe("DefaultCollectionService", () => {
// Arrange state provider // Arrange state provider
const userId = Utils.newGuid() as UserId; const userId = Utils.newGuid() as UserId;
const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId));
await fakeStateProvider.setUserState(ENCRYPTED_COLLECTION_DATA_KEY, null); await fakeStateProvider.setUserState(ENCRYPTED_COLLECTION_DATA_KEY, null, userId);
// Arrange cryptoService - orgKeys and mock decryption // Arrange cryptoService - orgKeys and mock decryption
const cryptoService = mockCryptoService(); const [cryptoService, encryptService] = mockCryptoService();
cryptoService.orgKeys$.mockReturnValue( cryptoService.orgKeys$.mockReturnValue(
of({ of({
[org1]: makeSymmetricCryptoKey<OrgKey>(), [org1]: makeSymmetricCryptoKey<OrgKey>(),
@ -94,7 +104,75 @@ describe("DefaultCollectionService", () => {
const collectionService = new DefaultCollectionvNextService( const collectionService = new DefaultCollectionvNextService(
cryptoService, cryptoService,
mock<EncryptService>(), encryptService,
mockI18nService(),
fakeStateProvider,
);
const encryptedCollections = await firstValueFrom(
collectionService.encryptedCollections$(of(userId)),
);
expect(encryptedCollections.length).toBe(0);
});
});
describe("encryptedCollections$", () => {
it("emits encrypted collections from state", async () => {
// Arrange test collections
const org1 = Utils.newGuid() as OrganizationId;
const org2 = Utils.newGuid() as OrganizationId;
const collection1 = collectionDataFactory(org1);
const collection2 = collectionDataFactory(org2);
// Arrange state provider
const userId = Utils.newGuid() as UserId;
const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId));
await fakeStateProvider.setUserState(
ENCRYPTED_COLLECTION_DATA_KEY,
{
[collection1.id]: collection1,
[collection2.id]: collection2,
},
userId,
);
// Arrange cryptoService - just so we don't get errors
const [cryptoService, encryptService] = mockCryptoService();
cryptoService.orgKeys$.mockReturnValue(of({}));
const collectionService = new DefaultCollectionvNextService(
cryptoService,
encryptService,
mockI18nService(),
fakeStateProvider,
);
const result = await firstValueFrom(collectionService.encryptedCollections$(of(userId)));
expect(result.length).toBe(2);
expect(result[0]).toMatchObject({
id: collection1.id,
name: makeEncString("ENC_NAME_" + collection1.id),
});
expect(result[1]).toMatchObject({
id: collection2.id,
name: makeEncString("ENC_NAME_" + collection2.id),
});
});
it("handles null collection state", async () => {
// Arrange state provider
const userId = Utils.newGuid() as UserId;
const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId));
await fakeStateProvider.setUserState(ENCRYPTED_COLLECTION_DATA_KEY, null);
// Arrange cryptoService - orgKeys and mock decryption
const [cryptoService, encryptService] = mockCryptoService();
cryptoService.orgKeys$.mockReturnValue(of({}));
const collectionService = new DefaultCollectionvNextService(
cryptoService,
encryptService,
mockI18nService(), mockI18nService(),
fakeStateProvider, fakeStateProvider,
); );
@ -103,11 +181,6 @@ describe("DefaultCollectionService", () => {
collectionService.decryptedCollections$(of(userId)), collectionService.decryptedCollections$(of(userId)),
); );
expect(decryptedCollections.length).toBe(0); expect(decryptedCollections.length).toBe(0);
const encryptedCollections = await firstValueFrom(
collectionService.encryptedCollections$(of(userId)),
);
expect(encryptedCollections.length).toBe(0);
}); });
}); });
}); });
@ -118,23 +191,34 @@ const mockI18nService = () => {
return i18nService; return i18nService;
}; };
const mockCryptoService = () => { const mockCryptoService: () => [MockProxy<CryptoService>, MockProxy<EncryptService>] = () => {
const cryptoService = mock<CryptoService>(); const cryptoService = mock<CryptoService>();
const encryptService = mock<EncryptService>(); const encryptService = mock<EncryptService>();
encryptService.decryptToUtf8 encryptService.decryptToUtf8
.calledWith(expect.any(EncString), expect.anything()) .calledWith(expect.any(EncString), expect.any(SymmetricCryptoKey))
.mockResolvedValue("DECRYPTED_STRING"); .mockImplementation((encString, key) =>
Promise.resolve(encString.data.replace("ENC_", "DEC_")),
);
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
return cryptoService; return [cryptoService, encryptService];
}; };
const collectionDataFactory = (orgId: OrganizationId) => { const collectionDataFactory = (orgId: OrganizationId) => {
const collection = new CollectionData({} as any); const collection = new CollectionData({} as any);
collection.id = Utils.newGuid() as CollectionId; collection.id = Utils.newGuid() as CollectionId;
collection.organizationId = orgId; collection.organizationId = orgId;
collection.name = makeEncString("ENC_STRING").encryptedString; collection.name = makeEncString("ENC_NAME_" + collection.id).encryptedString;
return collection; return collection;
}; };
const collectionViewFactory = (data: CollectionData) =>
Object.assign(new CollectionView(), {
id: data.id,
name: "DEC_NAME_" + data.id,
assigned: true,
externalId: null,
organizationId: data.organizationId,
});

View File

@ -46,8 +46,15 @@ export function makeStaticByteArray(length: number, start = 0) {
return arr; return arr;
} }
export function makeSymmetricCryptoKey<T extends SymmetricCryptoKey>(length: 32 | 64 = 64) { /**
return new SymmetricCryptoKey(makeStaticByteArray(length)) as T; * Creates a symmetric crypto key for use in tests. This is deterministic, i.e. it will produce identical keys
* for identical argument values. Provide a unique value to the `seed` parameter to create different keys.
*/
export function makeSymmetricCryptoKey<T extends SymmetricCryptoKey>(
length: 32 | 64 = 64,
seed = 0,
) {
return new SymmetricCryptoKey(makeStaticByteArray(length, seed)) as T;
} }
/** /**