feat: [AH-916]: upstream proxy edit tests (#3404)

* feat: [AH-916]: upstream proxy edit tests
* feat: [AH-916]: refactor upstream proxy create tests
* feat: [AH-916]: add unit tests for create upstream proxy flow
pull/3616/head
Shivanand Sonnad 2025-02-11 09:19:48 +00:00 committed by Harness
parent 12796dc321
commit 4250c0554a
20 changed files with 3231 additions and 177 deletions

View File

@ -0,0 +1,387 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor } from '@testing-library/react'
import type { Registry } from '@harnessio/react-har-service-client'
import { Parent } from '@ar/common/types'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import RepositoryListPage from '@ar/pages/repository-list/RepositoryListPage'
import { MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData } from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
import '../../RepositoryFactory'
const createRegistryFn = jest
.fn()
.mockImplementation(() => Promise.resolve(MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData))
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
const mockHistoryPush = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetAllRegistriesQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
data: { content: { data: { registries: [] }, status: 'SUCCESS' } },
refetch: jest.fn(),
error: null
})),
useCreateRegistryMutation: jest.fn().mockImplementation(() => ({
mutateAsync: createRegistryFn
}))
}))
describe('Verify create docker upstream registry flow', () => {
test('Verify Modal header', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
const modal = await upstreamProxyUtils.openModal(container)
const dialogHeader = screen.getByTestId('modaldialog-header')
expect(dialogHeader).toHaveTextContent('upstreamProxyDetails.createForm.title')
const closeButton = modal.querySelector('button[aria-label="Close"]')
expect(closeButton).toBeInTheDocument()
await userEvent.click(closeButton!)
expect(modal).not.toBeInTheDocument()
})
test('verify registry type selector', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
})
test('verify docker registry create form with success scenario > Source as DockerHub > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'Dockerhub', 'Anonymous')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: { auth: null, authType: 'Anonymous', source: 'Dockerhub', type: 'UPSTREAM', url: '' },
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
test('verify docker registry create form with success scenario > Source as DockerHub > Username Password', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'Dockerhub', 'UserPassword')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
test('verify docker registry create form with success scenario > Source as AWS ECR > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'AwsEcr', 'Anonymous')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: { auth: null, authType: 'Anonymous', source: 'AwsEcr', type: 'UPSTREAM', url: 'https://aws.ecr.com' },
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
test('verify docker registry create form with success scenario > Source as AWS ECR > Access Key and Secret Key', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'AwsEcr', 'AccessKeySecretKey')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: {
accessKey: 'accessKey',
accessKeyType: 'TEXT',
secretKeyIdentifier: 'secretKey',
authType: 'AccessKeySecretKey'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
test('verify docker registry create form with success scenario > Source as Custom > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'Custom', 'Anonymous')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
test('verify docker registry create form with success scenario > Source as Custom > Username Password', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'DOCKER')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(dialogBody, formData, 'Custom', 'UserPassword')
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'docker-up-repo',
packageType: 'DOCKER',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/docker-up-repo/configuration')
})
})
})

View File

@ -0,0 +1,533 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { fireEvent, getByTestId, render, waitFor } from '@testing-library/react'
import { DEFAULT_DATE_TIME_FORMAT } from '@ar/constants'
import { getReadableDateTime } from '@ar/common/dateUtils'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import { Parent, RepositoryPackageType } from '@ar/common/types'
import repositoryFactory from '@ar/frameworks/RepositoryStep/RepositoryFactory'
import { queryByNameAttribute } from 'utils/test/testUtils'
import RepositoryDetailsPage from '../../RepositoryDetailsPage'
import {
MockGetArtifactsByRegistryResponse,
MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData
} from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
const modifyRepository = jest.fn().mockImplementation(
() =>
new Promise(onSuccess => {
onSuccess({ content: { status: 'SUCCESS' } })
})
)
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
const mockHistoryPush = jest.fn()
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData
})),
useGetAllArtifactsByRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetArtifactsByRegistryResponse
})),
useDeleteRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: jest.fn()
})),
useModifyRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: modifyRepository
}))
}))
describe('Verify header section for docker artifact registry', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('Verify breadcrumbs', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = container.querySelector('div[data-testid=page-header]')
expect(pageHeader).toBeInTheDocument()
const breadcrumbsSection = pageHeader?.querySelector('div[class*=PageHeader--breadcrumbsDiv--]')
expect(breadcrumbsSection).toBeInTheDocument()
expect(breadcrumbsSection).toHaveTextContent('breadcrumbs.repositories')
})
test('Verify registry icon, registry name, tag, lables, description and last updated', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
expect(pageHeader).toBeInTheDocument()
expect(pageHeader?.querySelector('span[data-icon=docker-step]')).toBeInTheDocument()
const data = MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data
expect(pageHeader).toHaveTextContent(data.identifier)
expect(pageHeader).toHaveTextContent(getReadableDateTime(Number(data.modifiedAt), DEFAULT_DATE_TIME_FORMAT))
})
test('Verify registry setup client action not visible', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const setupClientBtn = pageHeader.querySelector('button[aria-label="actions.setupClient"]')
expect(setupClientBtn).not.toBeInTheDocument()
})
test('Verify other registry actions', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const actions3DotsBtn = pageHeader.querySelector('span[data-icon=Options')
expect(actions3DotsBtn).toBeInTheDocument()
await userEvent.click(actions3DotsBtn!)
const dialogs = document.getElementsByClassName('bp3-popover')
await waitFor(() => expect(dialogs).toHaveLength(1))
const selectPopover = dialogs[0] as HTMLElement
const items = selectPopover.getElementsByClassName('bp3-menu-item')
for (let idx = 0; idx < items.length; idx++) {
const actionItem = items[idx]
expect(actionItem.querySelector('span[data-icon=code-delete]')).toBeInTheDocument()
expect(actionItem).toHaveTextContent('actions.delete')
}
})
test('Verify tab selection status', async () => {
const { container } = render(
<ArTestWrapper
path="/registries/:repositoryIdentifier/:tab"
pathParams={{ repositoryIdentifier: 'abcd', tab: 'packages' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const tabList = container.querySelector('div[role=tablist]')
expect(tabList).toBeInTheDocument()
const artifactsTab = tabList?.querySelector('div[data-tab-id=packages][aria-selected=true]')
expect(artifactsTab).toBeInTheDocument()
const configurationTab = tabList?.querySelector('div[data-tab-id=configuration][aria-selected=false]')
expect(configurationTab).toBeInTheDocument()
await userEvent.click(configurationTab!)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/abcd/configuration')
})
})
describe('Verify configuration form', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('should render form correctly with all data prefilled', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
// Artifact registry defination section
const registryDefinitionSection = getByTestId(container, 'upstream-registry-definition')
const nameField = queryByNameAttribute('identifier', registryDefinitionSection)
expect(nameField).toBeInTheDocument()
expect(nameField).toBeDisabled()
expect(nameField).toHaveAttribute(
'value',
MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data.identifier
)
const descriptionField = queryByNameAttribute('description', registryDefinitionSection)
expect(descriptionField).toBeInTheDocument()
expect(descriptionField).not.toBeDisabled()
expect(descriptionField).toHaveTextContent(
MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data.description
)
const tags = registryDefinitionSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
tags.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data.labels[idx]
)
})
// verify source selection
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
const sourceSection = sourceAuthSection.querySelector('input[type=radio][name="config.source"][value=Dockerhub]')
expect(sourceSection).toBeChecked()
expect(sourceSection).not.toBeDisabled()
// verify auth type selection
const authTypeSelection = sourceAuthSection.querySelector(
'input[type=radio][name="config.authType"][value=Anonymous]'
)
expect(authTypeSelection).toBeChecked()
expect(authTypeSelection).not.toBeDisabled()
// Security scan section
const securityScanSection = getByTestId(container, 'security-scan-section')
expect(securityScanSection).toBeInTheDocument()
const checkboxes = securityScanSection.querySelectorAll('label.bp3-control.bp3-checkbox')
const supportedScanners =
repositoryFactory.getRepositoryType(RepositoryPackageType.DOCKER)?.getSupportedScanners() || []
checkboxes.forEach((each, idx) => {
const ele = each.querySelector(`input[value=${supportedScanners[idx]}][type=checkbox]`)
expect(ele).toBeInTheDocument()
expect(ele).toBeChecked()
expect(ele).toBeDisabled()
})
// artifact filtering rules
const filteringRulesSection = getByTestId(container, 'include-exclude-patterns-section')
expect(filteringRulesSection).toBeInTheDocument()
const blockedPatternsSection = filteringRulesSection.querySelectorAll('div.bp3-form-group')[1]
const blockedPatterns = blockedPatternsSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
blockedPatterns.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data.blockedPattern[idx]
)
})
// cleanup policy section
const cleanupPoliciesSection = getByTestId(container, 'cleanup-policy-section')
expect(cleanupPoliciesSection).toBeInTheDocument()
const addCleanupPolicyBtn = cleanupPoliciesSection.querySelector(
'a[role=button][aria-label="cleanupPolicy.addBtn"]'
)
expect(addCleanupPolicyBtn).toBeInTheDocument()
expect(addCleanupPolicyBtn).toHaveAttribute('disabled', '')
// action buttons
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).toBeDisabled()
})
test('should able to submit the form with updated data: Success Scenario', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.updateSuccessMessage'
)
})
})
test('Verify source and auth section with multiple scenarios', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS} path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const saveBtn = container.querySelector('button[aria-label=save]')
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
// verify Dockerhub, UserPassword
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Dockerhub',
'UserPassword',
'Dockerhub',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
blockedPattern: ['test3', 'test4'],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify AwsEcr, Anonymous
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'AwsEcr',
'Anonymous',
'Dockerhub',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
blockedPattern: ['test3', 'test4'],
config: {
auth: null,
authType: 'Anonymous',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify AwsEcr, AccessKeySecretKey
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'AwsEcr',
'AccessKeySecretKey',
'Dockerhub',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: 'accessKey',
accessKeyType: 'TEXT',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify Custom, Anonymous
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Custom',
'Anonymous',
'Dockerhub',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
blockedPattern: ['test3', 'test4'],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify Custom, UserPassword
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Custom',
'UserPassword',
'Dockerhub',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
blockedPattern: ['test3', 'test4'],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
})
test('should able to submit the form with updated data: Failure Scenario', async () => {
modifyRepository.mockImplementationOnce(
() =>
new Promise((_, onReject) => {
onReject({ message: 'error message' })
})
)
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showErrorToast).toHaveBeenLastCalledWith('error message')
})
})
})

