diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml
new file mode 100644
index 00000000..0ae7d495
--- /dev/null
+++ b/.github/workflows/backend-tests.yml
@@ -0,0 +1,81 @@
+name: Backend Tests
+
+on:
+ pull_request:
+ branches:
+ - dev
+ push:
+ branches:
+ - dev
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ full:
+ name: Full backend suite
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Setup Go
+ uses: actions/setup-go@v6
+ with:
+ go-version-file: 'go.mod'
+
+ - name: Setup Java
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: '17'
+
+ - name: Prepare frontend embed
+ shell: bash
+ run: |
+ mkdir -p frontend/dist
+ printf '
GoNavi test\n' > frontend/dist/index.html
+
+ - name: Run backend tests
+ run: go test ./... -count=1 -timeout=30m
+
+ platform-sensitive:
+ name: Platform-sensitive tests (${{ matrix.os }})
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - macos-latest
+ - windows-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Setup Go
+ uses: actions/setup-go@v6
+ with:
+ go-version-file: 'go.mod'
+
+ - name: Setup Java
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: '17'
+
+ - name: Test AI file errors
+ run: go test ./internal/ai/service -run '^(TestMCPClientInstallConfigIOFailuresUseServiceLanguage|TestAISaveSessionUsesCurrentLanguageForStructuredErrors)$' -count=1
+
+ - name: Test Claude CLI timing
+ run: go test ./internal/ai/provider -run '^TestClaudeCLIProvider_(ChatStreamUsesRequestTimeoutWhenNoMeaningfulResponseArrives|ChatStreamAllowsDelayedMeaningfulResponse)$' -count=1
+
+ - name: Test JVM fixtures
+ run: go test ./internal/jvm -run '^(TestAgentProviderRealAgentRoundTrip|TestHTTPProviderRealEndpointRoundTrip|TestJMXProviderRealJMXRoundTrip)$' -count=1 -timeout=5m
+
+ - name: Test optional driver-agent cleanup
+ run: go test ./internal/db -run '^TestOptionalDriverAgentUnresponsiveProcessIsReapedAfterTimeout$' -count=1
diff --git a/internal/ai/provider/claude_cli_test.go b/internal/ai/provider/claude_cli_test.go
index f186ee85..de0fafba 100644
--- a/internal/ai/provider/claude_cli_test.go
+++ b/internal/ai/provider/claude_cli_test.go
@@ -676,12 +676,11 @@ func assertClaudeCLIRuntimeErrorIsEnglish(t *testing.T, message string) {
}
func TestClaudeCLIProvider_ChatStreamAllowsDelayedMeaningfulResponse(t *testing.T) {
- fakeClaude := writeFakeClaudeScript(t, "#!/bin/sh\necho '{\"type\":\"system\",\"subtype\":\"init\"}'\nsleep 0.2\necho '{\"type\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"OK\"}]}}'\necho '{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\":\"OK\"}'\n")
- restore := overrideClaudeCLIForTest(t, fakeClaude)
+ restore := overrideClaudeCLIWithTestProcess(t, "delayed-stream-success")
defer restore()
originalRequestTimeout := claudeCLIRequestTimeout
- claudeCLIRequestTimeout = 1 * time.Second
+ claudeCLIRequestTimeout = 5 * time.Second
defer func() {
claudeCLIRequestTimeout = originalRequestTimeout
}()
@@ -960,6 +959,12 @@ func TestClaudeCLIHelperProcess(t *testing.T) {
case "model-success":
_, _ = os.Stdout.WriteString(`{"type":"result","subtype":"success","is_error":false,"result":"model request should not run"}`)
os.Exit(0)
+ case "delayed-stream-success":
+ _, _ = os.Stdout.WriteString("{\"type\":\"system\",\"subtype\":\"init\"}\n")
+ time.Sleep(200 * time.Millisecond)
+ _, _ = os.Stdout.WriteString("{\"type\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"OK\"}]}}\n")
+ _, _ = os.Stdout.WriteString("{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\":\"OK\"}\n")
+ os.Exit(0)
case "sleep":
time.Sleep(5 * time.Second)
os.Exit(0)
diff --git a/internal/ai/service/claude_code_mcp.go b/internal/ai/service/claude_code_mcp.go
index 17a7bfeb..a162c6ce 100644
--- a/internal/ai/service/claude_code_mcp.go
+++ b/internal/ai/service/claude_code_mcp.go
@@ -503,6 +503,9 @@ func readClaudeCodeMCPServerConfig(configPath string, serverID string, textFuncs
func upsertClaudeCodeMCPServerConfig(configPath string, serverID string, serverConfig claudeCodeMCPServerConfig, textFuncs ...mcpClientInstallTextFunc) error {
text := firstMCPClientInstallText(textFuncs)
+ if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
+ return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.claude_code.config_dir_create_failed", map[string]any{"detail": err.Error()}))
+ }
root, err := readClaudeCodeConfig(configPath, text)
if err != nil {
return err
@@ -526,9 +529,6 @@ func upsertClaudeCodeMCPServerConfig(configPath string, serverID string, serverC
return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.claude_code.config_serialize_failed", map[string]any{"detail": err.Error()}))
}
- if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
- return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.claude_code.config_dir_create_failed", map[string]any{"detail": err.Error()}))
- }
if err := os.WriteFile(configPath, append(data, '\n'), 0o644); err != nil {
return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.claude_code.config_write_failed", map[string]any{"detail": err.Error()}))
}
@@ -593,15 +593,15 @@ func readCodexMCPServerConfig(configPath string, serverID string, textFuncs ...m
func upsertCodexMCPServerConfig(configPath string, serverID string, serverConfig codexMCPServerConfig, textFuncs ...mcpClientInstallTextFunc) error {
text := firstMCPClientInstallText(textFuncs)
+ if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
+ return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.codex.config_dir_create_failed", map[string]any{"detail": err.Error()}))
+ }
data, err := os.ReadFile(configPath)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.codex.config_read_failed", map[string]any{"detail": err.Error()}))
}
updated := replaceOrAppendCodexMCPServerBlock(string(data), strings.TrimSpace(serverID), renderCodexMCPServerBlock(serverID, serverConfig))
- if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
- return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.codex.config_dir_create_failed", map[string]any{"detail": err.Error()}))
- }
if err := os.WriteFile(configPath, []byte(updated), 0o644); err != nil {
return fmt.Errorf("%s", mcpClientInstallText(text, "ai.service.mcp_client.codex.config_write_failed", map[string]any{"detail": err.Error()}))
}
diff --git a/internal/ai/service/service.go b/internal/ai/service/service.go
index d979603e..db4d6f50 100644
--- a/internal/ai/service/service.go
+++ b/internal/ai/service/service.go
@@ -2056,7 +2056,17 @@ func (s *Service) loadSessionFile(sessionID string) (sessionFileData, error) {
return sessionData, nil
}
+func (s *Service) ensureSessionsDir() error {
+ if err := os.MkdirAll(s.sessionsDir(), 0o755); err != nil {
+ return s.serviceError("ai_service.backend.error.sessions_dir_create_failed", nil, err)
+ }
+ return nil
+}
+
func (s *Service) loadOrCreateSessionFile(sessionID string) (sessionFileData, error) {
+ if err := s.ensureSessionsDir(); err != nil {
+ return sessionFileData{}, err
+ }
sessionData, err := s.loadSessionFile(sessionID)
if err == nil {
return sessionData, nil
@@ -2073,9 +2083,8 @@ func (s *Service) loadOrCreateSessionFile(sessionID string) (sessionFileData, er
}
func (s *Service) saveSessionFile(sessionID string, sessionData sessionFileData) error {
- dir := s.sessionsDir()
- if err := os.MkdirAll(dir, 0o755); err != nil {
- return s.serviceError("ai_service.backend.error.sessions_dir_create_failed", nil, err)
+ if err := s.ensureSessionsDir(); err != nil {
+ return err
}
if strings.TrimSpace(sessionData.ID) == "" {
sessionData.ID = sessionID
diff --git a/internal/app/methods_db_audited_test.go b/internal/app/methods_db_audited_test.go
index 3a45dfd2..77dc19f5 100644
--- a/internal/app/methods_db_audited_test.go
+++ b/internal/app/methods_db_audited_test.go
@@ -133,6 +133,7 @@ func TestDirectDBQueryCannotBypassWriteAuditWhenBatchStartsWithRead(t *testing.T
}
func TestDirectDBQueryCannotBypassAuditWithNestedWriteSyntax(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalVerifyDriverAgentRevisionFunc := verifyDriverAgentRevisionFunc
t.Cleanup(func() {
diff --git a/internal/app/methods_db_i18n_test.go b/internal/app/methods_db_i18n_test.go
index ac95af97..588676be 100644
--- a/internal/app/methods_db_i18n_test.go
+++ b/internal/app/methods_db_i18n_test.go
@@ -196,6 +196,9 @@ func TestMethodsDBConnectionValidationAndReleaseUseEnglishMessages(t *testing.T)
func TestMethodsDBConnectUsesCurrentLanguageForDriverRuntimeReason(t *testing.T) {
tmpDir := t.TempDir()
db.SetExternalDriverDownloadDirectory(tmpDir)
+ t.Cleanup(func() {
+ db.SetExternalDriverDownloadDirectory("")
+ })
app := NewAppWithSecretStore(newFakeAppSecretStore())
app.configDir = tmpDir
diff --git a/internal/app/methods_db_metadata_retry_test.go b/internal/app/methods_db_metadata_retry_test.go
index 13c26f65..05a34617 100644
--- a/internal/app/methods_db_metadata_retry_test.go
+++ b/internal/app/methods_db_metadata_retry_test.go
@@ -232,6 +232,7 @@ func TestDBGetIndexesUsesSearchPathForPostgresPureTableMetadata(t *testing.T) {
}
func TestDBGetColumnsKeepsCurrentDatabaseForKingbaseQualifiedTableMetadata(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
t.Cleanup(func() {
@@ -270,6 +271,7 @@ func TestDBGetColumnsKeepsCurrentDatabaseForKingbaseQualifiedTableMetadata(t *te
}
func TestDBGetIndexesKeepsCurrentDatabaseForKingbaseQualifiedTableMetadata(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
t.Cleanup(func() {
@@ -342,6 +344,7 @@ func TestDBGetColumnsKeepsDatabaseForMySQLMetadata(t *testing.T) {
}
func TestDBGetColumnsInfersOceanBaseOracleFieldsWhenAgentMetadataIsEmpty(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
t.Cleanup(func() {
@@ -422,6 +425,7 @@ func TestDBGetColumnsInfersOceanBaseOracleFieldsWhenAgentMetadataIsEmpty(t *test
}
func TestDBGetColumnsFallsBackToEmptySelectWhenOceanBaseOracleDictionaryIsEmpty(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
t.Cleanup(func() {
diff --git a/internal/app/methods_db_multi_test.go b/internal/app/methods_db_multi_test.go
index 37694a62..02d34611 100644
--- a/internal/app/methods_db_multi_test.go
+++ b/internal/app/methods_db_multi_test.go
@@ -796,6 +796,7 @@ var _ db.QueryMessageExecer = (*fakeBatchWriteDB)(nil)
var _ db.StatementQueryMessageExecer = (*fakeBatchWriteSession)(nil)
func TestDBQueryWithCancelReturnsResultSetForExecStoredProcedure(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -907,6 +908,7 @@ func TestDBQueryWithCancelRoutesMilvusSelectPreviewToQuery(t *testing.T) {
}
func TestDBQueryWithCancelReturnsMessagesForSQLServerQuery(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -942,6 +944,7 @@ func TestDBQueryWithCancelReturnsMessagesForSQLServerQuery(t *testing.T) {
}
func TestDBQueryWithCancel_DuckDBQueriesDoNotInheritConnectTimeout(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalVerifyDriverAgentRevisionFunc := verifyDriverAgentRevisionFunc
t.Cleanup(func() {
@@ -1113,6 +1116,7 @@ func TestDBQueryMultiTransactionalKeepsDMLTransactionOpenUntilCommit(t *testing.
}
func TestDBQueryMultiTransactionalKeepsSQLServerBeginEndBlockOpenUntilRollback(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -1492,6 +1496,7 @@ func TestDBQueryMultiTransactionalOraclePrefersTransactionProviderForFinish(t *t
}
func TestDBQueryMultiTransactionalUsesOracleImplicitSessionForOceanBaseOracleProtocol(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
originalVerifyDriverAgentRevisionFunc := verifyDriverAgentRevisionFunc
t.Cleanup(func() {
@@ -1865,6 +1870,7 @@ func TestDBQueryMultiTransactionalSkipsManagedTransactionForExplicitTransactionS
}
func TestDBQueryMultiPrefersResultSetForExecStoredProcedure(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -1909,6 +1915,7 @@ func TestDBQueryMultiPrefersResultSetForExecStoredProcedure(t *testing.T) {
}
func TestDBQueryMultiDoesNotBatchExecStoredProcedureAsWriteStatement(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -1953,6 +1960,7 @@ func TestDBQueryMultiDoesNotBatchExecStoredProcedureAsWriteStatement(t *testing.
}
func TestDBQueryMultiRunsSQLServerStatisticsBatchNatively(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2019,6 +2027,7 @@ func TestDBQueryMultiRunsSQLServerStatisticsBatchNatively(t *testing.T) {
}
func TestDBQueryMultiFallsBackWhenNativeReadOnlyBatchReturnsEmptyResults(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2248,6 +2257,7 @@ func TestDBQueryMultiFallsBackToPlainQueryWhenSequentialMultiStillReturnsBlankRe
}
func TestDBQueryMultiPrefersPlainQueryForKingbaseReadResults(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2370,6 +2380,7 @@ func TestDBQueryMultiPrefersPlainQueryForDamengReadResults(t *testing.T) {
}
func TestDBQueryMultiPrefersPlainQueryForOceanBaseOracleReadResults(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2437,6 +2448,7 @@ func TestDBQueryMultiPrefersPlainQueryForOceanBaseOracleReadResults(t *testing.T
}
func TestDBQueryMultiUsesPinnedSessionForSequentialFallback(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2498,6 +2510,7 @@ func TestDBQueryMultiUsesPinnedSessionForSequentialFallback(t *testing.T) {
}
func TestDBQueryMultiKeepsAllResultSetsFromSingleSQLServerStatement(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2552,6 +2565,7 @@ func TestDBQueryMultiKeepsAllResultSetsFromSingleSQLServerStatement(t *testing.T
}
func TestDBQueryMultiNormalizesSingleSQLServerSelectAffectedRowsStatementIndex(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2597,6 +2611,7 @@ func TestDBQueryMultiNormalizesSingleSQLServerSelectAffectedRowsStatementIndex(t
}
func TestDBQueryMultiNormalizesSQLServerSelectAffectedRowsPairsByStatement(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2672,6 +2687,7 @@ func TestNormalizeNativeResultStatementIndexesKeepsAmbiguousSQLServerResultsUnas
}
func TestDBQueryMultiTreatsBareSQLServerProcedureCallAsQueryFirst(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2767,6 +2783,7 @@ func TestDBQueryMultiTreatsReturningWriteAsQueryFirst(t *testing.T) {
}
func TestDBQueryMultiTreatsSQLServerOutputWriteAsQueryFirst(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
@@ -2814,6 +2831,7 @@ func TestDBQueryMultiTreatsSQLServerOutputWriteAsQueryFirst(t *testing.T) {
}
func TestDBQueryMultiTreatsWrappedMessageBlocksAsQueryFirst(t *testing.T) {
+ installFakeOptionalDriverRuntime(t)
originalNewDatabaseFunc := newDatabaseFunc
t.Cleanup(func() {
newDatabaseFunc = originalNewDatabaseFunc
diff --git a/internal/app/methods_driver_i18n_test.go b/internal/app/methods_driver_i18n_test.go
index 8d868d29..8e87653d 100644
--- a/internal/app/methods_driver_i18n_test.go
+++ b/internal/app/methods_driver_i18n_test.go
@@ -132,7 +132,7 @@ func TestMethodsDriverReleaseHelpersUseLocalizedText(t *testing.T) {
"driver_manager.backend.error.driver_version_list_parse_failed",
},
},
- "func fetchDriverBundleAssetSizeIndex": {
+ "func fetchDriverBundleAssetIndex": {
rawMessages: []string{
`fmt.Errorf("release 为空")`,
`fmt.Errorf("未找到驱动总包索引资产")`,
diff --git a/internal/app/methods_driver_test_helpers_test.go b/internal/app/methods_driver_test_helpers_test.go
index bbfdb9e6..da638375 100644
--- a/internal/app/methods_driver_test_helpers_test.go
+++ b/internal/app/methods_driver_test_helpers_test.go
@@ -4,6 +4,8 @@ import (
"os"
"strings"
"testing"
+
+ "GoNavi-Wails/internal/connection"
)
func methodsDriverSource(t *testing.T) string {
@@ -24,6 +26,19 @@ func methodsDriverSource(t *testing.T) string {
return strings.Join(parts, "\n\n")
}
+func installFakeOptionalDriverRuntime(t *testing.T) {
+ t.Helper()
+
+ originalDriverRuntimeSupportStatusFunc := driverRuntimeSupportStatusFunc
+ originalVerifyDriverAgentRevisionFunc := verifyDriverAgentRevisionFunc
+ driverRuntimeSupportStatusFunc = func(string) (bool, string) { return true, "" }
+ verifyDriverAgentRevisionFunc = func(connection.ConnectionConfig) error { return nil }
+ t.Cleanup(func() {
+ driverRuntimeSupportStatusFunc = originalDriverRuntimeSupportStatusFunc
+ verifyDriverAgentRevisionFunc = originalVerifyDriverAgentRevisionFunc
+ })
+}
+
func disableGlobalProxyForTest(t *testing.T) {
t.Helper()
diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go
index 7c8a48be..e2dc90f1 100644
--- a/internal/app/methods_driver_version_test.go
+++ b/internal/app/methods_driver_version_test.go
@@ -294,6 +294,8 @@ func TestDriverReleaseDownloadCoordinates(t *testing.T) {
}
func TestFetchDriverReleaseIndexByURLBuildsMirrorAssets(t *testing.T) {
+ disableGlobalProxyForTest(t)
+
for _, name := range []string{
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY",
"http_proxy", "https_proxy", "all_proxy",
diff --git a/internal/app/methods_jvm_test.go b/internal/app/methods_jvm_test.go
index 2adf413f..23d5c170 100644
--- a/internal/app/methods_jvm_test.go
+++ b/internal/app/methods_jvm_test.go
@@ -103,6 +103,14 @@ func expectedAuditAppendError(t *testing.T, auditRoot string) string {
return err.Error()
}
+func setJVMTestLanguage(t *testing.T, app *App, language string) {
+ t.Helper()
+ app.SetLanguage(language)
+ t.Cleanup(func() {
+ app.SetLanguage("zh-CN")
+ })
+}
+
func assertEnglishJVMConfirmationMessage(t *testing.T, got string, want string) {
t.Helper()
@@ -118,7 +126,7 @@ func assertEnglishJVMConfirmationMessage(t *testing.T, got string, want string)
func TestTestJVMConnectionUsesPreferredProvider(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
var gotMode string
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
gotMode = mode
@@ -172,6 +180,7 @@ func TestTestJVMConnectionReturnsProviderError(t *testing.T) {
func TestTestJVMConnectionTranslatesJMXBusinessPortError(t *testing.T) {
app := NewAppWithSecretStore(nil)
+ setJVMTestLanguage(t, app, "zh-CN")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{testErr: errors.New("jmx test connection failed: jmx helper ping failed for localhost:18080: JMX command ping failed for localhost:18080: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]; details={\"exception\":\"java.lang.IllegalStateException\"}")}, nil
})
@@ -203,10 +212,7 @@ func TestTestJVMConnectionTranslatesJMXBusinessPortError(t *testing.T) {
func TestTestJVMConnectionLocalizesJMXBusinessPortErrorInEnglish(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
- t.Cleanup(func() {
- app.SetLanguage("zh-CN")
- })
+ setJVMTestLanguage(t, app, "en-US")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{testErr: errors.New("jmx test connection failed: jmx helper ping failed for localhost:18080: JMX command ping failed for localhost:18080: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]; details={\"exception\":\"java.lang.IllegalStateException\"}")}, nil
@@ -239,6 +245,7 @@ func TestTestJVMConnectionLocalizesJMXBusinessPortErrorInEnglish(t *testing.T) {
func TestTestJVMConnectionTranslatesAgentConnectionRefused(t *testing.T) {
app := NewAppWithSecretStore(nil)
+ setJVMTestLanguage(t, app, "zh-CN")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{testErr: errors.New("agent probe request failed: Get \"http://127.0.0.1:19090/gonavi/agent/jvm\": dial tcp 127.0.0.1:19090: connect: connection refused")}, nil
})
@@ -266,10 +273,7 @@ func TestTestJVMConnectionTranslatesAgentConnectionRefused(t *testing.T) {
func TestTestJVMConnectionLocalizesAgentConnectionRefusedInEnglish(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
- t.Cleanup(func() {
- app.SetLanguage("zh-CN")
- })
+ setJVMTestLanguage(t, app, "en-US")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{testErr: errors.New("agent probe request failed: Get \"http://127.0.0.1:19090/gonavi/agent/jvm\": dial tcp 127.0.0.1:19090: connect: connection refused")}, nil
@@ -301,7 +305,7 @@ func TestTestJVMConnectionLocalizesAgentConnectionRefusedInEnglish(t *testing.T)
func TestTestJVMConnectionLocalizesEndpointErrorInEnglish(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{testErr: errors.New(`endpoint baseurl is invalid: parse ":bad-url": missing protocol scheme`)}, nil
@@ -414,7 +418,7 @@ func TestJVMProbeCapabilitiesIncludesReasonWhenProbeFails(t *testing.T) {
func TestJVMProbeCapabilitiesLocalizesBuiltInReadOnlyReasons(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
restore := swapJVMProviderFactory(jvm.NewProvider)
defer restore()
@@ -464,7 +468,7 @@ func TestJVMProbeCapabilitiesLocalizesBuiltInReadOnlyReasons(t *testing.T) {
func TestJVMProbeCapabilitiesKeepsProviderReasonThatLooksLikeCatalogKeyRaw(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
providerReason := "jvm.backend.error.change_blocked_read_only"
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{
@@ -503,6 +507,7 @@ func TestJVMProbeCapabilitiesKeepsProviderReasonThatLooksLikeCatalogKeyRaw(t *te
func TestJVMProbeCapabilitiesTranslatesJMXProbeErrorUsingCurrentMode(t *testing.T) {
app := NewAppWithSecretStore(nil)
+ setJVMTestLanguage(t, app, "zh-CN")
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
return fakeJVMProvider{
probeErr: errors.New("jmx test connection failed: jmx helper ping failed for localhost:18080: JMX command ping failed for localhost:18080: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]; details={\"exception\":\"java.lang.IllegalStateException\"}"),
@@ -565,6 +570,7 @@ func TestJVMProbeCapabilitiesIncludesReasonWhenProviderFactoryFails(t *testing.T
func TestJVMProbeCapabilitiesUsesReadableLabelForAgentValidationError(t *testing.T) {
app := NewAppWithSecretStore(nil)
+ setJVMTestLanguage(t, app, "zh-CN")
restore := swapJVMProviderFactory(jvm.NewProvider)
defer restore()
@@ -594,6 +600,7 @@ func TestJVMProbeCapabilitiesUsesReadableLabelForAgentValidationError(t *testing
func TestJVMProbeCapabilitiesUsesReadableLabelForEndpointValidationError(t *testing.T) {
app := NewAppWithSecretStore(nil)
+ setJVMTestLanguage(t, app, "zh-CN")
restore := swapJVMProviderFactory(jvm.NewProvider)
defer restore()
@@ -704,7 +711,7 @@ func TestJVMGetValueReturnsProviderPayload(t *testing.T) {
func TestJVMApplyChangeRequiresConfirmationTokenForHighRiskPreview(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := false
var applyReq jvm.ChangeRequest
@@ -826,7 +833,7 @@ func TestJVMApplyChangeReturnsProviderPayload(t *testing.T) {
func TestJVMApplyChangeUsesEnglishGuardFallbackWhenBlockingReasonEmpty(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := false
var applyReq jvm.ChangeRequest
@@ -878,7 +885,7 @@ func TestJVMApplyChangeUsesEnglishGuardFallbackWhenBlockingReasonEmpty(t *testin
func TestJVMProviderBlockingReasonThatLooksLikeCatalogKeyStaysRaw(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := false
providerReason := "jvm.backend.error.change_blocked_read_only"
@@ -941,7 +948,7 @@ func TestJVMProviderBlockingReasonThatLooksLikeCatalogKeyStaysRaw(t *testing.T)
func TestJVMPreviewChange(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
readOnly := true
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
@@ -986,7 +993,7 @@ func TestJVMPreviewChange(t *testing.T) {
func TestJVMApplyChange(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := true
var applyReq jvm.ChangeRequest
@@ -1131,7 +1138,7 @@ func TestJVMApplyChangePreviewTokenAllowsConfirmedApply(t *testing.T) {
func TestIssueJVMPreviewConfirmationTokenLocalizesPayloadHashError(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
readOnly := false
_, err := app.issueJVMPreviewConfirmationToken(connection.ConnectionConfig{
@@ -1175,7 +1182,7 @@ func TestIssueJVMPreviewConfirmationTokenLocalizesPayloadHashError(t *testing.T)
func TestJVMPreviewChangeLocalizesConfirmationTokenFailure(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
readOnly := false
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
@@ -1225,7 +1232,7 @@ func TestJVMPreviewChangeLocalizesConfirmationTokenFailure(t *testing.T) {
func TestJVMApplyChangeLocalizesConfirmationTokenFailure(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
readOnly := false
restore := swapJVMProviderFactory(func(mode string) (jvm.Provider, error) {
@@ -1275,7 +1282,7 @@ func TestJVMApplyChangeLocalizesConfirmationTokenFailure(t *testing.T) {
func TestJVMApplyChangeRejectsUnissuedDeterministicConfirmationToken(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := false
var applyReq jvm.ChangeRequest
@@ -1348,7 +1355,7 @@ func TestJVMApplyChangeRejectsUnissuedDeterministicConfirmationToken(t *testing.
func TestJVMApplyChangeRejectsMismatchedPreviewConfirmationContext(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
readOnly := false
applyCalls := 0
@@ -1497,7 +1504,7 @@ func TestJVMApplyChangeRejectsReplayedPreviewConfirmationToken(t *testing.T) {
func TestJVMApplyChangeRejectsExpiredPreviewConfirmationToken(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
app.configDir = t.TempDir()
app.jvmPreviewTokenTTL = time.Nanosecond
readOnly := false
@@ -1727,7 +1734,7 @@ func TestJVMApplyChangeNormalizesRequestBeforeProviderAndAudit(t *testing.T) {
func TestJVMPreviewChangeRejectsDisallowedProviderMode(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
cfg := connection.ConnectionConfig{
Type: "jvm",
@@ -1799,7 +1806,7 @@ func TestJVMListAuditRecordsReturnsLatestRecords(t *testing.T) {
func TestJVMApplyChangeFailsClosedWhenInitialAuditWriteFails(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
blockerPath := filepath.Join(tempDir, "audit-blocker")
if err := os.WriteFile(blockerPath, []byte("blocker"), 0o600); err != nil {
@@ -1916,7 +1923,7 @@ func TestJVMApplyChangeLatestAuditRecordIsTerminal(t *testing.T) {
func TestJVMApplyChangeApplySuccessKeepsSuccessWhenTerminalAuditFails(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -1982,7 +1989,7 @@ func TestJVMApplyChangeApplySuccessKeepsSuccessWhenTerminalAuditFails(t *testing
func TestJVMApplyChangeApplyFailureReportsFailedAuditWriteError(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -2085,7 +2092,7 @@ func TestJVMApplyChangeApplyFailureKeepsProviderErrorWhenFailedAuditSucceeds(t *
func TestJVMApplyChangeUsesProviderErrorWhenFailedAuditAlsoFails(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -2140,7 +2147,7 @@ func TestJVMApplyChangeUsesProviderErrorWhenFailedAuditAlsoFails(t *testing.T) {
func TestJVMApplyChangeTerminalAuditWarningAppendsToExistingResultMessage(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -2192,7 +2199,7 @@ func TestJVMApplyChangeTerminalAuditWarningAppendsToExistingResultMessage(t *tes
func TestJVMApplyChangeTerminalAuditWarningUsesStandaloneMessageWhenResultMessageEmpty(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -2244,7 +2251,7 @@ func TestJVMApplyChangeTerminalAuditWarningUsesStandaloneMessageWhenResultMessag
func TestJVMApplyChangeFailedAuditFailureMessageIncludesUnderlyingError(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
@@ -2287,7 +2294,7 @@ func TestJVMApplyChangeFailedAuditFailureMessageIncludesUnderlyingError(t *testi
func TestJVMApplyChangeFailureMessageSeparatorUsesLocalizedEnglishSeparator(t *testing.T) {
app := NewAppWithSecretStore(nil)
- app.SetLanguage("en-US")
+ setJVMTestLanguage(t, app, "en-US")
tempDir := t.TempDir()
auditDir := filepath.Join(tempDir, "audit")
if err := os.MkdirAll(auditDir, 0o755); err != nil {
diff --git a/internal/db/optional_driver_agent_impl_test.go b/internal/db/optional_driver_agent_impl_test.go
index 4d5c3c35..ed3f0821 100644
--- a/internal/db/optional_driver_agent_impl_test.go
+++ b/internal/db/optional_driver_agent_impl_test.go
@@ -499,8 +499,8 @@ func TestOptionalDriverAgentUnresponsiveProcessIsReapedAfterTimeout(t *testing.T
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("expected context deadline error, got %v", err)
}
- if cmd.ProcessState == nil || !cmd.ProcessState.Exited() {
- t.Fatalf("timed-out driver-agent process was not reaped: %#v", cmd.ProcessState)
+ if cmd.ProcessState == nil {
+ t.Fatal("timed-out driver-agent process was not reaped")
}
}
diff --git a/internal/jvm/agent_provider_test.go b/internal/jvm/agent_provider_test.go
index efba2d5c..567ffa16 100644
--- a/internal/jvm/agent_provider_test.go
+++ b/internal/jvm/agent_provider_test.go
@@ -232,6 +232,9 @@ func startAgentFixture(t *testing.T) agentFixtureProcess {
}
classesDir := filepath.Join(t.TempDir(), "agent-fixture-classes")
+ if err := os.MkdirAll(classesDir, 0o755); err != nil {
+ t.Fatalf("create agent fixture classes directory failed: %v", err)
+ }
sourceRoot := filepath.Join(testRepoRoot(t), "internal", "jvm", "testdata", "agentfixture", "src")
javaFiles, err := filepath.Glob(filepath.Join(sourceRoot, "com", "gonavi", "fixture", "*.java"))
if err != nil {
@@ -241,7 +244,8 @@ func startAgentFixture(t *testing.T) agentFixtureProcess {
t.Fatalf("expected agent fixture java files under %s", sourceRoot)
}
- compileCmd := exec.Command(javacBin, append([]string{"-d", classesDir}, javaFiles...)...)
+ compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
+ compileCmd := exec.Command(javacBin, compileArgs...)
output, err := compileCmd.CombinedOutput()
if err != nil {
t.Fatalf("compile agent fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
diff --git a/internal/jvm/http_provider_test.go b/internal/jvm/http_provider_test.go
index 7f6698b4..c89b8b08 100644
--- a/internal/jvm/http_provider_test.go
+++ b/internal/jvm/http_provider_test.go
@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"net/http/httptest"
+ "os"
"os/exec"
"path/filepath"
"strings"
@@ -521,6 +522,9 @@ func startEndpointFixture(t *testing.T) endpointFixtureProcess {
}
classesDir := filepath.Join(t.TempDir(), "endpoint-fixture-classes")
+ if err := os.MkdirAll(classesDir, 0o755); err != nil {
+ t.Fatalf("create endpoint fixture classes directory failed: %v", err)
+ }
sourceRoot := filepath.Join(testRepoRoot(t), "internal", "jvm", "testdata", "endpointfixture", "src")
javaFiles, err := filepath.Glob(filepath.Join(sourceRoot, "com", "gonavi", "fixture", "*.java"))
if err != nil {
@@ -530,7 +534,8 @@ func startEndpointFixture(t *testing.T) endpointFixtureProcess {
t.Fatalf("expected endpoint fixture java files under %s", sourceRoot)
}
- compileCmd := exec.Command(javacBin, append([]string{"-d", classesDir}, javaFiles...)...)
+ compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
+ compileCmd := exec.Command(javacBin, compileArgs...)
output, err := compileCmd.CombinedOutput()
if err != nil {
t.Fatalf("compile endpoint fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
diff --git a/internal/jvm/jmx_provider_test.go b/internal/jvm/jmx_provider_test.go
index 3a183aa3..ea407aa4 100644
--- a/internal/jvm/jmx_provider_test.go
+++ b/internal/jvm/jmx_provider_test.go
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net"
+ "os"
"os/exec"
"path/filepath"
"runtime"
@@ -775,6 +776,9 @@ func startJMXFixture(t *testing.T) jmxFixtureProcess {
}
classesDir := filepath.Join(t.TempDir(), "fixture-classes")
+ if err := os.MkdirAll(classesDir, 0o755); err != nil {
+ t.Fatalf("create JMX fixture classes directory failed: %v", err)
+ }
sourceRoot := filepath.Join(testRepoRoot(t), "internal", "jvm", "testdata", "jmxfixture", "src")
javaFiles, err := filepath.Glob(filepath.Join(sourceRoot, "com", "gonavi", "fixture", "*.java"))
if err != nil {
@@ -784,7 +788,8 @@ func startJMXFixture(t *testing.T) jmxFixtureProcess {
t.Fatalf("expected fixture java files under %s", sourceRoot)
}
- compileCmd := exec.Command(javacBin, append([]string{"-d", classesDir}, javaFiles...)...)
+ compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
+ compileCmd := exec.Command(javacBin, compileArgs...)
output, err := compileCmd.CombinedOutput()
if err != nil {
t.Fatalf("compile fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
diff --git a/internal/jvm/testdata/agentfixture/src/com/gonavi/fixture/GoNaviTestAgent.java b/internal/jvm/testdata/agentfixture/src/com/gonavi/fixture/GoNaviTestAgent.java
index 42bce696..879ce0b2 100644
--- a/internal/jvm/testdata/agentfixture/src/com/gonavi/fixture/GoNaviTestAgent.java
+++ b/internal/jvm/testdata/agentfixture/src/com/gonavi/fixture/GoNaviTestAgent.java
@@ -5,6 +5,7 @@ import com.sun.net.httpserver.HttpServer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
import java.lang.instrument.Instrumentation;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
@@ -350,7 +351,11 @@ public final class GoNaviTestAgent {
}
private static String decode(String value) {
- return URLDecoder.decode(value, StandardCharsets.UTF_8);
+ try {
+ return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException err) {
+ throw new IllegalStateException("UTF-8 is unavailable", err);
+ }
}
private static AgentArgs parseArgs(String rawArgs) {
diff --git a/internal/jvm/testdata/endpointfixture/src/com/gonavi/fixture/EndpointTestServer.java b/internal/jvm/testdata/endpointfixture/src/com/gonavi/fixture/EndpointTestServer.java
index 7ef51c65..f7016b08 100644
--- a/internal/jvm/testdata/endpointfixture/src/com/gonavi/fixture/EndpointTestServer.java
+++ b/internal/jvm/testdata/endpointfixture/src/com/gonavi/fixture/EndpointTestServer.java
@@ -5,6 +5,7 @@ import com.sun.net.httpserver.HttpServer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@@ -331,7 +332,11 @@ public final class EndpointTestServer {
}
private static String decode(String value) {
- return URLDecoder.decode(value, StandardCharsets.UTF_8);
+ try {
+ return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException err) {
+ throw new IllegalStateException("UTF-8 is unavailable", err);
+ }
}
private static Map requiredObject(Object value, String field) {