Skip to content
Merged
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
@@ -0,0 +1,215 @@
import { useSelector } from 'react-redux';
import { K8sResourceKind } from '@console/dynamic-plugin-sdk/src';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { ConfigMapModel } from '@console/internal/models';
import { testHook } from '@console/shared/src/test-utils/hooks-utils';
import { USER_SETTING_CONFIGMAP_NAMESPACE } from '../../utils/user-settings';
import { useGetUserSettingConfigMap } from '../useGetUserSettingConfigMap';

// Mock dependencies
const useK8sWatchResourceMock = useK8sWatchResource as jest.Mock;
const useSelectorMock = useSelector as jest.Mock;

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

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
}));

describe('useGetUserSettingConfigMap', () => {
const mockConfigMapData: K8sResourceKind = {
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'user-settings-test-uid',
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
},
data: {
'test.key': 'test value',
},
};

beforeEach(() => {
jest.resetAllMocks();

// Default mock implementation for useK8sWatchResource
useK8sWatchResourceMock.mockImplementation((resource) => {
if (!resource) {
return [null, true, null];
}
return [mockConfigMapData, true, null];
});
});

describe('user identification scenarios', () => {
it('should use impersonated user name when present', () => {
const impersonateUserInfo = {
impersonateName: 'impersonate-user',
uid: 'test-uid',
username: 'test-username',
};

useSelectorMock.mockReturnValue(impersonateUserInfo);
useK8sWatchResourceMock.mockReturnValue([mockConfigMapData, true, null]);

const { result } = testHook(() => useGetUserSettingConfigMap());

// Should use impersonate name directly and return config map data
expect(result.current).toEqual([mockConfigMapData, true, null]);

// Verify the correct resource spec was passed to useK8sWatchResource
expect(useK8sWatchResourceMock).toHaveBeenCalledWith({
kind: ConfigMapModel.kind,
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
isList: false,
name: 'user-settings-impersonate-user',
});
});

it('should use uid when no impersonation and uid is available', () => {
const userInfoWithUid = {
impersonateName: null,
uid: 'test-uid-123',
username: 'test-username',
};

useSelectorMock.mockReturnValue(userInfoWithUid);
useK8sWatchResourceMock.mockReturnValue([mockConfigMapData, true, null]);

const { result } = testHook(() => useGetUserSettingConfigMap());

// Should use uid directly and return config map data
expect(result.current).toEqual([mockConfigMapData, true, null]);

// Verify the correct resource spec was passed
expect(useK8sWatchResourceMock).toHaveBeenCalledWith({
kind: ConfigMapModel.kind,
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
isList: false,
name: 'user-settings-test-uid-123',
});
});

it('should handle empty user info gracefully', () => {
const emptyUserInfo = {
impersonateName: null,
uid: null,
username: null,
};

useSelectorMock.mockReturnValue(emptyUserInfo);

const { result } = testHook(() => useGetUserSettingConfigMap());

// Should return null config map resource when no user identifier
expect(result.current).toEqual([null, true, null]);

// Verify null resource was passed to useK8sWatchResource
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(null);
});

it('should handle username-only scenario', () => {
const userInfoWithUsername = {
impersonateName: null,
uid: null,
username: 'test-username',
};

useSelectorMock.mockReturnValue(userInfoWithUsername);

const { result } = testHook(() => useGetUserSettingConfigMap());

// Initially, should return null since hashing is async
expect(result.current[0]).toBeNull();
});
});

describe('integration with useK8sWatchResource', () => {
it('should pass through loading state', () => {
const userInfoWithUid = {
impersonateName: null,
uid: 'test-uid',
username: 'test-username',
};

useSelectorMock.mockReturnValue(userInfoWithUid);
useK8sWatchResourceMock.mockReturnValue([null, false, null]);

const { result } = testHook(() => useGetUserSettingConfigMap());

expect(result.current).toEqual([null, false, null]);
});

it('should pass through error state', () => {
const userInfoWithUid = {
impersonateName: null,
uid: 'test-uid',
username: 'test-username',
};

const error = new Error('K8s API error');
useSelectorMock.mockReturnValue(userInfoWithUid);
useK8sWatchResourceMock.mockReturnValue([null, false, error]);

const { result } = testHook(() => useGetUserSettingConfigMap());

expect(result.current).toEqual([null, false, error]);
});

it('should pass through successful data', () => {
const userInfoWithUid = {
impersonateName: null,
uid: 'test-uid',
username: 'test-username',
};

useSelectorMock.mockReturnValue(userInfoWithUid);
useK8sWatchResourceMock.mockReturnValue([mockConfigMapData, true, null]);

const { result } = testHook(() => useGetUserSettingConfigMap());

expect(result.current).toEqual([mockConfigMapData, true, null]);
});

it('should create ConfigMap resource with correct parameters', () => {
const userInfo = {
impersonateName: null,
uid: 'test-uid-456',
username: 'test-user',
};

useSelectorMock.mockReturnValue(userInfo);
useK8sWatchResourceMock.mockReturnValue([mockConfigMapData, true, null]);

testHook(() => useGetUserSettingConfigMap());

// Verify useK8sWatchResource was called with correct resource spec
expect(useK8sWatchResourceMock).toHaveBeenCalledWith({
kind: ConfigMapModel.kind,
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
isList: false,
name: 'user-settings-test-uid-456',
});
});

it('should use null resource when no user identifier is available', () => {
const emptyUserInfo = {
impersonateName: null,
uid: null,
username: '',
};

useSelectorMock.mockReturnValue(emptyUserInfo);
useK8sWatchResourceMock.mockReturnValue([null, true, null]);

const { result } = testHook(() => useGetUserSettingConfigMap());

// Should call useK8sWatchResource with null
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(null);
expect(result.current).toEqual([null, true, null]);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createHash } from 'crypto';
import { useMemo } from 'react';
import { useMemo, useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { getImpersonate, getUser, K8sResourceKind } from '@console/dynamic-plugin-sdk/src';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
Expand All @@ -8,26 +7,47 @@ import { RootState } from '@console/internal/redux';
import { USER_SETTING_CONFIGMAP_NAMESPACE } from '../utils/user-settings';

export const useGetUserSettingConfigMap = () => {
const hashNameOrKubeadmin = (name: string): string | null => {
const [hashedUsername, setHashedUsername] = useState<string | null>(null);

const hashNameOrKubeadmin = async (name: string): Promise<string | null> => {
if (!name) {
return null;
}

if (name === 'kube:admin') {
return 'kubeadmin';
}
const hash = createHash('sha256');
hash.update(name);
return hash.digest('hex');

const encoder = new TextEncoder();
const data = encoder.encode(name);
const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
};
// User and impersonate
const userUid = useSelector((state: RootState) => {

// User and impersonate info
const userInfo = useSelector((state: RootState) => {
const impersonateName = getImpersonate(state)?.name;
const { uid, username } = getUser(state) ?? {};
const hashName = hashNameOrKubeadmin(username);
return impersonateName || uid || hashName || '';
return { impersonateName, uid, username };
});

// Hash the username asynchronously
useEffect(() => {
if (userInfo.username) {
hashNameOrKubeadmin(userInfo.username)
.then(setHashedUsername)
.catch(() => {
setHashedUsername(null);
});
} else {
setHashedUsername(null);
}
}, [userInfo.username]);

// Compute the final user UID
const userUid = userInfo.impersonateName || userInfo.uid || hashedUsername || '';

const configMapResource = useMemo(
() =>
!userUid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ Feature: Create the different workloads from Add page
| Middleware | Apache HTTP Server | httpd-example |
| Other | Nginx HTTP server and a reverse proxy | nginx-example |

@broken-test
Scenario Outline: Deploy <image> image with Runtime icon from external registry: A-02-TC02
Given user is at Deploy Image page
When user enters Image name from external registry as "<image_name>"
Expand Down Expand Up @@ -83,7 +82,6 @@ Feature: Create the different workloads from Add page
| image_name | custom_icon | name |
| ghcr.io/logonoff/fortune-cowsay-motd:latest | https://i.imgur.com/cxiObse.jpeg | fortune-cowsay-motd |

@broken-test
Scenario: Edit Runtime Icon while Editing Image: A-02-TC05
Given user has deployed container Image "openshift/hello-openshift" from external registry
And user is at Topology page
Expand Down Expand Up @@ -165,7 +163,7 @@ Feature: Create the different workloads from Add page

@regression
Scenario: Quick Starts page when Quick Start has completed: QS-03-TC03
When user selects QuickStarts from the help menu icon on the masthead
When user selects QuickStarts from the help menu icon on the masthead
And user has completed "Get started with a sample application" Quick Start
Then user can see time taken to complete the "Get started with a sample application" tour on the card
And user can see Complete label on "Get started with a sample application" card
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const gitPage = {
cy.get(gitPO.appName).click();
cy.get('[data-test="console-select-search-input"]').type(appName);
cy.get('[data-test="console-select-menu-list"]').then(($el) => {
if ($el.find('[data-test="console-select-item"]').length === 0) {
if ($el.find(`[data-test-dropdown-menu="${appName}"]`).length === 0) {
cy.byTestDropDownMenu('#CREATE_APPLICATION_KEY#').click();
cy.get('#form-input-application-name-field')
.clear()
Expand Down