View File

@ -120,3 +120,26 @@ export const MockGetSetupClientOnRegistryConfigPageResponse = {
status: 'SUCCESS'
}
}
export const MockGetDockerUpstreamRegistryResponseWithDockerhubSourceAllData = {
content: {
data: {
blockedPattern: ['test3', 'test4'],
config: {
auth: null,
authType: 'Anonymous',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
createdAt: '1738516362995',
identifier: 'docker-up-repo',
description: 'test description',
modifiedAt: '1738516362995',
packageType: 'DOCKER',
labels: ['label1', 'label2', 'label3', 'label4'],
url: ''
},
status: 'SUCCESS'
}
}

View File

@ -0,0 +1,323 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor } from '@testing-library/react'
import type { Registry } from '@harnessio/react-har-service-client'
import { Parent } from '@ar/common/types'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import RepositoryListPage from '@ar/pages/repository-list/RepositoryListPage'
import { MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData } from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
import '../../RepositoryFactory'
const createRegistryFn = jest
.fn()
.mockImplementation(() => Promise.resolve(MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData))
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
const mockHistoryPush = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetAllRegistriesQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
data: { content: { data: { registries: [] }, status: 'SUCCESS' } },
refetch: jest.fn(),
error: null
})),
useCreateRegistryMutation: jest.fn().mockImplementation(() => ({
mutateAsync: createRegistryFn
}))
}))
describe('Verify create helm upstream registry flow', () => {
test('Verify Modal header', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
const modal = await upstreamProxyUtils.openModal(container)
const dialogHeader = screen.getByTestId('modaldialog-header')
expect(dialogHeader).toHaveTextContent('upstreamProxyDetails.createForm.title')
const closeButton = modal.querySelector('button[aria-label="Close"]')
expect(closeButton).toBeInTheDocument()
await userEvent.click(closeButton!)
expect(modal).not.toBeInTheDocument()
})
test('verify registry type selector', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'HELM')
})
test('verify helm registry create form with success scenario > Source as Custom > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'HELM')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'Custom',
'Anonymous',
'Custom',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'helm-up-repo',
packageType: 'HELM',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/helm-up-repo/configuration')
})
})
test('verify helm registry create form with success scenario > Source as Custom > Username Password', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'HELM')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'Custom',
'UserPassword',
'Custom',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'helm-up-repo',
packageType: 'HELM',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/helm-up-repo/configuration')
})
})
test('verify helm registry create form with success scenario > Source as AWS ECR > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'HELM')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'AwsEcr',
'Anonymous',
'Custom',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: { auth: null, authType: 'Anonymous', source: 'AwsEcr', type: 'UPSTREAM', url: 'https://aws.ecr.com' },
description: 'test description',
identifier: 'helm-up-repo',
packageType: 'HELM',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/helm-up-repo/configuration')
})
})
test('verify helm registry create form with success scenario > Source as AWS ECR > Access Key and Secret Key', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'HELM')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'AwsEcr',
'AccessKeySecretKey',
'Custom',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: {
accessKey: 'accessKey',
accessKeyType: 'TEXT',
secretKeyIdentifier: 'secretKey',
authType: 'AccessKeySecretKey'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
description: 'test description',
identifier: 'helm-up-repo',
packageType: 'HELM',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/helm-up-repo/configuration')
})
})
})

View File

