mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
test(electron): cover managed download paths
This commit is contained in:
1 parent
b598444eff
commit
6eb58760fa
1 file changed
+209
-10
@@ -7,6 +7,20 @@ const mockBroadcastDownloadUpdate = jest.fn();
|
||||
const mockRemovePartialDownloadFile = jest.fn();
|
||||
const mockPauseDownload = jest.fn();
|
||||
const mockResumeDownloadRequest = jest.fn();
|
||||
const mockExistsSync = jest.fn();
|
||||
const mockOpenPath = jest.fn();
|
||||
const mockShowItemInFolder = jest.fn();
|
||||
const mockEq = jest.fn();
|
||||
let downloadsFilePathColumn: unknown;
|
||||
|
||||
const MANAGED_PATH_STATE = {
|
||||
ERROR: 'error',
|
||||
MANAGED: 'managed',
|
||||
UNMANAGED: 'unmanaged',
|
||||
} as const;
|
||||
|
||||
type ManagedPathState =
|
||||
(typeof MANAGED_PATH_STATE)[keyof typeof MANAGED_PATH_STATE];
|
||||
|
||||
function getHandler(channel: string): IpcHandler {
|
||||
const handler = mockRegisteredHandlers.get(channel);
|
||||
@@ -34,7 +48,24 @@ describe('downloads events', () => {
|
||||
mockRemovePartialDownloadFile.mockReset();
|
||||
mockPauseDownload.mockReset();
|
||||
mockResumeDownloadRequest.mockReset();
|
||||
mockExistsSync.mockReset();
|
||||
mockOpenPath.mockReset().mockResolvedValue('');
|
||||
mockShowItemInFolder.mockReset();
|
||||
mockEq.mockReset();
|
||||
|
||||
jest.doMock('node:fs', () => ({
|
||||
...jest.requireActual<typeof import('node:fs')>('node:fs'),
|
||||
existsSync: mockExistsSync,
|
||||
}));
|
||||
jest.doMock('drizzle-orm', () => {
|
||||
const actual =
|
||||
jest.requireActual<typeof import('drizzle-orm')>('drizzle-orm');
|
||||
mockEq.mockImplementation(actual.eq);
|
||||
return {
|
||||
...actual,
|
||||
eq: mockEq,
|
||||
};
|
||||
});
|
||||
jest.doMock('electron', () => ({
|
||||
app: {
|
||||
getPath: jest.fn((name: string) =>
|
||||
@@ -50,8 +81,8 @@ describe('downloads events', () => {
|
||||
}),
|
||||
},
|
||||
shell: {
|
||||
openPath: jest.fn(),
|
||||
showItemInFolder: jest.fn(),
|
||||
openPath: mockOpenPath,
|
||||
showItemInFolder: mockShowItemInFolder,
|
||||
},
|
||||
}));
|
||||
jest.doMock('../../database/connection', () => ({
|
||||
@@ -77,8 +108,45 @@ describe('downloads events', () => {
|
||||
}));
|
||||
|
||||
await import('./downloads.events');
|
||||
const schema = await import('../../database/schema');
|
||||
downloadsFilePathColumn = schema.downloads.filePath;
|
||||
});
|
||||
|
||||
function mockManagedPath(state: ManagedPathState) {
|
||||
const limit = jest.fn(() => {
|
||||
if (state === MANAGED_PATH_STATE.ERROR) {
|
||||
return Promise.reject(new Error('database unavailable'));
|
||||
}
|
||||
return Promise.resolve(
|
||||
state === MANAGED_PATH_STATE.MANAGED ? [{ id: 42 }] : []
|
||||
);
|
||||
});
|
||||
const where = jest.fn((_predicate: unknown) => ({ limit }));
|
||||
const from = jest.fn(() => ({ where }));
|
||||
const select = jest.fn(() => ({ from }));
|
||||
const db = { select };
|
||||
mockGetDatabase.mockResolvedValue(db);
|
||||
return { from, limit, select, where };
|
||||
}
|
||||
|
||||
function expectManagedPathLookup(
|
||||
lookup: ReturnType<typeof mockManagedPath>,
|
||||
filePath: string
|
||||
) {
|
||||
expect(mockGetDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(lookup.select).toHaveBeenCalledTimes(1);
|
||||
expect(lookup.from).toHaveBeenCalledTimes(1);
|
||||
expect(lookup.where).toHaveBeenCalledTimes(1);
|
||||
expect(mockEq).toHaveBeenCalledTimes(1);
|
||||
expect(mockEq.mock.calls[0][0] === downloadsFilePathColumn).toBe(true);
|
||||
expect(mockEq.mock.calls[0][1]).toBe(filePath);
|
||||
expect(
|
||||
lookup.where.mock.calls[0][0] === mockEq.mock.results[0].value
|
||||
).toBe(true);
|
||||
expect(lookup.limit).toHaveBeenCalledTimes(1);
|
||||
expect(lookup.limit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
|
||||
function mockDownloadRow(row: { filePath: string | null; status: string }) {
|
||||
const deleteWhere = jest.fn().mockResolvedValue(undefined);
|
||||
const db = {
|
||||
@@ -101,7 +169,9 @@ describe('downloads events', () => {
|
||||
const deleteWhere = jest.fn().mockResolvedValue(undefined);
|
||||
const selectWhere = jest
|
||||
.fn()
|
||||
.mockResolvedValue(rows.map((row, index) => ({ id: index + 1, ...row })));
|
||||
.mockResolvedValue(
|
||||
rows.map((row, index) => ({ id: index + 1, ...row }))
|
||||
);
|
||||
const db = {
|
||||
delete: jest.fn(() => ({ where: deleteWhere })),
|
||||
select: jest.fn(() => ({
|
||||
@@ -117,9 +187,11 @@ describe('downloads events', () => {
|
||||
it('removes queued resumed partial files before deleting the row', async () => {
|
||||
const { deleteWhere } = mockDownloadRow(createDownloadRow('queued'));
|
||||
|
||||
await expect(getHandler('DOWNLOADS_REMOVE')(null, 42)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
await expect(getHandler('DOWNLOADS_REMOVE')(null, 42)).resolves.toEqual(
|
||||
{
|
||||
success: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockRemoveDownloadFromRuntime).toHaveBeenCalledWith(42);
|
||||
expect(mockRemovePartialDownloadFile).toHaveBeenCalledWith(
|
||||
@@ -127,7 +199,9 @@ describe('downloads events', () => {
|
||||
);
|
||||
expect(
|
||||
mockRemovePartialDownloadFile.mock.invocationCallOrder[0]
|
||||
).toBeLessThan(mockRemoveDownloadFromRuntime.mock.invocationCallOrder[0]);
|
||||
).toBeLessThan(
|
||||
mockRemoveDownloadFromRuntime.mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(
|
||||
mockRemovePartialDownloadFile.mock.invocationCallOrder[0]
|
||||
).toBeLessThan(deleteWhere.mock.invocationCallOrder[0]);
|
||||
@@ -137,9 +211,11 @@ describe('downloads events', () => {
|
||||
it('removes completed partial files before deleting the row', async () => {
|
||||
const { deleteWhere } = mockDownloadRow(createDownloadRow('completed'));
|
||||
|
||||
await expect(getHandler('DOWNLOADS_REMOVE')(null, 42)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
await expect(getHandler('DOWNLOADS_REMOVE')(null, 42)).resolves.toEqual(
|
||||
{
|
||||
success: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockRemovePartialDownloadFile).toHaveBeenCalledWith(
|
||||
'/downloads/resume.mp4'
|
||||
@@ -328,4 +404,127 @@ describe('downloads events', () => {
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{
|
||||
channel: 'DOWNLOADS_REVEAL_FILE',
|
||||
filePath: '/downloads/reveal-boundary.mp4',
|
||||
operation: 'reveal',
|
||||
},
|
||||
{
|
||||
channel: 'DOWNLOADS_PLAY_FILE',
|
||||
filePath: '/downloads/play-boundary.mp4',
|
||||
operation: 'play',
|
||||
},
|
||||
])('$operation managed-path boundary', ({ channel, filePath }) => {
|
||||
it('rejects an unmanaged database path before accessing the filesystem', async () => {
|
||||
const lookup = mockManagedPath(MANAGED_PATH_STATE.UNMANAGED);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
await expect(getHandler(channel)(null, filePath)).resolves.toEqual({
|
||||
error: 'File not found',
|
||||
success: false,
|
||||
});
|
||||
|
||||
expectManagedPathLookup(lookup, filePath);
|
||||
expect(mockExistsSync).not.toHaveBeenCalled();
|
||||
expect(mockOpenPath).not.toHaveBeenCalled();
|
||||
expect(mockShowItemInFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects a managed database path that is missing from disk', async () => {
|
||||
const lookup = mockManagedPath(MANAGED_PATH_STATE.MANAGED);
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
await expect(getHandler(channel)(null, filePath)).resolves.toEqual({
|
||||
error: 'File not found',
|
||||
success: false,
|
||||
});
|
||||
|
||||
expectManagedPathLookup(lookup, filePath);
|
||||
expect(mockExistsSync).toHaveBeenCalledTimes(1);
|
||||
expect(mockExistsSync).toHaveBeenCalledWith(filePath);
|
||||
expect(mockOpenPath).not.toHaveBeenCalled();
|
||||
expect(mockShowItemInFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fails closed when the managed-path database query rejects', async () => {
|
||||
const lookup = mockManagedPath(MANAGED_PATH_STATE.ERROR);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
const consoleError = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
getHandler(channel)(null, filePath)
|
||||
).resolves.toEqual({
|
||||
error: 'File not found',
|
||||
success: false,
|
||||
});
|
||||
expect(consoleError).toHaveBeenCalledTimes(1);
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
'Error verifying managed download path:',
|
||||
expect.objectContaining({
|
||||
message: 'database unavailable',
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
consoleError.mockRestore();
|
||||
}
|
||||
|
||||
expectManagedPathLookup(lookup, filePath);
|
||||
expect(mockExistsSync).not.toHaveBeenCalled();
|
||||
expect(mockOpenPath).not.toHaveBeenCalled();
|
||||
expect(mockShowItemInFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('reveals a managed file that exists on disk', async () => {
|
||||
const filePath = '/downloads/reveal-success.mp4';
|
||||
const lookup = mockManagedPath(MANAGED_PATH_STATE.MANAGED);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
await expect(
|
||||
getHandler('DOWNLOADS_REVEAL_FILE')(null, filePath)
|
||||
).resolves.toEqual({ success: true });
|
||||
|
||||
expectManagedPathLookup(lookup, filePath);
|
||||
expect(mockExistsSync).toHaveBeenCalledTimes(1);
|
||||
expect(mockExistsSync).toHaveBeenCalledWith(filePath);
|
||||
expect(mockShowItemInFolder).toHaveBeenCalledTimes(1);
|
||||
expect(mockShowItemInFolder).toHaveBeenCalledWith(filePath);
|
||||
expect(mockOpenPath).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('waits for the native shell before reporting a managed file as played', async () => {
|
||||
const filePath = '/downloads/play-success.mp4';
|
||||
const lookup = mockManagedPath(MANAGED_PATH_STATE.MANAGED);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
let resolveOpenPath!: (value: string) => void;
|
||||
const openPathResult = new Promise<string>((resolve) => {
|
||||
resolveOpenPath = resolve;
|
||||
});
|
||||
mockOpenPath.mockReturnValue(openPathResult);
|
||||
|
||||
let responseSettled = false;
|
||||
const response = getHandler('DOWNLOADS_PLAY_FILE')(null, filePath).then(
|
||||
(result) => {
|
||||
responseSettled = true;
|
||||
return result;
|
||||
}
|
||||
);
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
|
||||
expectManagedPathLookup(lookup, filePath);
|
||||
expect(mockExistsSync).toHaveBeenCalledTimes(1);
|
||||
expect(mockExistsSync).toHaveBeenCalledWith(filePath);
|
||||
expect(mockOpenPath).toHaveBeenCalledTimes(1);
|
||||
expect(mockOpenPath).toHaveBeenCalledWith(filePath);
|
||||
expect(mockShowItemInFolder).not.toHaveBeenCalled();
|
||||
expect(responseSettled).toBe(false);
|
||||
|
||||
resolveOpenPath('');
|
||||
await expect(response).resolves.toEqual({ success: true });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user