-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Oleksandr Dubenko <[email protected]>
- Loading branch information
Showing
4 changed files
with
316 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { KubeObjectClass, KubeObjectInterface } from '../../cluster'; | ||
import { KubeList, KubeListUpdateEvent } from './KubeList'; | ||
|
||
class MockKubeObject implements KubeObjectInterface { | ||
apiVersion = 'v1'; | ||
kind = 'MockKubeObject'; | ||
metadata: any = { | ||
uid: 'mock-uid', | ||
resourceVersion: '1', | ||
}; | ||
|
||
constructor(data: Partial<KubeObjectInterface>) { | ||
Object.assign(this, data); | ||
} | ||
} | ||
|
||
describe('KubeList.applyUpdate', () => { | ||
const itemClass = MockKubeObject as unknown as KubeObjectClass; | ||
const initialList = { | ||
kind: 'MockKubeList', | ||
apiVersion: 'v1', | ||
items: [ | ||
{ apiVersion: 'v1', kind: 'MockKubeObject', metadata: { uid: '1', resourceVersion: '1' } }, | ||
], | ||
metadata: { | ||
resourceVersion: '1', | ||
}, | ||
}; | ||
|
||
it('should add a new item on ADDED event', () => { | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'ADDED', | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '2', resourceVersion: '2' }, | ||
}, | ||
}; | ||
|
||
const updatedList = KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(updatedList.items).toHaveLength(2); | ||
expect(updatedList.items[1].metadata.uid).toBe('2'); | ||
expect(updatedList.items[1] instanceof MockKubeObject).toBe(true); | ||
}); | ||
|
||
it('should modify an existing item on MODIFIED event', () => { | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'MODIFIED', | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '1', resourceVersion: '2' }, | ||
}, | ||
}; | ||
|
||
const updatedList = KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(updatedList.items).toHaveLength(1); | ||
expect(updatedList.items[0].metadata.resourceVersion).toBe('2'); | ||
expect(updatedList.items[0] instanceof MockKubeObject).toBe(true); | ||
}); | ||
|
||
it('should add a new item on MODIFIED event', () => { | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'MODIFIED', | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '3', resourceVersion: '3' }, | ||
}, | ||
}; | ||
|
||
const updatedList = KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(updatedList.items).toHaveLength(2); | ||
expect(updatedList.items[1].metadata.uid).toBe('3'); | ||
expect(updatedList.items[1] instanceof MockKubeObject).toBe(true); | ||
}); | ||
|
||
it('should delete an existing item on DELETED event', () => { | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'DELETED', | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '1', resourceVersion: '2' }, | ||
}, | ||
}; | ||
|
||
const updatedList = KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(updatedList.items).toHaveLength(0); | ||
}); | ||
|
||
it('should log an error on ERROR event', () => { | ||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'ERROR', | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '1', resourceVersion: '2' }, | ||
}, | ||
}; | ||
|
||
KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(consoleErrorSpy).toHaveBeenCalledWith('Error in update', updateEvent); | ||
consoleErrorSpy.mockRestore(); | ||
}); | ||
|
||
it('should log an error on unknown event type', () => { | ||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); | ||
const updateEvent: KubeListUpdateEvent<MockKubeObject> = { | ||
type: 'UNKNOWN' as any, | ||
object: { | ||
apiVersion: 'v1', | ||
kind: 'MockKubeObject', | ||
metadata: { uid: '1', resourceVersion: '2' }, | ||
}, | ||
}; | ||
|
||
KubeList.applyUpdate(initialList, updateEvent, itemClass); | ||
|
||
expect(consoleErrorSpy).toHaveBeenCalledWith('Unknown update type', updateEvent); | ||
consoleErrorSpy.mockRestore(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { KubeObjectEndpoint } from './KubeObjectEndpoint'; | ||
|
||
describe('KubeObjectEndpoint', () => { | ||
describe('toUrl', () => { | ||
it('should generate URL for core resources without namespace', () => { | ||
const endpoint = { version: 'v1', resource: 'pods' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint); | ||
expect(url).toBe('api/v1/pods'); | ||
}); | ||
|
||
it('should generate URL for core resources with namespace', () => { | ||
const endpoint = { version: 'v1', resource: 'pods' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint, 'default'); | ||
expect(url).toBe('api/v1/namespaces/default/pods'); | ||
}); | ||
|
||
it('should generate URL for custom resources without namespace', () => { | ||
const endpoint = { group: 'apps', version: 'v1', resource: 'deployments' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint); | ||
expect(url).toBe('apis/apps/v1/deployments'); | ||
}); | ||
|
||
it('should generate URL for custom resources with namespace', () => { | ||
const endpoint = { group: 'apps', version: 'v1', resource: 'deployments' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint, 'default'); | ||
expect(url).toBe('apis/apps/v1/namespaces/default/deployments'); | ||
}); | ||
|
||
it('should generate URL for custom resources with empty group', () => { | ||
const endpoint = { group: '', version: 'v1', resource: 'services' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint); | ||
expect(url).toBe('api/v1/services'); | ||
}); | ||
|
||
it('should generate URL for custom resources with empty group and namespace', () => { | ||
const endpoint = { group: '', version: 'v1', resource: 'services' }; | ||
const url = KubeObjectEndpoint.toUrl(endpoint, 'default'); | ||
expect(url).toBe('api/v1/namespaces/default/services'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import nock from 'nock'; | ||
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; | ||
import { findKubeconfigByClusterName, getUserIdFromLocalStorage } from '../../../../stateless'; | ||
import { getToken, setToken } from '../../../auth'; | ||
import { getClusterAuthType } from '../v1/clusterRequests'; | ||
import { BASE_HTTP_URL, clusterFetch } from './fetch'; | ||
|
||
vi.mock('../../../auth', () => ({ | ||
getToken: vi.fn(), | ||
setToken: vi.fn(), | ||
})); | ||
|
||
vi.mock('../../../../stateless', () => ({ | ||
findKubeconfigByClusterName: vi.fn(), | ||
getUserIdFromLocalStorage: vi.fn(), | ||
})); | ||
|
||
vi.mock('../v1/clusterRequests', () => ({ | ||
getClusterAuthType: vi.fn(), | ||
})); | ||
|
||
vi.mock('../v1/tokenApi', () => ({ | ||
refreshToken: vi.fn(), | ||
})); | ||
|
||
describe('clusterFetch', () => { | ||
const clusterName = 'test-cluster'; | ||
const testUrl = '/test/url'; | ||
const mockResponse = { message: 'mock response' }; | ||
const token = 'test-token'; | ||
const newToken = 'new-token'; | ||
const kubeconfig = 'mock-kubeconfig'; | ||
const userID = 'mock-user-id'; | ||
|
||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
(getToken as Mock).mockReturnValue(token); | ||
(findKubeconfigByClusterName as Mock).mockResolvedValue(kubeconfig); | ||
(getUserIdFromLocalStorage as Mock).mockReturnValue(userID); | ||
(getClusterAuthType as Mock).mockReturnValue('serviceAccount'); | ||
}); | ||
|
||
afterEach(() => { | ||
nock.cleanAll(); | ||
}); | ||
|
||
it('Successfully makes a request', async () => { | ||
nock(BASE_HTTP_URL).get(`/clusters/${clusterName}${testUrl}`).reply(200, mockResponse); | ||
|
||
const response = await clusterFetch(testUrl, { cluster: clusterName }); | ||
const responseBody = await response.json(); | ||
|
||
expect(responseBody).toEqual(mockResponse); | ||
}); | ||
|
||
it('Sets Authorization header with token', async () => { | ||
nock(BASE_HTTP_URL) | ||
.get(`/clusters/${clusterName}${testUrl}`) | ||
.matchHeader('Authorization', `Bearer ${token}`) | ||
.reply(200, mockResponse); | ||
|
||
await clusterFetch(testUrl, { cluster: clusterName }); | ||
}); | ||
|
||
it('Sets KUBECONFIG and X-HEADLAMP-USER-ID headers if kubeconfig exists', async () => { | ||
nock(BASE_HTTP_URL) | ||
.get(`/clusters/${clusterName}${testUrl}`) | ||
.matchHeader('KUBECONFIG', kubeconfig) | ||
.matchHeader('X-HEADLAMP-USER-ID', userID) | ||
.reply(200, mockResponse); | ||
|
||
await clusterFetch(testUrl, { cluster: clusterName }); | ||
}); | ||
|
||
it('Sets new token if X-Authorization header is present in response', async () => { | ||
nock(BASE_HTTP_URL) | ||
.get(`/clusters/${clusterName}${testUrl}`) | ||
.reply(200, mockResponse, { 'X-Authorization': newToken }); | ||
|
||
await clusterFetch(testUrl, { cluster: clusterName }); | ||
|
||
expect(setToken).toHaveBeenCalledWith(clusterName, newToken); | ||
}); | ||
|
||
it('Throws an error if response is not ok', async () => { | ||
nock(BASE_HTTP_URL).get(`/clusters/${clusterName}${testUrl}`).reply(500); | ||
|
||
await expect(clusterFetch(testUrl, { cluster: clusterName })).rejects.toThrow('Unreachable'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { makeUrl } from './makeUrl'; | ||
|
||
describe('makeUrl', () => { | ||
it('should create a URL from parts without query parameters', () => { | ||
const urlParts = ['http://example.com', 'path', 'to', 'resource']; | ||
const result = makeUrl(urlParts); | ||
expect(result).toBe('http://example.com/path/to/resource'); | ||
}); | ||
|
||
it('should create a URL from parts with query parameters', () => { | ||
const urlParts = ['http://example.com', 'path', 'to', 'resource']; | ||
const query = { key1: 'value1', key2: 'value2' }; | ||
const result = makeUrl(urlParts, query); | ||
expect(result).toBe('http://example.com/path/to/resource?key1=value1&key2=value2'); | ||
}); | ||
|
||
it('should handle empty urlParts', () => { | ||
const urlParts: any[] = []; | ||
const result = makeUrl(urlParts); | ||
expect(result).toBe(''); | ||
}); | ||
|
||
it('should handle empty query parameters', () => { | ||
const urlParts = ['http://example.com', 'path', 'to', 'resource']; | ||
const query = {}; | ||
const result = makeUrl(urlParts, query); | ||
expect(result).toBe('http://example.com/path/to/resource'); | ||
}); | ||
|
||
it('should replace multiple slashes with a single one', () => { | ||
const urlParts = ['http://example.com/', '/path/', '/to/', '/resource']; | ||
const result = makeUrl(urlParts); | ||
expect(result).toBe('http://example.com/path/to/resource'); | ||
}); | ||
|
||
it('should handle special characters in query parameters', () => { | ||
const urlParts = ['http://example.com', 'path', 'to', 'resource']; | ||
const query = { | ||
'key with spaces': 'value with spaces', | ||
'key&with&special&chars': 'value&with&special&chars', | ||
}; | ||
const result = makeUrl(urlParts, query); | ||
expect(result).toBe( | ||
'http://example.com/path/to/resource?key+with+spaces=value+with+spaces&key%26with%26special%26chars=value%26with%26special%26chars' | ||
); | ||
}); | ||
|
||
it('should handle numeric and boolean values in urlParts', () => { | ||
const urlParts = ['http://example.com', 123, true, 'resource']; | ||
const result = makeUrl(urlParts); | ||
expect(result).toBe('http://example.com/123/true/resource'); | ||
}); | ||
}); |