@ -0,0 +1,460 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { fireEvent, getByTestId, queryByTestId, render, waitFor } from '@testing-library/react'
import { Parent } from '@ar/common/types'
import { DEFAULT_DATE_TIME_FORMAT } from '@ar/constants'
import { getReadableDateTime } from '@ar/common/dateUtils'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import { queryByNameAttribute } from 'utils/test/testUtils'
import RepositoryDetailsPage from '../../RepositoryDetailsPage'
import {
MockGetHelmArtifactsByRegistryResponse,
MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData
} from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
const modifyRepository = jest.fn().mockImplementation(
() =>
new Promise(onSuccess => {
onSuccess({ content: { status: 'SUCCESS' } })
})
)
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
const mockHistoryPush = jest.fn()
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData
})),
useGetAllArtifactsByRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetHelmArtifactsByRegistryResponse
})),
useDeleteRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: jest.fn()
})),
useModifyRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: modifyRepository
}))
}))
describe('Verify header section for docker artifact registry', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('Verify breadcrumbs', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = container.querySelector('div[data-testid=page-header]')
expect(pageHeader).toBeInTheDocument()
const breadcrumbsSection = pageHeader?.querySelector('div[class*=PageHeader--breadcrumbsDiv--]')
expect(breadcrumbsSection).toBeInTheDocument()
expect(breadcrumbsSection).toHaveTextContent('breadcrumbs.repositories')
})
test('Verify registry icon, registry name, tag, lables, description and last updated', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
expect(pageHeader).toBeInTheDocument()
expect(pageHeader?.querySelector('span[data-icon=service-helm]')).toBeInTheDocument()
const data = MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data
expect(pageHeader).toHaveTextContent(data.identifier)
expect(pageHeader).toHaveTextContent(getReadableDateTime(Number(data.modifiedAt), DEFAULT_DATE_TIME_FORMAT))
})
test('Verify registry setup client action not visible', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const setupClientBtn = pageHeader.querySelector('button[aria-label="actions.setupClient"]')
expect(setupClientBtn).not.toBeInTheDocument()
})
test('Verify other registry actions', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const actions3DotsBtn = pageHeader.querySelector('span[data-icon=Options')
expect(actions3DotsBtn).toBeInTheDocument()
await userEvent.click(actions3DotsBtn!)
const dialogs = document.getElementsByClassName('bp3-popover')
await waitFor(() => expect(dialogs).toHaveLength(1))
const selectPopover = dialogs[0] as HTMLElement
const items = selectPopover.getElementsByClassName('bp3-menu-item')
for (let idx = 0; idx < items.length; idx++) {
const actionItem = items[idx]
expect(actionItem.querySelector('span[data-icon=code-delete]')).toBeInTheDocument()
expect(actionItem).toHaveTextContent('actions.delete')
}
})
test('Verify tab selection status', async () => {
const { container } = render(
<ArTestWrapper
path="/registries/:repositoryIdentifier/:tab"
pathParams={{ repositoryIdentifier: 'abcd', tab: 'packages' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const tabList = container.querySelector('div[role=tablist]')
expect(tabList).toBeInTheDocument()
const artifactsTab = tabList?.querySelector('div[data-tab-id=packages][aria-selected=true]')
expect(artifactsTab).toBeInTheDocument()
const configurationTab = tabList?.querySelector('div[data-tab-id=configuration][aria-selected=false]')
expect(configurationTab).toBeInTheDocument()
await userEvent.click(configurationTab!)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/abcd/configuration')
})
})
describe('Verify configuration form', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('should render form correctly with all data prefilled', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
// Artifact registry defination section
const registryDefinitionSection = getByTestId(container, 'upstream-registry-definition')
const nameField = queryByNameAttribute('identifier', registryDefinitionSection)
expect(nameField).toBeInTheDocument()
expect(nameField).toBeDisabled()
expect(nameField).toHaveAttribute(
'value',
MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data.identifier
)
const descriptionField = queryByNameAttribute('description', registryDefinitionSection)
expect(descriptionField).toBeInTheDocument()
expect(descriptionField).not.toBeDisabled()
expect(descriptionField).toHaveTextContent(
MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data.description
)
const tags = registryDefinitionSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
tags.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data.labels[idx]
)
})
// verify source selection
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
const sourceSection = sourceAuthSection.querySelector('input[type=radio][name="config.source"][value=Custom]')
expect(sourceSection).toBeChecked()
expect(sourceSection).not.toBeDisabled()
// verify auth type selection
const authTypeSelection = sourceAuthSection.querySelector(
'input[type=radio][name="config.authType"][value=Anonymous]'
)
expect(authTypeSelection).toBeChecked()
expect(authTypeSelection).not.toBeDisabled()
// Security scan section
const securityScanSection = queryByTestId(container, 'security-scan-section')
expect(securityScanSection).not.toBeInTheDocument()
// artifact filtering rules
const filteringRulesSection = getByTestId(container, 'include-exclude-patterns-section')
expect(filteringRulesSection).toBeInTheDocument()
const allowedPatternsSection = filteringRulesSection.querySelectorAll('div.bp3-form-group')[0]
const allowedPatterns = allowedPatternsSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
allowedPatterns.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data.allowedPattern[idx]
)
})
// cleanup policy section
const cleanupPoliciesSection = getByTestId(container, 'cleanup-policy-section')
expect(cleanupPoliciesSection).toBeInTheDocument()
const addCleanupPolicyBtn = cleanupPoliciesSection.querySelector(
'a[role=button][aria-label="cleanupPolicy.addBtn"]'
)
expect(addCleanupPolicyBtn).toBeInTheDocument()
expect(addCleanupPolicyBtn).toHaveAttribute('disabled', '')
// action buttons
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).toBeDisabled()
})
test('should able to submit the form with updated data: Success Scenario', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
let sourceSection = sourceAuthSection.querySelector('input[type=radio][name="config.authType"][value=UserPassword]')
await userEvent.click(sourceSection!)
sourceSection = sourceAuthSection.querySelector('input[type=radio][name="config.authType"][value=Anonymous]')
await userEvent.click(sourceSection!)
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.updateSuccessMessage'
)
})
})
test('Verify source and auth section with multiple scenarios', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS} path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const saveBtn = container.querySelector('button[aria-label=save]')
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
// verify Custom, UserPassword
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Custom',
'UserPassword',
'Custom',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'helm-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'HELM',
cleanupPolicy: [],
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify AwsEcr, Anonymous
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'AwsEcr',
'Anonymous',
'Custom',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
config: {
auth: null,
authType: 'Anonymous',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'helm-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'HELM',
cleanupPolicy: [],
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify AwsEcr, AccessKeySecretKey
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'AwsEcr',
'AccessKeySecretKey',
'Custom',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
config: {
auth: {
accessKey: 'accessKey',
accessKeyType: 'TEXT',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'helm-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'HELM',
cleanupPolicy: [],
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
})
test('should able to submit the form with updated data: Failure Scenario', async () => {
modifyRepository.mockImplementationOnce(
() =>
new Promise((_, onReject) => {
onReject({ message: 'error message' })
})
)
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showErrorToast).toHaveBeenLastCalledWith('error message')
})
})
})

View File

@ -110,3 +110,27 @@ export const MockGetHelmSetupClientOnRegistryConfigPageResponse = {
status: 'SUCCESS'
}
}
export const MockGetHelmUpstreamRegistryResponseWithCustomSourceAllData = {
content: {
data: {
allowedPattern: ['test1', 'test2'],
cleanupPolicy: [],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
identifier: 'helm-up-repo',
description: 'test description',
modifiedAt: '1738516362995',
packageType: 'HELM',
labels: ['label1', 'label2', 'label3', 'label4'],
url: ''
},
status: 'SUCCESS'
}
}

View File

