mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-18 19:32:47 +08:00
🐛 fix(external-sql): 修复外部 SQL 文件丢失后标签无法关闭
- 后端读取 SQL 文件失败时返回 file_not_found 结构化错误码 - 前端识别文件被删除或移动的场景,允许用户确认关闭标签 - 保留权限、网络盘异常等非缺失错误的关闭拦截,避免误丢草稿 - 补充前后端测试覆盖缺失文件识别与标签关闭提示 Close #566
This commit is contained in:
@@ -3,6 +3,8 @@ import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
getSQLFileTabPath,
|
||||
hasSQLFileTabUnsavedChanges,
|
||||
isSQLFileMissingErrorMessage,
|
||||
isSQLFileMissingReadResult,
|
||||
isSQLFileQueryTab,
|
||||
normalizeSQLFileReadContent,
|
||||
} from './sqlFileTabDirty';
|
||||
@@ -34,4 +36,24 @@ describe('sqlFileTabDirty', () => {
|
||||
query: 'select 2;',
|
||||
} as any, 'select 1;')).toBe(true);
|
||||
});
|
||||
|
||||
it('detects missing SQL file read failures by structured error code', () => {
|
||||
expect(isSQLFileMissingReadResult({
|
||||
success: false,
|
||||
message: '无法读取文件信息: stat /tmp/missing.sql: no such file or directory',
|
||||
data: { errorCode: 'file_not_found', filePath: '/tmp/missing.sql' },
|
||||
})).toBe(true);
|
||||
|
||||
expect(isSQLFileMissingReadResult({
|
||||
success: false,
|
||||
message: '无法读取文件信息: permission denied',
|
||||
data: { filePath: '/tmp/report.sql' },
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps platform-specific missing file messages as a fallback', () => {
|
||||
expect(isSQLFileMissingErrorMessage('GetFileAttributesEx C:\\Users\\me\\missing.sql: The system cannot find the file specified.')).toBe(true);
|
||||
expect(isSQLFileMissingErrorMessage('stat /Users/me/missing.sql: no such file or directory')).toBe(true);
|
||||
expect(isSQLFileMissingErrorMessage('无法读取文件信息: 权限不足')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { TabData } from '../types';
|
||||
|
||||
const toTrimmedString = (value: unknown): string => String(value ?? '').trim();
|
||||
|
||||
export const SQL_FILE_NOT_FOUND_ERROR_CODE = 'file_not_found';
|
||||
|
||||
export const getSQLFileTabPath = (tab: Pick<TabData, 'type' | 'filePath'> | null | undefined): string => {
|
||||
if (!tab || tab.type !== 'query') return '';
|
||||
return toTrimmedString(tab.filePath);
|
||||
@@ -28,3 +30,35 @@ export const hasSQLFileTabUnsavedChanges = (
|
||||
if (!isSQLFileQueryTab(tab)) return false;
|
||||
return String(tab.query ?? '') !== diskContent;
|
||||
};
|
||||
|
||||
const SQL_FILE_MISSING_MESSAGE_PATTERNS = [
|
||||
'no such file or directory',
|
||||
'cannot find the file specified',
|
||||
'system cannot find the file specified',
|
||||
'does not exist',
|
||||
'not exist',
|
||||
'系统找不到指定的文件',
|
||||
'文件不存在',
|
||||
];
|
||||
|
||||
export const isSQLFileMissingErrorMessage = (message: unknown): boolean => {
|
||||
const normalizedMessage = toTrimmedString(message).toLowerCase();
|
||||
if (!normalizedMessage) return false;
|
||||
return SQL_FILE_MISSING_MESSAGE_PATTERNS.some((pattern) => normalizedMessage.includes(pattern));
|
||||
};
|
||||
|
||||
export const isSQLFileMissingReadResult = (result: unknown): boolean => {
|
||||
if (!result || typeof result !== 'object') return false;
|
||||
const payload = result as Record<string, unknown>;
|
||||
if (payload.success === true) return false;
|
||||
|
||||
const data = payload.data;
|
||||
if (data && typeof data === 'object') {
|
||||
const errorCode = toTrimmedString((data as Record<string, unknown>).errorCode).toLowerCase();
|
||||
if (errorCode === SQL_FILE_NOT_FOUND_ERROR_CODE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return isSQLFileMissingErrorMessage(payload.message);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user