1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-30 16:23:53 +00:00

Add tests for SDK path

This commit is contained in:
Nik Gilmore
2025-12-31 15:39:41 -08:00
parent e5863249f0
commit e5067c3df2

View File

@@ -55,9 +55,9 @@ function encryptText(clearText: string | Uint8Array) {
const ENCRYPTED_BYTES = mock<EncArrayBuffer>();
const cipherData: CipherData = {
id: "id",
organizationId: "4ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b2" as OrganizationId,
folderId: "folderId",
id: "5ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b22" as CipherId,
organizationId: "4ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b21" as OrganizationId,
folderId: "6ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b23",
edit: true,
viewPassword: true,
organizationUseTotp: true,
@@ -210,6 +210,13 @@ describe("Cipher Service", () => {
});
describe("createWithServer()", () => {
let mockSdkClient: any;
let mockCiphersSdk: any;
let mockAdminSdk: any;
let mockVaultSdk: any;
let sdkTestCipher: Cipher;
let sdkTestCipherData: CipherData;
beforeEach(() => {
// Mock encrypt to return encryptionContext for legacy path
jest.spyOn(cipherService, "encrypt").mockResolvedValue(encryptionContext);
@@ -217,11 +224,39 @@ describe("Cipher Service", () => {
jest.spyOn(cipherService, "decrypt").mockImplementation(async (cipher) => {
return new CipherView(cipher);
});
// Create cipher data with valid UUIDs for SDK tests
sdkTestCipherData = cipherData;
sdkTestCipher = new Cipher(sdkTestCipherData);
// Mock the SDK client chain - define mockAdminSdk first before referencing it
mockAdminSdk = {
create: jest.fn(),
};
mockCiphersSdk = {
create: jest.fn(),
admin: jest.fn().mockReturnValue(mockAdminSdk),
};
mockVaultSdk = {
ciphers: jest.fn().mockReturnValue(mockCiphersSdk),
};
const mockSdkValue = {
vault: jest.fn().mockReturnValue(mockVaultSdk),
};
mockSdkClient = {
take: jest.fn().mockReturnValue({
value: mockSdkValue,
[Symbol.dispose]: jest.fn(),
}),
};
// Mock sdkService to return the mock client
sdkService.userClient$.mockReturnValue(of(mockSdkClient));
});
it("should call apiService.postCipherAdmin when orgAdmin param is true and the cipher orgId != null", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
const spy = jest
.spyOn(apiService, "postCipherAdmin")
@@ -232,11 +267,12 @@ describe("Cipher Service", () => {
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(expectedObj);
expect(mockSdkClient.take).not.toHaveBeenCalled();
});
it("should call apiService.postCipher when orgAdmin param is true and the cipher orgId is null", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
encryptionContext.cipher.organizationId = null!;
const spy = jest
@@ -248,11 +284,12 @@ describe("Cipher Service", () => {
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(expectedObj);
expect(mockSdkClient.take).not.toHaveBeenCalled();
});
it("should call apiService.postCipherCreate if collectionsIds != null", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
encryptionContext.cipher.collectionIds = ["123"];
const spy = jest
@@ -264,11 +301,12 @@ describe("Cipher Service", () => {
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(expectedObj);
expect(mockSdkClient.take).not.toHaveBeenCalled();
});
it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
const spy = jest
.spyOn(apiService, "postCipher")
@@ -279,10 +317,68 @@ describe("Cipher Service", () => {
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(expectedObj);
expect(mockSdkClient.take).not.toHaveBeenCalled();
});
it("should use SDK to create cipher when feature flag is enabled", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(true);
const cipherView = new CipherView(sdkTestCipher);
const mockSdkCipherView = cipherView.toSdkCipherView();
// Mock SDK create to return a cipher view
mockCiphersSdk.create.mockResolvedValue(mockSdkCipherView);
const apiSpy = jest.spyOn(apiService, "postCipher");
const result = await cipherService.createWithServer(cipherView, userId);
expect(apiSpy).not.toHaveBeenCalled();
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
expect(mockCiphersSdk.create).toHaveBeenCalledWith(
expect.objectContaining({
name: cipherView.name,
organizationId: expect.anything(),
}),
);
expect(result).toBeInstanceOf(CipherView);
expect(result.name).toBe(cipherView.name);
});
it("should use SDK to create admin cipher when feature flag is enabled and admin flag is passed", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(true);
const cipherView = new CipherView(sdkTestCipher);
const mockSdkCipherView = cipherView.toSdkCipherView();
// Mock SDK admin create to return a cipher view
mockAdminSdk.create.mockResolvedValue(mockSdkCipherView);
const apiSpy = jest.spyOn(apiService, "postCipherAdmin");
const result = await cipherService.createWithServer(cipherView, userId, true);
expect(apiSpy).not.toHaveBeenCalled();
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
expect(mockCiphersSdk.admin).toHaveBeenCalled();
expect(mockAdminSdk.create).toHaveBeenCalledWith(
expect.objectContaining({
name: cipherView.name,
}),
);
expect(result).toBeInstanceOf(CipherView);
expect(result.name).toBe(cipherView.name);
});
});
describe("updateWithServer()", () => {
let mockSdkClient: any;
let mockCiphersSdk: any;
let mockAdminSdk: any;
let mockVaultSdk: any;
beforeEach(() => {
// Mock encrypt to return encryptionContext for legacy path
jest.spyOn(cipherService, "encrypt").mockResolvedValue(encryptionContext);
@@ -294,11 +390,35 @@ describe("Cipher Service", () => {
jest.spyOn(cipherService, "upsert").mockResolvedValue({
[cipherData.id as CipherId]: cipherData,
});
// Mock the SDK client chain for admin operations
mockAdminSdk = {
edit: jest.fn(),
};
mockCiphersSdk = {
edit: jest.fn(),
admin: jest.fn().mockReturnValue(mockAdminSdk),
};
mockVaultSdk = {
ciphers: jest.fn().mockReturnValue(mockCiphersSdk),
};
const mockSdkValue = {
vault: jest.fn().mockReturnValue(mockVaultSdk),
};
mockSdkClient = {
take: jest.fn().mockReturnValue({
value: mockSdkValue,
[Symbol.dispose]: jest.fn(),
}),
};
// Mock sdkService to return the mock client
sdkService.userClient$.mockReturnValue(of(mockSdkClient));
});
it("should call apiService.putCipherAdmin when orgAdmin param is true", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
// Create a fresh cipher with organizationId for this test
@@ -320,7 +440,7 @@ describe("Cipher Service", () => {
it("should call apiService.putCipher if cipher.edit is true", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
encryptionContext.cipher.edit = true;
const spy = jest
@@ -336,7 +456,7 @@ describe("Cipher Service", () => {
it("should call apiService.putPartialCipher when orgAdmin, and edit are false", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.SdkCipherOperations)
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(false);
encryptionContext.cipher.edit = false;
const spy = jest
@@ -349,6 +469,67 @@ describe("Cipher Service", () => {
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(encryptionContext.cipher.id, expectedObj);
});
it("should use SDK to update cipher when feature flag is enabled", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(true);
const testCipher = new Cipher(cipherData);
const cipherView = new CipherView(testCipher);
const mockSdkCipherView = cipherView.toSdkCipherView();
// Mock SDK edit to return a cipher view
mockCiphersSdk.edit.mockResolvedValue(mockSdkCipherView);
const result = await cipherService.updateWithServer(cipherView, userId);
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
expect(mockCiphersSdk.edit).toHaveBeenCalledWith(
expect.objectContaining({
id: expect.anything(),
name: cipherView.name,
}),
);
expect(result).toBeInstanceOf(CipherView);
expect(result.name).toBe(cipherView.name);
});
it("should use SDK admin API when orgAdmin is true", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(true);
// sdkTestCipherData already has a valid organizationId, use it directly
const testCipher = new Cipher(cipherData);
const cipherView = new CipherView(testCipher);
const originalCipherView = new CipherView(testCipher);
const mockSdkCipherView = cipherView.toSdkCipherView();
const apiSpy = jest.spyOn(apiService, "putCipherAdmin");
// Mock SDK admin edit to return a cipher view
mockAdminSdk.edit.mockResolvedValue(mockSdkCipherView);
const result = await cipherService.updateWithServer(
cipherView,
userId,
originalCipherView,
true,
);
expect(apiSpy).not.toHaveBeenCalled();
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
expect(mockCiphersSdk.admin).toHaveBeenCalled();
expect(mockAdminSdk.edit).toHaveBeenCalledWith(
expect.objectContaining({
id: expect.anything(),
name: cipherView.name,
}),
originalCipherView.toSdkCipherView(),
);
expect(result).toBeInstanceOf(CipherView);
});
});
describe("encrypt", () => {