This is an automated email from the ASF dual-hosted git repository.

elizabeth pushed a commit to branch csp-frame
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7186f92d38b8d089503e189e2c74d068a65b37a3
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Fri Oct 17 11:47:04 2025 -0700

    test: add comprehensive unhappy path tests for export
    
    Add tests for error scenarios including:
    - Network errors and logging
    - 404 errors when resource not found
    - Empty response handling
    - Download failure with proper cleanup
    - Malformed Content-Disposition header
    - Missing headers fallback
    - Empty IDs array edge case
    
    These tests ensure proper error handling and cleanup in all failure modes.
---
 superset-frontend/src/utils/export.test.ts | 120 +++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/superset-frontend/src/utils/export.test.ts 
b/superset-frontend/src/utils/export.test.ts
index bfe936bdcd..c6900d5362 100644
--- a/superset-frontend/src/utils/export.test.ts
+++ b/superset-frontend/src/utils/export.test.ts
@@ -276,3 +276,123 @@ test('handles various resource types', async () => {
 
   expect(doneMock).toHaveBeenCalledTimes(5);
 });
+
+test('handles network errors and logs them', async () => {
+  const networkError = new Error('Network request failed');
+  (SupersetClient.get as jest.Mock).mockRejectedValue(networkError);
+
+  const doneMock = jest.fn();
+
+  await expect(
+    handleResourceExport('dashboard', [1], doneMock),
+  ).rejects.toThrow('Network request failed');
+
+  expect(logging.error).toHaveBeenCalledWith(
+    'Resource export failed:',
+    networkError,
+  );
+  expect(doneMock).toHaveBeenCalled();
+});
+
+test('handles 404 errors when resource not found', async () => {
+  const notFoundError = new Error('Not found');
+  (SupersetClient.get as jest.Mock).mockRejectedValue(notFoundError);
+
+  const doneMock = jest.fn();
+
+  await expect(
+    handleResourceExport('dashboard', [999], doneMock),
+  ).rejects.toThrow('Not found');
+
+  expect(doneMock).toHaveBeenCalled();
+});
+
+test('handles empty response from server', async () => {
+  const emptyBlob = new Blob([], { type: 'application/zip' });
+  mockResponse = {
+    headers: new Headers({
+      'Content-Disposition': 'attachment; filename="empty.zip"',
+    }),
+    blob: jest.fn().mockResolvedValue(emptyBlob),
+  } as unknown as Response;
+  (SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
+
+  const doneMock = jest.fn();
+  await handleResourceExport('dashboard', [1], doneMock);
+
+  expect(window.URL.createObjectURL).toHaveBeenCalledWith(emptyBlob);
+  expect(doneMock).toHaveBeenCalled();
+});
+
+test('cleans up blob URL even when download fails', async () => {
+  const mockAnchor = document.createElement('a');
+  mockAnchor.click = jest.fn().mockImplementation(() => {
+    throw new Error('Click failed');
+  });
+
+  createElementSpy.mockRestore();
+  createElementSpy = jest
+    .spyOn(document, 'createElement')
+    .mockReturnValue(mockAnchor);
+
+  const doneMock = jest.fn();
+
+  await expect(
+    handleResourceExport('dashboard', [1], doneMock),
+  ).rejects.toThrow('Click failed');
+
+  // Verify cleanup still happens
+  expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url');
+  expect(doneMock).toHaveBeenCalled();
+});
+
+test('handles malformed Content-Disposition header', async () => {
+  mockResponse = {
+    headers: new Headers({
+      'Content-Disposition': 'not-a-valid-header',
+    }),
+    blob: jest.fn().mockResolvedValue(mockBlob),
+  } as unknown as Response;
+  (SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
+
+  (contentDisposition.parse as jest.Mock).mockImplementationOnce(() => {
+    throw new Error('Parse error');
+  });
+
+  const doneMock = jest.fn();
+  await handleResourceExport('dataset', [5], doneMock);
+
+  // Should fall back to default filename
+  const anchor = document.createElement('a');
+  expect(anchor.download).toBe('dataset_export.zip');
+  expect(logging.warn).toHaveBeenCalledWith(
+    'Failed to parse Content-Disposition header:',
+    expect.any(Error),
+  );
+});
+
+test('handles missing headers object', async () => {
+  mockResponse = {
+    headers: new Headers(),
+    blob: jest.fn().mockResolvedValue(mockBlob),
+  } as unknown as Response;
+  (SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
+
+  const doneMock = jest.fn();
+  await handleResourceExport('chart', [7], doneMock);
+
+  const anchor = document.createElement('a');
+  expect(anchor.download).toBe('chart_export.zip');
+  expect(doneMock).toHaveBeenCalled();
+});
+
+test('handles export with empty IDs array', async () => {
+  const doneMock = jest.fn();
+  await handleResourceExport('dashboard', [], doneMock);
+
+  expect(SupersetClient.get).toHaveBeenCalledWith(
+    expect.objectContaining({
+      endpoint: '/api/v1/dashboard/export/?q=!()',
+    }),
+  );
+});

Reply via email to