🐛 fix(jvm): 修正连接表单模式回填与超时同步

- 保留编辑态 JVM 连接的原始 preferredMode,避免旧配置被静默降级
- 将 JVM 可见超时统一同步到 Endpoint 探测配置
- 抽取 JVM 可编辑模式判定与回填逻辑,统一 ConnectionModal 行为
- 补充 JVM 模式与超时纯函数测试,覆盖 unsupported preferredMode 分支
- 更新需求追踪文档,记录 Task 3 实现、复审与验证结果
This commit is contained in:
Syngnat
2026-04-23 10:20:47 +08:00
parent 9bb7ece2dd
commit 7ddb49a81d
4 changed files with 189 additions and 57 deletions

View File

@@ -1,6 +1,12 @@
import { describe, expect, it } from 'vitest';
import { buildDefaultJVMConnectionValues, buildJVMConnectionConfig } from './jvmConnectionConfig';
import {
buildDefaultJVMConnectionValues,
buildJVMConnectionConfig,
hasUnsupportedJVMEditableModes,
normalizeEditableJVMModes,
resolveEditableJVMModeSelection,
} from './jvmConnectionConfig';
describe('jvmConnectionConfig', () => {
it('defaults to readonly jmx mode', () => {
@@ -58,4 +64,56 @@ describe('jvmConnectionConfig', () => {
expect(config.jvm?.environment).toBe('prod');
expect(config.jvm?.readOnly).toBe(false);
});
it('keeps the visible timeout as the source of truth for endpoint probing', () => {
const config = buildJVMConnectionConfig({
host: 'orders.internal',
port: 9010,
timeout: 45,
jvmEndpointTimeoutSeconds: 30,
jvmAllowedModes: ['endpoint'],
jvmPreferredMode: 'endpoint',
jvmEndpointEnabled: true,
jvmEndpointBaseUrl: 'https://orders.internal/manage/jvm',
});
expect(config.timeout).toBe(45);
expect(config.jvm?.endpoint?.timeoutSeconds).toBe(45);
});
it('normalizes editable JVM modes to the supported form subset', () => {
expect(normalizeEditableJVMModes([' endpoint ', 'agent', 'JMX', 'endpoint'])).toEqual(['endpoint', 'jmx']);
});
it('detects unsupported editable JVM modes without downgrading them silently', () => {
expect(hasUnsupportedJVMEditableModes({
allowedModes: ['agent', 'jmx'],
preferredMode: 'agent',
})).toBe(true);
expect(hasUnsupportedJVMEditableModes({
allowedModes: ['endpoint', 'jmx'],
preferredMode: 'agent',
})).toBe(true);
expect(hasUnsupportedJVMEditableModes({
allowedModes: ['endpoint', 'jmx'],
preferredMode: 'endpoint',
})).toBe(false);
});
it('preserves preferred mode when rebuilding editable mode selection from stored config', () => {
expect(resolveEditableJVMModeSelection({
allowedModes: [],
preferredMode: 'agent',
})).toEqual({
allowedModes: ['agent'],
preferredMode: 'agent',
});
expect(resolveEditableJVMModeSelection({
allowedModes: ['endpoint', 'jmx'],
preferredMode: 'agent',
})).toEqual({
allowedModes: ['endpoint', 'jmx'],
preferredMode: 'agent',
});
});
});

View File

@@ -4,12 +4,15 @@ const DEFAULT_JMX_PORT = 9010;
const DEFAULT_TIMEOUT_SECONDS = 30;
const DEFAULT_ENVIRONMENT = 'dev';
const JVM_MODES = ['jmx', 'endpoint', 'agent'] as const;
export const JVM_EDITABLE_MODES = ['jmx', 'endpoint'] as const;
type JVMMode = typeof JVM_MODES[number];
type JVMEditableMode = typeof JVM_EDITABLE_MODES[number];
type JVMEnvironment = 'dev' | 'uat' | 'prod';
type JVMConnectionFormValues = Record<string, unknown>;
const isJVMMode = (value: string): value is JVMMode => JVM_MODES.includes(value as JVMMode);
const isJVMEditableMode = (value: string): value is JVMEditableMode => JVM_EDITABLE_MODES.includes(value as JVMEditableMode);
const toStringValue = (value: unknown): string => {
if (typeof value === 'string') {
@@ -51,6 +54,61 @@ const normalizeModes = (value: unknown): JVMMode[] => {
return result.length > 0 ? result : ['jmx'];
};
export const normalizeEditableJVMModes = (value: unknown): JVMEditableMode[] => {
if (!Array.isArray(value)) {
return ['jmx'];
}
const result: JVMEditableMode[] = [];
const seen = new Set<JVMEditableMode>();
for (const item of value) {
const mode = toStringValue(item).toLowerCase();
if (!isJVMEditableMode(mode) || seen.has(mode)) {
continue;
}
seen.add(mode);
result.push(mode);
}
return result.length > 0 ? result : ['jmx'];
};
export const hasUnsupportedJVMEditableModes = ({
allowedModes,
preferredMode,
}: {
allowedModes: unknown;
preferredMode: unknown;
}): boolean => {
const allowed = Array.isArray(allowedModes)
? allowedModes.map((item) => toStringValue(item).toLowerCase()).filter((item) => item !== '')
: [];
const preferred = toStringValue(preferredMode).toLowerCase();
return allowed.some((mode) => !isJVMEditableMode(mode))
|| (preferred !== '' && !isJVMEditableMode(preferred));
};
export const resolveEditableJVMModeSelection = ({
allowedModes,
preferredMode,
}: {
allowedModes: unknown;
preferredMode: unknown;
}): { allowedModes: string[]; preferredMode: string } => {
const normalizedAllowedModes = Array.isArray(allowedModes)
? allowedModes.map((item) => toStringValue(item).toLowerCase()).filter((item) => item !== '')
: [];
const normalizedPreferredMode = toStringValue(preferredMode).toLowerCase();
const resolvedAllowedModes = normalizedAllowedModes.length > 0
? Array.from(new Set(normalizedAllowedModes))
: (normalizedPreferredMode ? [normalizedPreferredMode] : ['jmx']);
return {
allowedModes: resolvedAllowedModes,
preferredMode: normalizedPreferredMode || resolvedAllowedModes[0],
};
};
const normalizePreferredMode = (value: unknown, allowedModes: JVMMode[]): JVMMode => {
const preferred = toStringValue(value).toLowerCase();
if (isJVMMode(preferred) && allowedModes.includes(preferred)) {
@@ -91,7 +149,9 @@ export const buildJVMConnectionConfig = (values: JVMConnectionFormValues): Conne
const allowedModes = normalizeModes(values.jvmAllowedModes);
const preferredMode = normalizePreferredMode(values.jvmPreferredMode, allowedModes);
const port = toInteger(values.port, DEFAULT_JMX_PORT);
const timeout = toInteger(values.timeout, DEFAULT_TIMEOUT_SECONDS);
const timeout = values.timeout === undefined || values.timeout === null || values.timeout === ''
? toInteger(values.jvmEndpointTimeoutSeconds, DEFAULT_TIMEOUT_SECONDS)
: toInteger(values.timeout, DEFAULT_TIMEOUT_SECONDS);
return {
type: 'jvm',
@@ -116,7 +176,7 @@ export const buildJVMConnectionConfig = (values: JVMConnectionFormValues): Conne
enabled: values.jvmEndpointEnabled === true,
baseUrl: toStringValue(values.jvmEndpointBaseUrl),
apiKey: toStringValue(values.jvmEndpointApiKey),
timeoutSeconds: toInteger(values.jvmEndpointTimeoutSeconds, timeout),
timeoutSeconds: timeout,
},
},
};