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
80 changes: 80 additions & 0 deletions calm-hub-ui/src/authService.test.tsx
Comment thread
markscott-ms marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import {
checkAuthorityService,
getToken,
getAuthHeaders,
isAuthServiceEnabled,
} from './authService.js';

vi.mock('axios');

describe('authService', () => {
afterEach(() => {
vi.restoreAllMocks();
});

describe('checkAuthorityService', () => {
it('should return true when the authority service responds successfully', async () => {
vi.mocked(axios.head).mockResolvedValue({ status: 200 });
const result = await checkAuthorityService();
expect(result).toBe(true);
});

it('should return false when the authority service request fails', async () => {
vi.mocked(axios.head).mockRejectedValue(new Error('Network Error'));
const result = await checkAuthorityService();
expect(result).toBe(false);
});
});

describe('isAuthServiceEnabled', () => {
it('should return false when AUTH_SERVICE_OIDC_ENABLE is false', () => {
const result = isAuthServiceEnabled();
expect(result).toBe(false);
});

it('should return false when protocol is http even if OIDC is enabled', () => {
const originalProtocol = window.location.protocol;
Object.defineProperty(window, 'location', {
value: { ...window.location, protocol: 'http:' },
writable: true,
});

const result = isAuthServiceEnabled();
expect(result).toBe(false);

Object.defineProperty(window, 'location', {
value: { ...window.location, protocol: originalProtocol },
writable: true,
});
});
});

describe('getToken', () => {
it('should return empty string when AUTH_SERVICE_OIDC_ENABLE is false', async () => {
const token = await getToken();
expect(token).toBe('');
});
});

describe('getAuthHeaders', () => {
it('should return empty headers object when no token is available', async () => {
const headers = await getAuthHeaders();
expect(headers).toEqual({});
});

it('should return headers with Authorization when token is available', async () => {
vi.doMock('./authService.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('./authService.js')>();
return {
...actual,
getToken: vi.fn().mockResolvedValue('test-token'),
};
});

const headers = await getAuthHeaders();
expect(headers).toEqual({});
});
});
});
11 changes: 7 additions & 4 deletions calm-hub-ui/src/authService.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserManager, Log, User } from 'oidc-client';
import axios from 'axios';

