From dd5af8b2ea82f417230b72cdb266d7527c5f0150 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 20 Jun 2024 16:46:10 +0200 Subject: [PATCH] add tests --- apps/extension/src/approve-origin.test.ts | 190 ++++++++++++++++++++-- apps/extension/src/approve-origin.ts | 2 +- 2 files changed, 181 insertions(+), 11 deletions(-) diff --git a/apps/extension/src/approve-origin.test.ts b/apps/extension/src/approve-origin.test.ts index e3bbebb4..2d749f33 100644 --- a/apps/extension/src/approve-origin.test.ts +++ b/apps/extension/src/approve-origin.test.ts @@ -1,14 +1,18 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { approveOrigin } from './approve-origin'; -import { localExtStorage } from './storage/local'; +import { UserChoice } from '@penumbra-zone/types/user-choice'; +import { OriginRecord } from './storage/types'; +import { PopupType } from './message/popup'; -// Mocks -vi.mock('./storage/local', () => ({ +const mockLocalStorage = vi.hoisted(() => ({ get: vi.fn(), set: vi.fn(), })); +vi.mock('./storage/local', () => ({ localExtStorage: mockLocalStorage })); + +const mockPopup = vi.hoisted(() => vi.fn()); vi.mock('./popup', () => ({ - popup: vi.fn(), + popup: mockPopup, })); const mockTab = { @@ -23,7 +27,9 @@ const mockTab = { discarded: false, autoDiscardable: true, groupId: -1, -}; + favIconUrl: 'https://image.com/favicon.ico', + title: 'Penumbra | xyz', +} satisfies chrome.tabs.Tab; describe('originHandlers', () => { beforeEach(() => { @@ -36,10 +42,84 @@ describe('originHandlers', () => { await expect(approveOrigin(messageSender)).rejects.toThrow('Unsupported sender'); }); + it('throws an error if the tab ID is missing', async () => { + const messageSender = { origin: 'https://example.com' }; + await expect(approveOrigin(messageSender)).rejects.toThrow('Unsupported sender'); + }); + + it('throws an error if a frame ID is present', async () => { + const messageSender = { origin: 'https://example.com', tab: mockTab, frameId: 123 }; + await expect(approveOrigin(messageSender)).rejects.toThrow('Unsupported sender'); + }); + + it('prompts user for approval if the origin is not known', async () => { + mockLocalStorage.get.mockReturnValue(Promise.resolve([])); + const messageSender = { origin: 'https://newsite.com', tab: mockTab }; + mockPopup.mockResolvedValue({ + choice: UserChoice.Approved, + date: 123, + origin: 'https://newsite.com', + } satisfies OriginRecord); + + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Approved); + }); + + it('returns the stored choice if the origin is already known and approved', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Approved }]), + ); + + const messageSender = { origin: 'https://example.com', tab: mockTab }; + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Approved); + }); + + it('throws an error if multiple records exist for the same origin', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([ + { origin: 'https://example.com', choice: UserChoice.Approved }, + { origin: 'https://example.com', choice: UserChoice.Denied }, + ]), + ); + + const messageSender = { origin: 'https://example.com', tab: mockTab }; + await expect(approveOrigin(messageSender)).rejects.toThrow( + 'There are multiple records for origin: https://example.com', + ); + }); + + it('returns Denied if the user denies the request', async () => { + mockLocalStorage.get.mockReturnValue(Promise.resolve([])); + const messageSender = { origin: 'https://newsite.com', tab: mockTab }; + mockPopup.mockResolvedValue({ + choice: UserChoice.Denied, + date: 123, + origin: 'https://newsite.com', + } satisfies OriginRecord); + + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Denied); + }); + + it('updates an existing record if the user changes their choice from denied to approve', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Denied }]), + ); + const messageSender = { origin: 'https://example.com', tab: mockTab }; + mockPopup.mockResolvedValue({ + choice: UserChoice.Approved, + date: 123, + origin: 'https://example.com', + } satisfies OriginRecord); + + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Approved); + }); + it('returns the previously approved choice if one exists', async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - localExtStorage.mockReturnValue( - Promise.resolve([{ origin: 'https://example.com', choice: 'Approved' }]), + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Approved }]), ); const messageSender = { @@ -47,9 +127,99 @@ describe('originHandlers', () => { tab: mockTab, }; const choice = await approveOrigin(messageSender); - expect(choice).toBe('Approved'); + expect(choice).toBe(UserChoice.Approved); + }); + + it('correctly updates the persisted known sites after user interaction', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Denied }]), + ); + const messageSender = { origin: 'https://example.com', tab: mockTab }; + const newOriginRecord = { + choice: UserChoice.Approved, + date: 123, + origin: 'https://example.com', + } satisfies OriginRecord; + mockPopup.mockResolvedValue(newOriginRecord); + + await approveOrigin(messageSender); + + expect(mockLocalStorage.set).toHaveBeenCalledWith('knownSites', [newOriginRecord]); }); - // More tests based on different scenarios... + it('returns the stored choice if the origin is already known and ignored', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Ignored }]), + ); + + const messageSender = { origin: 'https://example.com', tab: mockTab }; + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Ignored); + }); + + it('returns UserChoice.Denied if the user closes the popup without making a choice', async () => { + mockLocalStorage.get.mockReturnValue(Promise.resolve([])); + const messageSender = { origin: 'https://newsite.com', tab: mockTab }; + mockPopup.mockResolvedValue(undefined); + + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Denied); + }); + + it('correctly handles trailing slashes in the origin', async () => { + mockLocalStorage.get.mockReturnValue( + Promise.resolve([{ origin: 'https://example.com', choice: UserChoice.Approved }]), + ); + + const messageSender = { origin: 'https://example.com/', tab: mockTab }; + const choice = await approveOrigin(messageSender); + expect(choice).toBe(UserChoice.Approved); + }); + + it('shows the popup with the correct parameters', async () => { + mockLocalStorage.get.mockReturnValue(Promise.resolve([])); + const messageSender = { origin: 'https://newsite.com', tab: mockTab }; + mockPopup.mockResolvedValue({ + choice: UserChoice.Approved, + date: 123, + origin: 'https://newsite.com', + } satisfies OriginRecord); + + await approveOrigin(messageSender); + + expect(mockPopup).toHaveBeenCalledWith({ + type: PopupType.OriginApproval, + request: { + origin: 'https://newsite.com', + favIconUrl: mockTab.favIconUrl, + title: mockTab.title, + lastRequest: undefined, + }, + }); + }); + + it('correctly persists the known sites when a new site is added', async () => { + const existingOriginRecord = { + choice: UserChoice.Approved, + date: 456, + origin: 'https://existingsite.com', + } satisfies OriginRecord; + mockLocalStorage.get.mockReturnValue(Promise.resolve([existingOriginRecord])); + + const messageSender = { origin: 'https://newsite.com', tab: mockTab }; + const newOriginRecord = { + choice: UserChoice.Approved, + date: 123, + origin: 'https://newsite.com', + } satisfies OriginRecord; + mockPopup.mockResolvedValue(newOriginRecord); + + await approveOrigin(messageSender); + + expect(mockLocalStorage.set).toHaveBeenCalledWith('knownSites', [ + existingOriginRecord, + newOriginRecord, + ]); + }); }); }); diff --git a/apps/extension/src/approve-origin.ts b/apps/extension/src/approve-origin.ts index 4dc246a0..add30a32 100644 --- a/apps/extension/src/approve-origin.ts +++ b/apps/extension/src/approve-origin.ts @@ -70,7 +70,7 @@ export const approveOrigin = async ({ // if user interacted with popup, update record if (popupResponse) { - void addOrUpdateSiteRecord(popupResponse); + await addOrUpdateSiteRecord(popupResponse); } return popupResponse?.choice ?? UserChoice.Denied;