@ -0,0 +1,318 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor } from '@testing-library/react'
import type { Registry } from '@harnessio/react-har-service-client'
import { Parent } from '@ar/common/types'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import RepositoryListPage from '@ar/pages/repository-list/RepositoryListPage'
import { MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData } from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
import '../../RepositoryFactory'
const createRegistryFn = jest
.fn()
.mockImplementation(() => Promise.resolve(MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData))
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
const mockHistoryPush = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetAllRegistriesQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
data: { content: { data: { registries: [] }, status: 'SUCCESS' } },
refetch: jest.fn(),
error: null
})),
useCreateRegistryMutation: jest.fn().mockImplementation(() => ({
mutateAsync: createRegistryFn
}))
}))
describe('Verify create maven upstream registry flow', () => {
test('Verify Modal header', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
const modal = await upstreamProxyUtils.openModal(container)
const dialogHeader = screen.getByTestId('modaldialog-header')
expect(dialogHeader).toHaveTextContent('upstreamProxyDetails.createForm.title')
const closeButton = modal.querySelector('button[aria-label="Close"]')
expect(closeButton).toBeInTheDocument()
await userEvent.click(closeButton!)
expect(modal).not.toBeInTheDocument()
})
test('verify registry type selector', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'MAVEN')
})
test('verify maven registry create form with success scenario > Source as MavenCentral > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'MAVEN')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'MavenCentral',
'Anonymous',
'MavenCentral',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: { auth: null, authType: 'Anonymous', source: 'MavenCentral', type: 'UPSTREAM', url: '' },
description: 'test description',
identifier: 'maven-up-repo',
packageType: 'MAVEN',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/maven-up-repo/configuration')
})
})
test('verify maven registry create form with success scenario > Source as Maven Central > UserPassword', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'MAVEN')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'MavenCentral',
'UserPassword',
'MavenCentral',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'MavenCentral',
type: 'UPSTREAM',
url: ''
},
description: 'test description',
identifier: 'maven-up-repo',
packageType: 'MAVEN',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/maven-up-repo/configuration')
})
})
test('verify maven registry create form with success scenario > Source as Custom > Anonymous', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'MAVEN')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'Custom',
'Anonymous',
'MavenCentral',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'maven-up-repo',
packageType: 'MAVEN',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/maven-up-repo/configuration')
})
})
test('verify maven registry create form with success scenario > Source as Custom > Username Password', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryListPage />
</ArTestWrapper>
)
await upstreamProxyUtils.openModal(container)
const dialogBody = screen.getByTestId('modaldialog-body')
expect(dialogBody).toBeInTheDocument()
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'MAVEN')
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
const formData = MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data as Registry
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
dialogBody,
formData,
'Custom',
'UserPassword',
'MavenCentral',
'Anonymous'
)
const createButton = await upstreamProxyUtils.getSubmitButton()
await userEvent.click(createButton!)
await waitFor(() => {
expect(createRegistryFn).toHaveBeenLastCalledWith({
body: {
cleanupPolicy: [],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
description: 'test description',
identifier: 'maven-up-repo',
packageType: 'MAVEN',
parentRef: 'undefined',
scanners: []
},
queryParams: { space_ref: 'undefined' }
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
)
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/maven-up-repo/configuration')
})
})
})

View File

