Skip to content
Closed
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
123 changes: 123 additions & 0 deletions calm-plugins/vscode/src/extension/extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import * as vscode from 'vscode';
import { activate, disconnectFromHub } from './extension';

vi.mock('./services/hub-auth-service', () => ({
HubAuthService: class {
async initialize(): Promise<void> {}
async discoverAndAuthenticate(): Promise<boolean> {
return false;
}
},
}));

type CommandCallback = (...args: unknown[]) => unknown;

const commands = new Map<string, CommandCallback>();
const configUpdates: Array<{ key: string; value: unknown }> = [];
let hubUrl: string | undefined;
let statusBarItem: vscode.StatusBarItem;
let inputValue: string | undefined;

function context(): vscode.ExtensionContext {
return {
subscriptions: [],
extensionUri: vscode.Uri.file('/tmp/extension'),
secrets: {
get: async () => undefined,
store: async () => {},
delete: async () => {},
onDidChange: () => ({ dispose: () => {} }),
},
} as unknown as vscode.ExtensionContext;
}

beforeEach(() => {
commands.clear();
configUpdates.length = 0;
hubUrl = 'https://calmhub.example.com';
inputValue = undefined;

(vscode.commands as unknown as { registerCommand: unknown }).registerCommand = (
command: string,
callback: CommandCallback
) => {
commands.set(command, callback);
return { dispose: () => {} };
};

(vscode.workspace as unknown as { getConfiguration: unknown }).getConfiguration = (
section?: string
) => ({
get: <T>(key: string, defaultValue?: T): T | undefined => {
if (section === 'calm.hub' && key === 'url') return hubUrl as T | undefined;
if (section === 'calm.hub' && key === 'autoConnect') return false as T;
return defaultValue;
},
update: async (key: string, value: unknown) => {
configUpdates.push({ key, value });
if (section === 'calm.hub' && key === 'url') {
hubUrl = value as string | undefined;
}
},
});

const window = vscode.window as unknown as {
createStatusBarItem: () => vscode.StatusBarItem;
showQuickPick: (items: Array<{ label: string }>) => Promise<unknown>;
showInputBox: (options: { value?: string }) => Promise<string | undefined>;
};
window.createStatusBarItem = () => {
statusBarItem = {
text: '',
tooltip: undefined,
command: undefined,
backgroundColor: undefined,
show: () => {},
hide: () => {},
dispose: () => {},
};
return statusBarItem;
};
window.showQuickPick = async (items) => items.find((item) => item.label.includes('Clear URL'));
window.showInputBox = async (options) => {
inputValue = options.value;
return undefined;
};

activate(context());
});

describe('CALM Hub Clear URL', () => {
it('forgets the status bar URL and opens an empty reconnect prompt', async () => {
const connect = commands.get('calm.connectToHub');
expect(connect).toBeDefined();

await connect!();
expect(statusBarItem.tooltip).toContain('Failed to connect to https://calmhub.example.com');

await connect!();

expect(hubUrl).toBeUndefined();
expect(configUpdates).toEqual([{ key: 'url', value: undefined }]);
expect(statusBarItem.tooltip).toBe('Click to connect to CalmHub');

await connect!();

expect(inputValue).toBe('');
});

it('keeps the saved URL when Disconnect runs', async () => {
const connect = commands.get('calm.connectToHub');
expect(connect).toBeDefined();

await connect!();
await disconnectFromHub();

expect(hubUrl).toBe('https://calmhub.example.com');
expect(configUpdates).toEqual([]);
expect(statusBarItem.tooltip).toBe(
'Disconnected from https://calmhub.example.com\nClick to reconnect'
);
});
});
16 changes: 13 additions & 3 deletions calm-plugins/vscode/src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async function connectToHub(
: [
{ label: '$(pencil) Change Hub URL', action: 'change' },
{ label: '$(debug-restart) Retry Connection', action: 'retry' },
{ label: '$(sign-out) Clear URL', action: 'disconnect' },
{ label: '$(sign-out) Clear URL', action: 'clearUrl' },
];

const choice = await vscode.window.showQuickPick(items, {
Expand All @@ -167,6 +167,7 @@ async function connectToHub(
});
if (!choice) return;
if (choice.action === 'disconnect') { await disconnectFromHub(); return; }
if (choice.action === 'clearUrl') { await disconnectFromHub({ clearUrl: true }); return; }
if (choice.action === 'namespaces') { await selectNamespaces(); return; }
if (choice.action === 'refresh' || choice.action === 'retry') {
// Fall through to reconnect with existing URL
Expand Down Expand Up @@ -252,15 +253,24 @@ async function connectToHub(
}
}

async function disconnectFromHub(): Promise<void> {
export interface DisconnectOptions {
clearUrl?: boolean;
}

export async function disconnectFromHub(options: DisconnectOptions = {}): Promise<void> {
if (hubAuthService) {
await hubAuthService.signOut();
}
hubClient = undefined;
hubAuthService = undefined;
if (options.clearUrl) {
hubStatusBar?.clearHubUrl();
}
hubStatusBar?.setState('disconnected');
void canvasPanel?.setHubConnection(undefined);
await vscode.workspace.getConfiguration('calm.hub').update('url', undefined, vscode.ConfigurationTarget.Global);
if (options.clearUrl) {
await vscode.workspace.getConfiguration('calm.hub').update('url', undefined, vscode.ConfigurationTarget.Global);
}
outputChannel.appendLine('[INFO] Disconnected from CalmHub');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ describe('HubStatusBar', () => {
expect(mockItem.text).toBe('$(cloud) CALM Hub (7)');
});

it('forgets a cached URL only when it is cleared', () => {
statusBar.setState('connecting', undefined, 'https://calmhub.example.com');
statusBar.setState('error');
statusBar.setState('disconnected');

expect(mockItem.tooltip).toBe(
'Disconnected from https://calmhub.example.com\nClick to reconnect'
);

statusBar.clearHubUrl();

expect(mockItem.tooltip).toBe('Click to connect to CalmHub');
});

it('shows selected vs available with setAvailableAndSelected', () => {
statusBar.setState('connected', 3);
statusBar.setAvailableAndSelected(['ns1', 'ns2', 'ns3'], ['ns1', 'ns3']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export class HubStatusBar {
this.update();
}

clearHubUrl(): void {
this.hubUrl = undefined;
this.update();
}

setAvailableAndSelected(available: string[], selected: string[]): void {
this.availableNamespaces = available;
this.selectedNamespaces = selected;
Expand Down
Loading