This is an automated email from the ASF dual-hosted git repository. jli pushed a commit to branch fix-app-prefix-export in repository https://gitbox.apache.org/repos/asf/superset.git
commit ce99812b9d4e0b654a3f522a0598ae9e974ba34a Author: Joe Li <[email protected]> AuthorDate: Fri Dec 19 00:22:00 2025 -0800 test(explore): add exportChart streaming export URL prefix tests Adds tests to verify URL prefixes are correctly applied when using onStartStreamingExport callback for subdirectory deployments. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> --- .../src/explore/exploreUtils/exportChart.test.ts | 147 +++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/superset-frontend/src/explore/exploreUtils/exportChart.test.ts b/superset-frontend/src/explore/exploreUtils/exportChart.test.ts new file mode 100644 index 0000000000..9189b844d7 --- /dev/null +++ b/superset-frontend/src/explore/exploreUtils/exportChart.test.ts @@ -0,0 +1,147 @@ +/** + * 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 { exportChart } from '.'; + +// Mock pathUtils to control app root prefix +jest.mock('src/utils/pathUtils', () => ({ + ensureAppRoot: jest.fn((path: string) => path), +})); + +// Mock SupersetClient +jest.mock('@superset-ui/core', () => ({ + ...jest.requireActual('@superset-ui/core'), + SupersetClient: { + postForm: jest.fn(), + get: jest.fn().mockResolvedValue({ json: {} }), + post: jest.fn().mockResolvedValue({ json: {} }), + }, + getChartBuildQueryRegistry: jest.fn().mockReturnValue({ + get: jest.fn().mockReturnValue(() => () => ({})), + }), + getChartMetadataRegistry: jest.fn().mockReturnValue({ + get: jest.fn().mockReturnValue({ parseMethod: 'json' }), + }), +})); + +const { ensureAppRoot } = jest.requireMock('src/utils/pathUtils'); + + +describe('exportChart URL prefix for streaming export', () => { + beforeEach(() => { + jest.clearAllMocks(); + // Default: no prefix + ensureAppRoot.mockImplementation((path: string) => path); + }); + + // Minimal formData that won't trigger legacy API (useLegacyApi = false) + const baseFormData = { + datasource: '1__table', + viz_type: 'table', + }; + + describe('v1 API endpoint', () => { + test('passes prefixed URL to onStartStreamingExport when app root is configured', async () => { + const appRoot = '/superset'; + ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`); + + const onStartStreamingExport = jest.fn(); + + await exportChart({ + formData: baseFormData, + resultFormat: 'csv', + onStartStreamingExport: onStartStreamingExport as unknown as null, + }); + + expect(onStartStreamingExport).toHaveBeenCalledTimes(1); + const callArgs = onStartStreamingExport.mock.calls[0][0]; + expect(callArgs.url).toBe('/superset/api/v1/chart/data'); + expect(callArgs.exportType).toBe('csv'); + }); + + test('passes unprefixed URL when no app root is configured', async () => { + ensureAppRoot.mockImplementation((path: string) => path); + + const onStartStreamingExport = jest.fn(); + + await exportChart({ + formData: baseFormData, + resultFormat: 'csv', + onStartStreamingExport: onStartStreamingExport as unknown as null, + }); + + expect(onStartStreamingExport).toHaveBeenCalledTimes(1); + const callArgs = onStartStreamingExport.mock.calls[0][0]; + expect(callArgs.url).toBe('/api/v1/chart/data'); + }); + + test('passes nested prefix for deeply nested deployments', async () => { + const appRoot = '/my-company/analytics/superset'; + ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`); + + const onStartStreamingExport = jest.fn(); + + await exportChart({ + formData: baseFormData, + resultFormat: 'xlsx', + onStartStreamingExport: onStartStreamingExport as unknown as null, + }); + + expect(onStartStreamingExport).toHaveBeenCalledTimes(1); + const callArgs = onStartStreamingExport.mock.calls[0][0]; + expect(callArgs.url).toBe( + '/my-company/analytics/superset/api/v1/chart/data', + ); + expect(callArgs.exportType).toBe('xlsx'); + }); + }); + + describe('exportType passthrough', () => { + test('passes csv exportType for CSV exports', async () => { + const onStartStreamingExport = jest.fn(); + + await exportChart({ + formData: baseFormData, + resultFormat: 'csv', + onStartStreamingExport: onStartStreamingExport as unknown as null, + }); + + expect(onStartStreamingExport).toHaveBeenCalledWith( + expect.objectContaining({ + exportType: 'csv', + }), + ); + }); + + test('passes xlsx exportType for Excel exports', async () => { + const onStartStreamingExport = jest.fn(); + + await exportChart({ + formData: baseFormData, + resultFormat: 'xlsx', + onStartStreamingExport: onStartStreamingExport as unknown as null, + }); + + expect(onStartStreamingExport).toHaveBeenCalledWith( + expect.objectContaining({ + exportType: 'xlsx', + }), + ); + }); + }); +});