@ -0,0 +1,543 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import userEvent from '@testing-library/user-event'
import { fireEvent, getByTestId, queryByTestId, render, waitFor } from '@testing-library/react'
import { Parent } from '@ar/common/types'
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
import { queryByNameAttribute } from 'utils/test/testUtils'
import RepositoryDetailsPage from '../../RepositoryDetailsPage'
import {
MockGetMavenArtifactsByRegistryResponse,
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData
} from './__mockData__'
import upstreamProxyUtils from '../../__tests__/utils'
const modifyRepository = jest.fn().mockImplementation(
() =>
new Promise(onSuccess => {
onSuccess({ content: { status: 'SUCCESS' } })
})
)
const deleteRegistry = jest.fn().mockImplementation(
() =>
new Promise(onSuccess => {
onSuccess({ content: { status: 'SUCCESS' } })
})
)
const showSuccessToast = jest.fn()
const showErrorToast = jest.fn()
jest.mock('@harnessio/uicore', () => ({
...jest.requireActual('@harnessio/uicore'),
useToaster: jest.fn().mockImplementation(() => ({
showSuccess: showSuccessToast,
showError: showErrorToast,
clear: jest.fn()
}))
}))
const mockHistoryPush = jest.fn()
// eslint-disable-next-line jest-no-mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush
})
}))
jest.mock('@harnessio/react-har-service-client', () => ({
useGetRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData
})),
useGetAllArtifactsByRegistryQuery: jest.fn().mockImplementation(() => ({
isFetching: false,
refetch: jest.fn(),
error: false,
data: MockGetMavenArtifactsByRegistryResponse
})),
useDeleteRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: deleteRegistry
})),
useModifyRegistryMutation: jest.fn().mockImplementation(() => ({
isLoading: false,
mutateAsync: modifyRepository
}))
}))
describe('Verify header section for docker artifact registry', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('Verify breadcrumbs', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = container.querySelector('div[data-testid=page-header]')
expect(pageHeader).toBeInTheDocument()
const breadcrumbsSection = pageHeader?.querySelector('div[class*=PageHeader--breadcrumbsDiv--]')
expect(breadcrumbsSection).toBeInTheDocument()
expect(breadcrumbsSection).toHaveTextContent('breadcrumbs.repositories')
})
test('Verify registry icon, registry name, tag, lables, description and last updated', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
expect(pageHeader).toBeInTheDocument()
expect(pageHeader?.querySelector('span[data-icon=maven-repository-type]')).toBeInTheDocument()
const data = MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data
expect(pageHeader).toHaveTextContent(data.identifier)
expect(pageHeader).toHaveTextContent('na')
})
test('Verify registry setup client action not visible', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const setupClientBtn = pageHeader.querySelector('button[aria-label="actions.setupClient"]')
expect(setupClientBtn).not.toBeInTheDocument()
})
test('Verify other registry actions', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const actions3DotsBtn = pageHeader.querySelector('span[data-icon=Options')
expect(actions3DotsBtn).toBeInTheDocument()
await userEvent.click(actions3DotsBtn!)
const dialogs = document.getElementsByClassName('bp3-popover')
await waitFor(() => expect(dialogs).toHaveLength(1))
const selectPopover = dialogs[0] as HTMLElement
const items = selectPopover.getElementsByClassName('bp3-menu-item')
for (let idx = 0; idx < items.length; idx++) {
const actionItem = items[idx]
expect(actionItem.querySelector('span[data-icon=code-delete]')).toBeInTheDocument()
expect(actionItem).toHaveTextContent('actions.delete')
}
})
test('verify delete action: Success', async () => {
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const actions3DotsBtn = pageHeader.querySelector('span[data-icon=Options')
expect(actions3DotsBtn).toBeInTheDocument()
await userEvent.click(actions3DotsBtn!)
const selectPopover = document.getElementsByClassName('bp3-popover')[0] as HTMLElement
const items = selectPopover.getElementsByClassName('bp3-menu-item')
const actionItem = items[0]
expect(actionItem.querySelector('span[data-icon=code-delete]')).toBeInTheDocument()
expect(actionItem).toHaveTextContent('actions.delete')
await userEvent.click(actionItem)
let deleteDialog = document.getElementsByClassName('bp3-dialog')[0]
expect(deleteDialog).toBeInTheDocument()
expect(deleteDialog).toHaveTextContent('upstreamProxyDetails.actions.delete.title')
expect(deleteDialog).toHaveTextContent('upstreamProxyDetails.actions.delete.contentText')
const cancelButton = deleteDialog.querySelector('button[aria-label=cancel]')
expect(cancelButton).toBeInTheDocument()
await userEvent.click(cancelButton!)
expect(deleteDialog).not.toBeInTheDocument()
await userEvent.click(actionItem!)
deleteDialog = document.getElementsByClassName('bp3-dialog')[0]
expect(deleteDialog).toBeInTheDocument()
const deleteBtn = deleteDialog.querySelector('button[aria-label=delete]')
expect(deleteBtn).toBeInTheDocument()
await userEvent.click(deleteBtn!)
await waitFor(() => {
expect(deleteRegistry).toHaveBeenLastCalledWith({ registry_ref: 'undefined/maven-up-repo/+' })
expect(mockHistoryPush).toHaveBeenCalledWith('/registries')
expect(showSuccessToast).toHaveBeenCalledWith('upstreamProxyDetails.actions.delete.repositoryDeleted')
})
expect(deleteDialog).not.toBeInTheDocument()
})
test('verify delete action: Failure', async () => {
deleteRegistry.mockImplementationOnce(
() =>
new Promise((_, onReject) => {
onReject({ message: 'error message' })
})
)
const { container } = render(
<ArTestWrapper>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const pageHeader = getByTestId(container, 'upstream-registry-header-container')
const actions3DotsBtn = pageHeader.querySelector('span[data-icon=Options')
expect(actions3DotsBtn).toBeInTheDocument()
await userEvent.click(actions3DotsBtn!)
const selectPopover = document.getElementsByClassName('bp3-popover')[0] as HTMLElement
const items = selectPopover.getElementsByClassName('bp3-menu-item')
const actionItem = items[0]
expect(actionItem.querySelector('span[data-icon=code-delete]')).toBeInTheDocument()
expect(actionItem).toHaveTextContent('actions.delete')
await userEvent.click(actionItem)
const deleteDialog = document.getElementsByClassName('bp3-dialog')[0]
expect(deleteDialog).toBeInTheDocument()
expect(deleteDialog).toHaveTextContent('upstreamProxyDetails.actions.delete.title')
expect(deleteDialog).toHaveTextContent('upstreamProxyDetails.actions.delete.contentText')
const deleteBtn = deleteDialog.querySelector('button[aria-label=delete]')
expect(deleteBtn).toBeInTheDocument()
await userEvent.click(deleteBtn!)
await waitFor(() => {
expect(deleteRegistry).toHaveBeenLastCalledWith({ registry_ref: 'undefined/maven-up-repo/+' })
expect(mockHistoryPush).not.toHaveBeenCalledWith('/registries')
expect(showErrorToast).toHaveBeenLastCalledWith('error message')
})
expect(deleteDialog).not.toBeInTheDocument()
})
test('Verify tab selection status', async () => {
const { container } = render(
<ArTestWrapper
path="/registries/:repositoryIdentifier/:tab"
pathParams={{ repositoryIdentifier: 'abcd', tab: 'packages' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const tabList = container.querySelector('div[role=tablist]')
expect(tabList).toBeInTheDocument()
const artifactsTab = tabList?.querySelector('div[data-tab-id=packages][aria-selected=true]')
expect(artifactsTab).toBeInTheDocument()
const configurationTab = tabList?.querySelector('div[data-tab-id=configuration][aria-selected=false]')
expect(configurationTab).toBeInTheDocument()
await userEvent.click(configurationTab!)
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/abcd/configuration')
})
})
describe('Verify configuration form', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('should render form correctly with all data prefilled', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
// Artifact registry defination section
const registryDefinitionSection = getByTestId(container, 'upstream-registry-definition')
const nameField = queryByNameAttribute('identifier', registryDefinitionSection)
expect(nameField).toBeInTheDocument()
expect(nameField).toBeDisabled()
expect(nameField).toHaveAttribute(
'value',
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data.identifier
)
const descriptionField = queryByNameAttribute('description', registryDefinitionSection)
expect(descriptionField).toBeInTheDocument()
expect(descriptionField).not.toBeDisabled()
expect(descriptionField).toHaveTextContent(
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data.description
)
const tags = registryDefinitionSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
tags.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data.labels[idx]
)
})
// verify source selection
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
const sourceSection = sourceAuthSection.querySelector('input[type=radio][name="config.source"][value=MavenCentral]')
expect(sourceSection).toBeChecked()
expect(sourceSection).not.toBeDisabled()
// verify auth type selection
const authTypeSelection = sourceAuthSection.querySelector(
'input[type=radio][name="config.authType"][value=Anonymous]'
)
expect(authTypeSelection).toBeChecked()
expect(authTypeSelection).not.toBeDisabled()
// Security scan section
const securityScanSection = queryByTestId(container, 'security-scan-section')
expect(securityScanSection).not.toBeInTheDocument()
// artifact filtering rules
const filteringRulesSection = getByTestId(container, 'include-exclude-patterns-section')
expect(filteringRulesSection).toBeInTheDocument()
const allowedPatternsSection = filteringRulesSection.querySelectorAll('div.bp3-form-group')[0]
const allowedPatterns = allowedPatternsSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
allowedPatterns.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data.allowedPattern[idx]
)
})
const blockedPatternsSection = filteringRulesSection.querySelectorAll('div.bp3-form-group')[1]
const blockedPatterns = blockedPatternsSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
blockedPatterns.forEach((each, idx) => {
expect(each).toHaveTextContent(
MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data.blockedPattern[idx]
)
})
// cleanup policy section
const cleanupPoliciesSection = getByTestId(container, 'cleanup-policy-section')
expect(cleanupPoliciesSection).toBeInTheDocument()
const addCleanupPolicyBtn = cleanupPoliciesSection.querySelector(
'a[role=button][aria-label="cleanupPolicy.addBtn"]'
)
expect(addCleanupPolicyBtn).toBeInTheDocument()
expect(addCleanupPolicyBtn).toHaveAttribute('disabled', '')
// action buttons
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).toBeDisabled()
})
test('should able to submit the form with updated data: Success Scenario', async () => {
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showSuccessToast).toHaveBeenLastCalledWith(
'upstreamProxyDetails.actions.createUpdateModal.updateSuccessMessage'
)
})
})
test('Verify source and auth section with multiple scenarios', async () => {
const { container } = render(
<ArTestWrapper parent={Parent.OSS} path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const saveBtn = container.querySelector('button[aria-label=save]')
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
// verify MavenCentral, UserPassword
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'MavenCentral',
'UserPassword',
'MavenCentral',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
blockedPattern: ['test3', 'test4'],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'MavenCentral',
type: 'UPSTREAM',
url: ''
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'maven-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
packageType: 'MAVEN',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify Custom, Anonymous
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Custom',
'Anonymous',
'MavenCentral',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
blockedPattern: ['test3', 'test4'],
config: {
auth: null,
authType: 'Anonymous',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'maven-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
packageType: 'MAVEN',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
// verify Custom, UserPassword
{
await upstreamProxyUtils.verifySourceAndAuthSection(
sourceAuthSection,
'Custom',
'UserPassword',
'MavenCentral',
'Anonymous'
)
userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
allowedPattern: ['test1', 'test2'],
blockedPattern: ['test3', 'test4'],
config: {
auth: { authType: 'UserPassword', secretIdentifier: 'password', userName: 'username' },
authType: 'UserPassword',
source: 'Custom',
type: 'UPSTREAM',
url: 'https://custom.docker.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'maven-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
packageType: 'MAVEN',
url: ''
},
registry_ref: 'undefined/abcd/+'
})
})
}
})
test('should able to submit the form with updated data: Failure Scenario', async () => {
modifyRepository.mockImplementationOnce(
() =>
new Promise((_, onReject) => {
onReject({ message: 'error message' })
})
)
const { container } = render(
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
<RepositoryDetailsPage />
</ArTestWrapper>
)
const descriptionField = queryByNameAttribute('description', container)
fireEvent.change(descriptionField!, { target: { value: 'updated description' } })
expect(descriptionField).toHaveTextContent('updated description')
const saveBtn = container.querySelector('button[aria-label=save]')
expect(saveBtn).not.toBeDisabled()
const discardBtn = container.querySelector('button[aria-label=discard]')
expect(discardBtn).not.toBeDisabled()
await userEvent.click(saveBtn!)
await waitFor(() => {
expect(modifyRepository).toHaveBeenLastCalledWith({
body: {
...MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData.content.data,
description: 'updated description'
},
registry_ref: 'undefined/abcd/+'
})
expect(showErrorToast).toHaveBeenLastCalledWith('error message')
})
})
})

