mirror of
https://github.com/apache/superset.git
synced 2026-07-02 12:55:35 +00:00
Compare commits
1 Commits
master
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3bb8e0e0a |
@@ -74,7 +74,7 @@
|
||||
"antd": "^6.4.5",
|
||||
"baseline-browser-mapping": "^2.10.38",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
"docusaurus-plugin-openapi-docs": "^5.0.2",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.0",
|
||||
"docusaurus-theme-openapi-docs": "^5.0.2",
|
||||
"js-yaml": "^5.1.0",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
|
||||
@@ -7163,10 +7163,10 @@ doctrine@^2.1.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
docusaurus-plugin-openapi-docs@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.0.2.tgz#f00028621deb9179065fe7d6a541256692ef941b"
|
||||
integrity sha512-WCC2m6PpylXZfNga+ScelTG0a7jUGtbB9+AmbR9lUj93FPryTs8VHTMJ3fKtO0senJTWgOU3MDvZw0v+mE3ztA==
|
||||
docusaurus-plugin-openapi-docs@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.1.0.tgz#9732f81f45a5bc126bcafcb150332b7623ddece7"
|
||||
integrity sha512-ocRemE3KmdhhPKaow5hja1m1NLIPfNlfRYFt7pja+nG26Wlp0MEC9ERS99gSWEWaxU+txDFBpUXsxo7nGlk8ZA==
|
||||
dependencies:
|
||||
"@apidevtools/json-schema-ref-parser" "^15.3.3"
|
||||
"@redocly/openapi-core" "^2.25.2"
|
||||
|
||||
18
superset-frontend/package-lock.json
generated
18
superset-frontend/package-lock.json
generated
@@ -101,7 +101,7 @@
|
||||
"geostyler-openlayers-parser": "^5.7.1",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.9.0",
|
||||
"google-auth-library": "^10.7.0",
|
||||
"immer": "^11.1.8",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
@@ -202,7 +202,7 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
@@ -11706,9 +11706,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
|
||||
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
|
||||
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
@@ -22193,9 +22193,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz",
|
||||
"integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==",
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.7.0.tgz",
|
||||
"integrity": "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
@@ -44345,7 +44345,7 @@
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/jquery": "^4.0.1",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-table": "^7.7.20",
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
"geostyler-openlayers-parser": "^5.7.1",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.9.0",
|
||||
"google-auth-library": "^10.7.0",
|
||||
"immer": "^11.1.8",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
@@ -284,7 +284,7 @@
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/jquery": "^4.0.1",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-table": "^7.7.20",
|
||||
|
||||
@@ -1132,49 +1132,4 @@ describe('ResultSet', () => {
|
||||
});
|
||||
expect(mockOpenInNewTab).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('filters the results table when typing in the search box', async () => {
|
||||
const filterableQuery = {
|
||||
...queries[0],
|
||||
id: 'filterableQueryId',
|
||||
cached: false,
|
||||
results: {
|
||||
columns: [{ is_dttm: false, column_name: 'fruit', type: 'STRING' }],
|
||||
selected_columns: [
|
||||
{ is_dttm: false, column_name: 'fruit', type: 'STRING' },
|
||||
],
|
||||
data: [{ fruit: 'apple' }, { fruit: 'banana' }],
|
||||
},
|
||||
};
|
||||
const { getByTestId, getByPlaceholderText, queryByText } = setup(
|
||||
{ ...mockedProps, cache: false, queryId: filterableQuery.id },
|
||||
mockStore({
|
||||
...initialState,
|
||||
user,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
queries: {
|
||||
[filterableQuery.id]: filterableQuery,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('table-container')).toBeInTheDocument();
|
||||
});
|
||||
// Both rows are visible before filtering
|
||||
expect(queryByText('apple')).toBeInTheDocument();
|
||||
expect(queryByText('banana')).toBeInTheDocument();
|
||||
|
||||
// Typing in the search box filters the rows (case-insensitive substring)
|
||||
fireEvent.change(getByPlaceholderText('Filter results'), {
|
||||
target: { value: 'APP' },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('banana')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(queryByText('apple')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,28 +127,6 @@ describe('SaveDatasetModal', () => {
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('disables the save button when the dataset name is empty or whitespace-only', async () => {
|
||||
renderModal();
|
||||
|
||||
const nameInput = screen.getByRole('textbox');
|
||||
const saveBtn = screen.getByRole('button', { name: /save/i });
|
||||
|
||||
// Default name is present, so save starts enabled
|
||||
expect(saveBtn).toBeEnabled();
|
||||
|
||||
// Clearing the name disables save
|
||||
await userEvent.clear(nameInput);
|
||||
await waitFor(() => expect(saveBtn).toBeDisabled());
|
||||
|
||||
// Whitespace-only name keeps save disabled
|
||||
await userEvent.type(nameInput, ' ');
|
||||
await waitFor(() => expect(saveBtn).toBeDisabled());
|
||||
|
||||
// A non-empty name re-enables save
|
||||
await userEvent.type(nameInput, 'My dataset');
|
||||
await waitFor(() => expect(saveBtn).toBeEnabled());
|
||||
});
|
||||
|
||||
test('renders an overwrite button when "Overwrite existing" is selected', () => {
|
||||
renderModal();
|
||||
|
||||
@@ -267,22 +245,6 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('trims surrounding whitespace from the dataset name on save', async () => {
|
||||
renderModal();
|
||||
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: ' my dataset ' } });
|
||||
|
||||
const saveConfirmationBtn = screen.getByRole('button', {
|
||||
name: /save/i,
|
||||
});
|
||||
userEvent.click(saveConfirmationBtn);
|
||||
|
||||
expect(createDatasource).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ datasourceName: 'my dataset' }),
|
||||
);
|
||||
});
|
||||
|
||||
test('sends the catalog when creating the dataset', async () => {
|
||||
renderModal({
|
||||
datasource: { ...mockedProps.datasource, catalog: 'public' },
|
||||
|
||||
@@ -371,7 +371,7 @@ export const SaveDatasetModal = ({
|
||||
catalog: datasource?.catalog ?? null,
|
||||
schema: datasource?.schema ?? '',
|
||||
templateParams,
|
||||
datasourceName: datasetName.trim(),
|
||||
datasourceName: datasetName,
|
||||
}),
|
||||
)
|
||||
.then((data: { id: number }) => {
|
||||
@@ -417,7 +417,7 @@ export const SaveDatasetModal = ({
|
||||
|
||||
const disableSaveAndExploreBtn =
|
||||
(newOrOverwrite === DatasetRadioState.SaveNew &&
|
||||
datasetName.trim().length === 0) ||
|
||||
datasetName.length === 0) ||
|
||||
(newOrOverwrite === DatasetRadioState.OverwriteDataset &&
|
||||
isEmpty(selectedDatasetToOverwrite));
|
||||
|
||||
|
||||
@@ -337,32 +337,4 @@ describe('SavedQuery', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('disables the save button when the query name is empty or whitespace-only', async () => {
|
||||
render(<SaveQuery {...mockedProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: /save/i }));
|
||||
|
||||
const nameInput = screen.getAllByRole('textbox')[0] as HTMLInputElement;
|
||||
const modalSaveBtn = () =>
|
||||
screen.getAllByRole('button', { name: /save/i })[1];
|
||||
|
||||
// Default label is present, so the save button starts enabled
|
||||
expect(modalSaveBtn()).toBeEnabled();
|
||||
|
||||
// Clearing the name disables the save button
|
||||
userEvent.clear(nameInput);
|
||||
await waitFor(() => expect(modalSaveBtn()).toBeDisabled());
|
||||
|
||||
// A whitespace-only name keeps the save button disabled
|
||||
userEvent.type(nameInput, ' ');
|
||||
await waitFor(() => expect(modalSaveBtn()).toBeDisabled());
|
||||
|
||||
// A non-empty name re-enables the save button
|
||||
userEvent.type(nameInput, 'My query');
|
||||
await waitFor(() => expect(modalSaveBtn()).toBeEnabled());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,7 +112,6 @@ const SaveQuery = ({
|
||||
const [showSave, setShowSave] = useState<boolean>(false);
|
||||
const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);
|
||||
const isSaved = !!query.remoteId;
|
||||
const isLabelEmpty = label.trim().length === 0;
|
||||
const canExploreDatabase = !!database?.allows_virtual_table_explore;
|
||||
const shouldShowSaveButton =
|
||||
database?.allows_virtual_table_explore !== undefined;
|
||||
@@ -236,18 +235,12 @@ const SaveQuery = ({
|
||||
<Button
|
||||
buttonStyle={isSaved ? 'secondary' : 'primary'}
|
||||
onClick={onSaveWrapper}
|
||||
disabled={isLabelEmpty}
|
||||
cta
|
||||
>
|
||||
{isSaved ? t('Save as new') : t('Save')}
|
||||
</Button>
|
||||
{isSaved && (
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
onClick={onUpdateWrapper}
|
||||
disabled={isLabelEmpty}
|
||||
cta
|
||||
>
|
||||
<Button buttonStyle="primary" onClick={onUpdateWrapper} cta>
|
||||
{t('Update')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo, useCallback, memo } from 'react';
|
||||
import { useMemo, useCallback, useRef, memo } from 'react';
|
||||
import { GridSize } from 'src/components/GridTable/constants';
|
||||
import { GridTable } from 'src/components/GridTable';
|
||||
import { type ColDef } from 'src/components/GridTable/types';
|
||||
@@ -109,15 +109,15 @@ export const FilterableTable = ({
|
||||
[orderedColumnKeys, allowHTML, getCellContent],
|
||||
);
|
||||
|
||||
const keywordFilter = useCallback(
|
||||
(node: { data: Datum }) => {
|
||||
if (filterText && node.data) {
|
||||
return hasMatch(filterText, node.data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[filterText],
|
||||
);
|
||||
const keyword = useRef<string | undefined>(filterText);
|
||||
keyword.current = filterText;
|
||||
|
||||
const keywordFilter = useCallback((node: { data: Datum }) => {
|
||||
if (keyword.current && node.data) {
|
||||
return hasMatch(keyword.current, node.data);
|
||||
}
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="filterable-table-container" data-test="table-container">
|
||||
|
||||
8
superset-websocket/package-lock.json
generated
8
superset-websocket/package-lock.json
generated
@@ -22,7 +22,7 @@
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
@@ -769,9 +769,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
|
||||
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
|
||||
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/node": "^26.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
|
||||
@@ -52,7 +52,6 @@ from superset.queries.saved_queries.schemas import (
|
||||
get_delete_ids_schema,
|
||||
get_export_ids_schema,
|
||||
openapi_spec_methods_override,
|
||||
validate_label,
|
||||
)
|
||||
from superset.utils import json
|
||||
from superset.utils.core import sanitize_cookie_token
|
||||
@@ -194,12 +193,8 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
||||
allowed_rel_fields = {"database", "changed_by", "created_by"}
|
||||
allowed_distinct_fields = {"catalog", "schema"}
|
||||
|
||||
validators_columns = {"label": validate_label}
|
||||
|
||||
def pre_add(self, item: SavedQuery) -> None:
|
||||
item.user = g.user
|
||||
if item.label:
|
||||
item.label = item.label.strip()
|
||||
|
||||
def pre_update(self, item: SavedQuery) -> None:
|
||||
self.pre_add(item)
|
||||
|
||||
@@ -14,17 +14,9 @@
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from flask_babel import lazy_gettext as _
|
||||
from marshmallow import fields, Schema, ValidationError
|
||||
from marshmallow import fields, Schema
|
||||
from marshmallow.validate import Length
|
||||
|
||||
|
||||
def validate_label(value: str | None) -> None:
|
||||
"""Reject blank or whitespace-only saved query labels."""
|
||||
if value is None or not value.strip():
|
||||
raise ValidationError(_("Label must not be empty."))
|
||||
|
||||
|
||||
openapi_spec_methods_override = {
|
||||
"get": {"get": {"summary": "Get a saved query"}},
|
||||
"get_list": {
|
||||
|
||||
@@ -677,69 +677,6 @@ class TestSavedQueryApi(SupersetTestCase):
|
||||
db.session.delete(model)
|
||||
db.session.commit()
|
||||
|
||||
def test_create_saved_query_blank_label(self) -> None:
|
||||
"""
|
||||
Saved Query API: Test create rejects blank/whitespace-only labels
|
||||
"""
|
||||
example_db = get_example_database()
|
||||
self.login(ADMIN_USERNAME)
|
||||
uri = "api/v1/saved_query/"
|
||||
|
||||
for label in ("", " ", "\t\n"):
|
||||
post_data = {
|
||||
"schema": "schema1",
|
||||
"label": label,
|
||||
"description": "some description",
|
||||
"sql": "SELECT col1, col2 from table1",
|
||||
"db_id": example_db.id,
|
||||
}
|
||||
rv = self.client.post(uri, json=post_data)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 422
|
||||
assert "label" in data["message"]
|
||||
|
||||
def test_create_saved_query_strips_label(self) -> None:
|
||||
"""
|
||||
Saved Query API: Test create trims surrounding whitespace from labels
|
||||
"""
|
||||
example_db = get_example_database()
|
||||
self.login(ADMIN_USERNAME)
|
||||
uri = "api/v1/saved_query/"
|
||||
|
||||
post_data = {
|
||||
"schema": "schema1",
|
||||
"label": " label1 ",
|
||||
"description": "some description",
|
||||
"sql": "SELECT col1, col2 from table1",
|
||||
"db_id": example_db.id,
|
||||
}
|
||||
rv = self.client.post(uri, json=post_data)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 201
|
||||
|
||||
model = db.session.query(SavedQuery).get(data.get("id"))
|
||||
assert model.label == "label1"
|
||||
|
||||
# Rollback changes
|
||||
db.session.delete(model)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
def test_update_saved_query_blank_label(self) -> None:
|
||||
"""
|
||||
Saved Query API: Test update rejects blank/whitespace-only labels
|
||||
"""
|
||||
saved_query = (
|
||||
db.session.query(SavedQuery).filter(SavedQuery.label == "label1").all()[0]
|
||||
)
|
||||
self.login(ADMIN_USERNAME)
|
||||
uri = f"api/v1/saved_query/{saved_query.id}"
|
||||
|
||||
rv = self.client.put(uri, json={"label": " "})
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 422
|
||||
assert "label" in data["message"]
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
def test_update_saved_query(self):
|
||||
"""
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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 pytest
|
||||
from marshmallow import ValidationError
|
||||
|
||||
from superset.queries.saved_queries.schemas import validate_label
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["", " ", "\t\n", None])
|
||||
def test_validate_label_rejects_blank(value: str | None) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
validate_label(value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["my query", " padded "])
|
||||
def test_validate_label_accepts_non_blank(value: str) -> None:
|
||||
# Should not raise for labels containing non-whitespace characters.
|
||||
validate_label(value)
|
||||
Reference in New Issue
Block a user