diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 98f67b57e..495d32f30 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -227,7 +227,7 @@ jobs: path: Splunk_TA_UCCExample*.tar.gz test-ui: - name: test-ui Splunk ${{ matrix.splunk.version }} -m ${{ matrix.test-mark }} + name: test-ui Splunk ${{ matrix.splunk.version }} -group ${{ matrix.test-group }} runs-on: ubuntu-22.04 permissions: id-token: write @@ -243,14 +243,17 @@ jobs: fail-fast: false matrix: splunk: ${{ fromJson(needs.meta.outputs.matrix_supportedSplunk) }} - test-mark: - - "logging" - - "proxy" - - "account" - - "custom" - - "alert" - - "input" - - "configuration" + test-group: + - "1" + - "2" + - "3" + - "4" + - "5" + - "6" + - "7" + - "8" + - "9" + - "10" steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -272,16 +275,16 @@ jobs: ./scripts/run_splunk.sh ${{ matrix.splunk.version }} until curl -Lsk "https://localhost:8088/services/collector/health" &>/dev/null ; do echo -n "Waiting for HEC-" && sleep 5 ; done timeout-minutes: 5 - - run: poetry run pytest tests/ui -m "${{ matrix.test-mark }}" --headless --junitxml=test-results/junit.xml + - run: poetry run pytest tests/ui --test-group-count 10 --test-group ${{ matrix.test-group }} --test-group-random-seed 123456 --headless --junitxml=test-results/junit.xml - uses: actions/upload-artifact@v4 if: success() || failure() with: - name: test-results-ui-${{ matrix.splunk.version }}-${{ matrix.test-mark }} + name: test-results-ui-${{ matrix.splunk.version }}-${{ matrix.test-group }} path: test-results/* - uses: dorny/test-reporter@v1 if: success() || failure() with: - name: test-report-ui-${{ matrix.splunk.version }}-${{ matrix.test-mark }} + name: test-report-ui-${{ matrix.splunk.version }}-${{ matrix.test-group }} path: "test-results/*.xml" reporter: java-junit diff --git a/poetry.lock b/poetry.lock index 0aade8ad5..138e67e22 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1239,6 +1239,20 @@ importlib-metadata = {version = ">=1", markers = "python_version < \"3.8\""} packaging = ">=17.1" pytest = ">=5.3" +[[package]] +name = "pytest-split-tests" +version = "1.1.0" +description = "A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups." +optional = false +python-versions = "*" +files = [ + {file = "pytest-split-tests-1.1.0.tar.gz", hash = "sha256:e55bbf127d94ff913a248f32e64f07cd3201e8a82397e8152694e4ffe456e21c"}, + {file = "pytest_split_tests-1.1.0-py2.py3-none-any.whl", hash = "sha256:21be7d6f95291002790fe24e28faa950059694e7abc430d4993d99a4c773bde4"}, +] + +[package.dependencies] +pytest = ">=2.5" + [[package]] name = "pytest-splunk-addon" version = "5.4.1" @@ -1768,4 +1782,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7,<3.13" -content-hash = "f0072f7b472f7556af341f351358bed09701afd1b0b014e296a4c5fec118c4c6" +content-hash = "0435209576291a24a097f17bcf32f89216d592fb01e88fe72c521cb7379ff474" diff --git a/pyproject.toml b/pyproject.toml index 52f58c61a..fd3879d92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ mkdocs-print-site-plugin = "^2.3.6" pytest-cov = "^4.0.0" covdefaults = "^2.3.0" xmldiff = "^2.6.3" +pytest-split-tests = "^1.1.0" [tool.poetry.scripts] ucc-gen="splunk_add_on_ucc_framework.main:main" diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx index eec9726e6..f4c2b624d 100644 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx @@ -49,8 +49,8 @@ const renderFeature = (additionalProps?: Partial) => const mockedEntries = [ { name: 'dataApiTest1', content: { testLabel: 'firstLabel', testValue: 'firstValue' } }, { name: 'dataApiTest2', content: { testLabel: 'secondLabel', testValue: 'secondValue' } }, - { name: 'dataApiTest3', content: { testLabel: 'thirdLabel', testValue: 'thirdValue' } }, - { name: 'dataApiTest4', content: { testLabel: 'fourthLabel', testValue: 'fourthValue' } }, + { name: 'true', content: { testLabel: 'thirdLabel', testValue: 'thirdValue' } }, + { name: '0', content: { testLabel: 'fourthLabel', testValue: 'fourthValue' } }, ]; const MOCK_API_URL = '/demo_addon_for_splunk/some_API_endpint_for_select_data'; @@ -167,3 +167,67 @@ it.each([ const { label } = autoCompleteFields[0]; expect(within(inputComponent).getByText(label)).toBeInTheDocument(); }); + +it('should fetch options from API when endpointUrl is provided', async () => { + // server responses with a filtered mockedEntries based on the name parameter + server.use( + http.get(MOCK_API_URL, ({ request }) => { + const url = new URL(request.url); + + const nameParameter = url.searchParams.get('name'); + return HttpResponse.json( + getMockServerResponseForInput( + mockedEntries.filter((entry) => entry.name === nameParameter) + ) + ); + }) + ); + const baseProps = { + ...defaultInputProps, + value: '', + controlOptions: { + createSearchChoice: true, + dependencies: ['name', 'region'], + endpointUrl: MOCK_API_URL, + labelField: 'testLabel', + valueField: 'testValue', + }, + }; + const { rerender } = render(); + + await userEvent.click(screen.getByRole('combobox')); + await screen.findByRole('menuitem', { name: 'No matches' }); + + // undefined value must be omitted + const firstEntry = mockedEntries[0]; + rerender( + + ); + await userEvent.click(screen.getByRole('combobox')); + await screen.findByRole('option', { name: firstEntry.content.testLabel }); + + const secondEntry = mockedEntries[1]; + rerender( + + ); + await userEvent.click(screen.getByRole('combobox')); + await screen.findByRole('option', { name: secondEntry.content.testLabel }); + + const thirdEntry = mockedEntries[2]; + rerender( + + ); + await userEvent.click(screen.getByRole('combobox')); + await screen.findByRole('option', { name: thirdEntry.content.testLabel }); + + const fourthEntry = mockedEntries[3]; + rerender(); + await userEvent.click(screen.getByRole('combobox')); + await screen.findByRole('option', { name: fourthEntry.content.testLabel }); +}); diff --git a/ui/src/util/api.ts b/ui/src/util/api.ts index e44a7657e..b0265eaa9 100755 --- a/ui/src/util/api.ts +++ b/ui/src/util/api.ts @@ -5,9 +5,11 @@ import { generateToast, getUnifiedConfigs } from './util'; import { parseErrorMsg } from './messageUtil'; import { ResponseError } from './ResponseError'; +type ParamsRecord = Record; + export interface RequestParams { endpointUrl: string; - params?: Record; + params?: ParamsRecord; signal?: AbortSignal; body?: BodyInit; handleError: boolean; @@ -21,14 +23,14 @@ export function generateEndPointUrl(name: string) { const DEFAULT_PARAMS = { output_mode: 'json' }; -function createUrl(endpointUrl: string, params: Record): URL { +function createUrl(endpointUrl: string, params: ParamsRecord): URL { const url = new URL( createRESTURL(endpointUrl, { app, owner: 'nobody' }), window.location.origin ); - Object.entries({ ...DEFAULT_PARAMS, ...params }).forEach(([key, value]) => - url.searchParams.append(key, value.toString()) - ); + Object.entries({ ...DEFAULT_PARAMS, ...params }) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => url.searchParams.append(key, value.toString())); return url; }