Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,16 +244,22 @@ const NodeTerminal: FC<NodeTerminalProps> = ({ obj: node }) => {
createDebugPod();
window.addEventListener('beforeunload', closeTab);
return () => {
deleteNamespace(namespace.metadata.name);
if (namespace) {
deleteNamespace(namespace.metadata.name);
}
window.removeEventListener('beforeunload', closeTab);
};
}, [nodeName, isWindows]);

return errorMessage ? (
<NodeTerminalError error={errorMessage} />
) : (
<NodeTerminalInner pod={pod} loaded={loaded} loadError={loadError} />
);
if (errorMessage) {
return <NodeTerminalError error={errorMessage} />;
}

if (!podName) {
return <LoadingBox />;
}

return <NodeTerminalInner pod={pod} loaded={loaded} loadError={loadError} />;
};

export default NodeTerminal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { render, screen, act } from '@testing-library/react';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import type { NodeKind, PodKind } from '@console/internal/module/k8s';
import { k8sCreate, k8sGet, k8sKillByName } from '@console/internal/module/k8s';
import NodeTerminal from '../NodeTerminal';

jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({
useK8sWatchResource: jest.fn(),
}));

jest.mock('@console/internal/components/pod', () => ({
PodConnectLoader: jest.fn(() => 'PodConnectLoader'),
}));

jest.mock('@console/internal/module/k8s', () => ({
k8sCreate: jest.fn(),
k8sGet: jest.fn(),
k8sKillByName: jest.fn(),
}));

const mockNode = {
apiVersion: 'v1',
kind: 'Node',
metadata: { name: 'test-node', uid: 'test-uid' },
status: { nodeInfo: { operatingSystem: 'linux' } },
} as NodeKind;

const mockNamespace = { metadata: { name: 'openshift-debug-abc' } };

const mockPod = {
apiVersion: 'v1',
kind: 'Pod',
metadata: { name: 'test-node-debug', namespace: 'openshift-debug-abc' },
};

const setupPodCreation = () => {
(k8sCreate as jest.Mock).mockResolvedValueOnce(mockNamespace).mockResolvedValueOnce(mockPod);
(k8sGet as jest.Mock).mockRejectedValue(new Error('not found'));
(k8sKillByName as jest.Mock).mockResolvedValue({});
};

const renderAndCreatePod = async () => {
jest.useFakeTimers();
await act(async () => {
render(<NodeTerminal obj={mockNode} />);
});
await act(async () => {
jest.advanceTimersByTime(1100);
});
jest.useRealTimers();
};

describe('NodeTerminal', () => {
beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation();
jest.spyOn(console, 'error').mockImplementation();
});

afterEach(() => {
jest.restoreAllMocks();
jest.useRealTimers();
});

it('should show loading spinner while debug pod is being created', () => {
(k8sCreate as jest.Mock).mockReturnValue(new Promise(() => {}));
(k8sGet as jest.Mock).mockReturnValue(new Promise(() => {}));
(useK8sWatchResource as jest.Mock).mockReturnValue([undefined, true, undefined]);

render(<NodeTerminal obj={mockNode} />);

expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByText('Debug pod not found or was deleted.')).not.toBeInTheDocument();
});

it('should show error when watch returns a load error', async () => {
setupPodCreation();
(useK8sWatchResource as jest.Mock).mockImplementation((resource) =>
resource ? [{}, true, new Error('Connection refused')] : [undefined, true, undefined],
);

await renderAndCreatePod();

expect(screen.getByText('Connection refused')).toBeVisible();
});

it('should show loading when watch has not loaded yet', async () => {
setupPodCreation();
(useK8sWatchResource as jest.Mock).mockImplementation((resource) =>
resource ? [{}, false, undefined] : [undefined, true, undefined],
);

await renderAndCreatePod();

expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

it('should show not found error when pod is loaded but missing', async () => {
setupPodCreation();
(useK8sWatchResource as jest.Mock).mockImplementation((resource) =>
resource ? [undefined, true, undefined] : [undefined, true, undefined],
);

await renderAndCreatePod();

expect(screen.getByText('Debug pod not found or was deleted.')).toBeVisible();
});

it('should show error with message when pod phase is Failed', async () => {
const failedPod = ({
...mockPod,
status: { phase: 'Failed', message: 'ImagePullBackOff' },
} as unknown) as PodKind;

setupPodCreation();
(useK8sWatchResource as jest.Mock).mockImplementation((resource) =>
resource ? [failedPod, true, undefined] : [undefined, true, undefined],
);

await renderAndCreatePod();

expect(screen.getByText(/The debug pod failed.*ImagePullBackOff/)).toBeVisible();
});

it('should render terminal when pod is Running', async () => {
const runningPod = ({
...mockPod,
status: { phase: 'Running' },
} as unknown) as PodKind;

setupPodCreation();
(useK8sWatchResource as jest.Mock).mockImplementation((resource) =>
resource ? [runningPod, true, undefined] : [undefined, true, undefined],
);

await renderAndCreatePod();

expect(screen.getByText('PodConnectLoader')).toBeInTheDocument();
});

it('should show error when pod creation fails', async () => {
(k8sCreate as jest.Mock).mockRejectedValue(new Error('Forbidden'));
(k8sGet as jest.Mock).mockRejectedValue(new Error('not found'));
(k8sKillByName as jest.Mock).mockResolvedValue({});
(useK8sWatchResource as jest.Mock).mockReturnValue([undefined, true, undefined]);

await act(async () => {
render(<NodeTerminal obj={mockNode} />);
});

expect(screen.getByText('Forbidden')).toBeVisible();
});
});