Skip to content
Draft
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
95 changes: 95 additions & 0 deletions src/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,98 @@ test('push image to sandbox does not change title after it is finished', async (

expect(report).not.toHaveBeenCalledWith(expect.objectContaining({ message: 'data-chunk-1' }));
});

describe('connection error property', () => {
let config: KubeConfig;
let capturedConnection: podmanDesktopApi.KubernetesProviderConnection | undefined;
let providerMock: podmanDesktopApi.Provider;
let connectionFactory: podmanDesktopApi.KubernetesProviderConnectionFactory;

beforeEach(async () => {
config = new KubeConfig();
capturedConnection = undefined;

vi.mocked(sandbox.getSignUpStatus).mockResolvedValue({
apiEndpoint: 'https://sandbox-host-url',
username: 'username',
status: {
ready: true,
},
} as unknown as sandbox.SBSignupResponse);
vi.spyOn(openshift, 'getPipelineServiceAccountToken').mockResolvedValue('token');
vi.spyOn(kubeconfig, 'createOrLoadFromFile').mockReturnValue(config);
vi.spyOn(kubeconfig, 'exportToFile').mockImplementation(vi.fn());

providerMock = {
setKubernetesProviderConnectionFactory: vi.fn(),
registerKubernetesProviderConnection: (connection: podmanDesktopApi.KubernetesProviderConnection) => {
capturedConnection = connection;
return {
dispose: vi.fn(),
};
},
} as any as podmanDesktopApi.Provider;
vi.spyOn(podmanDesktopApi.provider, 'createProvider').mockReturnValue(providerMock);
vi.spyOn(podmanDesktopApi.authentication, 'getSession').mockResolvedValue({
id: '1',
accessToken: 'accessTokenString',
idToken: 'idTokenString',
} as unknown as podmanDesktopApi.AuthenticationSession);

await extension.activate(context);
connectionFactory = vi.mocked(providerMock.setKubernetesProviderConnectionFactory).mock.calls[0][0];
});

test('connection error property is accessible via getter', async () => {
await connectionFactory.create?.({
'redhat.sandbox.context.name': 'test-context',
});

expect(capturedConnection).toBeDefined();
expect(capturedConnection?.error).toBeUndefined();
});

test('connection error is set when token validation fails', async () => {
// Mock got to fail token validation
vi.mocked(got).mockRejectedValue(new Error('Token has expired.'));

await connectionFactory.create?.({
'redhat.sandbox.context.name': 'test-context',
});

// Wait for the connection status check to complete
await vi.waitFor(() => {
expect(capturedConnection?.error).toBeDefined();
}, 3000);

expect(capturedConnection?.error).toContain('Token has expired');
});

test('connection error is cleared when connection succeeds', async () => {
// First fail, then succeed
vi.mocked(got)
.mockRejectedValueOnce(new Error('Connection failed'))
.mockResolvedValue({
statusCode: 200,
body: JSON.stringify({ kind: 'User', metadata: { name: 'system:serviceaccount:username-dev:pipeline' } }),
} as any);

await connectionFactory.create?.({
'redhat.sandbox.context.name': 'test-context',
});

// Wait for initial error
await vi.waitFor(() => {
expect(capturedConnection?.error).toBeDefined();
}, 3000);

expect(capturedConnection?.error).toContain('Connection failed');

// Wait for periodic update to clear the error
await vi.waitFor(() => {
expect(capturedConnection?.error).toBeUndefined();
}, 5000);

expect(capturedConnection?.status()).toEqual('started');
});
});
35 changes: 26 additions & 9 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface ConnectionData {
disposable?: extensionApi.Disposable;
connection: extensionApi.KubernetesProviderConnection;
status: extensionApi.ProviderConnectionStatus;
error?: string;
}

const StartedStatus: extensionApi.ProviderConnectionStatus = 'started';
Expand Down Expand Up @@ -148,11 +149,9 @@ async function deleteConnectionAndUpdateKubeconfig(contextName: string): Promise
}

async function registerConnection(contextName: string, apiURL: string, token: string): Promise<ConnectionData> {
// check if cluster is accessible
// const status = await getConnectionStatus(apiURL, token);
const connection = {
const connection: extensionApi.KubernetesProviderConnection = {
name: contextName,
status: () => registeredConnections.get(contextName).status,
status: () => registeredConnections.get(contextName)?.status ?? 'unknown',
endpoint: {
apiURL,
},
Expand All @@ -161,10 +160,24 @@ async function registerConnection(contextName: string, apiURL: string, token: st
return deleteConnectionAndUpdateKubeconfig(contextName);
},
},
get error() {
return registeredConnections.get(contextName)?.error;
},
};
const connectionData: ConnectionData = { connection, status: UnknownStatus };
registeredConnections.set(contextName, connectionData);
connectionData.disposable = provider.registerKubernetesProviderConnection(connection);

// Check initial connection status
try {
const statusResult = await getConnectionStatus(apiURL, token);
connectionData.status = statusResult.status;
connectionData.error = statusResult.error;
} catch (error) {
console.error('Failed to get initial connection status:', error);
connectionData.error = `Failed to verify connection: ${String(error)}`;
}

return connectionData;
}

Expand Down Expand Up @@ -377,8 +390,9 @@ async function updateConnections(): Promise<void> {
// get current token from config file
const token = config.getUser(config.getContextObject(contextName).user).token;
const connectionData = registeredConnections.get(contextName);
return getConnectionStatus(connectionData.connection.endpoint.apiURL, token).then(status => {
connectionData.status = status;
return getConnectionStatus(connectionData.connection.endpoint.apiURL, token).then(result => {
connectionData.status = result.status;
connectionData.error = result.error;
});
});

Expand All @@ -398,14 +412,17 @@ async function updateConnections(): Promise<void> {
);
}

async function getConnectionStatus(apiURL: string, token: string): Promise<extensionApi.ProviderConnectionStatus> {
async function getConnectionStatus(
apiURL: string,
token: string,
): Promise<{ status: extensionApi.ProviderConnectionStatus; error?: string }> {
return isTokenValid(apiURL, token)
.then(() => {
return StartedStatus;
return { status: StartedStatus };
})
.catch(error => {
console.error('Failed to connect to cluster:', error);
return UnknownStatus;
return { status: UnknownStatus, error: String(error) };
});
}

Expand Down