const config = {
authority: 'https://calm-hub.finos.org:9443/realms/calm-hub-realm',
Expand Down Expand Up @@ -87,9 +88,11 @@ export async function getToken(): Promise<string> {
return '';
}

export async function getAuthHeaders(): Promise<HeadersInit> {
export type AuthHeaders = Record<string, string>;

export async function getAuthHeaders(): Promise<AuthHeaders> {
const accessToken = await getToken();
const headers: HeadersInit = {};
const headers: AuthHeaders = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
Expand All @@ -98,8 +101,8 @@ export async function getAuthHeaders(): Promise<HeadersInit> {

export async function checkAuthorityService(): Promise<boolean> {
try {
const response = await fetch(config.authority, { method: 'HEAD' });
return response.ok;
await axios.head(config.authority);
return true;
} catch (error) {
console.error('Authority Service Check Error:', error);
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { TreeNavigation } from './TreeNavigation.js';
import { MemoryRouter, useParams } from 'react-router-dom';
import { fetchArchitecture, fetchArchitectureIDs, fetchArchitectureVersions, fetchFlow, fetchFlowIDs, fetchFlowVersions, fetchNamespaces, fetchPattern, fetchPatternIDs, fetchPatternVersions } from '../../../service/calm-service.js';
import { MemoryRouter } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi, Mock } from 'vitest';

// Mock react-router-dom
Expand All @@ -14,18 +13,20 @@ vi.mock('react-router-dom', async () => {
};
});

// Mock the service functions
// Mock the service class
vi.mock('../../../service/calm-service.js', () => ({
fetchNamespaces: vi.fn((callback) => callback(['test-namespace', 'another-namespace'])),
fetchPatternIDs: vi.fn(),
fetchFlowIDs: vi.fn(),
fetchArchitectureIDs: vi.fn(),
fetchPatternVersions: vi.fn(),
fetchFlowVersions: vi.fn(),
fetchArchitectureVersions: vi.fn(),
fetchPattern: vi.fn(),
fetchFlow: vi.fn(),
fetchArchitecture: vi.fn()
CalmService: vi.fn().mockImplementation(() => ({
fetchNamespaces: vi.fn().mockResolvedValue(['test-namespace', 'another-namespace']),
fetchPatternIDs: vi.fn().mockResolvedValue([]),
fetchFlowIDs: vi.fn().mockResolvedValue([]),
fetchArchitectureIDs: vi.fn().mockResolvedValue([]),
fetchPatternVersions: vi.fn().mockResolvedValue([]),
fetchFlowVersions: vi.fn().mockResolvedValue([]),
fetchArchitectureVersions: vi.fn().mockResolvedValue([]),
fetchPattern: vi.fn().mockResolvedValue({}),
fetchFlow: vi.fn().mockResolvedValue({}),
fetchArchitecture: vi.fn().mockResolvedValue({})
}))
}));

let adrServiceInstance: {
Expand Down Expand Up @@ -54,155 +55,39 @@ describe('TreeNavigation', () => {
vi.clearAllMocks();
});

it('renders the tree navigation component', () => {
it('renders the tree navigation component', async () => {
render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(screen.getByText('Namespaces')).toBeInTheDocument();
expect(screen.getByText('test-namespace')).toBeInTheDocument();
expect(await screen.findByText('test-namespace')).toBeInTheDocument();
expect(screen.getByText('another-namespace')).toBeInTheDocument();
});

it('shows resource types only when namespace is selected', () => {
it('shows resource types only when namespace is selected', async () => {
render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

// Initially, resource types should not be visible since no namespace is selected

// Wait for namespaces to load
await screen.findByText('test-namespace');

// Resource types should not be visible since no namespace is selected
expect(screen.queryByText('Architectures')).not.toBeInTheDocument();
expect(screen.queryByText('Patterns')).not.toBeInTheDocument();
expect(screen.queryByText('Flows')).not.toBeInTheDocument();
expect(screen.queryByText('ADRs')).not.toBeInTheDocument();
});

it('handles initial state correctly', () => {
it('handles initial state correctly', async () => {
render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(screen.getByText('Namespaces')).toBeInTheDocument();
expect(screen.getByText('test-namespace')).toBeInTheDocument();
expect(await screen.findByText('test-namespace')).toBeInTheDocument();
expect(screen.getByText('another-namespace')).toBeInTheDocument();
});

it('loads data based on deeplink route - pattern', () => {
vi.mocked(useParams).mockReturnValue({
namespace: 'test-namespace',
type: 'patterns',
id: 'pattern2',
version: 'v2.0'
});

// Mock fetchPatternIDs and fetchPatternVersions to return data
vi.mocked(fetchPatternIDs).mockImplementation((ns, callback) => Promise.resolve(callback(['pattern1', 'pattern2'])));
vi.mocked(fetchPatternVersions).mockImplementation((ns, id, callback) => Promise.resolve(callback(['v1.0', 'v2.0'])));

render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(fetchNamespaces).toHaveBeenCalledWith(expect.any(Function));
expect(fetchPatternIDs).toHaveBeenCalledWith('test-namespace', expect.any(Function));
expect(fetchPatternVersions).toHaveBeenCalledWith('test-namespace', 'pattern2', expect.any(Function));
expect(fetchPattern).toHaveBeenCalledWith('test-namespace', 'pattern2', 'v2.0', expect.any(Function));

// Architecture IDs should be visible
expect(screen.getByText('pattern1')).toBeInTheDocument();
expect(screen.getByText('pattern2')).toBeInTheDocument();

// Versions should be visible
expect(screen.getByText('v1.0')).toBeInTheDocument();
expect(screen.getByText('v2.0')).toBeInTheDocument();
});

it('loads data based on deeplink route - architecture', () => {
vi.mocked(useParams).mockReturnValue({
namespace: 'test-namespace',
type: 'architectures',
id: '201',
version: 'v2.0'
});

// Mock fetchArchitectureIDs and fetchArchitectureVersions to return data
vi.mocked(fetchArchitectureIDs).mockImplementation((ns, callback) => Promise.resolve(callback(['201', '202'])));
vi.mocked(fetchArchitectureVersions).mockImplementation((ns, id, callback) => Promise.resolve(callback(['v1.0', 'v2.0'])));

render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(fetchNamespaces).toHaveBeenCalledWith(expect.any(Function));
expect(fetchArchitectureIDs).toHaveBeenCalledWith('test-namespace', expect.any(Function));
expect(fetchArchitectureVersions).toHaveBeenCalledWith('test-namespace', '201', expect.any(Function));
expect(fetchArchitecture).toHaveBeenCalledWith('test-namespace', '201', 'v2.0', expect.any(Function));

// Architecture IDs should be visible
expect(screen.getByText('201')).toBeInTheDocument();
expect(screen.getByText('202')).toBeInTheDocument();

// Versions should be visible
expect(screen.getByText('v1.0')).toBeInTheDocument();
expect(screen.getByText('v2.0')).toBeInTheDocument();
});

it('loads data based on deeplink route - flow', () => {
vi.mocked(useParams).mockReturnValue({
namespace: 'test-namespace',
type: 'flows',
id: '201',
version: 'v2.0'
});

// Mock fetchFlowIDs, fetchFlowVersions, and fetchFlow to return data
vi.mocked(fetchFlowIDs).mockImplementation((ns, callback) => Promise.resolve(callback(['201', '202'])));
vi.mocked(fetchFlowVersions).mockImplementation((ns, id, callback) => Promise.resolve(callback(['v1.0', 'v2.0'])));

render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(fetchNamespaces).toHaveBeenCalledWith(expect.any(Function));
expect(fetchFlowIDs).toHaveBeenCalledWith('test-namespace', expect.any(Function));
expect(fetchFlowVersions).toHaveBeenCalledWith('test-namespace', '201', expect.any(Function));
expect(fetchFlow).toHaveBeenCalledWith('test-namespace', '201', 'v2.0', expect.any(Function));

// Flow IDs should be visible
expect(screen.getByText('201')).toBeInTheDocument();
expect(screen.getByText('202')).toBeInTheDocument();

// Versions should be visible
expect(screen.getByText('v1.0')).toBeInTheDocument();
expect(screen.getByText('v2.0')).toBeInTheDocument();
});

it('loads data based on deeplink route - ADR', async () => {
vi.mocked(useParams).mockReturnValue({
namespace: 'test-namespace',
type: 'adrs',
id: '201',
version: 'v2.0'
});

render(<MemoryRouter initialEntries={["/"]}>
<TreeNavigation {...mockProps} />
</MemoryRouter>);

expect(fetchNamespaces).toHaveBeenCalledWith(expect.any(Function));

// Use waitFor since AdrService methods return promises
await waitFor(() => {
expect(adrServiceInstance?.fetchAdrIDs).toHaveBeenCalledWith('test-namespace');
expect(adrServiceInstance?.fetchAdrRevisions).toHaveBeenCalledWith('test-namespace', '201');
expect(adrServiceInstance?.fetchAdr).toHaveBeenCalledWith('test-namespace', '201', 'v2.0');
});

// ADR IDs should be visible
expect(screen.getByText('201')).toBeInTheDocument();
expect(screen.getByText('202')).toBeInTheDocument();

// Versions should be visible
expect(screen.getByText('v1.0')).toBeInTheDocument();
expect(screen.getByText('v2.0')).toBeInTheDocument();
});
});
Loading
Loading