mirror of
https://github.com/bitwarden/browser
synced 2026-02-04 02:33:33 +00:00
Added addUrls function to use instead of saveUrls so appending daily does not clear all urls
This commit is contained in:
@@ -41,6 +41,7 @@ describe("PhishingDataService", () => {
|
||||
mockIndexedDbService.hasUrl.mockResolvedValue(false);
|
||||
mockIndexedDbService.loadAllUrls.mockResolvedValue([]);
|
||||
mockIndexedDbService.saveUrls.mockResolvedValue(undefined);
|
||||
mockIndexedDbService.addUrls.mockResolvedValue(undefined);
|
||||
mockIndexedDbService.saveUrlsFromStream.mockResolvedValue(undefined);
|
||||
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
@@ -139,7 +140,7 @@ describe("PhishingDataService", () => {
|
||||
expect(mockIndexedDbService.saveUrlsFromStream).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update daily dataset via saveUrls", async () => {
|
||||
it("should update daily dataset via addUrls", async () => {
|
||||
// Mock daily update
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
@@ -149,10 +150,7 @@ describe("PhishingDataService", () => {
|
||||
|
||||
await firstValueFrom(service["_updateDailyDataSet"]());
|
||||
|
||||
expect(mockIndexedDbService.saveUrls).toHaveBeenCalledWith([
|
||||
"newphish.com",
|
||||
"anotherbad.net",
|
||||
]);
|
||||
expect(mockIndexedDbService.addUrls).toHaveBeenCalledWith(["newphish.com", "anotherbad.net"]);
|
||||
});
|
||||
|
||||
it("should get updated meta information", async () => {
|
||||
|
||||
@@ -260,7 +260,7 @@ export class PhishingDataService {
|
||||
}
|
||||
|
||||
return from(this.fetchToday(todayUrl)).pipe(
|
||||
switchMap((lines) => from(this.indexedDbService.saveUrls(lines))),
|
||||
switchMap((lines) => from(this.indexedDbService.addUrls(lines))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,86 @@ describe("PhishingIndexedDbService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addUrls", () => {
|
||||
it("appends URLs to IndexedDB without clearing", async () => {
|
||||
// Pre-populate store with existing data
|
||||
mockStore.set("https://existing.com", { url: "https://existing.com" });
|
||||
|
||||
const urls = ["https://phishing.com", "https://malware.net"];
|
||||
const result = await service.addUrls(urls);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockDb.transaction).toHaveBeenCalledWith("phishing-urls", "readwrite");
|
||||
expect(mockObjectStore.clear).not.toHaveBeenCalled();
|
||||
expect(mockObjectStore.put).toHaveBeenCalledTimes(2);
|
||||
// Existing data should still be present
|
||||
expect(mockStore.has("https://existing.com")).toBe(true);
|
||||
expect(mockStore.size).toBe(3);
|
||||
expect(mockDb.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles empty array without clearing", async () => {
|
||||
mockStore.set("https://existing.com", { url: "https://existing.com" });
|
||||
|
||||
const result = await service.addUrls([]);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockObjectStore.clear).not.toHaveBeenCalled();
|
||||
expect(mockStore.has("https://existing.com")).toBe(true);
|
||||
});
|
||||
|
||||
it("trims whitespace from URLs", async () => {
|
||||
const urls = [" https://example.com ", "\nhttps://test.org\n"];
|
||||
|
||||
await service.addUrls(urls);
|
||||
|
||||
expect(mockObjectStore.put).toHaveBeenCalledWith({ url: "https://example.com" });
|
||||
expect(mockObjectStore.put).toHaveBeenCalledWith({ url: "https://test.org" });
|
||||
});
|
||||
|
||||
it("skips empty lines", async () => {
|
||||
const urls = ["https://example.com", "", " ", "https://test.org"];
|
||||
|
||||
await service.addUrls(urls);
|
||||
|
||||
expect(mockObjectStore.put).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("handles duplicate URLs via upsert", async () => {
|
||||
mockStore.set("https://example.com", { url: "https://example.com" });
|
||||
|
||||
const urls = [
|
||||
"https://example.com", // Already exists
|
||||
"https://test.org",
|
||||
];
|
||||
|
||||
const result = await service.addUrls(urls);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockObjectStore.put).toHaveBeenCalledTimes(2);
|
||||
expect(mockStore.size).toBe(2);
|
||||
});
|
||||
|
||||
it("logs error and returns false on failure", async () => {
|
||||
const error = new Error("IndexedDB error");
|
||||
mockOpenRequest.error = error;
|
||||
(global.indexedDB.open as jest.Mock).mockImplementation(() => {
|
||||
setTimeout(() => {
|
||||
mockOpenRequest.onerror?.();
|
||||
}, 0);
|
||||
return mockOpenRequest;
|
||||
});
|
||||
|
||||
const result = await service.addUrls(["https://test.com"]);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
"[PhishingIndexedDbService] Add failed",
|
||||
expect.any(Error),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasUrl", () => {
|
||||
it("returns true for existing URL", async () => {
|
||||
mockStore.set("https://example.com", { url: "https://example.com" });
|
||||
|
||||
@@ -67,6 +67,27 @@ export class PhishingIndexedDbService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an array of phishing URLs to IndexedDB.
|
||||
* Appends to existing data without clearing.
|
||||
*
|
||||
* @param urls - Array of phishing URLs to add
|
||||
* @returns `true` if add succeeded, `false` on error
|
||||
*/
|
||||
async addUrls(urls: string[]): Promise<boolean> {
|
||||
let db: IDBDatabase | null = null;
|
||||
try {
|
||||
db = await this.openDatabase();
|
||||
await this.saveChunked(db, urls);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logService.error("[PhishingIndexedDbService] Add failed", error);
|
||||
return false;
|
||||
} finally {
|
||||
db?.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves URLs in chunks to prevent transaction timeouts and UI freezes.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user