mirror of https://github.com/harness/drone.git
feat: [AH-969]: add new package type NPM (#3463)
* feat: [AH-969]: add new package type NPM * feat: [AH-969]: Support NPM in create/edit registry and create/edit upstream registryjobatzil/login/xforwardedfor
parent
3a1348893d
commit
620a6cbb25
|
@ -51,7 +51,7 @@
|
|||
"@codemirror/view": "^6.9.6",
|
||||
"@harnessio/design-system": "^2.1.1",
|
||||
"@harnessio/icons": "^2.1.9",
|
||||
"@harnessio/react-har-service-client": "^0.10.0",
|
||||
"@harnessio/react-har-service-client": "^0.12.0",
|
||||
"@harnessio/react-ssca-manager-client": "^0.65.0",
|
||||
"@harnessio/uicore": "^4.1.2",
|
||||
"@tanstack/react-query": "4.20.4",
|
||||
|
|
|
@ -133,5 +133,6 @@ export interface MFEAppProps {
|
|||
}
|
||||
|
||||
export enum FeatureFlags {
|
||||
HAR_TRIGGERS = 'HAR_TRIGGERS'
|
||||
HAR_TRIGGERS = 'HAR_TRIGGERS',
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED = 'HAR_NPM_PACKAGE_TYPE_ENABLED'
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import type { IconName } from '@harnessio/icons'
|
||||
import type { FeatureFlags } from '@ar/MFEAppTypes'
|
||||
import { FeatureFlags } from '@ar/MFEAppTypes'
|
||||
import type { StringsMap } from '@ar/frameworks/strings'
|
||||
import { RepositoryPackageType } from '@ar/common/types'
|
||||
import { useFeatureFlags } from './useFeatureFlag'
|
||||
|
@ -70,6 +70,7 @@ const RepositoryTypes: RepositoryTypeListItem[] = [
|
|||
value: RepositoryPackageType.NPM,
|
||||
icon: 'npm-repository-type',
|
||||
tooltip: 'Coming Soon!',
|
||||
featureFlag: FeatureFlags.HAR_NPM_PACKAGE_TYPE_ENABLED,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import type { IconName } from '@harnessio/icons'
|
||||
|
||||
import type { FeatureFlags } from '@ar/MFEAppTypes'
|
||||
import { FeatureFlags } from '@ar/MFEAppTypes'
|
||||
import type { StringsMap } from '@ar/frameworks/strings'
|
||||
import { UpstreamProxyPackageType } from '@ar/pages/upstream-proxy-details/types'
|
||||
|
||||
|
@ -61,5 +61,13 @@ export const UpstreamProxyPackageTypeList: UpstreamRepositoryPackageTypeListItem
|
|||
label: 'repositoryTypes.maven',
|
||||
value: UpstreamProxyPackageType.MAVEN,
|
||||
icon: 'maven-repository-type'
|
||||
},
|
||||
{
|
||||
label: 'repositoryTypes.npm',
|
||||
value: UpstreamProxyPackageType.NPM,
|
||||
icon: 'npm-repository-type',
|
||||
tooltip: 'Coming Soon!',
|
||||
featureFlag: FeatureFlags.HAR_NPM_PACKAGE_TYPE_ENABLED,
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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 type { IconName } from '@harnessio/icons'
|
||||
|
||||
import { RepositoryConfigType, RepositoryPackageType } from '@ar/common/types'
|
||||
import UpstreamProxyActions from '@ar/pages/upstream-proxy-details/components/UpstreamProxyActions/UpstreamProxyActions'
|
||||
import UpstreamProxyConfigurationForm from '@ar/pages/upstream-proxy-details/components/Forms/UpstreamProxyConfigurationForm'
|
||||
import UpstreamProxyCreateFormContent from '@ar/pages/upstream-proxy-details/components/FormContent/UpstreamProxyCreateFormContent'
|
||||
import UpstreamProxyDetailsHeader from '@ar/pages/upstream-proxy-details/components/UpstreamProxyDetailsHeader/UpstreamProxyDetailsHeader'
|
||||
import {
|
||||
type CreateRepositoryFormProps,
|
||||
type RepositoryActionsProps,
|
||||
type RepositoryConfigurationFormProps,
|
||||
type RepositoryDetailsHeaderProps,
|
||||
RepositoryStep,
|
||||
type RepositoySetupClientProps
|
||||
} from '@ar/frameworks/RepositoryStep/Repository'
|
||||
import {
|
||||
UpstreamProxyAuthenticationMode,
|
||||
type UpstreamRegistryRequest,
|
||||
UpstreamRepositoryURLInputSource
|
||||
} from '@ar/pages/upstream-proxy-details/types'
|
||||
|
||||
import type { Repository, VirtualRegistryRequest } from '../types'
|
||||
import RepositoryActions from '../components/Actions/RepositoryActions'
|
||||
import RedirectPageView from '../components/RedirectPageView/RedirectPageView'
|
||||
import SetupClientContent from '../components/SetupClientContent/SetupClientContent'
|
||||
import RepositoryConfigurationForm from '../components/Forms/RepositoryConfigurationForm'
|
||||
import RepositoryCreateFormContent from '../components/FormContent/RepositoryCreateFormContent'
|
||||
import RepositoryDetailsHeader from '../components/RepositoryDetailsHeader/RepositoryDetailsHeader'
|
||||
|
||||
export class NpmRepositoryType extends RepositoryStep<VirtualRegistryRequest> {
|
||||
protected packageType = RepositoryPackageType.NPM
|
||||
protected repositoryName = 'NPM Repository'
|
||||
protected repositoryIcon: IconName = 'npm-repository-type'
|
||||
protected supportedScanners = []
|
||||
protected supportsUpstreamProxy = true
|
||||
protected supportedUpstreamURLSources = [
|
||||
UpstreamRepositoryURLInputSource.NpmJS,
|
||||
UpstreamRepositoryURLInputSource.Custom
|
||||
]
|
||||
|
||||
protected defaultValues: VirtualRegistryRequest = {
|
||||
packageType: RepositoryPackageType.NPM,
|
||||
identifier: '',
|
||||
config: {
|
||||
type: RepositoryConfigType.VIRTUAL
|
||||
},
|
||||
scanners: []
|
||||
}
|
||||
|
||||
protected defaultUpstreamProxyValues: UpstreamRegistryRequest = {
|
||||
packageType: RepositoryPackageType.NPM,
|
||||
identifier: '',
|
||||
config: {
|
||||
type: RepositoryConfigType.UPSTREAM,
|
||||
source: UpstreamRepositoryURLInputSource.NpmJS,
|
||||
authType: UpstreamProxyAuthenticationMode.ANONYMOUS,
|
||||
url: ''
|
||||
},
|
||||
cleanupPolicy: [],
|
||||
scanners: []
|
||||
}
|
||||
|
||||
renderCreateForm(props: CreateRepositoryFormProps): JSX.Element {
|
||||
const { type } = props
|
||||
if (type === RepositoryConfigType.VIRTUAL) {
|
||||
return <RepositoryCreateFormContent isEdit={false} />
|
||||
} else {
|
||||
return <UpstreamProxyCreateFormContent isEdit={false} readonly={false} />
|
||||
}
|
||||
}
|
||||
|
||||
renderCofigurationForm(props: RepositoryConfigurationFormProps<Repository>): JSX.Element {
|
||||
const { type } = props
|
||||
if (type === RepositoryConfigType.VIRTUAL) {
|
||||
return <RepositoryConfigurationForm ref={props.formikRef} readonly={props.readonly} />
|
||||
} else {
|
||||
return <UpstreamProxyConfigurationForm ref={props.formikRef} readonly={props.readonly} />
|
||||
}
|
||||
}
|
||||
|
||||
renderActions(props: RepositoryActionsProps<Repository>): JSX.Element {
|
||||
if (props.type === RepositoryConfigType.VIRTUAL) {
|
||||
return <RepositoryActions data={props.data} readonly={props.readonly} pageType={props.pageType} />
|
||||
}
|
||||
return <UpstreamProxyActions data={props.data} readonly={props.readonly} pageType={props.pageType} />
|
||||
}
|
||||
|
||||
renderSetupClient(props: RepositoySetupClientProps): JSX.Element {
|
||||
const { repoKey, onClose, artifactKey, versionKey } = props
|
||||
return (
|
||||
<SetupClientContent
|
||||
repoKey={repoKey}
|
||||
artifactKey={artifactKey}
|
||||
versionKey={versionKey}
|
||||
onClose={onClose}
|
||||
packageType={RepositoryPackageType.NPM}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderRepositoryDetailsHeader(props: RepositoryDetailsHeaderProps<Repository>): JSX.Element {
|
||||
const { type } = props
|
||||
if (type === RepositoryConfigType.VIRTUAL) {
|
||||
return <RepositoryDetailsHeader data={props.data} />
|
||||
} else {
|
||||
return <UpstreamProxyDetailsHeader data={props.data} />
|
||||
}
|
||||
}
|
||||
|
||||
renderRedirectPage(): JSX.Element {
|
||||
return <RedirectPageView />
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* 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, getByText, render, screen, waitFor } from '@testing-library/react'
|
||||
|
||||
import ArTestWrapper from '@ar/utils/testUtils/ArTestWrapper'
|
||||
import RepositoryListPage from '@ar/pages/repository-list/RepositoryListPage'
|
||||
|
||||
import { queryByNameAttribute } from 'utils/test/testUtils'
|
||||
import { MockGetNpmRegistryResponseWithAllData } from './__mockData__'
|
||||
import '../../RepositoryFactory'
|
||||
|
||||
const createRegistryFn = jest.fn().mockImplementation(() => Promise.resolve(MockGetNpmRegistryResponseWithAllData))
|
||||
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 npm registry flow', () => {
|
||||
test('Verify Modal header', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
const pageSubHeader = getByTestId(container, 'page-subheader')
|
||||
const createRegistryButton = getByText(pageSubHeader, 'repositoryList.newRepository')
|
||||
expect(createRegistryButton).toBeInTheDocument()
|
||||
await userEvent.click(createRegistryButton)
|
||||
|
||||
const modal = document.getElementsByClassName('bp3-dialog')[0]
|
||||
expect(modal).toBeInTheDocument()
|
||||
|
||||
const dialogHeader = screen.getByTestId('modaldialog-header')
|
||||
expect(dialogHeader).toHaveTextContent('repositoryDetails.repositoryForm.modalTitle')
|
||||
expect(dialogHeader).toHaveTextContent('repositoryDetails.repositoryForm.modalSubTitle')
|
||||
|
||||
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
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
const pageSubHeader = getByTestId(container, 'page-subheader')
|
||||
const createRegistryButton = getByText(pageSubHeader, 'repositoryList.newRepository')
|
||||
expect(createRegistryButton).toBeInTheDocument()
|
||||
await userEvent.click(createRegistryButton)
|
||||
|
||||
const modal = document.getElementsByClassName('bp3-dialog')[0]
|
||||
expect(modal).toBeInTheDocument()
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
expect(dialogBody).toHaveTextContent('repositoryDetails.repositoryForm.selectRepoType')
|
||||
const registryTypeOption = dialogBody.querySelector('input[type="checkbox"][name=packageType][value="NPM"]')
|
||||
expect(registryTypeOption).not.toBeDisabled()
|
||||
fireEvent.change(registryTypeOption!, { target: { checked: true } })
|
||||
expect(registryTypeOption).toBeChecked()
|
||||
})
|
||||
|
||||
test('verify npm registry create form with success scenario', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
const pageSubHeader = getByTestId(container, 'page-subheader')
|
||||
const createRegistryButton = getByText(pageSubHeader, 'repositoryList.newRepository')
|
||||
expect(createRegistryButton).toBeInTheDocument()
|
||||
await userEvent.click(createRegistryButton)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
expect(dialogBody).toHaveTextContent('repositoryDetails.repositoryForm.selectRepoType')
|
||||
const registryTypeOption = dialogBody.querySelector('input[type="checkbox"][name=packageType][value="NPM"]')
|
||||
expect(registryTypeOption).not.toBeDisabled()
|
||||
await userEvent.click(registryTypeOption!)
|
||||
fireEvent.change(registryTypeOption!, { target: { checked: true } })
|
||||
expect(registryTypeOption).toBeChecked()
|
||||
|
||||
expect(dialogBody).toHaveTextContent('repositoryDetails.repositoryForm.title')
|
||||
|
||||
const formData = MockGetNpmRegistryResponseWithAllData.content.data
|
||||
const nameField = queryByNameAttribute('identifier', dialogBody)
|
||||
expect(nameField).toBeInTheDocument()
|
||||
expect(nameField).not.toBeDisabled()
|
||||
fireEvent.change(nameField!, { target: { value: formData.identifier } })
|
||||
|
||||
const descriptionEditButton = getByTestId(dialogBody, 'description-edit')
|
||||
expect(descriptionEditButton).toBeInTheDocument()
|
||||
await userEvent.click(descriptionEditButton)
|
||||
const descriptionField = queryByNameAttribute('description', dialogBody)
|
||||
expect(descriptionField).toBeInTheDocument()
|
||||
expect(descriptionField).not.toBeDisabled()
|
||||
fireEvent.change(descriptionField!, { target: { value: formData.description } })
|
||||
|
||||
const dialogFooter = screen.getByTestId('modaldialog-footer')
|
||||
expect(dialogFooter).toBeInTheDocument()
|
||||
const createButton = dialogFooter.querySelector(
|
||||
'button[type="submit"][aria-label="repositoryDetails.repositoryForm.create"]'
|
||||
)
|
||||
expect(createButton).toBeInTheDocument()
|
||||
await userEvent.click(createButton!)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createRegistryFn).toHaveBeenCalledWith({
|
||||
body: {
|
||||
cleanupPolicy: [],
|
||||
config: { type: 'VIRTUAL', upstreamProxies: [] },
|
||||
description: 'custom description',
|
||||
identifier: 'npm-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showSuccessToast).toHaveBeenCalledWith('repositoryDetails.repositoryForm.repositoryCreated')
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith('/registries/npm-repo/configuration')
|
||||
})
|
||||
})
|
||||
|
||||
test('verify npm registry create form with failure scenario', async () => {
|
||||
createRegistryFn.mockImplementation(() => Promise.reject({ message: 'error message' }))
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
const pageSubHeader = getByTestId(container, 'page-subheader')
|
||||
const createRegistryButton = getByText(pageSubHeader, 'repositoryList.newRepository')
|
||||
expect(createRegistryButton).toBeInTheDocument()
|
||||
await userEvent.click(createRegistryButton)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
expect(dialogBody).toHaveTextContent('repositoryDetails.repositoryForm.selectRepoType')
|
||||
const registryTypeOption = dialogBody.querySelector('input[type="checkbox"][name=packageType][value="NPM"]')
|
||||
expect(registryTypeOption).not.toBeDisabled()
|
||||
await userEvent.click(registryTypeOption!)
|
||||
fireEvent.change(registryTypeOption!, { target: { checked: true } })
|
||||
expect(registryTypeOption).toBeChecked()
|
||||
|
||||
expect(dialogBody).toHaveTextContent('repositoryDetails.repositoryForm.title')
|
||||
|
||||
const formData = MockGetNpmRegistryResponseWithAllData.content.data
|
||||
const nameField = queryByNameAttribute('identifier', dialogBody)
|
||||
expect(nameField).toBeInTheDocument()
|
||||
expect(nameField).not.toBeDisabled()
|
||||
fireEvent.change(nameField!, { target: { value: formData.identifier } })
|
||||
|
||||
const dialogFooter = screen.getByTestId('modaldialog-footer')
|
||||
expect(dialogFooter).toBeInTheDocument()
|
||||
const createButton = dialogFooter.querySelector(
|
||||
'button[type="submit"][aria-label="repositoryDetails.repositoryForm.create"]'
|
||||
)
|
||||
expect(createButton).toBeInTheDocument()
|
||||
await userEvent.click(createButton!)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createRegistryFn).toHaveBeenCalledWith({
|
||||
body: {
|
||||
cleanupPolicy: [],
|
||||
config: { type: 'VIRTUAL', upstreamProxies: [] },
|
||||
identifier: 'npm-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showErrorToast).toHaveBeenCalledWith('error message')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* 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 { MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData } from './__mockData__'
|
||||
import upstreamProxyUtils from '../../__tests__/utils'
|
||||
import '../../RepositoryFactory'
|
||||
|
||||
const createRegistryFn = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData))
|
||||
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 npm upstream registry flow', () => {
|
||||
test('Verify Modal header', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
await upstreamProxyUtils.openModal(container)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'NPM')
|
||||
})
|
||||
|
||||
test('verify NPM registry create form with success scenario > Source as NpmJs > Anonymous', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
await upstreamProxyUtils.openModal(container)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'NPM')
|
||||
|
||||
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
|
||||
|
||||
const formData = MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data as Registry
|
||||
|
||||
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
|
||||
dialogBody,
|
||||
formData,
|
||||
'NpmJs',
|
||||
'Anonymous',
|
||||
'NpmJs',
|
||||
'Anonymous'
|
||||
)
|
||||
|
||||
const createButton = await upstreamProxyUtils.getSubmitButton()
|
||||
await userEvent.click(createButton!)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createRegistryFn).toHaveBeenLastCalledWith({
|
||||
body: {
|
||||
cleanupPolicy: [],
|
||||
config: { auth: null, authType: 'Anonymous', source: 'NpmJs', type: 'UPSTREAM', url: '' },
|
||||
description: 'test description',
|
||||
identifier: 'npm-up-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showSuccessToast).toHaveBeenLastCalledWith(
|
||||
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
|
||||
)
|
||||
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/npm-up-repo/configuration')
|
||||
})
|
||||
})
|
||||
|
||||
test('verify NPM registry create form with success scenario > Source as NpmJs > UserPassword', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}
|
||||
parent={Parent.OSS}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
await upstreamProxyUtils.openModal(container)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'NPM')
|
||||
|
||||
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
|
||||
|
||||
const formData = MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data as Registry
|
||||
|
||||
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
|
||||
dialogBody,
|
||||
formData,
|
||||
'NpmJs',
|
||||
'UserPassword',
|
||||
'NpmJs',
|
||||
'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: 'NpmJs',
|
||||
type: 'UPSTREAM',
|
||||
url: ''
|
||||
},
|
||||
description: 'test description',
|
||||
identifier: 'npm-up-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showSuccessToast).toHaveBeenLastCalledWith(
|
||||
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
|
||||
)
|
||||
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/npm-up-repo/configuration')
|
||||
})
|
||||
})
|
||||
|
||||
test('verify npm registry create form with success scenario > Source as Custom > Anonymous', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
await upstreamProxyUtils.openModal(container)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'NPM')
|
||||
|
||||
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
|
||||
|
||||
const formData = MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data as Registry
|
||||
|
||||
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
|
||||
dialogBody,
|
||||
formData,
|
||||
'Custom',
|
||||
'Anonymous',
|
||||
'NpmJs',
|
||||
'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: 'npm-up-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showSuccessToast).toHaveBeenLastCalledWith(
|
||||
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
|
||||
)
|
||||
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/npm-up-repo/configuration')
|
||||
})
|
||||
})
|
||||
|
||||
test('verify NPM registry create form with success scenario > Source as Custom > Username Password', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}
|
||||
parent={Parent.OSS}>
|
||||
<RepositoryListPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
await upstreamProxyUtils.openModal(container)
|
||||
|
||||
const dialogBody = screen.getByTestId('modaldialog-body')
|
||||
expect(dialogBody).toBeInTheDocument()
|
||||
|
||||
await upstreamProxyUtils.verifyPackageTypeSelector(dialogBody, 'NPM')
|
||||
|
||||
expect(dialogBody).toHaveTextContent('upstreamProxyDetails.form.title')
|
||||
|
||||
const formData = MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data as Registry
|
||||
|
||||
await upstreamProxyUtils.verifyUpstreamProxyCreateForm(
|
||||
dialogBody,
|
||||
formData,
|
||||
'Custom',
|
||||
'UserPassword',
|
||||
'NpmJs',
|
||||
'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: 'npm-up-repo',
|
||||
packageType: 'NPM',
|
||||
parentRef: 'undefined',
|
||||
scanners: []
|
||||
},
|
||||
queryParams: { space_ref: 'undefined' }
|
||||
})
|
||||
expect(showSuccessToast).toHaveBeenLastCalledWith(
|
||||
'upstreamProxyDetails.actions.createUpdateModal.createSuccessMessage'
|
||||
)
|
||||
expect(mockHistoryPush).toHaveBeenLastCalledWith('/registries/npm-up-repo/configuration')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* 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, getByText, queryByTestId, render, waitFor } from '@testing-library/react'
|
||||
import { useGetClientSetupDetailsQuery, useGetRegistryQuery } from '@harnessio/react-har-service-client'
|
||||
|
||||
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 {
|
||||
MockGetNpmArtifactsByRegistryResponse,
|
||||
MockGetNpmRegistryResponseWithAllData,
|
||||
MockGetNpmSetupClientOnRegistryConfigPageResponse
|
||||
} from './__mockData__'
|
||||
import '../../RepositoryFactory'
|
||||
|
||||
const modifyRepository = jest.fn().mockImplementation(
|
||||
() =>
|
||||
new Promise(onSuccess => {
|
||||
onSuccess({ content: { status: 'SUCCESS' } })
|
||||
})
|
||||
)
|
||||
|
||||
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: MockGetNpmRegistryResponseWithAllData
|
||||
})),
|
||||
useGetAllArtifactsByRegistryQuery: jest.fn().mockImplementation(() => ({
|
||||
isFetching: false,
|
||||
refetch: jest.fn(),
|
||||
error: false,
|
||||
data: MockGetNpmArtifactsByRegistryResponse
|
||||
})),
|
||||
useGetClientSetupDetailsQuery: jest.fn().mockImplementation(() => ({
|
||||
isFetching: false,
|
||||
refetch: jest.fn(),
|
||||
error: false,
|
||||
data: MockGetNpmSetupClientOnRegistryConfigPageResponse
|
||||
})),
|
||||
useDeleteRegistryMutation: jest.fn().mockImplementation(() => ({
|
||||
isLoading: false,
|
||||
mutateAsync: jest.fn()
|
||||
})),
|
||||
useModifyRegistryMutation: jest.fn().mockImplementation(() => ({
|
||||
isLoading: false,
|
||||
mutateAsync: modifyRepository
|
||||
})),
|
||||
useGetAllRegistriesQuery: jest.fn().mockImplementation(() => ({
|
||||
isFetching: false,
|
||||
data: { content: { data: { registries: [] }, status: 'SUCCESS' } },
|
||||
refetch: jest.fn(),
|
||||
error: null
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('Verify header section for NPM artifact registry', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('Verify breadcrumbs', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper>
|
||||
<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, 'registry-header-container')
|
||||
expect(pageHeader).toBeInTheDocument()
|
||||
|
||||
expect(pageHeader?.querySelector('span[data-icon=npm-repository-type]')).toBeInTheDocument()
|
||||
const data = MockGetNpmRegistryResponseWithAllData.content.data
|
||||
|
||||
const title = getByTestId(container, 'registry-title')
|
||||
expect(title).toHaveTextContent(data.identifier)
|
||||
|
||||
const description = getByTestId(container, 'registry-description')
|
||||
expect(description).toHaveTextContent(data.description)
|
||||
|
||||
expect(pageHeader?.querySelector('svg[data-icon=tag]')).toBeInTheDocument()
|
||||
|
||||
const lastModifiedAt = getByTestId(container, 'registry-last-modified-at')
|
||||
expect(lastModifiedAt).toHaveTextContent(getReadableDateTime(Number(data.modifiedAt), DEFAULT_DATE_TIME_FORMAT))
|
||||
})
|
||||
|
||||
test('Verify registry setup client action', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper path="/registries/abcd/:tab" pathParams={{ tab: 'configuration' }}>
|
||||
<RepositoryDetailsPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
const pageHeader = getByTestId(container, 'registry-header-container')
|
||||
const setupClientBtn = pageHeader.querySelector('button[aria-label="actions.setupClient"]')
|
||||
expect(setupClientBtn).toBeInTheDocument()
|
||||
await userEvent.click(setupClientBtn!)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useGetClientSetupDetailsQuery).toHaveBeenLastCalledWith({
|
||||
queryParams: { artifact: undefined, version: undefined },
|
||||
registry_ref: 'undefined/npm-repo/+'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Verify other registry actions', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper>
|
||||
<RepositoryDetailsPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
const pageHeader = getByTestId(container, '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' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryDetailsPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
// Artifact registry defination section
|
||||
const registryDefinitionSection = getByTestId(container, 'registry-definition')
|
||||
const nameField = queryByNameAttribute('identifier', registryDefinitionSection)
|
||||
expect(nameField).toBeInTheDocument()
|
||||
expect(nameField).toBeDisabled()
|
||||
expect(nameField).toHaveAttribute('value', MockGetNpmRegistryResponseWithAllData.content.data.identifier)
|
||||
|
||||
const descriptionField = queryByNameAttribute('description', registryDefinitionSection)
|
||||
expect(descriptionField).toBeInTheDocument()
|
||||
expect(descriptionField).not.toBeDisabled()
|
||||
expect(descriptionField).toHaveTextContent(MockGetNpmRegistryResponseWithAllData.content.data.description)
|
||||
|
||||
const tags = registryDefinitionSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
|
||||
tags.forEach((each, idx) => {
|
||||
expect(each).toHaveTextContent(MockGetNpmRegistryResponseWithAllData.content.data.labels[idx])
|
||||
})
|
||||
|
||||
// Security scan section
|
||||
const securityScanSection = queryByTestId(container, 'security-section')
|
||||
expect(securityScanSection).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(MockGetNpmRegistryResponseWithAllData.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(MockGetNpmRegistryResponseWithAllData.content.data.blockedPattern[idx])
|
||||
})
|
||||
|
||||
// upstream proxy section
|
||||
const upstreamProxySection = getByTestId(container, 'upstream-proxy-section')
|
||||
expect(upstreamProxySection).toBeInTheDocument()
|
||||
const selectedItemList = upstreamProxySection.querySelectorAll('ul[aria-label=orderable-list] .bp3-menu-item')
|
||||
selectedItemList.forEach((each, idx) => {
|
||||
expect(each).toHaveTextContent(MockGetNpmRegistryResponseWithAllData.content.data.config.upstreamProxies[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', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
path="/registries/abcd/:tab"
|
||||
pathParams={{ tab: 'configuration' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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).toHaveBeenCalledWith({
|
||||
body: {
|
||||
...MockGetNpmRegistryResponseWithAllData.content.data,
|
||||
description: 'updated description'
|
||||
},
|
||||
registry_ref: 'undefined/abcd/+'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('should able to discard the changes', async () => {
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
path="/registries/abcd/:tab"
|
||||
pathParams={{ tab: 'configuration' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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(discardBtn!)
|
||||
await waitFor(() => {
|
||||
expect(saveBtn).toBeDisabled()
|
||||
expect(discardBtn).toBeDisabled()
|
||||
expect(descriptionField).toHaveTextContent(MockGetNpmRegistryResponseWithAllData.content.data.description)
|
||||
})
|
||||
})
|
||||
|
||||
test('should render retry if failed to load get registry api', async () => {
|
||||
const refetchFn = jest.fn()
|
||||
;(useGetRegistryQuery as jest.Mock).mockImplementation(() => ({
|
||||
isFetching: false,
|
||||
isError: true,
|
||||
error: { message: 'failed to load registry' },
|
||||
data: null,
|
||||
refetch: refetchFn
|
||||
}))
|
||||
|
||||
const { container } = render(
|
||||
<ArTestWrapper
|
||||
queryParams={{ tab: 'configuration' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryDetailsPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
expect(getByText(container, 'failed to load registry')).toBeInTheDocument()
|
||||
const retryBtn = container.querySelector('button[aria-label=Retry]')
|
||||
expect(retryBtn).toBeInTheDocument()
|
||||
await userEvent.click(retryBtn!)
|
||||
await waitFor(() => {
|
||||
expect(refetchFn).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* 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, getByText, 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 {
|
||||
MockGetNpmArtifactsByRegistryResponse,
|
||||
MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData
|
||||
} 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: MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData
|
||||
})),
|
||||
useGetAllArtifactsByRegistryQuery: jest.fn().mockImplementation(() => ({
|
||||
isFetching: false,
|
||||
refetch: jest.fn(),
|
||||
error: false,
|
||||
data: MockGetNpmArtifactsByRegistryResponse
|
||||
})),
|
||||
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=npm-repository-type]')).toBeInTheDocument()
|
||||
const data = MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.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/npm-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/npm-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' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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',
|
||||
MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data.identifier
|
||||
)
|
||||
|
||||
const descriptionField = queryByNameAttribute('description', registryDefinitionSection)
|
||||
expect(descriptionField).toBeInTheDocument()
|
||||
expect(descriptionField).not.toBeDisabled()
|
||||
expect(descriptionField).toHaveTextContent(
|
||||
MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data.description
|
||||
)
|
||||
|
||||
const tags = registryDefinitionSection.querySelectorAll('div.bp3-tag-input-values .bp3-tag')
|
||||
tags.forEach((each, idx) => {
|
||||
expect(each).toHaveTextContent(MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.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=NpmJs]')
|
||||
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-section')
|
||||
expect(securityScanSection).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(
|
||||
MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.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(
|
||||
MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data.blockedPattern[idx]
|
||||
)
|
||||
})
|
||||
|
||||
const advancedOptionTitle = getByText(container, 'repositoryDetails.repositoryForm.advancedOptionsTitle')
|
||||
expect(advancedOptionTitle).toBeInTheDocument()
|
||||
await userEvent.click(advancedOptionTitle)
|
||||
|
||||
// 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' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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: {
|
||||
...MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.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' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<RepositoryDetailsPage />
|
||||
</ArTestWrapper>
|
||||
)
|
||||
|
||||
const saveBtn = container.querySelector('button[aria-label=save]')
|
||||
|
||||
const sourceAuthSection = getByTestId(container, 'upstream-source-auth-definition')
|
||||
|
||||
// verify NpmJs, UserPassword
|
||||
{
|
||||
await upstreamProxyUtils.verifySourceAndAuthSection(
|
||||
sourceAuthSection,
|
||||
'NpmJs',
|
||||
'UserPassword',
|
||||
'NpmJs',
|
||||
'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: 'NpmJs',
|
||||
type: 'UPSTREAM',
|
||||
url: ''
|
||||
},
|
||||
createdAt: '1738516362995',
|
||||
description: 'test description',
|
||||
identifier: 'npm-up-repo',
|
||||
labels: ['label1', 'label2', 'label3', 'label4'],
|
||||
packageType: 'NPM',
|
||||
url: ''
|
||||
},
|
||||
registry_ref: 'undefined/abcd/+'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// verify Custom, Anonymous
|
||||
{
|
||||
await upstreamProxyUtils.verifySourceAndAuthSection(
|
||||
sourceAuthSection,
|
||||
'Custom',
|
||||
'Anonymous',
|
||||
'NpmJs',
|
||||
'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: 'npm-up-repo',
|
||||
labels: ['label1', 'label2', 'label3', 'label4'],
|
||||
packageType: 'NPM',
|
||||
url: ''
|
||||
},
|
||||
registry_ref: 'undefined/abcd/+'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// verify Custom, UserPassword
|
||||
{
|
||||
await upstreamProxyUtils.verifySourceAndAuthSection(
|
||||
sourceAuthSection,
|
||||
'Custom',
|
||||
'UserPassword',
|
||||
'NpmJs',
|
||||
'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: 'npm-up-repo',
|
||||
labels: ['label1', 'label2', 'label3', 'label4'],
|
||||
packageType: 'NPM',
|
||||
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' }}
|
||||
featureFlags={{
|
||||
HAR_NPM_PACKAGE_TYPE_ENABLED: true
|
||||
}}>
|
||||
<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: {
|
||||
...MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData.content.data,
|
||||
description: 'updated description'
|
||||
},
|
||||
registry_ref: 'undefined/abcd/+'
|
||||
})
|
||||
expect(showErrorToast).toHaveBeenLastCalledWith('error message')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* 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 type { GetAllArtifactsByRegistryOkResponse } from '@harnessio/react-har-service-client'
|
||||
|
||||
export const MockGetNpmRegistryResponseWithAllData = {
|
||||
content: {
|
||||
data: {
|
||||
config: {
|
||||
type: 'VIRTUAL',
|
||||
upstreamProxies: ['npm-js-proxy']
|
||||
},
|
||||
createdAt: '1738040653409',
|
||||
description: 'custom description',
|
||||
identifier: 'npm-repo',
|
||||
labels: ['label1', 'label2', 'label3', 'label4'],
|
||||
modifiedAt: '1738040653409',
|
||||
packageType: 'NPM',
|
||||
url: 'http://host.docker.internal:3000/artifact-registry/maven-repo',
|
||||
allowedPattern: ['test1', 'test2'],
|
||||
blockedPattern: ['test3', 'test4']
|
||||
},
|
||||
status: 'SUCCESS'
|
||||
}
|
||||
}
|
||||
|
||||
export const MockGetNpmArtifactsByRegistryResponse: GetAllArtifactsByRegistryOkResponse = {
|
||||
content: {
|
||||
data: {
|
||||
artifacts: [
|
||||
{
|
||||
downloadsCount: 0,
|
||||
lastModified: '1738048875014',
|
||||
latestVersion: 'v1',
|
||||
name: 'artifact',
|
||||
packageType: 'NPM',
|
||||
registryIdentifier: 'npm-repo',
|
||||
registryPath: ''
|
||||
}
|
||||
],
|
||||
itemCount: 0,
|
||||
pageCount: 0,
|
||||
pageIndex: 0,
|
||||
pageSize: 50
|
||||
},
|
||||
status: 'SUCCESS'
|
||||
}
|
||||
}
|
||||
|
||||
export const MockGetNpmSetupClientOnRegistryConfigPageResponse = {
|
||||
content: {
|
||||
data: {
|
||||
mainHeader: 'Maven Client Setup',
|
||||
secHeader: 'Follow these instructions to install/use Maven artifacts or compatible packages.',
|
||||
sections: [
|
||||
{
|
||||
header: '1. Generate Identity Token',
|
||||
secHeader: 'An identity token will serve as the password for uploading and downloading artifacts.',
|
||||
steps: [
|
||||
{
|
||||
header: 'Generate an identity token',
|
||||
type: 'GenerateToken'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{
|
||||
header: 'Maven',
|
||||
sections: [
|
||||
{
|
||||
header: '2. Pull a Maven Package',
|
||||
secHeader: 'Set default repository in your pom.xml file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'\u003crepositories\u003e\n \u003crepository\u003e\n \u003cid\u003emaven-dev\u003c/id\u003e\n \u003curl\u003ehttp://host.docker.internal:3000/maven/artifact-registry/maven-repo\u003c/url\u003e\n \u003creleases\u003e\n \u003cenabled\u003etrue\u003c/enabled\u003e\n \u003cupdatePolicy\u003ealways\u003c/updatePolicy\u003e\n \u003c/releases\u003e\n \u003csnapshots\u003e\n \u003cenabled\u003etrue\u003c/enabled\u003e\n \u003cupdatePolicy\u003ealways\u003c/updatePolicy\u003e\n \u003c/snapshots\u003e\n \u003c/repository\u003e\n\u003c/repositories\u003e'
|
||||
}
|
||||
],
|
||||
header: 'To set default registry in your pom.xml file by adding the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'\u003csettings\u003e\n \u003cservers\u003e\n \u003cserver\u003e\n \u003cid\u003emaven-dev\u003c/id\u003e\n \u003cusername\u003eadmin@gitness.io\u003c/username\u003e\n \u003cpassword\u003eidentity-token\u003c/password\u003e\n \u003c/server\u003e\n \u003c/servers\u003e\n\u003c/settings\u003e'
|
||||
}
|
||||
],
|
||||
header:
|
||||
'Copy the following your ~/ .m2/settings.xml file for MacOs, or $USERPROFILE$\\ .m2\\settings.xml for Windows to authenticate with token to pull from your Maven registry.',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'\u003cdependency\u003e\n \u003cgroupId\u003e\u003cGROUP_ID\u003e\u003c/groupId\u003e\n \u003cartifactId\u003e\u003cARTIFACT_ID\u003e\u003c/artifactId\u003e\n \u003cversion\u003e\u003cVERSION\u003e\u003c/version\u003e\n\u003c/dependency\u003e'
|
||||
}
|
||||
],
|
||||
header:
|
||||
"Add a dependency to the project's pom.xml (replace \u003cGROUP_ID\u003e, \u003cARTIFACT_ID\u003e \u0026 \u003cVERSION\u003e with your own):",
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'mvn install'
|
||||
}
|
||||
],
|
||||
header: 'Install dependencies in pom.xml file',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
},
|
||||
{
|
||||
header: '3. Push a Maven Package',
|
||||
secHeader: 'Set default repository in your pom.xml file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'\u003cdistributionManagement\u003e\n \u003csnapshotRepository\u003e\n \u003cid\u003emaven-dev\u003c/id\u003e\n \u003curl\u003ehttp://host.docker.internal:3000/maven/artifact-registry/maven-repo\u003c/url\u003e\n \u003c/snapshotRepository\u003e\n \u003crepository\u003e\n \u003cid\u003emaven-dev\u003c/id\u003e\n \u003curl\u003ehttp://host.docker.internal:3000/maven/artifact-registry/maven-repo\u003c/url\u003e\n \u003c/repository\u003e\n\u003c/distributionManagement\u003e'
|
||||
}
|
||||
],
|
||||
header: 'To set default registry in your pom.xml file by adding the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'\u003csettings\u003e\n \u003cservers\u003e\n \u003cserver\u003e\n \u003cid\u003emaven-dev\u003c/id\u003e\n \u003cusername\u003eadmin@gitness.io\u003c/username\u003e\n \u003cpassword\u003eidentity-token\u003c/password\u003e\n \u003c/server\u003e\n \u003c/servers\u003e\n\u003c/settings\u003e'
|
||||
}
|
||||
],
|
||||
header:
|
||||
'Copy the following your ~/ .m2/setting.xml file for MacOs, or $USERPROFILE$\\ .m2\\settings.xml for Windows to authenticate with token to push to your Maven registry.',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'mvn deploy'
|
||||
}
|
||||
],
|
||||
header: 'Publish package to your Maven registry.',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
header: 'Gradle',
|
||||
sections: [
|
||||
{
|
||||
header: '2. Pull a Gradle Package',
|
||||
secHeader: 'Set default repository in your build.gradle file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'repositories{\n maven{\n url “http://host.docker.internal:3000/maven/artifact-registry/maven-repo”\n\n credentials {\n username “admin@gitness.io”\n password “identity-token”\n }\n }\n}'
|
||||
}
|
||||
],
|
||||
header: 'Set the default registry in your project’s build.gradle by adding the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'repositoryUser=admin@gitness.io\nrepositoryPassword={{identity-token}}'
|
||||
}
|
||||
],
|
||||
header:
|
||||
'As this is a private registry, you’ll need to authenticate. Create or add to the ~/.gradle/gradle.properties file with the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'dependencies {\n implementation ‘\u003cGROUP_ID\u003e:\u003cARTIFACT_ID\u003e:\u003cVERSION\u003e’\n}'
|
||||
}
|
||||
],
|
||||
header: 'Add a dependency to the project’s build.gradle',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'gradlew build // Linux or OSX\n gradlew.bat build // Windows'
|
||||
}
|
||||
],
|
||||
header: 'Install dependencies in build.gradle file',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
},
|
||||
{
|
||||
header: '3. Push a Gradle Package',
|
||||
secHeader: 'Set default repository in your build.gradle file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
"publishing {\n publications {\n maven(MavenPublication) {\n groupId = '\u003cGROUP_ID\u003e'\n artifactId = '\u003cARTIFACT_ID\u003e'\n version = '\u003cVERSION\u003e'\n\n from components.java\n }\n }\n}"
|
||||
}
|
||||
],
|
||||
header: 'Add a maven publish plugin configuration to the project’s build.gradle.',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'gradlew publish'
|
||||
}
|
||||
],
|
||||
header: 'Publish package to your Maven registry.',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
header: 'Sbt/Scala',
|
||||
sections: [
|
||||
{
|
||||
header: '2. Pull a Sbt/Scala Package',
|
||||
secHeader: 'Set default repository in your build.sbt file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'resolver += “Harness Registry” at “http://host.docker.internal:3000/maven/artifact-registry/maven-repo”\ncredentials += Credentials(Path.userHome / “.sbt” / “.Credentials”)'
|
||||
}
|
||||
],
|
||||
header: 'Set the default registry in your project’s build.sbt by adding the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'realm=Harness Registry\nhost=host.docker.internal:3000\nuser=admin@gitness.io\npassword={{identity-token}}'
|
||||
}
|
||||
],
|
||||
header:
|
||||
'As this is a private registry, you’ll need to authenticate. Create or add to the ~/.sbt/.credentials file with the following:',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'libraryDependencies += “\u003cGROUP_ID\u003e” % “\u003cARTIFACT_ID\u003e” % “\u003cVERSION\u003e”'
|
||||
}
|
||||
],
|
||||
header: 'Add a dependency to the project’s build.sbt',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'sbt update'
|
||||
}
|
||||
],
|
||||
header: 'Install dependencies in build.sbt file',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
},
|
||||
{
|
||||
header: '3. Push a Sbt/Scala Package',
|
||||
secHeader: 'Set default repository in your build.sbt file.',
|
||||
steps: [
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value:
|
||||
'publishTo := Some("Harness Registry" at "http://host.docker.internal:3000/maven/artifact-registry/maven-repo")'
|
||||
}
|
||||
],
|
||||
header: 'Add publish configuration to the project’s build.sbt.',
|
||||
type: 'Static'
|
||||
},
|
||||
{
|
||||
commands: [
|
||||
{
|
||||
value: 'sbt publish'
|
||||
}
|
||||
],
|
||||
header: 'Publish package to your Maven registry.',
|
||||
type: 'Static'
|
||||
}
|
||||
],
|
||||
type: 'INLINE'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
type: 'TABS'
|
||||
}
|
||||
]
|
||||
},
|
||||
status: 'SUCCESS'
|
||||
}
|
||||
}
|
||||
|
||||
export const MockGetNpmUpstreamRegistryResponseWithNpmJsSourceAllData = {
|
||||
content: {
|
||||
data: {
|
||||
allowedPattern: ['test1', 'test2'],
|
||||
blockedPattern: ['test3', 'test4'],
|
||||
config: {
|
||||
auth: null,
|
||||
authType: 'Anonymous',
|
||||
source: 'NpmJs',
|
||||
type: 'UPSTREAM',
|
||||
url: ''
|
||||
},
|
||||
createdAt: '1738516362995',
|
||||
identifier: 'npm-up-repo',
|
||||
description: 'test description',
|
||||
packageType: 'NPM',
|
||||
labels: ['label1', 'label2', 'label3', 'label4'],
|
||||
url: ''
|
||||
},
|
||||
status: 'SUCCESS'
|
||||
}
|
||||
}
|
|
@ -19,8 +19,10 @@ import { DockerRepositoryType } from './DockerRepository/DockerRepositoryType'
|
|||
import { MavenRepositoryType } from './MavenRepository/MavenRepository'
|
||||
import { HelmRepositoryType } from './HelmRepository/HelmRepositoryType'
|
||||
import { GenericRepositoryType } from './GenericRepository/GenericRepositoryType'
|
||||
import { NpmRepositoryType } from './NpmRepository/NpmRepositoryType'
|
||||
|
||||
repositoryFactory.registerStep(new DockerRepositoryType())
|
||||
repositoryFactory.registerStep(new HelmRepositoryType())
|
||||
repositoryFactory.registerStep(new GenericRepositoryType())
|
||||
repositoryFactory.registerStep(new MavenRepositoryType())
|
||||
repositoryFactory.registerStep(new NpmRepositoryType())
|
||||
|
|
|
@ -58,5 +58,9 @@ export const URLSourceToSupportedAuthTypesMapping: Record<
|
|||
[UpstreamRepositoryURLInputSource.MavenCentral]: [
|
||||
UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
],
|
||||
[UpstreamRepositoryURLInputSource.NpmJS]: [
|
||||
UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
]
|
||||
}
|
||||
|
|
|
@ -37,5 +37,9 @@ export const UpstreamURLSourceConfig: Record<UpstreamRepositoryURLInputSource, R
|
|||
[UpstreamRepositoryURLInputSource.MavenCentral]: {
|
||||
label: 'upstreamProxyDetails.createForm.source.mavenCentral',
|
||||
value: UpstreamRepositoryURLInputSource.MavenCentral
|
||||
},
|
||||
[UpstreamRepositoryURLInputSource.NpmJS]: {
|
||||
label: 'upstreamProxyDetails.createForm.source.npmjs',
|
||||
value: UpstreamRepositoryURLInputSource.NpmJS
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ createForm:
|
|||
ecr: AWS ECR
|
||||
custom: Custom
|
||||
mavenCentral: Maven Central
|
||||
npmjs: npmjs
|
||||
authentication:
|
||||
title: Authentication
|
||||
userNameAndPassword: Username and Password
|
||||
|
|
|
@ -21,12 +21,14 @@ export enum UpstreamProxyPackageType {
|
|||
DOCKER = 'DOCKER',
|
||||
HELM = 'HELM',
|
||||
GENERIC = 'GENERIC',
|
||||
MAVEN = 'MAVEN'
|
||||
MAVEN = 'MAVEN',
|
||||
NPM = 'NPM'
|
||||
}
|
||||
|
||||
export enum UpstreamRepositoryURLInputSource {
|
||||
Dockerhub = 'Dockerhub',
|
||||
MavenCentral = 'MavenCentral',
|
||||
NpmJS = 'NpmJs',
|
||||
AwsEcr = 'AwsEcr',
|
||||
Custom = 'Custom'
|
||||
}
|
||||
|
|
|
@ -143,6 +143,7 @@ export interface StringsMap {
|
|||
'upstreamProxyDetails.createForm.source.dockerHub': string
|
||||
'upstreamProxyDetails.createForm.source.ecr': string
|
||||
'upstreamProxyDetails.createForm.source.mavenCentral': string
|
||||
'upstreamProxyDetails.createForm.source.npmjs': string
|
||||
'upstreamProxyDetails.createForm.source.title': string
|
||||
'upstreamProxyDetails.createForm.title': string
|
||||
'upstreamProxyDetails.createForm.url': string
|
||||
|
|
|
@ -1945,10 +1945,10 @@
|
|||
yargs "^17.6.2"
|
||||
zod "^3.19.1"
|
||||
|
||||
"@harnessio/react-har-service-client@^0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.10.0.tgz#84a9a89ec3f02b35313dae02857789ec25a095dc"
|
||||
integrity sha512-TC84QK0mas2F3dtN7EK4yJkjJYPCq0uEtXCdMCyvX36Mw/4mY+pEcbfhQhaP+cugqWJT2/eZ21TrPrx07rG/ig==
|
||||
"@harnessio/react-har-service-client@^0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.12.0.tgz#9ad2dec1a4ba2d4150e9c4dea67e2f867d9141ed"
|
||||
integrity sha512-bBDrVL/OkX14SdG3XS5/h7W5pQ0BH6tXhK1w2F9eWf5qupyZe2lwaDpVRzxi2fkzr0wojf2wd/e3p3IiB47X0g==
|
||||
dependencies:
|
||||
"@harnessio/oats-cli" "^3.0.0"
|
||||
|
||||
|
|
Loading…
Reference in New Issue