mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
* chore(main): prefer-import-in-mock Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> * fix: type issue Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com> --------- Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
369 lines
12 KiB
TypeScript
369 lines
12 KiB
TypeScript
/**********************************************************************
|
|
* Copyright (C) 2023-2024 Red Hat, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
***********************************************************************/
|
|
|
|
import type { SimpleContainerInfo } from '@podman-desktop/core-api';
|
|
import type Electron from 'electron';
|
|
import type { IpcMainEvent } from 'electron';
|
|
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
import type { ContainerProviderRegistry } from '/@/plugin/container-registry.js';
|
|
import type { ContributionManager } from '/@/plugin/contribution-manager.js';
|
|
|
|
import { DockerPluginAdapter } from './docker-plugin-adapter.js';
|
|
|
|
let dockerPluginAdapter: TestDockerPluginAdapter;
|
|
|
|
vi.mock(import('electron'), () => {
|
|
const mockIpcMain = {
|
|
on: vi.fn().mockReturnThis(),
|
|
};
|
|
return { ipcMain: mockIpcMain } as unknown as typeof Electron;
|
|
});
|
|
|
|
const contributionManager = {} as unknown as ContributionManager;
|
|
|
|
class TestDockerPluginAdapter extends DockerPluginAdapter {
|
|
override async getVmServiceContainer(contributionId: string): Promise<SimpleContainerInfo> {
|
|
return super.getVmServiceContainer(contributionId);
|
|
}
|
|
}
|
|
|
|
const listSimpleContainersMock = vi.fn();
|
|
const execInContainerMock = vi.fn();
|
|
const containerProviderRegistry = {
|
|
listSimpleContainers: listSimpleContainersMock,
|
|
execInContainer: execInContainerMock,
|
|
} as unknown as ContainerProviderRegistry;
|
|
|
|
beforeAll(async () => {
|
|
dockerPluginAdapter = new TestDockerPluginAdapter(contributionManager, containerProviderRegistry);
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('getVmServiceContainer', async () => {
|
|
test('Check getVmServiceContainer being found', async () => {
|
|
const container1 = {
|
|
Labels: {},
|
|
};
|
|
|
|
// another app
|
|
const container2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': 'fooBar',
|
|
},
|
|
};
|
|
const extensionName = 'myExtension';
|
|
|
|
// 2 plugins, only one is the VM service container
|
|
const pluginContainer1 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
},
|
|
};
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
};
|
|
|
|
listSimpleContainersMock.mockResolvedValueOnce([container1, container2, pluginContainer1, pluginServiceContainer2]);
|
|
|
|
const result = await dockerPluginAdapter.getVmServiceContainer('myExtension');
|
|
expect(result).toBeDefined();
|
|
expect(result).toEqual(pluginServiceContainer2);
|
|
});
|
|
|
|
test('Check getVmServiceContainer app not found', async () => {
|
|
const container1 = {
|
|
Labels: {},
|
|
};
|
|
|
|
// another app
|
|
const container2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': 'fooBar',
|
|
},
|
|
};
|
|
const extensionName = 'myExtension';
|
|
|
|
// 2 plugins, only one is the VM service container
|
|
const pluginContainer1 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
},
|
|
};
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
};
|
|
|
|
listSimpleContainersMock.mockResolvedValueOnce([container1, container2, pluginContainer1, pluginServiceContainer2]);
|
|
|
|
await expect(dockerPluginAdapter.getVmServiceContainer('anotherApp')).rejects.toThrowError(
|
|
`No container having the label 'io.podman_desktop.PodmanDesktop.extensionName'`,
|
|
);
|
|
});
|
|
|
|
test('Check getVmServiceContainer vm service not found', async () => {
|
|
const container1 = {
|
|
Labels: {},
|
|
};
|
|
|
|
// another app
|
|
const container2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': 'fooBar',
|
|
},
|
|
};
|
|
const extensionName = 'myExtension';
|
|
|
|
// 2 plugins, only one is the VM service container
|
|
const pluginContainer1 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
},
|
|
};
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer2 = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'false',
|
|
},
|
|
};
|
|
|
|
listSimpleContainersMock.mockResolvedValueOnce([container1, container2, pluginContainer1, pluginServiceContainer2]);
|
|
|
|
await expect(dockerPluginAdapter.getVmServiceContainer('myExtension')).rejects.toThrowError(
|
|
`No container having the label 'io.podman_desktop.PodmanDesktop.vm-service' == 'true' found.`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('handle exec', async () => {
|
|
test('handle exec inside the VM', async () => {
|
|
const extensionName = 'myExtension';
|
|
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
} as unknown as SimpleContainerInfo;
|
|
|
|
const spyGetVmServiceContainer = vi.spyOn(dockerPluginAdapter, 'getVmServiceContainer');
|
|
spyGetVmServiceContainer.mockResolvedValueOnce(pluginServiceContainer);
|
|
|
|
// mock execution
|
|
execInContainerMock.mockImplementation(
|
|
async (
|
|
_engineId: string,
|
|
_id: string,
|
|
_command: string[],
|
|
onStdout: (data: Buffer) => void,
|
|
onStderr: (data: Buffer) => void,
|
|
) => {
|
|
// write hello world as stdout
|
|
onStdout(Buffer.from('hello\n'));
|
|
onStdout(Buffer.from('world\n'));
|
|
|
|
// write warning as stderr
|
|
onStderr(Buffer.from('warning: text1\n'));
|
|
onStderr(Buffer.from('warning: text2\n'));
|
|
},
|
|
);
|
|
|
|
const result = await dockerPluginAdapter.handleExec(extensionName, 'VM_SERVICE', 'echo', ['hello', 'world']);
|
|
expect(result.code).toEqual(0);
|
|
|
|
expect(result.stdout).toEqual('hello\nworld\n');
|
|
expect(result.stderr).toEqual('warning: text1\nwarning: text2\n');
|
|
});
|
|
|
|
test('handle exec with error inside the VM', async () => {
|
|
const extensionName = 'myExtension';
|
|
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
} as unknown as SimpleContainerInfo;
|
|
|
|
const spyGetVmServiceContainer = vi.spyOn(dockerPluginAdapter, 'getVmServiceContainer');
|
|
spyGetVmServiceContainer.mockResolvedValueOnce(pluginServiceContainer);
|
|
|
|
// mock execution
|
|
execInContainerMock.mockImplementation(
|
|
async (
|
|
_engineId: string,
|
|
_id: string,
|
|
_command: string[],
|
|
_onStdout: (data: Buffer) => void,
|
|
_onStderr: (data: Buffer) => void,
|
|
) => {
|
|
throw new Error('This is a test error');
|
|
},
|
|
);
|
|
|
|
const result = await dockerPluginAdapter.handleExec(extensionName, 'VM_SERVICE', 'echo', ['hello', 'world']);
|
|
expect(result.code).toEqual(1);
|
|
|
|
expect(result.stdout).toEqual('');
|
|
expect(result.signal).toEqual('Error: This is a test error');
|
|
});
|
|
});
|
|
|
|
describe('handle execWithOptions', async () => {
|
|
test('handle execWithOptions inside the VM', async () => {
|
|
const extensionName = 'myExtension';
|
|
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
} as unknown as SimpleContainerInfo;
|
|
|
|
const spyGetVmServiceContainer = vi.spyOn(dockerPluginAdapter, 'getVmServiceContainer');
|
|
spyGetVmServiceContainer.mockResolvedValueOnce(pluginServiceContainer);
|
|
|
|
// mock execution
|
|
execInContainerMock.mockImplementation(
|
|
async (
|
|
_engineId: string,
|
|
_id: string,
|
|
_command: string[],
|
|
onStdout: (data: Buffer) => void,
|
|
onStderr: (data: Buffer) => void,
|
|
) => {
|
|
// write hello world as stdout
|
|
onStdout(Buffer.from('hello'));
|
|
onStdout(Buffer.from('world\n'));
|
|
|
|
// write warning as stderr
|
|
onStderr(Buffer.from('warning: text1\n'));
|
|
onStderr(Buffer.from('warning: text2\n'));
|
|
},
|
|
);
|
|
|
|
const replyMock = vi.fn();
|
|
const ipcMainEventMock = {
|
|
reply: replyMock,
|
|
} as unknown as IpcMainEvent;
|
|
|
|
const callbackId = 123;
|
|
|
|
await dockerPluginAdapter.handleExecWithOptions(
|
|
ipcMainEventMock,
|
|
extensionName,
|
|
'VM_SERVICE',
|
|
'echo',
|
|
callbackId,
|
|
{},
|
|
['hello', 'world'],
|
|
);
|
|
|
|
// check event reply
|
|
|
|
expect(replyMock).toHaveBeenNthCalledWith(
|
|
1,
|
|
'docker-plugin-adapter:execWithOptions-callback-stdout',
|
|
callbackId,
|
|
Buffer.from('hello'),
|
|
);
|
|
expect(replyMock).toHaveBeenNthCalledWith(
|
|
2,
|
|
'docker-plugin-adapter:execWithOptions-callback-stdout',
|
|
callbackId,
|
|
Buffer.from('world\n'),
|
|
);
|
|
|
|
expect(replyMock).toHaveBeenNthCalledWith(
|
|
3,
|
|
'docker-plugin-adapter:execWithOptions-callback-stderr',
|
|
callbackId,
|
|
Buffer.from('warning: text1\n'),
|
|
);
|
|
expect(replyMock).toHaveBeenNthCalledWith(
|
|
4,
|
|
'docker-plugin-adapter:execWithOptions-callback-stderr',
|
|
callbackId,
|
|
Buffer.from('warning: text2\n'),
|
|
);
|
|
|
|
expect(replyMock).toHaveBeenNthCalledWith(5, 'docker-plugin-adapter:execWithOptions-callback-close', callbackId, 0);
|
|
});
|
|
|
|
test('handle execWithOptions inside the VM with error', async () => {
|
|
const error = new Error('custom error');
|
|
const extensionName = 'myExtension';
|
|
|
|
// this is the container being used as service container
|
|
const pluginServiceContainer = {
|
|
Labels: {
|
|
'io.podman_desktop.PodmanDesktop.extensionName': extensionName,
|
|
'io.podman_desktop.PodmanDesktop.vm-service': 'true',
|
|
},
|
|
} as unknown as SimpleContainerInfo;
|
|
|
|
const spyGetVmServiceContainer = vi.spyOn(dockerPluginAdapter, 'getVmServiceContainer');
|
|
spyGetVmServiceContainer.mockResolvedValueOnce(pluginServiceContainer);
|
|
|
|
// mock execution
|
|
execInContainerMock.mockImplementation(() => {
|
|
throw error;
|
|
});
|
|
|
|
const replyMock = vi.fn();
|
|
const ipcMainEventMock = {
|
|
reply: replyMock,
|
|
} as unknown as IpcMainEvent;
|
|
|
|
const callbackId = 123;
|
|
|
|
await dockerPluginAdapter.handleExecWithOptions(
|
|
ipcMainEventMock,
|
|
extensionName,
|
|
'VM_SERVICE',
|
|
'echo',
|
|
callbackId,
|
|
{},
|
|
['hello', 'world'],
|
|
);
|
|
|
|
// check event reply with error and close
|
|
expect(replyMock).toHaveBeenNthCalledWith(
|
|
1,
|
|
'docker-plugin-adapter:execWithOptions-callback-error',
|
|
callbackId,
|
|
error,
|
|
);
|
|
expect(replyMock).toHaveBeenNthCalledWith(2, 'docker-plugin-adapter:execWithOptions-callback-close', callbackId, 1);
|
|
});
|
|
});
|