mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-24 09:50:04 +08:00
✨ feat(jvm): 落地 JVM 连接契约与配置归一化
- 新增 JVM 连接配置与共享 DTO,补齐 JMX 和 Endpoint 契约 - 实现后端归一化规则,支持默认只读、模式回退和 JMX 端口兜底 - 新增前端 JVM 默认值与配置构建工具,统一模式环境和端口收敛 - 补充 Go 与 Vitest 用例并更新需求追踪,记录 Task 1 验证证据
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
- [x] 阶段 2(影响分析):完成
|
||||
- [x] 阶段 3(方案设计):完成(已形成正式设计文档)
|
||||
- [x] 阶段 4(实施计划):完成(已形成正式实施计划)
|
||||
- [ ] 阶段 5(实现与自检):
|
||||
- [ ] 阶段 5(实现与自检):进行中(Task 1 已完成并通过回归)
|
||||
- [ ] 阶段 6(评审与交付):
|
||||
- [ ] 阶段 7(发布与观察):
|
||||
|
||||
@@ -40,10 +40,11 @@
|
||||
- 用户明确目标 Java 服务大概率不允许 `-javaagent` 或运行时动态 attach
|
||||
- 已形成 JVM 缓存可视化编辑正式设计文档
|
||||
- 已形成 JVM Connector MVP 正式实施计划文档
|
||||
- - 已完成 Task 1:JVM 共享契约与配置归一化
|
||||
- 进行中:
|
||||
- 等待用户选择执行方式并进入实现
|
||||
- Task 2:建立后端 Provider 注册与连接探测 API
|
||||
- 待处理:
|
||||
- 进入 MVP 分期实施与验证
|
||||
- Task 3+:Guard/Audit/App/UI/AI 结构化计划等后续任务
|
||||
|
||||
## 5. 风险与阻塞
|
||||
- 风险:
|
||||
@@ -71,10 +72,13 @@
|
||||
- GoNavi 现有 Redis/编辑器/UI 复用能力核查
|
||||
- JVM Connector 正式设计文档自检
|
||||
- JVM Connector 实施计划文档自检
|
||||
- Task 1:JVM 共享契约与配置归一化
|
||||
- 结果:
|
||||
- 已确认存在可复用的连接桥接与编辑器基础设施
|
||||
- 已完成正式设计文档落盘与自检,未发现占位词和明显范围冲突
|
||||
- 已完成正式实施计划落盘与自检,已补齐共享 DTO、provider factory 和审计落盘等关键实现细节
|
||||
- 已完成 JVM 连接共享契约、默认只读/默认 JMX 归一化、前端配置收敛与补测
|
||||
- Task 1 已完成规格审查与代码质量审查,结论均通过
|
||||
- 证据(日志/截图/链接):
|
||||
- `cmd/optional-driver-agent/main.go`
|
||||
- `internal/db/database.go`
|
||||
@@ -83,7 +87,19 @@
|
||||
- `frontend/src/components/QueryEditor.tsx`
|
||||
- `docs/superpowers/specs/2026-04-22-jvm-cache-visual-editing-design.md`
|
||||
- `docs/superpowers/plans/2026-04-22-jvm-connector-mvp.md`
|
||||
- `internal/connection/types.go`
|
||||
- `internal/jvm/types.go`
|
||||
- `internal/jvm/config.go`
|
||||
- `internal/jvm/config_test.go`
|
||||
- `frontend/src/types.ts`
|
||||
- `frontend/src/utils/jvmConnectionConfig.ts`
|
||||
- `frontend/src/utils/jvmConnectionConfig.test.ts`
|
||||
- `go test ./internal/jvm -count=1`
|
||||
- `go test ./...`
|
||||
- `cd frontend && npm test -- src/utils/jvmConnectionConfig.test.ts`
|
||||
- `cd frontend && npm test -- --run`
|
||||
- `cd frontend && npm run build`
|
||||
|
||||
## 8. 下一步
|
||||
- 下一步行动:请用户选择实施执行方式;推荐按 task 粒度执行并在每个 task 后做回归和提交
|
||||
- 下一步行动:进入 Task 2,建立 JVM Provider 注册、连接测试与能力探测 API,并在完成后生成/校验 Wails 绑定代码
|
||||
- 负责人:Codex
|
||||
|
||||
@@ -21,6 +21,72 @@ export interface HTTPTunnelConfig {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface JVMJMXConfig {
|
||||
enabled?: boolean;
|
||||
host?: string;
|
||||
port?: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
domainAllowlist?: string[];
|
||||
}
|
||||
|
||||
export interface JVMEndpointConfig {
|
||||
enabled?: boolean;
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
timeoutSeconds?: number;
|
||||
}
|
||||
|
||||
export interface JVMConfig {
|
||||
environment?: 'dev' | 'uat' | 'prod';
|
||||
readOnly?: boolean;
|
||||
allowedModes?: Array<'jmx' | 'endpoint' | 'agent'>;
|
||||
preferredMode?: 'jmx' | 'endpoint' | 'agent';
|
||||
jmx?: JVMJMXConfig;
|
||||
endpoint?: JVMEndpointConfig;
|
||||
}
|
||||
|
||||
export interface JVMCapability {
|
||||
mode: 'jmx' | 'endpoint' | 'agent';
|
||||
canBrowse: boolean;
|
||||
canWrite: boolean;
|
||||
canPreview: boolean;
|
||||
reason?: string;
|
||||
displayLabel: string;
|
||||
}
|
||||
|
||||
export interface JVMResourceSummary {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
path: string;
|
||||
providerMode: 'jmx' | 'endpoint' | 'agent';
|
||||
canRead: boolean;
|
||||
canWrite: boolean;
|
||||
hasChildren: boolean;
|
||||
sensitive?: boolean;
|
||||
}
|
||||
|
||||
export interface JVMValueSnapshot {
|
||||
resourceId: string;
|
||||
kind: string;
|
||||
format: string;
|
||||
version?: string;
|
||||
value: any;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface JVMChangePreview {
|
||||
allowed: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
summary: string;
|
||||
riskLevel: 'low' | 'medium' | 'high';
|
||||
blockingReason?: string;
|
||||
before: JVMValueSnapshot;
|
||||
after: JVMValueSnapshot;
|
||||
}
|
||||
|
||||
export interface ConnectionConfig {
|
||||
id?: string;
|
||||
type: string;
|
||||
@@ -56,6 +122,7 @@ export interface ConnectionConfig {
|
||||
mongoAuthMechanism?: string;
|
||||
mongoReplicaUser?: string;
|
||||
mongoReplicaPassword?: string;
|
||||
jvm?: JVMConfig;
|
||||
}
|
||||
|
||||
export interface MongoMemberInfo {
|
||||
@@ -344,4 +411,3 @@ export interface SecurityUpdateStatus {
|
||||
lastError?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
61
frontend/src/utils/jvmConnectionConfig.test.ts
Normal file
61
frontend/src/utils/jvmConnectionConfig.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildDefaultJVMConnectionValues, buildJVMConnectionConfig } from './jvmConnectionConfig';
|
||||
|
||||
describe('jvmConnectionConfig', () => {
|
||||
it('defaults to readonly jmx mode', () => {
|
||||
const values = buildDefaultJVMConnectionValues();
|
||||
expect(values.type).toBe('jvm');
|
||||
expect(values.jvmReadOnly).toBe(true);
|
||||
expect(values.jvmAllowedModes).toEqual(['jmx']);
|
||||
expect(values.jvmPreferredMode).toBe('jmx');
|
||||
});
|
||||
|
||||
it('builds nested jvm config payload', () => {
|
||||
const config = buildJVMConnectionConfig({
|
||||
name: 'Orders JVM',
|
||||
type: 'jvm',
|
||||
host: 'orders.internal',
|
||||
port: 9010,
|
||||
jvmReadOnly: true,
|
||||
jvmAllowedModes: ['jmx', 'endpoint'],
|
||||
jvmPreferredMode: 'endpoint',
|
||||
jvmEnvironment: 'prod',
|
||||
jvmEndpointEnabled: true,
|
||||
jvmEndpointBaseUrl: 'https://orders.internal/manage/jvm',
|
||||
jvmEndpointApiKey: 'token-1',
|
||||
});
|
||||
expect(config.jvm?.preferredMode).toBe('endpoint');
|
||||
expect(config.jvm?.endpoint?.baseUrl).toBe('https://orders.internal/manage/jvm');
|
||||
});
|
||||
|
||||
it('normalizes allowed modes and falls back preferred mode to first allowed mode', () => {
|
||||
const config = buildJVMConnectionConfig({
|
||||
host: 'cache.internal',
|
||||
port: 9010,
|
||||
jvmAllowedModes: [' Endpoint ', 'invalid', 'JMX', 'endpoint'],
|
||||
jvmPreferredMode: 'AGENT',
|
||||
});
|
||||
|
||||
expect(config.jvm?.allowedModes).toEqual(['endpoint', 'jmx']);
|
||||
expect(config.jvm?.preferredMode).toBe('endpoint');
|
||||
expect(config.jvm?.jmx?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('normalizes environment and port defaults when input is invalid', () => {
|
||||
const config = buildJVMConnectionConfig({
|
||||
host: 'orders.internal',
|
||||
port: 0,
|
||||
jvmJmxPort: '',
|
||||
jvmEnvironment: ' PROD ',
|
||||
jvmReadOnly: false,
|
||||
jvmAllowedModes: ['JMX'],
|
||||
jvmPreferredMode: 'jmx',
|
||||
});
|
||||
|
||||
expect(config.port).toBe(9010);
|
||||
expect(config.jvm?.jmx?.port).toBe(9010);
|
||||
expect(config.jvm?.environment).toBe('prod');
|
||||
expect(config.jvm?.readOnly).toBe(false);
|
||||
});
|
||||
});
|
||||
123
frontend/src/utils/jvmConnectionConfig.ts
Normal file
123
frontend/src/utils/jvmConnectionConfig.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { ConnectionConfig } from '../types';
|
||||
|
||||
const DEFAULT_JMX_PORT = 9010;
|
||||
const DEFAULT_TIMEOUT_SECONDS = 30;
|
||||
const DEFAULT_ENVIRONMENT = 'dev';
|
||||
const JVM_MODES = ['jmx', 'endpoint', 'agent'] as const;
|
||||
|
||||
type JVMMode = typeof JVM_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 toStringValue = (value: unknown): string => {
|
||||
if (typeof value === 'string') {
|
||||
return value.trim();
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value).trim();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const toInteger = (value: unknown, fallback: number): number => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return fallback;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return fallback;
|
||||
}
|
||||
const intValue = Math.trunc(parsed);
|
||||
return intValue > 0 ? intValue : fallback;
|
||||
};
|
||||
|
||||
const normalizeModes = (value: unknown): JVMMode[] => {
|
||||
if (!Array.isArray(value)) {
|
||||
return ['jmx'];
|
||||
}
|
||||
|
||||
const result: JVMMode[] = [];
|
||||
const seen = new Set<JVMMode>();
|
||||
for (const item of value) {
|
||||
const mode = toStringValue(item).toLowerCase();
|
||||
if (!isJVMMode(mode) || seen.has(mode)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(mode);
|
||||
result.push(mode);
|
||||
}
|
||||
return result.length > 0 ? result : ['jmx'];
|
||||
};
|
||||
|
||||
const normalizePreferredMode = (value: unknown, allowedModes: JVMMode[]): JVMMode => {
|
||||
const preferred = toStringValue(value).toLowerCase();
|
||||
if (isJVMMode(preferred) && allowedModes.includes(preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
return allowedModes[0];
|
||||
};
|
||||
|
||||
const normalizeEnvironment = (value: unknown): JVMEnvironment => {
|
||||
const env = toStringValue(value).toLowerCase();
|
||||
if (env === 'uat' || env === 'prod') {
|
||||
return env;
|
||||
}
|
||||
return DEFAULT_ENVIRONMENT;
|
||||
};
|
||||
|
||||
const normalizeReadOnly = (value: unknown): boolean => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const buildDefaultJVMConnectionValues = () => ({
|
||||
type: 'jvm',
|
||||
host: 'localhost',
|
||||
port: DEFAULT_JMX_PORT,
|
||||
jvmReadOnly: true,
|
||||
jvmAllowedModes: ['jmx'],
|
||||
jvmPreferredMode: 'jmx',
|
||||
jvmEnvironment: DEFAULT_ENVIRONMENT,
|
||||
jvmEndpointEnabled: false,
|
||||
jvmEndpointBaseUrl: '',
|
||||
jvmEndpointApiKey: '',
|
||||
});
|
||||
|
||||
export const buildJVMConnectionConfig = (values: JVMConnectionFormValues): ConnectionConfig => {
|
||||
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);
|
||||
|
||||
return {
|
||||
type: 'jvm',
|
||||
host: toStringValue(values.host),
|
||||
port,
|
||||
user: '',
|
||||
password: '',
|
||||
timeout,
|
||||
jvm: {
|
||||
environment: normalizeEnvironment(values.jvmEnvironment),
|
||||
readOnly: normalizeReadOnly(values.jvmReadOnly),
|
||||
allowedModes,
|
||||
preferredMode,
|
||||
jmx: {
|
||||
enabled: allowedModes.includes('jmx'),
|
||||
host: toStringValue(values.jvmJmxHost) || toStringValue(values.host),
|
||||
port: toInteger(values.jvmJmxPort, port),
|
||||
username: toStringValue(values.jvmJmxUsername),
|
||||
password: toStringValue(values.jvmJmxPassword),
|
||||
},
|
||||
endpoint: {
|
||||
enabled: values.jvmEndpointEnabled === true,
|
||||
baseUrl: toStringValue(values.jvmEndpointBaseUrl),
|
||||
apiKey: toStringValue(values.jvmEndpointApiKey),
|
||||
timeoutSeconds: toInteger(values.jvmEndpointTimeoutSeconds, timeout),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -26,6 +26,34 @@ type HTTPTunnelConfig struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// JVMJMXConfig 存储 JVM JMX 连接配置。
|
||||
type JVMJMXConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
DomainAllowlist []string `json:"domainAllowlist,omitempty"`
|
||||
}
|
||||
|
||||
// JVMEndpointConfig 存储 JVM Management Endpoint 连接配置。
|
||||
type JVMEndpointConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
BaseURL string `json:"baseUrl,omitempty"`
|
||||
APIKey string `json:"apiKey,omitempty"`
|
||||
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
||||
}
|
||||
|
||||
// JVMConfig 存储 JVM 连接的协议与能力偏好配置。
|
||||
type JVMConfig struct {
|
||||
Environment string `json:"environment,omitempty"`
|
||||
ReadOnly *bool `json:"readOnly,omitempty"`
|
||||
AllowedModes []string `json:"allowedModes,omitempty"`
|
||||
PreferredMode string `json:"preferredMode,omitempty"`
|
||||
JMX JVMJMXConfig `json:"jmx,omitempty"`
|
||||
Endpoint JVMEndpointConfig `json:"endpoint,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionConfig 存储数据库连接的完整配置,包括 SSH、代理、SSL 等网络层设置。
|
||||
type ConnectionConfig struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
@@ -62,6 +90,7 @@ type ConnectionConfig struct {
|
||||
MongoAuthMechanism string `json:"mongoAuthMechanism,omitempty"` // MongoDB authMechanism
|
||||
MongoReplicaUser string `json:"mongoReplicaUser,omitempty"` // MongoDB replica auth user
|
||||
MongoReplicaPassword string `json:"mongoReplicaPassword,omitempty"` // MongoDB replica auth password
|
||||
JVM JVMConfig `json:"jvm,omitempty"` // JVM connector config
|
||||
}
|
||||
|
||||
// ResultSetData 表示一个查询结果集(行 + 列名),用于多结果集场景。
|
||||
|
||||
82
internal/jvm/config.go
Normal file
82
internal/jvm/config.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
)
|
||||
|
||||
const defaultJMXPort = 9010
|
||||
|
||||
func NormalizeConnectionConfig(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
|
||||
cfg := raw
|
||||
if strings.ToLower(strings.TrimSpace(cfg.Type)) != "jvm" {
|
||||
return connection.ConnectionConfig{}, fmt.Errorf("unexpected connection type: %s", cfg.Type)
|
||||
}
|
||||
|
||||
cfg.Type = "jvm"
|
||||
cfg.JVM.Environment = strings.ToLower(strings.TrimSpace(cfg.JVM.Environment))
|
||||
if cfg.JVM.ReadOnly == nil {
|
||||
cfg.JVM.ReadOnly = boolPtr(true)
|
||||
}
|
||||
if cfg.JVM.JMX.Port <= 0 {
|
||||
if cfg.Port > 0 {
|
||||
cfg.JVM.JMX.Port = cfg.Port
|
||||
} else {
|
||||
cfg.JVM.JMX.Port = defaultJMXPort
|
||||
}
|
||||
}
|
||||
|
||||
cfg.JVM.AllowedModes = normalizeModes(cfg.JVM.AllowedModes)
|
||||
|
||||
preferredMode := strings.ToLower(strings.TrimSpace(cfg.JVM.PreferredMode))
|
||||
if preferredMode == "" || !containsMode(cfg.JVM.AllowedModes, preferredMode) {
|
||||
cfg.JVM.PreferredMode = cfg.JVM.AllowedModes[0]
|
||||
} else {
|
||||
cfg.JVM.PreferredMode = preferredMode
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func normalizeModes(input []string) []string {
|
||||
if len(input) == 0 {
|
||||
return []string{ModeJMX}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(input))
|
||||
seen := make(map[string]struct{}, len(input))
|
||||
for _, item := range input {
|
||||
mode := strings.ToLower(strings.TrimSpace(item))
|
||||
switch mode {
|
||||
case ModeJMX, ModeEndpoint, ModeAgent:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[mode]; exists {
|
||||
continue
|
||||
}
|
||||
seen[mode] = struct{}{}
|
||||
result = append(result, mode)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return []string{ModeJMX}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func containsMode(items []string, target string) bool {
|
||||
normalizedTarget := strings.ToLower(strings.TrimSpace(target))
|
||||
for _, item := range items {
|
||||
if strings.ToLower(strings.TrimSpace(item)) == normalizedTarget {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(value bool) *bool {
|
||||
return &value
|
||||
}
|
||||
93
internal/jvm/config_test.go
Normal file
93
internal/jvm/config_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
)
|
||||
|
||||
func TestNormalizeConnectionConfigDefaultsToReadOnlyJMX(t *testing.T) {
|
||||
raw := connection.ConnectionConfig{
|
||||
Type: "jvm",
|
||||
Host: "orders-prod.internal",
|
||||
Port: 9010,
|
||||
}
|
||||
|
||||
got, err := NormalizeConnectionConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeConnectionConfig returned error: %v", err)
|
||||
}
|
||||
if got.JVM.ReadOnly == nil || !*got.JVM.ReadOnly {
|
||||
t.Fatalf("expected JVM connection to default to readOnly")
|
||||
}
|
||||
if got.JVM.PreferredMode != ModeJMX {
|
||||
t.Fatalf("expected preferred mode %q, got %q", ModeJMX, got.JVM.PreferredMode)
|
||||
}
|
||||
if len(got.JVM.AllowedModes) != 1 || got.JVM.AllowedModes[0] != ModeJMX {
|
||||
t.Fatalf("expected allowed modes [jmx], got %#v", got.JVM.AllowedModes)
|
||||
}
|
||||
if got.JVM.JMX.Port != 9010 {
|
||||
t.Fatalf("expected JMX port to inherit root port 9010, got %d", got.JVM.JMX.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeConnectionConfigFallsBackToFirstAllowedMode(t *testing.T) {
|
||||
raw := connection.ConnectionConfig{
|
||||
Type: "jvm",
|
||||
Host: "cache-svc.internal",
|
||||
JVM: connection.JVMConfig{
|
||||
AllowedModes: []string{ModeEndpoint, ModeJMX},
|
||||
PreferredMode: ModeAgent,
|
||||
Endpoint: connection.JVMEndpointConfig{
|
||||
Enabled: true,
|
||||
BaseURL: "https://cache-svc.internal/manage/jvm",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := NormalizeConnectionConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeConnectionConfig returned error: %v", err)
|
||||
}
|
||||
if got.JVM.PreferredMode != ModeEndpoint {
|
||||
t.Fatalf("expected preferred mode %q, got %q", ModeEndpoint, got.JVM.PreferredMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeConnectionConfigKeepsExplicitReadOnlyFalse(t *testing.T) {
|
||||
readOnly := false
|
||||
raw := connection.ConnectionConfig{
|
||||
Type: "jvm",
|
||||
Port: 9010,
|
||||
JVM: connection.JVMConfig{
|
||||
ReadOnly: &readOnly,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := NormalizeConnectionConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeConnectionConfig returned error: %v", err)
|
||||
}
|
||||
if got.JVM.ReadOnly == nil {
|
||||
t.Fatalf("expected readOnly to remain explicitly configured")
|
||||
}
|
||||
if *got.JVM.ReadOnly {
|
||||
t.Fatalf("expected explicit readOnly=false to be preserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeConnectionConfigDefaultsJMXPortTo9010WhenPortsMissing(t *testing.T) {
|
||||
raw := connection.ConnectionConfig{
|
||||
Type: "jvm",
|
||||
Host: "orders-prod.internal",
|
||||
Port: 0,
|
||||
}
|
||||
|
||||
got, err := NormalizeConnectionConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("NormalizeConnectionConfig returned error: %v", err)
|
||||
}
|
||||
if got.JVM.JMX.Port != 9010 {
|
||||
t.Fatalf("expected JMX port default 9010, got %d", got.JVM.JMX.Port)
|
||||
}
|
||||
}
|
||||
74
internal/jvm/types.go
Normal file
74
internal/jvm/types.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package jvm
|
||||
|
||||
const (
|
||||
ModeJMX = "jmx"
|
||||
ModeEndpoint = "endpoint"
|
||||
ModeAgent = "agent"
|
||||
EnvPROD = "prod"
|
||||
)
|
||||
|
||||
type Capability struct {
|
||||
Mode string `json:"mode"`
|
||||
CanBrowse bool `json:"canBrowse"`
|
||||
CanWrite bool `json:"canWrite"`
|
||||
CanPreview bool `json:"canPreview"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
DisplayLabel string `json:"displayLabel"`
|
||||
}
|
||||
|
||||
type ResourceSummary struct {
|
||||
ID string `json:"id"`
|
||||
ParentID string `json:"parentId,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
ProviderMode string `json:"providerMode"`
|
||||
CanRead bool `json:"canRead"`
|
||||
CanWrite bool `json:"canWrite"`
|
||||
HasChildren bool `json:"hasChildren"`
|
||||
Sensitive bool `json:"sensitive,omitempty"`
|
||||
}
|
||||
|
||||
type ValueSnapshot struct {
|
||||
ResourceID string `json:"resourceId"`
|
||||
Kind string `json:"kind"`
|
||||
Format string `json:"format"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Value interface{} `json:"value"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ChangeRequest struct {
|
||||
ProviderMode string `json:"providerMode"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Action string `json:"action"`
|
||||
Reason string `json:"reason"`
|
||||
ExpectedVersion string `json:"expectedVersion,omitempty"`
|
||||
Payload map[string]any `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
type ChangePreview struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
RequiresConfirmation bool `json:"requiresConfirmation,omitempty"`
|
||||
Summary string `json:"summary"`
|
||||
RiskLevel string `json:"riskLevel"`
|
||||
BlockingReason string `json:"blockingReason,omitempty"`
|
||||
Before ValueSnapshot `json:"before"`
|
||||
After ValueSnapshot `json:"after"`
|
||||
}
|
||||
|
||||
type ApplyResult struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
UpdatedValue ValueSnapshot `json:"updatedValue"`
|
||||
}
|
||||
|
||||
type AuditRecord struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
ConnectionID string `json:"connectionId"`
|
||||
ProviderMode string `json:"providerMode"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Action string `json:"action"`
|
||||
Reason string `json:"reason"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
Reference in New Issue
Block a user