Skip to content

Commit ac0ef2f

Browse files
authored
test: more tests for pod terminal (#294)
* test: unit tests for pod-terminal stream Signed-off-by: Philippe Martin <[email protected]> * test: unit tests for pod-logs stream Signed-off-by: Philippe Martin <[email protected]> --------- Signed-off-by: Philippe Martin <[email protected]>
1 parent 5762c56 commit ac0ef2f

File tree

6 files changed

+242
-10
lines changed

6 files changed

+242
-10
lines changed

packages/webview/src/component/pods/PodLogs.svelte

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ onMount(async () => {
7070
object.metadata?.namespace ?? '',
7171
containerName,
7272
chunk => {
73-
if (chunk.containerName !== containerName) {
74-
return;
75-
}
7673
multiContainers(containerName, chunk.data, data => {
7774
if (noLogs) {
7875
noLogs = false;

packages/webview/src/component/pods/PodTerminal.svelte

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ async function initializeNewTerminal(
5959
6060
disposables.push(
6161
await streams.streamPodTerminals.subscribe(podName, namespace, containerName, chunk => {
62-
if (chunk.podName !== podName || chunk.namespace !== namespace || chunk.containerName !== containerName) {
63-
return;
64-
}
6562
shellTerminal.write(chunk.data);
6663
// save state to have an up to date backup of the terminal
6764
// in case the user leaves the webview of the extension
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**********************************************************************
2+
* Copyright (C) 2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
import type { Remote } from '../remote/remote';
20+
import { StreamPodLogs } from './pod-logs';
21+
import type { PodLogsApi } from '/@common/interface/pod-logs-api';
22+
import type { PodLogsChunk } from '/@common/model/pod-logs-chunk';
23+
import type { RpcBrowser } from '/@common/rpc/rpc';
24+
import type { IDisposable } from '/@common/types/disposable';
25+
26+
const remoteMock = {
27+
getProxy: vi.fn(),
28+
} as Remote;
29+
30+
const rpcBrowserMock = {
31+
on: vi.fn(),
32+
} as unknown as RpcBrowser;
33+
34+
const podLogsApiMock = {
35+
streamPodLogs: vi.fn(),
36+
stopStreamPodLogs: vi.fn(),
37+
} as unknown as PodLogsApi;
38+
39+
beforeEach(() => {
40+
vi.resetAllMocks();
41+
vi.mocked(remoteMock.getProxy).mockReturnValue(podLogsApiMock);
42+
vi.mocked(podLogsApiMock.streamPodLogs).mockResolvedValue(undefined);
43+
vi.mocked(podLogsApiMock.stopStreamPodLogs).mockResolvedValue(undefined);
44+
});
45+
46+
test('StreamPodLogs', async () => {
47+
const rpcDisposable1 = {
48+
dispose: vi.fn(),
49+
} as IDisposable;
50+
vi.mocked(rpcBrowserMock.on).mockReturnValueOnce(rpcDisposable1);
51+
const rpcDisposable2 = {
52+
dispose: vi.fn(),
53+
} as IDisposable;
54+
vi.mocked(rpcBrowserMock.on).mockReturnValueOnce(rpcDisposable2);
55+
const streamPodLogs = new StreamPodLogs(remoteMock, rpcBrowserMock);
56+
const callback1: (chunk: PodLogsChunk) => void = vi.fn();
57+
const callback2: (chunk: PodLogsChunk) => void = vi.fn();
58+
59+
const subscribeResult1 = await streamPodLogs.subscribe('podName', 'namespace', 'containerName1', callback1);
60+
const subscribeResult2 = await streamPodLogs.subscribe('podName', 'namespace', 'containerName2', callback2);
61+
expect(podLogsApiMock.streamPodLogs).toHaveBeenCalledWith('podName', 'namespace', 'containerName1');
62+
expect(podLogsApiMock.streamPodLogs).toHaveBeenCalledWith('podName', 'namespace', 'containerName2');
63+
expect(rpcBrowserMock.on).toHaveBeenCalledTimes(2);
64+
const chunkCallback1 = vi.mocked(rpcBrowserMock.on).mock.calls[0][1];
65+
const chunkCallback2 = vi.mocked(rpcBrowserMock.on).mock.calls[1][1];
66+
// backend will send data to all subscribers every time
67+
chunkCallback1({
68+
podName: 'podName',
69+
namespace: 'namespace',
70+
containerName: 'containerName1',
71+
data: Buffer.from('data1'),
72+
});
73+
chunkCallback2({
74+
podName: 'podName',
75+
namespace: 'namespace',
76+
containerName: 'containerName1',
77+
data: Buffer.from('data1'),
78+
});
79+
80+
chunkCallback1({
81+
podName: 'podName',
82+
namespace: 'namespace',
83+
containerName: 'containerName2',
84+
data: Buffer.from('data2'),
85+
});
86+
chunkCallback2({
87+
podName: 'podName',
88+
namespace: 'namespace',
89+
containerName: 'containerName2',
90+
data: Buffer.from('data2'),
91+
});
92+
93+
// expect the data is sent to the correct subscriber
94+
expect(callback1).toHaveBeenCalledOnce();
95+
expect(callback1).toHaveBeenCalledWith({
96+
podName: 'podName',
97+
namespace: 'namespace',
98+
containerName: 'containerName1',
99+
data: Buffer.from('data1'),
100+
});
101+
expect(callback2).toHaveBeenCalledOnce();
102+
expect(callback2).toHaveBeenCalledWith({
103+
podName: 'podName',
104+
namespace: 'namespace',
105+
containerName: 'containerName2',
106+
data: Buffer.from('data2'),
107+
});
108+
109+
// Expect disposables are disposed
110+
subscribeResult1.dispose();
111+
subscribeResult2.dispose();
112+
expect(rpcDisposable1.dispose).toHaveBeenCalledOnce();
113+
expect(rpcDisposable2.dispose).toHaveBeenCalledOnce();
114+
});

packages/webview/src/stream/pod-logs.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ export class StreamPodLogs {
3939
containerName: string,
4040
callback: (data: PodLogsChunk) => void,
4141
): Promise<IDisposable> {
42-
const disposable = this.rpcBrowser.on(POD_LOGS, data => {
43-
callback(data);
42+
const disposable = this.rpcBrowser.on(POD_LOGS, chunk => {
43+
if (chunk.podName !== podName || chunk.namespace !== namespace || chunk.containerName !== containerName) {
44+
return;
45+
}
46+
callback(chunk);
4447
});
4548
await this.#podLogsApi.streamPodLogs(podName, namespace, containerName);
4649
return Disposable.create(() => {
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**********************************************************************
2+
* Copyright (C) 2025 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
import type { Remote } from '../remote/remote';
20+
import { StreamPodTerminals } from './pod-terminals';
21+
import type { PodTerminalsApi } from '/@common/interface/pod-terminals-api';
22+
import type { PodTerminalChunk } from '/@common/model/pod-terminal-chunk';
23+
import type { RpcBrowser } from '/@common/rpc/rpc';
24+
import type { IDisposable } from '/@common/types/disposable';
25+
26+
const remoteMock = {
27+
getProxy: vi.fn(),
28+
} as Remote;
29+
30+
const rpcBrowserMock = {
31+
on: vi.fn(),
32+
} as unknown as RpcBrowser;
33+
34+
const podTerminalApiMock = {
35+
startTerminal: vi.fn(),
36+
} as unknown as PodTerminalsApi;
37+
38+
beforeEach(() => {
39+
vi.resetAllMocks();
40+
vi.mocked(remoteMock.getProxy).mockReturnValue(podTerminalApiMock);
41+
vi.mocked(podTerminalApiMock.startTerminal).mockResolvedValue(undefined);
42+
});
43+
44+
test('StreamPodTerminals', async () => {
45+
const rpcDisposable1 = {
46+
dispose: vi.fn(),
47+
} as IDisposable;
48+
vi.mocked(rpcBrowserMock.on).mockReturnValueOnce(rpcDisposable1);
49+
const rpcDisposable2 = {
50+
dispose: vi.fn(),
51+
} as IDisposable;
52+
vi.mocked(rpcBrowserMock.on).mockReturnValueOnce(rpcDisposable2);
53+
const streamPodTerminals = new StreamPodTerminals(remoteMock, rpcBrowserMock);
54+
const callback1: (chunk: PodTerminalChunk) => void = vi.fn();
55+
const callback2: (chunk: PodTerminalChunk) => void = vi.fn();
56+
57+
const subscribeResult1 = await streamPodTerminals.subscribe('podName', 'namespace', 'containerName1', callback1);
58+
const subscribeResult2 = await streamPodTerminals.subscribe('podName', 'namespace', 'containerName2', callback2);
59+
expect(podTerminalApiMock.startTerminal).toHaveBeenCalledWith('podName', 'namespace', 'containerName1');
60+
expect(podTerminalApiMock.startTerminal).toHaveBeenCalledWith('podName', 'namespace', 'containerName2');
61+
expect(rpcBrowserMock.on).toHaveBeenCalledTimes(2);
62+
const chunkCallback1 = vi.mocked(rpcBrowserMock.on).mock.calls[0][1];
63+
const chunkCallback2 = vi.mocked(rpcBrowserMock.on).mock.calls[1][1];
64+
// backend will send data to all subscribers every time
65+
chunkCallback1({
66+
podName: 'podName',
67+
namespace: 'namespace',
68+
containerName: 'containerName1',
69+
channel: 'stdout',
70+
data: Buffer.from('data1'),
71+
});
72+
chunkCallback2({
73+
podName: 'podName',
74+
namespace: 'namespace',
75+
containerName: 'containerName1',
76+
channel: 'stdout',
77+
data: Buffer.from('data1'),
78+
});
79+
80+
chunkCallback1({
81+
podName: 'podName',
82+
namespace: 'namespace',
83+
containerName: 'containerName2',
84+
channel: 'stdout',
85+
data: Buffer.from('data2'),
86+
});
87+
chunkCallback2({
88+
podName: 'podName',
89+
namespace: 'namespace',
90+
containerName: 'containerName2',
91+
channel: 'stdout',
92+
data: Buffer.from('data2'),
93+
});
94+
95+
// expect the data is sent to the correct subscriber
96+
expect(callback1).toHaveBeenCalledOnce();
97+
expect(callback1).toHaveBeenCalledWith({
98+
podName: 'podName',
99+
namespace: 'namespace',
100+
containerName: 'containerName1',
101+
channel: 'stdout',
102+
data: Buffer.from('data1'),
103+
});
104+
expect(callback2).toHaveBeenCalledOnce();
105+
expect(callback2).toHaveBeenCalledWith({
106+
podName: 'podName',
107+
namespace: 'namespace',
108+
containerName: 'containerName2',
109+
channel: 'stdout',
110+
data: Buffer.from('data2'),
111+
});
112+
113+
// Expect disposables are disposed
114+
subscribeResult1.dispose();
115+
subscribeResult2.dispose();
116+
expect(rpcDisposable1.dispose).toHaveBeenCalledOnce();
117+
expect(rpcDisposable2.dispose).toHaveBeenCalledOnce();
118+
});

packages/webview/src/stream/pod-terminals.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ export class StreamPodTerminals {
3939
containerName: string,
4040
callback: (data: PodTerminalChunk) => void,
4141
): Promise<IDisposable> {
42-
const disposable = this.rpcBrowser.on(POD_TERMINAL_DATA, data => {
43-
callback(data);
42+
const disposable = this.rpcBrowser.on(POD_TERMINAL_DATA, chunk => {
43+
if (chunk.podName !== podName || chunk.namespace !== namespace || chunk.containerName !== containerName) {
44+
return;
45+
}
46+
callback(chunk);
4447
});
4548
await this.#podTerminalsApi.startTerminal(podName, namespace, containerName);
4649
return Disposable.create(() => {

0 commit comments

Comments
 (0)