forked from flyerman/lace
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[LW-7834] Adds queue structure to request handle validations (#436)
* fix: lw-7834 adds queue structure to request handle validations in address book
- Loading branch information
Showing
4 changed files
with
260 additions
and
24 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
apps/browser-extension-wallet/src/hooks/__tests__/useUpdateAddressStatus.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { useUpdateAddressStatus } from '../useUpdateAddressStatus'; | ||
import { CustomConflictError, ensureHandleOwnerHasntChanged } from '@src/utils/validators'; | ||
import { Asset, Cardano, HandleProvider } from '@cardano-sdk/core'; | ||
|
||
jest.mock('@src/utils/validators', () => ({ | ||
...jest.requireActual<any>('@src/utils/validators'), | ||
ensureHandleOwnerHasntChanged: jest.fn() | ||
})); | ||
|
||
const mockHandleResolution = { | ||
backgroundImage: Asset.Uri('ipfs://zrljm7nskakjydxlr450ktsj08zuw6aktvgfkmmyw9semrkrezryq3yd'), | ||
cardanoAddress: Cardano.PaymentAddress( | ||
'addr_test1qzrljm7nskakjydxlr450ktsj08zuw6aktvgfkmmyw9semrkrezryq3ydtmkg0e7e2jvzg443h0ffzfwd09wpcxy2fuql9tk0g' | ||
), | ||
handle: 'bob', | ||
hasDatum: false, | ||
image: Asset.Uri('ipfs://c8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe'), | ||
policyId: Cardano.PolicyId('50fdcdbfa3154db86a87e4b5697ae30d272e0bbcfa8122efd3e301cb'), | ||
profilePic: Asset.Uri('ipfs://zrljm7nskakjydxlr450ktsj08zuw6aktvgfkmmyw9semrkrezryq3yd1') | ||
}; | ||
|
||
describe('useUpdateAddressStatus', () => { | ||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
const mockHandleResolver = { | ||
resolveHandles: jest.fn(), | ||
healthCheck: jest.fn(), | ||
getPolicyIds: jest.fn() | ||
} as HandleProvider; | ||
const addressList = [ | ||
{ id: 1, name: 'one', address: 'address1', handleResolution: mockHandleResolution, network: 1 }, | ||
{ id: 2, name: 'two', address: 'address2', handleResolution: mockHandleResolution, network: 1 } | ||
]; | ||
|
||
it('sets addresses to valid when handles resolve correctly', async () => { | ||
(ensureHandleOwnerHasntChanged as jest.Mock).mockResolvedValue(true); | ||
const { result, waitForNextUpdate } = renderHook(() => useUpdateAddressStatus(addressList, mockHandleResolver)); | ||
|
||
await waitForNextUpdate(); | ||
|
||
expect(result.current).toEqual({ | ||
address1: { isValid: true }, | ||
address2: { isValid: true } | ||
}); | ||
}); | ||
|
||
it('sets an address as invalid if a handle resolves with an error', async () => { | ||
const handleError = new CustomConflictError({ | ||
message: 'Unexpected values', | ||
expectedAddress: Cardano.PaymentAddress( | ||
'addr_test1qzrljm7nskakjydxlr450ktsj08zuw6aktvgfkmmyw9semrkrezryq3ydtmkg0e7e2jvzg443h0ffzfwd09wpcxy2fuql9tk0g' | ||
), | ||
actualAddress: Cardano.PaymentAddress( | ||
'addr_test1qzrljm7nskakjydxlr450ktsj08zuw6aktvgfkmmyw9semrkrezryq3ydtmkg0e7e2jvzg443h0ffzfwd09wpcxy2fuql9tk0g' | ||
) | ||
}); | ||
|
||
(ensureHandleOwnerHasntChanged as jest.Mock).mockResolvedValueOnce(true).mockRejectedValueOnce(handleError); | ||
|
||
const { result, waitForNextUpdate } = renderHook(() => useUpdateAddressStatus(addressList, mockHandleResolver)); | ||
|
||
await waitForNextUpdate(); | ||
|
||
expect(result.current).toEqual({ | ||
address1: { isValid: true }, | ||
address2: { isValid: false, error: handleError } | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
apps/browser-extension-wallet/src/utils/__tests__/createQueue.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/* eslint-disable promise/avoid-new */ | ||
import { createQueue, TaskQueue } from '../taskQueue'; | ||
|
||
describe('createQueue', () => { | ||
let queue: TaskQueue; | ||
const batchTasks = 4; | ||
const intervalBetweenBatch = 100; | ||
beforeEach(() => { | ||
queue = createQueue(batchTasks, intervalBetweenBatch); | ||
}); | ||
|
||
it('should enqueue and dequeue tasks correctly', async () => { | ||
const mockTask = jest.fn(); | ||
// The first task will be picked up and executed immediately | ||
queue.enqueue(mockTask); | ||
queue.enqueue(mockTask); | ||
|
||
expect(queue.isEmpty()).toBe(false); | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
expect(queue.isEmpty()).toBe(true); | ||
}); | ||
|
||
it('should drain the queue after stopping', () => { | ||
const mockTask = jest.fn(); | ||
queue.enqueue(mockTask); | ||
queue.stop(); | ||
expect(queue.isEmpty()).toBe(true); | ||
}); | ||
|
||
it('should execute tasks with rate limiting', async () => { | ||
const mockTask1 = jest.fn(); | ||
const mockTask2 = jest.fn(); | ||
const mockTask3 = jest.fn(); | ||
const mockTask4 = jest.fn(); | ||
const mockTask5 = jest.fn(); | ||
const mockTask6 = jest.fn(); | ||
|
||
queue.enqueue(mockTask1); | ||
queue.enqueue(mockTask2); | ||
queue.enqueue(mockTask3); | ||
queue.enqueue(mockTask4); | ||
queue.enqueue(mockTask5); | ||
queue.enqueue(mockTask6); | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
|
||
expect(mockTask1).toHaveBeenCalled(); | ||
expect(mockTask2).toHaveBeenCalled(); | ||
expect(mockTask3).toHaveBeenCalled(); | ||
expect(mockTask4).toHaveBeenCalled(); | ||
|
||
expect(mockTask5).not.toHaveBeenCalled(); | ||
expect(mockTask6).not.toHaveBeenCalled(); | ||
|
||
await new Promise((resolve) => setTimeout(resolve, intervalBetweenBatch)); | ||
|
||
expect(mockTask5).toHaveBeenCalled(); | ||
expect(mockTask6).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should handle stopping the queue', async () => { | ||
const mockTask1 = jest.fn(); | ||
const mockTask2 = jest.fn(); | ||
const mockTask3 = jest.fn(); | ||
const mockTask4 = jest.fn(); | ||
const mockTask5 = jest.fn(); | ||
const mockTask6 = jest.fn(); | ||
|
||
queue.enqueue(mockTask1); | ||
queue.enqueue(mockTask2); | ||
queue.enqueue(mockTask3); | ||
queue.enqueue(mockTask4); | ||
queue.enqueue(mockTask5); | ||
queue.enqueue(mockTask6); | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
|
||
expect(mockTask1).toHaveBeenCalled(); | ||
expect(mockTask2).toHaveBeenCalled(); | ||
expect(mockTask3).toHaveBeenCalled(); | ||
expect(mockTask4).toHaveBeenCalled(); | ||
|
||
queue.stop(); | ||
|
||
await new Promise((resolve) => setTimeout(resolve, intervalBetweenBatch)); | ||
|
||
expect(mockTask5).not.toHaveBeenCalled(); | ||
expect(mockTask6).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
type taskType = () => Promise<unknown>; | ||
|
||
export interface TaskQueue { | ||
enqueue: (task: taskType) => void; | ||
dequeue: () => taskType; | ||
isEmpty: () => boolean; | ||
stop: () => void; | ||
} | ||
|
||
export const createQueue = (batchTasks: number, intervalBetweenBatch: number): TaskQueue => { | ||
let tasks: Array<taskType> = []; | ||
let isRunning = false; | ||
let sent = 0; | ||
|
||
const stop = () => { | ||
sent = 0; | ||
isRunning = false; | ||
tasks = []; | ||
}; | ||
|
||
const isEmpty = () => tasks.length === 0; | ||
|
||
const dequeue = (): taskType | undefined => tasks.shift(); | ||
|
||
const execute = async () => { | ||
if (!isEmpty()) { | ||
const task = dequeue(); | ||
|
||
if (!task) { | ||
return; | ||
} | ||
|
||
await task(); | ||
sent++; | ||
|
||
if (isRunning && !isEmpty()) { | ||
if (sent >= batchTasks) { | ||
// Reset the sent count | ||
sent = 0; | ||
// eslint-disable-next-line promise/avoid-new | ||
await new Promise((resolve) => setTimeout(resolve, intervalBetweenBatch)); | ||
} | ||
execute(); | ||
} | ||
|
||
if (isEmpty()) { | ||
stop(); | ||
} | ||
} | ||
}; | ||
|
||
const enqueue = (item: taskType) => { | ||
tasks.push(item); | ||
|
||
if (!isRunning) { | ||
isRunning = true; | ||
execute(); | ||
} | ||
}; | ||
|
||
return { | ||
enqueue, | ||
dequeue, | ||
isEmpty, | ||
stop | ||
}; | ||
}; |