Skip to content

Commit

Permalink
[PM-14419] Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shane-melton committed Feb 6, 2025
1 parent 7012d4d commit 56a1e02
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
157 changes: 157 additions & 0 deletions libs/vault/src/services/default-change-login-password.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Jest needs to run in custom environment to mock Request/Response objects
* @jest-environment ./src/tests/fix-fetch-jsdom-environment.ts
*/

import { mock } from "jest-mock-extended";

import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";

import { DefaultChangeLoginPasswordService } from "./default-change-login-password.service";

describe("DefaultChangeLoginPasswordService", () => {
let service: DefaultChangeLoginPasswordService;

let mockShouldNotExistResponse: Response;
let mockWellKnownResponse: Response;

const mockApiService = mock<ApiService>();

beforeEach(() => {
mockApiService.nativeFetch.mockClear();

// Default responses to success state
mockShouldNotExistResponse = new Response("Not Found", { status: 404 });
mockWellKnownResponse = new Response("OK", { status: 200 });

mockApiService.nativeFetch.mockImplementation((request) => {
if (
request.url.endsWith("resource-that-should-not-exist-whose-status-code-should-not-be-200")
) {
return Promise.resolve(mockShouldNotExistResponse);
}

if (request.url.endsWith(".well-known/change-password")) {
return Promise.resolve(mockWellKnownResponse);
}

throw new Error("Unexpected request");
});
service = new DefaultChangeLoginPasswordService(mockApiService);
});

it("should return null for non-login ciphers", async () => {
const cipher = {
type: CipherType.Card,
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBeNull();
});

it("should return null for logins with no URIs", async () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), { uris: [] as LoginUriView[] }),
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBeNull();
});

it("should return null for logins with no valid HTTP/HTTPS URIs", async () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "ftp://example.com" }],
}),
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBeNull();
});

it("should check the origin for a reliable status code", async () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
}),
} as CipherView;

await service.getChangePasswordUrl(cipher);

expect(mockApiService.nativeFetch).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://example.com/.well-known/resource-that-should-not-exist-whose-status-code-should-not-be-200",
}),
);
});

it("should attempt to fetch the well-known change password URL", async () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
}),
} as CipherView;

await service.getChangePasswordUrl(cipher);

expect(mockApiService.nativeFetch).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://example.com/.well-known/change-password",
}),
);
});

it("should return the well-known change password URL when successful at verifying the response", async () => {
const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
}),
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBe("https://example.com/.well-known/change-password");
});

it("should return the original URI when unable to verify the response", async () => {
mockShouldNotExistResponse = new Response("Ok", { status: 200 });

const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
}),
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBe("https://example.com");
});

it("should return the original URI when the well-known URL is not found", async () => {
mockWellKnownResponse = new Response("Not Found", { status: 404 });

const cipher = {
type: CipherType.Login,
login: Object.assign(new LoginView(), {
uris: [{ uri: "https://example.com" }],
}),
} as CipherView;

const url = await service.getChangePasswordUrl(cipher);

expect(url).toBe("https://example.com");
});
});
15 changes: 15 additions & 0 deletions libs/vault/src/tests/fix-fetch-jsdom-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import JSDOMEnvironment from "jest-environment-jsdom";

/**
* Helper environment to allow fetch, Headers, Request, and Response to be used in tests that utilize the JSDOM environment.
* See https://github.com/jsdom/jsdom/issues/1724#issuecomment-1446858041
*/
export default class FixFetchJsdomEnvironment extends JSDOMEnvironment {
constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
super(...args);
this.global.fetch = fetch;
this.global.Headers = Headers;
this.global.Request = Request;
this.global.Response = Response;
}
}

0 comments on commit 56a1e02

Please sign in to comment.