View File

@ -332,3 +332,26 @@ export const MockGetMavenSetupClientOnRegistryConfigPageResponse = {
status: 'SUCCESS'
}
}
export const MockGetMavenUpstreamRegistryResponseWithMavenCentralSourceAllData = {
content: {
data: {
allowedPattern: ['test1', 'test2'],
blockedPattern: ['test3', 'test4'],
config: {
auth: null,
authType: 'Anonymous',
source: 'MavenCentral',
type: 'UPSTREAM',
url: ''
},
createdAt: '1738516362995',
identifier: 'maven-up-repo',
description: 'test description',
packageType: 'MAVEN',
labels: ['label1', 'label2', 'label3', 'label4'],
url: ''
},
status: 'SUCCESS'
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import userEvent from '@testing-library/user-event'
import type { Registry } from '@harnessio/react-har-service-client'
import { fireEvent, getByTestId, screen } from '@testing-library/react'
import { queryByNameAttribute } from 'utils/test/testUtils'
async function openModal(container: HTMLElement): Promise<Element> {
const pageSubHeader = getByTestId(container, 'page-subheader')
const createRegistryButton = pageSubHeader.querySelector('button span[icon="chevron-down"]')
expect(createRegistryButton).toBeInTheDocument()
await userEvent.click(createRegistryButton!)
const createUpstreamRegistryBtn = screen.getByText('repositoryList.upstreamProxy.label')
await userEvent.click(createUpstreamRegistryBtn!)
const modal = document.getElementsByClassName('bp3-dialog')[0]
expect(modal).toBeInTheDocument()
return modal
}
async function verifyPackageTypeSelector(container: HTMLElement, packageType: string): Promise<void> {
expect(container).toHaveTextContent('repositoryDetails.repositoryForm.selectRepoType')
const packageTypeOption = container.querySelector(`input[type="checkbox"][name=packageType][value=${packageType}]`)
expect(packageTypeOption).not.toBeDisabled()
await userEvent.click(packageTypeOption!)
expect(packageTypeOption).toBeChecked()
}
async function verifySourceAndAuthSection(
container: HTMLElement,
source: string,
authType: string,
defaultSource = 'Dockerhub',
defaultAuthType = 'Anonymous'
) {
// add source
const sourceOption = container.querySelector(`input[type="radio"][name="config.source"][value="${source}"]`)
expect(sourceOption).not.toBeDisabled()
if (defaultSource !== source) {
await userEvent.click(sourceOption!)
}
expect(sourceOption).toBeChecked()
// add registry url
if (source === 'Custom') {
const urlField = queryByNameAttribute('config.url', container)
expect(urlField).toBeInTheDocument()
expect(urlField).not.toBeDisabled()
fireEvent.change(urlField!, { target: { value: 'https://custom.docker.com' } })
} else if (source === 'AwsEcr') {
const urlField = queryByNameAttribute('config.url', container)
expect(urlField).toBeInTheDocument()
expect(urlField).not.toBeDisabled()
fireEvent.change(urlField!, { target: { value: 'https://aws.ecr.com' } })
} else {
const urlField = queryByNameAttribute('config.url', container)
expect(urlField).not.toBeInTheDocument
}
// add auth
const authTypeOption = container.querySelector(`input[type="radio"][name="config.authType"][value="${authType}"]`)
expect(authTypeOption).not.toBeDisabled()
if (defaultAuthType !== authType) {
await userEvent.click(authTypeOption!)
}
expect(authTypeOption).toBeChecked()
if (authType === 'UserPassword') {
// add username password
const usernameField = queryByNameAttribute('config.auth.userName', container)
expect(usernameField).toBeInTheDocument()
fireEvent.change(usernameField!, { target: { value: 'username' } })
const passwordField = queryByNameAttribute('config.auth.secretIdentifier', container)
expect(passwordField).toBeInTheDocument()
fireEvent.change(passwordField!, { target: { value: 'password' } })
} else if (authType === 'AccessKeySecretKey') {
// add access key and secret key
const accessKeyField = queryByNameAttribute('config.auth.accessKey', container)
expect(accessKeyField).toBeInTheDocument()
fireEvent.change(accessKeyField!, { target: { value: 'accessKey' } })
const secretKeyField = queryByNameAttribute('config.auth.secretKeyIdentifier', container)
expect(secretKeyField).toBeInTheDocument()
fireEvent.change(secretKeyField!, { target: { value: 'secretKey' } })
} else {
const usernameField = queryByNameAttribute('config.auth.userName', container)
expect(usernameField).not.toBeInTheDocument()
const passwordField = queryByNameAttribute('config.auth.secretIdentifier', container)
expect(passwordField).not.toBeInTheDocument()
const accessKeyField = queryByNameAttribute('config.auth.accessKey', container)
expect(accessKeyField).not.toBeInTheDocument()
const secretKeyField = queryByNameAttribute('config.auth.secretKeyIdentifier', container)
expect(secretKeyField).not.toBeInTheDocument()
}
}
async function verifyUpstreamProxyCreateForm(
container: HTMLElement,
formData: Registry,
source: string,
authType: string,
defaultSource = 'Dockerhub',
defaultAuthType = 'Anonymous'
): Promise<void> {
// Add name
const nameField = queryByNameAttribute('identifier', container)
expect(nameField).not.toBeDisabled()
fireEvent.change(nameField!, { target: { value: formData.identifier } })
// add description
const descriptionEditButton = getByTestId(container, 'description-edit')
expect(descriptionEditButton).toBeInTheDocument()
await userEvent.click(descriptionEditButton)
const descriptionField = queryByNameAttribute('description', container)
expect(descriptionField).not.toBeDisabled()
fireEvent.change(descriptionField!, { target: { value: formData.description } })
// verify source and auth section
await verifySourceAndAuthSection(container, source, authType, defaultSource, defaultAuthType)
}
async function getSubmitButton(): Promise<Element> {
const dialogFooter = screen.getByTestId('modaldialog-footer')
expect(dialogFooter).toBeInTheDocument()
const createButton = dialogFooter.querySelector(
'button[type="submit"][aria-label="upstreamProxyDetails.createForm.create"]'
)
expect(createButton).toBeInTheDocument()
return createButton!
}
const upstreamProxyUtils = {
openModal,
verifyPackageTypeSelector,
verifyUpstreamProxyCreateForm,
verifySourceAndAuthSection,
getSubmitButton
}
export default upstreamProxyUtils

View File

@ -28,7 +28,7 @@ export default function UpstreamProxyAuthenticationFormContent({
readonly
}: UpstreamProxyAuthenticationFormContentProps): JSX.Element {
return (
<Layout.Vertical spacing="small">
<Layout.Vertical data-testid="upstream-source-auth-definition" spacing="small">
<RepositoryUrlInput readonly={readonly} />
<AuthenticationFormInput readonly={readonly} />
</Layout.Vertical>

View File

@ -44,7 +44,7 @@ export default function UpstreamProxyCleanupPoliciesFormContent(
}
return (
<Layout.Vertical flex={{ alignItems: 'flex-start' }} spacing="xsmall">
<Layout.Vertical data-testid="cleanup-policy-section" flex={{ alignItems: 'flex-start' }} spacing="xsmall">
<Text font={{ variation: FontVariation.CARD_TITLE }}>
{getString('repositoryDetails.repositoryForm.cleanupPoliciesTitle')}
</Text>

View File

@ -36,7 +36,7 @@ export default function UpstreamProxyDetailsFormContent(props: UpstreamProxyDeta
const { description, labels } = values
const { getString } = useStrings()
return (
<Layout.Vertical>
<Layout.Vertical data-testid="upstream-registry-definition">
<FormInput.Text
name="identifier"
label={getString('upstreamProxyDetails.createForm.key')}

View File

@ -35,7 +35,7 @@ export default function UpstreamProxyIncludeExcludePatternFormContent(
const { isEdit, readonly, formikProps } = props
const { getString } = useStrings()
return (
<Layout.Vertical spacing="small">
<Layout.Vertical spacing="small" data-testid="include-exclude-patterns-section">
<Text font={{ variation: FontVariation.CARD_TITLE }}>
{getString('repositoryDetails.repositoryForm.includeExcludePatternsTitle')}
</Text>

View File

@ -0,0 +1,430 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Parent } from '@ar/common/types'
import type { UpstreamRegistryRequest } from '@ar/pages/upstream-proxy-details/types'
import {
getFormattedFormDataForCleanupPolicy,
getFormattedIntialValuesForCleanupPolicy
} from '@ar/components/CleanupPolicyList/utils'
import {
getFormattedFormDataForAuthType,
getFormattedInitialValuesForAuthType,
getReferenceStringFromSecretSpacePath,
getSecretSpacePath
} from '../utils'
const mockUpstreamProxyFormData = {
packageType: 'DOCKER',
identifier: 'test1',
config: {
type: 'UPSTREAM',
source: 'Dockerhub',
url: '',
authType: 'UserPassword',
auth: {
userName: 'admin',
secretIdentifier: {
name: 'prod-shiv-pass',
identifier: 'prod-shiv-pass',
orgIdentifier: 'org',
projectIdentifier: 'project',
type: 'SecretText',
referenceString: 'prod-shiv-pass'
}
}
},
cleanupPolicy: [],
scanners: []
} as UpstreamRegistryRequest
const mockUpstreamProxyFormDataForAwsEcr = {
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: 'accessKey',
accessKeySecretIdentifier: undefined,
accessKeySecretSpacePath: undefined,
accessKeyType: 'TEXT',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: {
identifier: 'secretKey',
name: 'secretKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'secretKey'
}
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
} as UpstreamRegistryRequest
const mockUpstreamProxyData = {
cleanupPolicy: [],
config: {
auth: {
authType: 'UserPassword',
secretIdentifier: 'prod-shiv-pass',
secretSpacePath: 'acc/org/project',
userName: 'admin'
},
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
identifier: 'test1',
packageType: 'DOCKER',
scanners: []
} as UpstreamRegistryRequest
const mockUpstreamProxyDataForAwsEcr = {
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: 'accessKey',
accessKeyType: 'TEXT',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
} as UpstreamRegistryRequest
const scope = {
accountId: 'acc',
orgIdentifier: 'org',
projectIdentifier: 'project',
space: 'acc/org/project'
}
describe('Verify Upstream Proxy Form utils', () => {
test('verify getFormattedFormDataForAuthType', () => {
const updatedValue = getFormattedFormDataForAuthType(mockUpstreamProxyFormData, Parent.Enterprise, scope)
expect(updatedValue).toEqual({
cleanupPolicy: [],
config: {
auth: {
authType: 'UserPassword',
secretIdentifier: 'prod-shiv-pass',
secretSpacePath: 'acc/org/project',
userName: 'admin'
},
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
identifier: 'test1',
packageType: 'DOCKER',
scanners: []
})
})
test('verify getFormattedFormDataForAuthType for aws ecr: Text', () => {
const updatedValue = getFormattedFormDataForAuthType(mockUpstreamProxyFormDataForAwsEcr, Parent.Enterprise, scope)
expect(updatedValue).toEqual({
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: 'accessKey',
accessKeySecretIdentifier: undefined,
accessKeySecretSpacePath: undefined,
accessKeyType: undefined,
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey',
secretKeySpacePath: 'acc/org/project'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
})
})
test('verify getFormattedFormDataForAuthType for aws ecr: Secret', () => {
const updatedValue = getFormattedFormDataForAuthType(
{
...mockUpstreamProxyFormDataForAwsEcr,
config: {
...mockUpstreamProxyFormDataForAwsEcr.config,
auth: {
accessKey: undefined,
accessKeySecretIdentifier: {
identifier: 'accessKey',
name: 'accessKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'accessKey'
},
accessKeyType: 'ENCRYPTED',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: {
identifier: 'secretKey',
name: 'secretKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'secretKey'
}
}
}
},
Parent.Enterprise,
scope
)
expect(updatedValue).toEqual({
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: undefined,
accessKeySecretIdentifier: 'accessKey',
accessKeySecretSpacePath: 'acc/org/project',
accessKeyType: undefined,
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey',
secretKeySpacePath: 'acc/org/project'
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
})
})
test('verify getFormattedFormDataForCleanupPolicy', () => {
const updatedValue = getFormattedFormDataForCleanupPolicy(mockUpstreamProxyFormData)
expect(updatedValue).toEqual({
cleanupPolicy: [],
config: {
auth: {
secretIdentifier: {
identifier: 'prod-shiv-pass',
name: 'prod-shiv-pass',
orgIdentifier: 'org',
projectIdentifier: 'project',
referenceString: 'prod-shiv-pass',
type: 'SecretText'
},
userName: 'admin'
},
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
identifier: 'test1',
packageType: 'DOCKER',
scanners: []
})
})
test('verify getFormattedInitialValuesForAuthType', () => {
const updatedValue = getFormattedInitialValuesForAuthType(mockUpstreamProxyData, Parent.Enterprise)
expect(updatedValue).toEqual({
cleanupPolicy: [],
config: {
auth: {
authType: 'UserPassword',
secretIdentifier: {
identifier: 'prod-shiv-pass',
name: 'prod-shiv-pass',
orgIdentifier: 'org',
projectIdentifier: 'project',
referenceString: 'prod-shiv-pass'
},
secretSpacePath: 'acc/org/project',
userName: 'admin'
},
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
identifier: 'test1',
packageType: 'DOCKER',
scanners: []
})
})
test('verify getFormattedInitialValuesForAuthType for aws ecr: Text', () => {
const updatedValue = getFormattedInitialValuesForAuthType(mockUpstreamProxyDataForAwsEcr, Parent.Enterprise)
expect(updatedValue).toEqual({
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: 'accessKey',
accessKeySecretIdentifier: undefined,
accessKeySecretSpacePath: undefined,
accessKeyType: 'TEXT',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: {
identifier: 'secretKey',
name: 'secretKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'secretKey'
}
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
})
})
test('verify getFormattedInitialValuesForAuthType for aws ecr: Secret', () => {
const updatedValue = getFormattedInitialValuesForAuthType(
{
...mockUpstreamProxyDataForAwsEcr,
config: {
...mockUpstreamProxyDataForAwsEcr.config,
auth: {
accessKeySecretIdentifier: 'accessKey',
accessKeyType: 'ENCRYPTED',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: 'secretKey'
}
}
},
Parent.Enterprise
)
expect(updatedValue).toEqual({
blockedPattern: ['test3', 'test4'],
config: {
auth: {
accessKey: undefined,
accessKeySecretIdentifier: {
identifier: 'accessKey',
name: 'accessKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'accessKey'
},
accessKeyType: 'ENCRYPTED',
authType: 'AccessKeySecretKey',
secretKeyIdentifier: {
identifier: 'secretKey',
name: 'secretKey',
orgIdentifier: undefined,
projectIdentifier: undefined,
referenceString: 'secretKey'
}
},
authType: 'AccessKeySecretKey',
source: 'AwsEcr',
type: 'UPSTREAM',
url: 'https://aws.ecr.com'
},
createdAt: '1738516362995',
description: 'test description',
identifier: 'docker-up-repo',
labels: ['label1', 'label2', 'label3', 'label4'],
modifiedAt: '1738516362995',
packageType: 'DOCKER',
url: ''
})
})
test('verify getFormattedIntialValuesForCleanupPolicy', () => {
const updatedValue = getFormattedIntialValuesForCleanupPolicy(mockUpstreamProxyData)
expect(updatedValue).toEqual({
cleanupPolicy: [],
config: {
auth: {
authType: 'UserPassword',
secretIdentifier: 'prod-shiv-pass',
secretSpacePath: 'acc/org/project',
userName: 'admin'
},
authType: 'UserPassword',
source: 'Dockerhub',
type: 'UPSTREAM',
url: ''
},
identifier: 'test1',
packageType: 'DOCKER',
scanners: []
})
})
test('verify getSecretSpacePath', () => {
let response = getSecretSpacePath('dummy')
expect(response).toEqual(response)
response = getSecretSpacePath('account.dummy', scope)
expect(response).toEqual('acc')
response = getSecretSpacePath('org.dummy', scope)
expect(response).toEqual('acc/org')
response = getSecretSpacePath('dummy', scope)
expect(response).toEqual('acc/org/project')
})
test('verify getReferenceStringFromSecretSpacePath', () => {
let response = getReferenceStringFromSecretSpacePath('dummy', 'acc/org/project')
expect(response).toEqual('dummy')
response = getReferenceStringFromSecretSpacePath('dummy', 'acc/org')
expect(response).toEqual('org.dummy')
response = getReferenceStringFromSecretSpacePath('dummy', 'acc')
expect(response).toEqual('account.dummy')
response = getReferenceStringFromSecretSpacePath('dummy', '')
expect(response).toEqual('dummy')
})
})

View File

@ -1,43 +0,0 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import { noop } from 'lodash-es'
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
import { useParentComponents } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings'
import type { UpstreamProxyActionProps } from './type'
export default function RestoreUpstreamProxy({ data }: UpstreamProxyActionProps): JSX.Element {
const { getString } = useStrings()
const { RbacMenuItem } = useParentComponents()
return (
<RbacMenuItem
icon="command-rollback"
text={getString('actions.restore')}
onClick={noop}
permission={{
resource: {
resourceType: ResourceType.ARTIFACT_REGISTRY,
resourceIdentifier: data.identifier
},
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
}}
/>
)
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react'
import { noop } from 'lodash-es'
import { useParentComponents } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings'
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
import type { UpstreamProxyActionProps } from './type'
export default function ScanUpstreamProxy({ data }: UpstreamProxyActionProps): JSX.Element {
const { getString } = useStrings()
const { RbacMenuItem } = useParentComponents()
return (
<RbacMenuItem
icon="sto-grey"
text={getString('actions.scan')}
onClick={noop}
permission={{
resource: {
resourceType: ResourceType.ARTIFACT_REGISTRY,
resourceIdentifier: data.identifier
},
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY
}}
/>
)
}

View File

@ -42,7 +42,10 @@ export default function UpstreamProxyDetailsHeaderContent(props: UpstreamProxyDe
const { getString } = useStrings()
return (
<Container>
<Layout.Horizontal spacing="medium" flex={{ alignItems: 'center' }}>
<Layout.Horizontal
data-testid="upstream-registry-header-container"
spacing="medium"
flex={{ alignItems: 'center' }}>
<RepositoryIcon packageType={packageType as RepositoryPackageType} iconProps={{ size: iconSize }} />
<Layout.Vertical spacing="small" className={css.nameContainer}>
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>

View File

@ -1,20 +0,0 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum UpstreamProxyDetailsTab {
PACKAGES = 'packages',
CONFIGURATION = 'configuration'
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { createContext } from 'react'
import type { FC, PropsWithChildren } from 'react'
import { noop } from 'lodash-es'
import { Page } from '@harnessio/uicore'
import { useGetRegistryQuery } from '@harnessio/react-har-service-client'
import { useGetSpaceRef } from '@ar/hooks'
import type { UpstreamRegistry } from '../types'
export interface UpstreamProxyConfigurationFormContextProps {
data: UpstreamRegistry | undefined
refetch: () => void
}
export const UpstreamProxyConfigurationFormContext = createContext<UpstreamProxyConfigurationFormContextProps>({
data: undefined,
refetch: noop
})
interface UpstreamProxyConfigurationFormProviderProps {
repoKey: string
className?: string
}
const UpstreamProxyConfigurationFormProvider: FC<PropsWithChildren<UpstreamProxyConfigurationFormProviderProps>> = ({
children,
repoKey,
className
}): JSX.Element => {
const spaceRef = useGetSpaceRef(repoKey)
const {
data: response,
error,
isFetching: loading,
refetch
} = useGetRegistryQuery({
registry_ref: spaceRef
})
return (
<UpstreamProxyConfigurationFormContext.Provider
value={{ data: response?.content?.data as UpstreamProxyConfigurationFormContextProps['data'], refetch }}>
<Page.Body className={className} loading={loading} error={error?.message} retryOnError={() => refetch()}>
{loading ? null : children}
</Page.Body>
</UpstreamProxyConfigurationFormContext.Provider>
)
}
export default UpstreamProxyConfigurationFormProvider