diff --git a/api/pom.xml b/api/pom.xml index 45b3ae08302..47850806e17 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -96,6 +96,10 @@ javax.annotation javax.annotation-api - + + org.springframework + spring-web + + diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java index 53b4c35735f..0fd8d0013c8 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java @@ -183,7 +183,20 @@ public enum ErrorCode { /** * server error. */ - SERVER_ERROR(30000, "server error"); + SERVER_ERROR(30000, "server error"), + + /** + * Config use 100001 ~ 100999. + **/ + METADATA_ILLEGAL(100002, "导入的元数据非法"), + + DATA_VALIDATION_FAILED(100003, "未读取到合法数据"), + + PARSING_DATA_FAILED(100004, "解析数据失败"), + + DATA_EMPTY(100005, "导入的文件数据为空"), + + NO_SELECTED_CONFIG(100006, "没有选择任何配置"); private final Integer code; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java index 98ae78c5f73..2d6e1e75cc3 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.api.model.v2; import java.io.Serializable; +import org.springframework.http.HttpStatus; /** * Response Result. @@ -96,6 +97,15 @@ public static Result failure(ErrorCode errorCode, T data) { return new Result<>(errorCode.getCode(), errorCode.getMsg(), data); } + /** + * Failed return with httpStatus, message and data. + * @param data type + * @return Result + */ + public static Result failure(HttpStatus httpStatus, T data) { + return new Result<>(httpStatus.value(), httpStatus.getReasonPhrase(), data); + } + @Override public String toString() { return "Result{" + "errorCode=" + code + ", message='" + message + '\'' + ", data=" + data + '}'; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java new file mode 100644 index 00000000000..8ca862cef5f --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.api.model.v2; + +/** + * Supported languages for announcements. + * + * @author zhangyukun on:2024/9/24 + */ +public enum SupportedLanguage { + /** + * Chinese language. + */ + ZH_CN("zh-CN"), + + /** + * English language. + */ + EN_US("en-US"); + + private final String language; + + SupportedLanguage(String language) { + this.language = language; + } + + public String getLanguage() { + return language; + } + + /** + * Check if the given language is supported. + * + * @param language the language to check + * @return true if the language is supported, false otherwise + */ + public static boolean isSupported(String language) { + for (SupportedLanguage lang : SupportedLanguage.values()) { + if (lang.getLanguage().equals(language)) { + return true; + } + } + return false; + } +} diff --git a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js index 5e1af4a796d..1f96be34380 100644 --- a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js +++ b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js @@ -72,8 +72,9 @@ class EditorNameSpace extends React.Component { this.field.setValues(record); request({ type: 'get', - url: `v1/console/namespaces?show=all&namespaceId=${record.namespace}`, + url: `v3/console/core/namespace?namespaceId=${record.namespace}`, success: res => { + res = res.data; if (res !== null) { this.field.setValue('namespaceDesc', res.namespaceDesc); } else { @@ -101,15 +102,18 @@ class EditorNameSpace extends React.Component { beforeSend: () => { this.openLoading(); }, - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', data: { - namespace: values.namespace, - namespaceShowName: values.namespaceShowName, + namespaceId: values.namespace, + namespaceName: values.namespaceShowName, namespaceDesc: values.namespaceDesc, }, success: res => { - if (res === true) { + // res = res.data; + console.log(3); + if (res.code === 0) { + res = res.data; this.closeDialog(); this.props.getNameSpaces(); this.refreshNameSpace(); // 刷新全局namespace @@ -131,9 +135,10 @@ class EditorNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + console.log(res); + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/components/NameSpaceList/NameSpaceList.js b/console-ui/src/components/NameSpaceList/NameSpaceList.js index 825337cfd53..8fe772cf634 100644 --- a/console-ui/src/components/NameSpaceList/NameSpaceList.js +++ b/console-ui/src/components/NameSpaceList/NameSpaceList.js @@ -111,9 +111,9 @@ class NameSpaceList extends React.Component { } else { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { this.handleNameSpaces(res.data); } else { Dialog.alert({ diff --git a/console-ui/src/components/NewNameSpace/NewNameSpace.js b/console-ui/src/components/NewNameSpace/NewNameSpace.js index 4f50e0c07d7..40b628e58e5 100644 --- a/console-ui/src/components/NewNameSpace/NewNameSpace.js +++ b/console-ui/src/components/NewNameSpace/NewNameSpace.js @@ -111,13 +111,14 @@ class NewNameSpace extends React.Component { } request({ type: 'get', - url: 'v1/console/namespaces?checkNamespaceIdExist=true', + url: 'v3/console/core/namespace/exist', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { customNamespaceId, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -130,7 +131,7 @@ class NewNameSpace extends React.Component { } else { request({ type: 'post', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { @@ -139,6 +140,7 @@ class NewNameSpace extends React.Component { namespaceDesc: values.namespaceDesc, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -167,9 +169,9 @@ class NewNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js index 51092c9fa1a..7860b793811 100644 --- a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js +++ b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js @@ -84,7 +84,7 @@ class ClusterNodeList extends React.Component { `keyword=${keyword}`, ]; request({ - url: `v1/core/cluster/nodes?${parameter.join('&')}`, + url: `v3/console/core/cluster/nodes?${parameter.join('&')}`, beforeSend: () => this.openLoading(), success: ({ count = 0, data = [] } = {}) => { this.setState({ @@ -107,7 +107,7 @@ class ClusterNodeList extends React.Component { const accessToken = JSON.parse(localStorage.token || '{}').accessToken; this.openLoading(); axios - .post(`v1/core/cluster/server/leave?accessToken=${accessToken}`, nodes) + .post(`v3/console/core/cluster/server/leave?accessToken=${accessToken}`, nodes) .then(response => { if (response.data.code === 200) { Message.success(locale.leaveSucc); diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js index 21c97ae2235..bf876e5423b 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js @@ -58,9 +58,9 @@ class ConfigCompared extends React.Component { getNamespaces() { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { const { namespacesDataSource } = this.state; this.setState({ namespacesDataSource: res.data }); } else { diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js index 59db9acfa39..3c87212384f 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js @@ -66,7 +66,7 @@ class ConfigDetail extends React.Component { this.group = getParams('group') || 'DEFAULT_GROUP'; this.ips = ''; this.valueMap = {}; // 存储不同版本的数据 - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.searchDataId = getParams('searchDataId') || ''; this.searchGroup = getParams('searchGroup') || ''; this.pageSize = getParams('pageSize'); @@ -147,18 +147,18 @@ class ConfigDetail extends React.Component { const { locale = {} } = this.props; const self = this; this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.edasAppName = getParams('edasAppName') || ''; this.inApp = this.edasAppName; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?&dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); @@ -186,7 +186,7 @@ class ConfigDetail extends React.Component { serverId: this.serverId, group: this.searchGroup, dataId: this.searchDataId, - namespace: this.tenant, + namespace: this.namespaceId, pageNo: this.pageNo, pageSize: this.pageSize, }) @@ -226,7 +226,7 @@ class ConfigDetail extends React.Component { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); - let url = `v1/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&group=${this.group}`; + let url = `v3/console/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { @@ -234,6 +234,7 @@ class ConfigDetail extends React.Component { }, success(result) { if (result != null) { + const result = result.data; let rightvalue = result.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); @@ -248,18 +249,19 @@ class ConfigDetail extends React.Component { }); } - openCompare = ([dataId, group, tenant]) => { + openCompare = ([dataId, group, namespaceId]) => { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); const params = { - show: 'all', + // show: 'all', group, dataId, - tenant, + namespaceId, }; - requestUtils.get('v1/cs/configs', { params }).then(res => { - if (res != null && res !== '') { + requestUtils.get('v3/console/cs/config', { params }).then(res => { + if (res != null && res !== '' && res.code === 0) { + res = res.data; let rightvalue = res.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); @@ -319,7 +321,7 @@ class ConfigDetail extends React.Component { )}
-

{this.tenant}

+

{this.namespaceId}

diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js index e1d181a14f7..97fe5d8c2fb 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js @@ -184,15 +184,15 @@ class ConfigEditor extends React.Component { const self = this; this.tenant = getParams('namespace') || ''; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); // self.field.setValue('content', data.content); @@ -369,12 +369,12 @@ class ConfigEditor extends React.Component { appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), group: this.field.getValue('group'), desc: this.field.getValue('desc'), - config_tags: this.state.config_tags.join(','), + configTags: this.state.config_tags.join(','), type: this.state.configType, content, - tenant: this.tenant, + namespaceId: this.tenant, }; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded', diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js index a8fdce74e7b..607a2bc5feb 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js @@ -264,13 +264,13 @@ class ConfigEditor extends React.Component { const stringify = require('qs/lib/stringify'); this.setState({ loading: true }); return request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', method: 'post', data: stringify(payload), headers, }).then( res => { - if (res) { + if (res.data) { if (isNewConfig) { this.setState({ isNewConfig: false }); } @@ -312,14 +312,14 @@ class ConfigEditor extends React.Component { stopBeta() { const { dataId, group } = this.state.form; - const tenant = getParams('namespace'); + const namespaceId = getParams('namespace'); return request - .delete('v1/cs/configs', { + .delete('v3/console/cs/config', { params: { beta: true, dataId, group, - tenant, + namespaceId, }, }) .then(res => { @@ -398,7 +398,7 @@ class ConfigEditor extends React.Component { } else { params.show = 'all'; } - return request.get('v1/cs/configs', { params }).then(res => { + return request.get('v3/console/cs/config', { params }).then(res => { const form = beta ? res.data : res; if (!form) return false; const { type, content, configTags, betaIps, md5 } = form; @@ -424,15 +424,15 @@ class ConfigEditor extends React.Component { tenant: namespace, }; // get subscribes of the namespace - return request.get('v1/cs/configs/listener', { params }).then(res => { + return request.get('v3/console/cs/config/listener', { params }).then(res => { const { subscriberDataSource } = this.state; - const lisentersGroupkeyIpMap = res.lisentersGroupkeyStatus; + const lisentersGroupkeyIpMap = res.data.lisentersGroupkeyStatus; if (lisentersGroupkeyIpMap) { this.setState({ subscriberDataSource: subscriberDataSource.concat(Object.keys(lisentersGroupkeyIpMap)), }); } - return res; + return res.data; }); } diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js index 9d058df05e5..120d79513cf 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js @@ -73,14 +73,14 @@ class ConfigRollback extends React.Component { getDataDetail() { const self = this; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`; + const url = `v3/console/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`; request({ url, success(result) { if (result != null) { - const data = result; + const data = result.data; const envName = self.serverId; self.id = data.id; // 详情的id self.field.setValue('dataId', data.dataId); @@ -143,12 +143,12 @@ class ConfigRollback extends React.Component { dataId: self.dataId, group: self.group, content: self.field.getValue('content'), - tenant: self.tenant, + namespaceId: self.tenant, }; - let url = 'v1/cs/configs'; + let url = 'v3/console/cs/config'; if (self.opType.trim() === 'I') { - url = `v1/cs/configs?dataId=${self.dataId}&group=${self.group}`; + url = `v3/console/cs/config?dataId=${self.dataId}&group=${self.group}`; postData = {}; } @@ -158,8 +158,8 @@ class ConfigRollback extends React.Component { contentType: 'application/x-www-form-urlencoded', url, data: postData, - success(data) { - if (data === true) { + success(res) { + if (res.data === true) { Dialog.alert({ content: locale.rollbackSuccessful }); } }, diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js index 7c02c3b269e..fdc6be61605 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js @@ -279,7 +279,7 @@ class ConfigurationManagement extends React.Component { config_tags: this.state.config_tags.join(','), pageNo: prePageNo ? prePageNo : pageNo, pageSize: prePageSize ? prePageSize : this.state.pageSize, - tenant: this.tenant, + namespaceId: this.tenant, }; setParams('pageSize', null); setParams('pageNo', null); @@ -294,6 +294,7 @@ class ConfigurationManagement extends React.Component { } props = this.props.getConfigsV2(params); } else { + console.log('Params before request:', params); props = this.props.getConfigs(params); } props @@ -362,7 +363,7 @@ class ConfigurationManagement extends React.Component { ), onOk: () => { - const url = `v1/cs/configs?dataId=${record.dataId}&group=${record.group}`; + const url = `v3/console/cs/config?dataId=${record.dataId}&group=${record.group}`; request({ url, type: 'delete', @@ -579,9 +580,8 @@ class ConfigurationManagement extends React.Component { exportData() { const { group, appName, dataId, openUri } = this; const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); - openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), + openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), group, appName, dataId, @@ -594,9 +594,8 @@ class ConfigurationManagement extends React.Component { exportDataNew() { const { group, appName, dataId, openUri } = this; const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); - openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), + openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), group, appName, dataId, @@ -619,9 +618,8 @@ class ConfigurationManagement extends React.Component { } configsTableSelected.forEach((value, key, map) => ids.push(key)); if (newVersion) { - this.openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), + this.openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), group: '', appName: '', ids: ids.join(','), @@ -629,9 +627,8 @@ class ConfigurationManagement extends React.Component { username, }); } else { - this.openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), + this.openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), group: '', appName: '', ids: ids.join(','), @@ -670,9 +667,9 @@ class ConfigurationManagement extends React.Component { ), onOk: () => { const url = - `v1/cs/configs?delType=ids&ids=${Array.from(configsTableSelected.keys()).join( + `v3/console/cs/config/batchDelete?&ids=${Array.from(configsTableSelected.keys()).join( ',' - )}&tenant=` + self.state.nownamespace_id; + )}&namespaceId=` + self.state.nownamespace_id; request({ url, type: 'delete', @@ -699,13 +696,14 @@ class ConfigurationManagement extends React.Component { return; } request({ - url: 'v1/console/namespaces?namespaceId=', + url: 'v3/console/core/namespace?namespaceId=', beforeSend() { self.openLoading(); }, success(data) { + data = data.data self.closeLoading(); - if (!data || data.code !== 200 || !data.data) { + if (!data || data.code !== 0 || !data.data) { Dialog.alert({ title: locale.getNamespaceFailed, content: locale.getNamespaceFailed, @@ -855,7 +853,7 @@ class ConfigurationManagement extends React.Component { let cloneTargetSpace = self.field.getValue('cloneTargetSpace'); let sameConfigPolicy = self.field.getValue('sameConfigPolicy'); request({ - url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`, + url: `v3/console/cs/config/clone?namespaceId=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`, method: 'post', data: JSON.stringify(clonePostData), contentType: 'application/json', @@ -1037,7 +1035,7 @@ class ConfigurationManagement extends React.Component { const { accessToken = '', username = '' } = token; const uploadProps = { accept: 'application/zip', - action: `v1/cs/configs?import=true&namespace=${getParams( + action: `v3/console/cs/config/import?namespaceId=${getParams( 'namespace' )}&accessToken=${accessToken}&username=${username}&tenant=${getParams('namespace')}`, headers: Object.assign({}, {}, { accessToken }), diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js index 19b0cecb763..78080e3651a 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js @@ -61,10 +61,10 @@ class HistoryDetail extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`, + url: `v3/console/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`, success(result) { if (result != null) { - const data = result; + const data = result.data; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); self.field.setValue('appName', self.inApp ? self.edasAppName : data.appName); diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js index 4fe7f28d98b..5d6746ba291 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js @@ -115,9 +115,10 @@ class HistoryRollback extends React.Component { beforeSend() { self.openLoading(); }, - url: `v1/cs/history?search=accurate&dataId=${this.state.dataId}&group=${this.state.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, - success(data) { - if (data != null) { + url: `v3/console/cs/history/list?dataId=${this.state.dataId}&group=${this.state.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, + success(res) { + if (res != null) { + const data = res.data self.setState({ dataSource: data.pageItems || [], total: data.totalCount, @@ -234,9 +235,9 @@ class HistoryRollback extends React.Component { return new Promise((resolve, reject) => { const { locale = {} } = this.props; const self = this; - this.tenant = tenant; + this.namespaceId = tenant; this.serverId = tenant; - const url = `v1/cs/configs?show=all&dataId=${dataId}&group=${group}`; + const url = `v3/console/cs/config?dataId=${dataId}&group=${group}`; request({ url, beforeSend() { @@ -244,7 +245,7 @@ class HistoryRollback extends React.Component { }, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, complete() { @@ -266,10 +267,10 @@ class HistoryRollback extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${dataId}&group=${group}&nid=${nid}`, + url: `v3/console/cs/history?dataId=${dataId}&group=${group}&nid=${nid}`, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, }); @@ -291,9 +292,10 @@ class HistoryRollback extends React.Component { this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 const self = this; request({ - url: `v1/cs/history/configs?tenant=${this.tenant}`, - success(result) { - if (result != null) { + url: `v3/console/cs/history/configs?namespaceId=${this.tenant}`, + success(res) { + if (res != null) { + const result = res.data; const dataIdList = []; const groupList = []; for (let i = 0; i < result.length; i++) { diff --git a/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js b/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js index 66e14396115..04802407bac 100644 --- a/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js +++ b/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js @@ -92,26 +92,26 @@ class ListeningToQuery extends React.Component { const type = this.getValue('type'); if (type === 1) { const ip = this.getValue('ip'); - queryUrl = `v1/cs/listener?ip=${ip}`; + queryUrl = `v3/console/cs/config/listener/ip?ip=${ip}`; const tenant = window.nownamespace || getParams('namespace') || ''; if (tenant) { - queryUrl += `&tenant=${tenant}`; + queryUrl += `&namespaceId=${tenant}`; } } else { const dataId = this.getValue('dataId'); const group = this.getValue('group'); if (!dataId || !group) return false; - queryUrl = `v1/cs/configs/listener?dataId=${dataId}&group=${group}`; + queryUrl = `v3/console/cs/config/listener?dataId=${dataId}&group=${group}`; } request({ url: queryUrl, beforeSend() { self.openLoading(); }, - success(data) { - if (data.collectStatus === 200) { + success(res) { + if (res.code === 0) { const dataSoureTmp = []; - const status = data.lisentersGroupkeyStatus; + const status = res.data; for (const key in status) { if (type === 1) { const obj = {}; diff --git a/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js b/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js index 0109d364e9b..b19bb9cd516 100644 --- a/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js +++ b/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js @@ -297,22 +297,28 @@ class NewConfig extends React.Component { const { locale = {} } = this.props; const { addonBefore } = this.state; request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', data: { - show: 'all', dataId: addonBefore + this.field.getValue('dataId'), group: this.field.getValue('group'), - tenant: getParams('namespace') || '', + namespaceId: getParams('namespace') || '', }, success: res => { - // 返回成功 说明存在就不提交配置 - Message.error({ - content: locale.dataIdExists, - align: 'cc cc', - }); + // 检查 res.data 是否为 null,如果不是 null,说明存在就不提交配置 + if (res.data !== null) { + console.log('Data exists, not submitting config'); // 输出提示信息 + Message.error({ + content: locale.dataIdExists, + align: 'cc cc', + }); + } else { + console.log('Data does not exist, proceeding to publish config'); // 输出提示信息 + // 如果 res.data 为 null,表示没有数据,可以继续处理 + this._publishConfig(content); + } }, error: err => { - // 后端接口很不规范 响应为空 说明没有数据 就可以新增 + // 后端接口很不规范,响应为空,说明没有数据,可以新增 const { status } = err || {}; if (status === 403) { Dialog.alert({ @@ -335,13 +341,13 @@ class NewConfig extends React.Component { group: this.field.getValue('group'), content, desc: this.field.getValue('desc'), - config_tags: config_tags.join(), + configTags: config_tags.join(), type: configType, appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), - tenant: this.tenant, + namespaceId: this.tenant, }; this.serverId = getParams('serverId') || 'center'; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded', diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java index 50f9746e42d..a2eb4c28839 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java @@ -18,6 +18,7 @@ package com.alibaba.nacos.console.controller.v3; import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.model.v2.SupportedLanguage; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.ServerStateProxy; import com.alibaba.nacos.core.paramcheck.ExtractorManager; @@ -64,6 +65,10 @@ public Result> serverState() { @GetMapping("/announcement") public Result getAnnouncement( @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { + // Validate the language parameter + if (!SupportedLanguage.isSupported(language)) { + return Result.failure("Unsupported language: " + language); + } String announcement = serverStateProxy.getAnnouncement(language); return Result.success(announcement); } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java index ac446d70d04..82752d28314 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java @@ -21,7 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; @@ -42,6 +42,7 @@ import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.http.ResponseEntity; +import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -291,6 +292,20 @@ public Result getListeners(@RequestParam("dataId") Stri return Result.success(configProxy.getListeners(dataId, group, namespaceId, sampleTime)); } + /** + * Get subscribe information from client side. + */ + @GetMapping("/listener/ip") + @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getAllSubClientConfigByIp(@RequestParam("ip") String ip, + @RequestParam(value = "all", required = false) boolean all, + @RequestParam(value = "namespaceId", required = false) String namespaceId, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime, ModelMap modelMap) + throws NacosException { + GroupkeyListenserStatus result = configProxy.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + return Result.success(result); + } + /** * Export configuration. * diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java index 6f5f6b6f85a..ef35895d4fe 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.core.ClusterProxy; import com.alibaba.nacos.core.cluster.Member; @@ -61,7 +62,8 @@ public ConsoleClusterController(ClusterProxy clusterProxy) { * @return all members */ @GetMapping(value = "/nodes") - @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result> getNodeList(@RequestParam(value = "keyword", required = false) String ipKeyWord) { Collection result = clusterProxy.getNodeList(ipKeyWord); return Result.success(result); diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java index 86da70d8678..f15d230529d 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.core.NamespaceProxy; @@ -51,7 +52,7 @@ * @author zhangyukun on:2024/8/27 */ @RestController -@RequestMapping("/v3/console/namespace") +@RequestMapping("/v3/console/core/namespace") @ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) public class ConsoleNamespaceController { @@ -88,27 +89,24 @@ public Result> getNamespaceList() throws NacosException { */ @GetMapping() @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result getNamespaceDetail(@RequestParam("namespaceId") String namespaceId) throws NacosException { return Result.success(namespaceProxy.getNamespaceDetail(namespaceId)); } /** * create namespace. - * - * @param namespaceForm namespaceForm. + * @param namespaceId custom namespace id + * @param namespaceName custom namespace name + * @param namespaceDesc custom namespace description * @return whether create ok */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) - public Result createNamespace(NamespaceForm namespaceForm) throws NacosException { - - namespaceForm.validate(); - - String namespaceId = namespaceForm.getNamespaceId(); - String namespaceName = namespaceForm.getNamespaceName(); - String namespaceDesc = namespaceForm.getNamespaceDesc(); + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result createNamespace(@RequestParam("customNamespaceId") String namespaceId, + @RequestParam("namespaceName") String namespaceName, + @RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) throws NacosException { if (StringUtils.isBlank(namespaceId)) { namespaceId = UUID.randomUUID().toString(); @@ -125,7 +123,7 @@ public Result createNamespace(NamespaceForm namespaceForm) throws Nacos // check unique if (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, - "the namespaceId is existed, namespaceId: " + namespaceForm.getNamespaceId()); + "the namespaceId is existed, namespaceId: " + namespaceId); } } // contains illegal chars @@ -144,7 +142,7 @@ public Result createNamespace(NamespaceForm namespaceForm) throws Nacos */ @PutMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result updateNamespace(NamespaceForm namespaceForm) throws NacosException { namespaceForm.validate(); // contains illegal chars @@ -163,7 +161,7 @@ public Result updateNamespace(NamespaceForm namespaceForm) throws Nacos */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result deleteNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { return Result.success(namespaceProxy.deleteNamespace(namespaceId)); } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java index 6402641ec9a..6cfb0c3ede0 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.enums.ApiType; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.naming.InstanceProxy; import com.alibaba.nacos.core.control.TpsControl; @@ -82,12 +83,12 @@ public ConsoleInstanceController(InstanceProxy instanceProxy, SwitchDomain switc @RequestMapping("/list") public Result getInstanceList( @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, - @RequestParam String serviceName, @RequestParam(required = false) Boolean healthyOnly, - @RequestParam(required = false) Boolean enabledOnly, @RequestParam(name = "pageNo") int page, - @RequestParam int pageSize) { - String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); - String groupName = NamingUtils.getGroupName(serviceName); - ObjectNode result = instanceProxy.listInstances(namespaceId, serviceNameWithoutGroup, groupName, page, pageSize, healthyOnly, enabledOnly); + @RequestParam String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName, + @RequestParam(required = false) Boolean healthyOnly, @RequestParam(required = false) Boolean enabledOnly, + @RequestParam(name = "pageNo") int page, @RequestParam int pageSize) throws NacosApiException { + checkServiceName(serviceName); + ObjectNode result = instanceProxy.listInstances(namespaceId, serviceName, groupName, page, pageSize, + healthyOnly, enabledOnly); return Result.success(result); } @@ -134,4 +135,11 @@ private Instance buildInstance(InstanceForm instanceForm) throws NacosException private String buildCompositeServiceName(InstanceForm instanceForm) { return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); } + + private void checkServiceName(String serviceName) throws NacosApiException { + if (StringUtils.isBlank(serviceName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'serviceName' type String is not present"); + } + } } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java index 7cc17bce54d..baa339ef309 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -25,7 +25,6 @@ import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker; import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; -import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.Selector; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.enums.ApiType; @@ -48,6 +47,7 @@ import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -111,6 +111,7 @@ public Result deleteService( @RequestParam("serviceName") String serviceName, @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName) throws Exception { + checkServiceName(serviceName); serviceProxy.deleteService(namespaceId, serviceName, groupName); return Result.success("ok"); } @@ -161,12 +162,13 @@ public Result subscribers(HttpServletRequest request) throws Excepti String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); + String groupName = WebUtils.optional(request, CommonParams.GROUP_NAME, Constants.DEFAULT_GROUP); + boolean aggregation = Boolean.parseBoolean( WebUtils.optional(request, "aggregation", String.valueOf(Boolean.TRUE))); - return Result.success(serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation)); - //下放参数 pageNo, pageSize, namespaceId, serviceName, aggregation - + return Result.success( + serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation)); } private Selector parseSelector(String selectorJsonString) throws Exception { @@ -226,10 +228,11 @@ public Object getServiceList(@RequestParam(required = false) boolean withInstanc @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) @GetMapping() public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, - String serviceName) throws NacosException { - String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); - String groupName = NamingUtils.getGroupName(serviceName); - return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName)); + @RequestParam("serviceName") String serviceName, + @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName) + throws NacosException { + checkServiceName(serviceName); + return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceName, groupName)); } /** @@ -246,7 +249,6 @@ public Result updateCluster(HttpServletRequest request) throws Exception Constants.DEFAULT_NAMESPACE_ID); final String clusterName = WebUtils.required(request, CommonParams.CLUSTER_NAME); final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); - ClusterMetadata clusterMetadata = new ClusterMetadata(); clusterMetadata.setHealthyCheckPort(NumberUtils.toInt(WebUtils.required(request, "checkPort"))); clusterMetadata.setUseInstancePortForCheck( @@ -257,9 +259,16 @@ public Result updateCluster(HttpServletRequest request) throws Exception clusterMetadata.setHealthyCheckType(healthChecker.getType()); clusterMetadata.setExtendData( UtilsAndCommons.parseMetadata(WebUtils.optional(request, "metadata", StringUtils.EMPTY))); - + serviceProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); return Result.success("ok"); } + private void checkServiceName(String serviceName) throws NacosApiException { + if (StringUtils.isBlank(serviceName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'serviceName' type String is not present"); + } + } + } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java index 00953a66dd8..d04dd935524 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java @@ -51,6 +51,7 @@ public interface ConfigHandler { * @param group The group to which the configuration belongs. * @param namespaceId The namespace identifier. * @param configAdvanceInfo Additional advanced search criteria. + * @return ConfigInfo containing all details of the specified configuration. * @throws IOException If an input or output exception occurs. * @throws ServletException If a servlet-specific exception occurs. * @throws NacosException If an error related to Nacos configuration occurs. @@ -161,6 +162,17 @@ Page getConfigListByContent(String search, int pageNo, int pageSize, GroupkeyListenserStatus getListeners(String dataId, String group, String namespaceId, int sampleTime) throws Exception; + /** + * Get subscription information based on IP, tenant, and other parameters. + * + * @param ip IP address of the client + * @param all Whether to retrieve all configurations + * @param namespaceId Tenant information + * @param sampleTime Sample time for the subscription + * @return GroupkeyListenserStatus object containing subscription information + */ + GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime); + /** * Imports and publishes a configuration from a file. * diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java index a58b5e4a7f0..a32407cf57a 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java @@ -42,6 +42,7 @@ import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.config.server.utils.YamlParserUtil; import com.alibaba.nacos.config.server.utils.ZipUtils; @@ -195,6 +196,36 @@ public GroupkeyListenserStatus getListeners(String dataId, String group, String return gls; } + @Override + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime) { + SampleResult collectSampleResult = configSubService.getCollectSampleResultByIp(ip, sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + Map configMd5Status = new HashMap<>(100); + + if (collectSampleResult.getLisentersGroupkeyStatus() == null) { + return gls; + } + + Map status = collectSampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry config : status.entrySet()) { + if (!StringUtils.isBlank(namespaceId) && config.getKey().contains(namespaceId)) { + configMd5Status.put(config.getKey(), config.getValue()); + continue; + } + if (all) { + configMd5Status.put(config.getKey(), config.getValue()); + } else { + String[] configKeys = GroupKey2.parseKey(config.getKey()); + if (StringUtils.isBlank(configKeys[2])) { + configMd5Status.put(config.getKey(), config.getValue()); + } + } + } + gls.setLisentersGroupkeyStatus(configMd5Status); + return gls; + } + @Override public ResponseEntity exportConfig(String dataId, String group, String namespaceId, String appName, List ids) throws Exception { diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java index 548f68f37ec..bf280df90ae 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java @@ -30,13 +30,13 @@ import com.alibaba.nacos.naming.core.SubscribeManager; import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.model.form.ServiceForm; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.selector.SelectorManager; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @@ -46,7 +46,7 @@ * * @author zhangyukun */ -@Service +@org.springframework.stereotype.Service public class ServiceInnerHandler implements ServiceHandler { private final ServiceOperatorV2Impl serviceOperatorV2; @@ -61,7 +61,8 @@ public class ServiceInnerHandler implements ServiceHandler { @Autowired public ServiceInnerHandler(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager, - CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, ClusterOperatorV2Impl clusterOperatorV2) { + CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, + ClusterOperatorV2Impl clusterOperatorV2) { this.serviceOperatorV2 = serviceOperatorV2; this.selectorManager = selectorManager; this.catalogServiceV2 = catalogServiceV2; @@ -100,15 +101,17 @@ public List getSelectorTypeList() { } @Override - public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, boolean aggregation) throws Exception { + Service service = Service.newService(namespaceId, groupName, serviceName); + ObjectNode result = JacksonUtils.createEmptyJsonNode(); int count = 0; try { - List subscribers = subscribeManager.getSubscribers(serviceName, namespaceId, aggregation); + List subscribers = subscribeManager.getSubscribers(service, aggregation); int start = (pageNo - 1) * pageSize; if (start < 0) { @@ -144,13 +147,13 @@ public Object getServiceList(boolean withInstances, String namespaceId, int page } @Override - public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) - throws NacosException { - return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceNameWithoutGroup); + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { + return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceName); } @Override - public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception { + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java index 404191f9388..8fcd8a89488 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java @@ -37,7 +37,7 @@ public interface ServiceHandler { /** * Create a new service. * - * @param serviceForm the service form containing the service details + * @param serviceForm the service form containing the service details * @param serviceMetadata the service metadata created from serviceForm * @throws Exception if an error occurs during service creation */ @@ -59,7 +59,7 @@ public interface ServiceHandler { * @param serviceForm the service form containing the service details * @param service the service object created from serviceForm * @param serviceMetadata the service metadata created from serviceForm - * @param metadata the service metadata + * @param metadata the service metadata * @throws Exception if an error occurs during service update */ void updateService(ServiceForm serviceForm, Service service, ServiceMetadata serviceMetadata, @@ -79,12 +79,13 @@ void updateService(ServiceForm serviceForm, Service service, ServiceMetadata ser * @param pageSize the size of the page * @param namespaceId the namespace ID * @param serviceName the service name + * @param groupName the group name * @param aggregation whether to aggregate the results * @return a JSON node containing the list of subscribers * @throws Exception if an error occurs during fetching subscribers */ - ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, boolean aggregation) - throws Exception; + ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, + boolean aggregation) throws Exception; /** * List service detail information. @@ -106,13 +107,13 @@ Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int /** * Get the detail of a specific service. * - * @param namespaceId the namespace ID - * @param serviceNameWithoutGroup the service name without group - * @param groupName the group name + * @param namespaceId the namespace ID + * @param serviceName the service name without group + * @param groupName the group name * @return service detail information * @throws NacosException if an error occurs during fetching service details */ - Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) throws NacosException; + Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException; /** * Update the metadata of a cluster. diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java index 2f9447becdd..db3e6a4b52c 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java @@ -141,6 +141,17 @@ public GroupkeyListenserStatus getListeners(String dataId, String group, String return configHandler.getListeners(dataId, group, namespaceId, sampleTime); } + /** + * Get subscription information based on IP, tenant, and other parameters. + */ + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime) throws NacosException { + ConfigHandler configHandler = configHandlerMap.get(consoleConfig.getType()); + if (configHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return configHandler.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + } + /** * Export configuration. */ diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java index d4c0632a9dc..94b874f7bda 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -19,7 +19,6 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.console.config.ConsoleConfig; -import com.alibaba.nacos.console.handler.core.ClusterHandler; import com.alibaba.nacos.console.handler.inner.naming.ServiceInnerHandler; import com.alibaba.nacos.console.handler.naming.ServiceHandler; import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; @@ -124,17 +123,18 @@ public List getSelectorTypeList() { * @param pageSize the size of the page * @param namespaceId the namespace ID * @param serviceName the service name + * @param groupName the group name * @param aggregation whether to aggregate the results * @return a JSON node containing the list of subscribers * @throws Exception if an error occurs during fetching subscribers */ - public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, boolean aggregation) throws Exception { ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); if (serviceHandler == null) { throw new IllegalArgumentException("Invalid deployment type"); } - return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation); + return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation); } /** @@ -165,18 +165,18 @@ public Object getServiceList(boolean withInstances, String namespaceId, int page * Retrieves the details of a specific service by delegating the operation to the appropriate handler. * * @param namespaceId the namespace ID - * @param serviceNameWithoutGroup the service name without group + * @param serviceName the service name without group * @param groupName the group name * @return service detail information * @throws NacosException if an error occurs during fetching service details */ - public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); if (serviceHandler == null) { throw new IllegalArgumentException("Invalid deployment type"); } - return serviceHandler.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName); + return serviceHandler.getServiceDetail(namespaceId, serviceName, groupName); } /** diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java new file mode 100644 index 00000000000..9a4adb9f9c9 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.HealthProxy; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * ConsoleHealthControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleHealthControllerTest { + + @Mock + private HealthProxy healthProxy; + + @InjectMocks + private ConsoleHealthController consoleHealthController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleHealthController).build(); + } + + @Test + void testLiveness() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/health/liveness"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ok", result.getData()); + } + + @Test + void testReadiness() throws Exception { + when(healthProxy.checkReadiness()).thenReturn(Result.success("ready")); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/health/readiness"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ready", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java new file mode 100644 index 00000000000..fb5bfcc2432 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.ServerStateProxy; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * ConsoleServerStateControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServerStateControllerTest { + + @Mock + private ServerStateProxy serverStateProxy; + + @InjectMocks + private ConsoleServerStateController consoleServerStateController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleServerStateController).build(); + } + + @Test + void testServerState() throws Exception { + Map state = new HashMap<>(); + state.put("state", "OK"); + when(serverStateProxy.getServerState()).thenReturn(state); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/state"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals("OK", result.getData().get("state")); + } + + @Test + void testGetAnnouncement() throws Exception { + when(serverStateProxy.getAnnouncement(anyString())).thenReturn("Test Announcement"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/announcement") + .param("language", "en"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Announcement", result.getData()); + } + + @Test + void testGetConsoleUiGuide() throws Exception { + when(serverStateProxy.getConsoleUiGuide()).thenReturn("Test Guide"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/guide"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Guide", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java index 66241e459f5..b6c63bfc714 100644 --- a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java @@ -73,7 +73,7 @@ import static org.mockito.Mockito.when; /** - * . + * ConsoleConfigControllerTest. * * @author zhangyukun on:2024/8/20 */ diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java new file mode 100644 index 00000000000..41e0ed88f75 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3.config; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.console.proxy.config.HistoryProxy; +import com.alibaba.nacos.persistence.model.Page; +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * ConsoleHistoryControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ConsoleHistoryControllerTest { + + @InjectMocks + private ConsoleHistoryController consoleHistoryController; + + @Mock + private HistoryProxy historyProxy; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleHistoryController).build(); + } + + @Test + void testGetConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getConfigHistoryInfo("testDataId", "testGroup", "", 1L)).thenReturn(configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history") + .param("dataId", "testDataId").param("group", "testGroup").param("nid", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testListConfigHistory() throws Exception { + Page page = new Page<>(); + page.setTotalCount(1); + page.setPageNumber(1); + page.setPagesAvailable(1); + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + page.setPageItems(Collections.singletonList(configHistoryInfo)); + + when(historyProxy.listConfigHistory("testDataId", "testGroup", "", 1, 100)).thenReturn(page); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/list") + .param("dataId", "testDataId").param("group", "testGroup").param("pageNo", "1") + .param("pageSize", "100"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + Page resultPage = result.getData(); + ConfigHistoryInfo resultConfigHistoryInfo = resultPage.getPageItems().get(0); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetPreviousConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getPreviousConfigHistoryInfo("testDataId", "testGroup", "", 1L)).thenReturn( + configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/previous") + .param("dataId", "testDataId").param("group", "testGroup").param("id", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetConfigsByTenant() throws Exception { + ConfigInfoWrapper configInfo = new ConfigInfoWrapper(); + configInfo.setDataId("testDataId"); + configInfo.setGroup("testGroup"); + List configInfoList = Collections.singletonList(configInfo); + + when(historyProxy.getConfigsByTenant("testNamespaceId")).thenReturn(configInfoList); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/configs") + .param("namespaceId", "testNamespaceId"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + List resultConfigInfoList = result.getData(); + + assertEquals(1, resultConfigInfoList.size()); + assertEquals("testDataId", resultConfigInfoList.get(0).getDataId()); + assertEquals("testGroup", resultConfigInfoList.get(0).getGroup()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java new file mode 100644 index 00000000000..e07730e45c4 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3.core; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.ClusterProxy; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * ConsoleClusterControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleClusterControllerTest { + + @Mock + private ClusterProxy clusterProxy; + + @InjectMocks + private ConsoleClusterController consoleClusterController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleClusterController).build(); + + MockedStatic mockedEnvUtil = mockStatic(EnvUtil.class); + mockedEnvUtil.when(() -> EnvUtil.getProperty(anyString())).thenReturn("default_value"); + } + + @Test + void testGetNodeList() throws Exception { + Member member = new Member(); + member.setIp("127.0.0.1"); + member.setPort(8848); + Collection members = Arrays.asList(member); + + when(clusterProxy.getNodeList(anyString())).thenReturn(members); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/cluster/nodes") + .param("keyword", "127.0.0.1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("127.0.0.1", result.getData().iterator().next().getIp()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java new file mode 100644 index 00000000000..09b78047300 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3.core; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.NamespaceProxy; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleNamespaceControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleNamespaceControllerTest { + + @Mock + private NamespaceProxy namespaceProxy; + + @InjectMocks + private ConsoleNamespaceController consoleNamespaceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleNamespaceController).build(); + } + + @Test + void testGetNamespaceList() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + List namespaces = Arrays.asList(namespace); + when(namespaceProxy.getNamespaceList()).thenReturn(namespaces); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/list"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("testNamespace", result.getData().get(0).getNamespace()); + } + + @Test + void testGetNamespaceDetail() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + when(namespaceProxy.getNamespaceDetail(anyString())).thenReturn(namespace); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("testNamespace", result.getData().getNamespace()); + } + + @Test + void testUpdateNamespace() throws Exception { + NamespaceForm namespaceForm = new NamespaceForm(); + namespaceForm.setNamespaceId("testNamespace"); + namespaceForm.setNamespaceName("testNamespaceName"); + namespaceForm.setNamespaceDesc("testDesc"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + + when(namespaceProxy.updateNamespace(any(NamespaceForm.class))).thenReturn(true); + + Result result = consoleNamespaceController.updateNamespace(namespaceForm); + + verify(namespaceProxy).updateNamespace(any(NamespaceForm.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData()); + } + + @Test + void testDeleteNamespace() throws Exception { + when(namespaceProxy.deleteNamespace(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/core/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + assertEquals(200, response.getStatus()); + } + + @Test + void testCheckNamespaceIdExist() throws Exception { + when(namespaceProxy.checkNamespaceIdExist(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/exist") + .param("customNamespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertTrue(result.getData()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java new file mode 100644 index 00000000000..6063cefe7c8 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.console.proxy.naming.InstanceProxy; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleInstanceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleInstanceControllerTest { + + @Mock + private InstanceProxy instanceProxy; + + @Mock + private SwitchDomain switchDomain; + + @InjectMocks + private ConsoleInstanceController consoleInstanceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleInstanceController).build(); + } + + @Test + void testGetInstanceList() throws Exception { + ObjectNode instances = JsonNodeFactory.instance.objectNode(); + when(instanceProxy.listInstances(anyString(), anyString(), anyString(), anyInt(), anyInt(), any(), + any())).thenReturn(instances); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/ns/instance/list") + .param("namespaceId", "default").param("serviceName", "testService").param("pageNo", "1") + .param("pageSize", "10"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertNotNull(result.getData()); + } + + @Test + void testUpdateInstance() throws Exception { + InstanceForm instanceForm = new InstanceForm(); + instanceForm.setServiceName("testService"); + instanceForm.setIp("127.0.0.1"); + instanceForm.setPort(8080); + instanceForm.setWeight(1.0); + + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(8080); + instance.setWeight(1.0); + + doNothing().when(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + Result result = consoleInstanceController.updateInstance(instanceForm); + + verify(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals("ok", result.getData()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java new file mode 100644 index 00000000000..199f49856c7 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java @@ -0,0 +1,216 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.console.controller.v3.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.naming.ServiceProxy; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.selector.LabelSelector; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.omg.CORBA.ServiceDetail; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +/** + * ConsoleServiceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServiceControllerTest { + + @Mock + private ServiceProxy serviceProxy; + + @Mock + private SelectorManager selectorManager; + + @InjectMocks + private ConsoleServiceController consoleServiceController; + + @BeforeEach + void setUp() { + consoleServiceController = new ConsoleServiceController(serviceProxy, selectorManager); + } + + @Test + void testCreateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.createService(serviceForm); + + verify(serviceProxy).createService(eq(serviceForm), any(ServiceMetadata.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testDeleteService() throws Exception { + Result actual = consoleServiceController.deleteService("testNamespace", "testService", "testGroup"); + + verify(serviceProxy).deleteService(eq("testNamespace"), eq("testService"), eq("testGroup")); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } + + @Test + void testUpdateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.updateService(serviceForm); + + verify(serviceProxy).updateService(eq(serviceForm), + eq(Service.newService("testNamespace", "testGroup", "testService")), any(ServiceMetadata.class), + any(Map.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testGetServiceDetail() throws Exception { + ServiceDetail serviceDetail = new ServiceDetail(); + + when(serviceProxy.getServiceDetail(any(String.class), any(String.class), any(String.class))).thenReturn( + serviceDetail); + + Result actual = (Result) consoleServiceController.getServiceDetail( + "testNamespace", "testService", "testGroup"); + + verify(serviceProxy).getServiceDetail(any(String.class), any(String.class), any(String.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(serviceDetail, actual.getData()); + } + + @Test + void testGetSelectorTypeList() throws Exception { + when(serviceProxy.getSelectorTypeList()).thenReturn(Collections.singletonList("label")); + + Result> actual = consoleServiceController.getSelectorTypeList(); + + verify(serviceProxy).getSelectorTypeList(); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(1, actual.getData().size()); + assertEquals("label", actual.getData().get(0)); + } + + @Test + void testGetSubscribers() throws Exception { + ObjectNode subscribers = new ObjectMapper().createObjectNode(); + subscribers.put("subscriber", "testSubscriber"); + + when(serviceProxy.getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), anyBoolean())).thenReturn( + subscribers); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("pageNo", "1"); + request.addParameter("pageSize", "10"); + request.addParameter("namespaceId", "testNamespace"); + request.addParameter("serviceName", "testService"); + request.addParameter("groupName", "testGroup"); + request.addParameter("aggregation", "true"); + + Result actual = consoleServiceController.subscribers(request); + + verify(serviceProxy).getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(subscribers, actual.getData()); + } + + @Test + void testGetServiceList() throws Exception { + when(serviceProxy.getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyString(), anyBoolean())).thenReturn(Collections.singletonList(new ServiceDetail())); + + Result> actual = (Result>) consoleServiceController.getServiceList(true, + "testNamespace", 1, 10, "testService", "testGroup", "instance", true); + + verify(serviceProxy).getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyString(), anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(1, actual.getData().size()); + } + + @Test + void testUpdateCluster() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("namespaceId", "testNamespace"); + request.addParameter("serviceName", "testService"); + request.addParameter("clusterName", "testCluster"); + request.addParameter("checkPort", "8080"); + request.addParameter("useInstancePort4Check", "true"); + request.addParameter("healthChecker", "{\"type\":\"TCP\"}"); + request.addParameter("metadata", "{\"key\":\"value\"}"); + + Result actual = consoleServiceController.updateCluster(request); + + verify(serviceProxy).updateClusterMetadata(anyString(), anyString(), anyString(), any(ClusterMetadata.class)); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java index 75bb9d93d08..9dd406960da 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -109,7 +109,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } - if (method.isAnnotationPresent(Secured.class) && (authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled())) { + boolean isAuthEnabled = authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled(); + if (method.isAnnotationPresent(Secured.class) && isAuthEnabled) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java index cb520b104c6..626f2b986f9 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java @@ -63,7 +63,8 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th try { Method method = getHandleMethod(handlerClazz); - if (method.isAnnotationPresent(Secured.class) && (authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled())) { + boolean isAuthEnabled = authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled(); + if (method.isAnnotationPresent(Secured.class) && isAuthEnabled) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {}", request.getClass().getSimpleName()); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java index 9f47ae0a489..5e65da610c6 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java @@ -16,12 +16,12 @@ package com.alibaba.nacos.naming.core; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.NamingSubscriberServiceAggregationImpl; import com.alibaba.nacos.naming.push.NamingSubscriberServiceLocalImpl; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; @@ -40,7 +40,7 @@ * @author xiweng.yy * @since 1.0.1 */ -@Service +@org.springframework.stereotype.Service public class SubscribeManager { @Autowired @@ -67,6 +67,23 @@ public List getSubscribers(String serviceName, String namespaceId, b } } + /** + * Get subscribers. + * + * @param service service info + * @param aggregation aggregation + * @return list of subscriber + */ + public List getSubscribers(Service service, boolean aggregation) { + if (aggregation) { + Collection result = aggregationService.getFuzzySubscribers(service); + return CollectionUtils.isNotEmpty(result) ? result.stream().filter(distinctByKey(Subscriber::toString)) + .collect(Collectors.toList()) : Collections.EMPTY_LIST; + } else { + return new LinkedList<>(localService.getFuzzySubscribers(service)); + } + } + public static Predicate distinctByKey(Function keyExtractor) { Map seen = new ConcurrentHashMap<>(128); return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java index 7dbf06cfcb1..28b3071139d 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java @@ -18,7 +18,6 @@ package com.alibaba.nacos.plugin.auth.impl.controller.v3; import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; @@ -42,6 +41,7 @@ import com.alibaba.nacos.plugin.auth.impl.utils.PasswordEncoderUtil; import com.alibaba.nacos.plugin.auth.impl.utils.PasswordGeneratorUtil; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -138,7 +138,7 @@ public Object createAdminUser(@RequestParam(required = false) String password) { if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { if (iAuthenticationManager.hasGlobalAdminRole()) { - return Result.failure(ErrorCode.CONFLICT, "have admin user cannot use it"); + return Result.failure(HttpStatus.CONFLICT, "have admin user cannot use it"); } String username = AuthConstants.DEFAULT_USER; userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); @@ -148,7 +148,7 @@ public Object createAdminUser(@RequestParam(required = false) String password) { result.put(AuthConstants.PARAM_PASSWORD, password); return Result.success(result); } else { - return Result.failure(ErrorCode.NOT_IMPLEMENTED, "not support"); + return Result.failure(HttpStatus.NOT_IMPLEMENTED, "not support"); } } @@ -322,7 +322,7 @@ public Object login(@RequestParam String username, @RequestParam String password response.addHeader(AuthConstants.AUTHORIZATION_HEADER, "Bearer " + token); return Result.success("Bearer " + token); } catch (BadCredentialsException authentication) { - return Result.failure(ErrorCode.UNAUTHORIZED, "Login failed"); + return Result.failure(HttpStatus.UNAUTHORIZED, "Login failed"); } } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java new file mode 100644 index 00000000000..96cec0da2e1 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * PermissionControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class PermissionControllerV3Test { + + @InjectMocks + private PermissionControllerV3 permissionController; + + @Mock + private NacosRoleServiceImpl nacosRoleService; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(permissionController).build(); + } + + @Test + void testGetPermissionListAccurateSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.getPermissionsFromDatabase(anyString(), anyInt(), anyInt())).thenReturn( + permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "accurate"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).getPermissionsFromDatabase("admin", 1, 10); + } + + @Test + void testGetPermissionListBlurSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.findPermissionsLike4Page(anyString(), anyInt(), anyInt())).thenReturn(permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "blur"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).findPermissionsLike4Page("admin", 1, 10); + } + + @Test + void testCreatePermission() { + Result result = (Result) permissionController.createPermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).addPermission("admin", "testResource", "write"); + assertEquals("add permission ok!", result.getData()); + } + + @Test + void testDeletePermission() { + Result result = (Result) permissionController.deletePermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).deletePermission("admin", "testResource", "write"); + assertEquals("delete permission ok!", result.getData()); + } +} \ No newline at end of file diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java new file mode 100644 index 00000000000..bfe508ecb69 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * RoleControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class RoleControllerV3Test { + + @Mock + private NacosRoleServiceImpl roleService; + + @InjectMocks + private RoleControllerV3 roleControllerV3; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(roleControllerV3).build(); + } + + @Test + void testCreateRole() { + Result result = (Result) roleControllerV3.createRole("test", "nacos"); + + verify(roleService, times(1)).addRole(anyString(), anyString()); + + assertEquals("add role ok!", result.getData()); + } + + @Test + void testDeleteRoleWithoutUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", ""); + + verify(roleService, times(1)).deleteRole(anyString()); + + assertEquals("delete role of user ok!", result.getData()); + } + + @Test + void testDeleteRoleWithUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", "nacos"); + + verify(roleService, times(1)).deleteRole(anyString(), anyString()); + + assertEquals("delete role of user nacos ok!", result.getData()); + } + + @Test + void testGetRoleListAccurateSearch() { + Page rolesTest = new Page(); + + when(roleService.getRolesFromDatabase(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "accurate"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListFuzzySearch() { + Page rolesTest = new Page(); + + when(roleService.findRolesLike4Page(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "blur"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListByRoleName() { + List rolesTest = new ArrayList<>(); + + when(roleService.findRolesLikeRoleName(anyString())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleListByRoleName("test"); + + assertEquals(rolesTest, result.getData()); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java new file mode 100644 index 00000000000..72dde868773 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java @@ -0,0 +1,218 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * UserControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +class UserControllerV3Test { + + @Mock + private NacosUserDetailsServiceImpl userDetailsService; + + @Mock + private NacosRoleServiceImpl roleService; + + @Mock + private AuthConfigs authConfigs; + + @Mock + private IAuthenticationManager iAuthenticationManager; + + @Mock + private TokenManagerDelegate jwtTokenManager; + + @InjectMocks + private UserControllerV3 userControllerV3; + + private NacosUser user; + + @BeforeEach + void setUp() { + user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + } + + @Test + void testCreateUserSuccess() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(null); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.createUser("test", "testPass"); + + verify(userDetailsService, times(1)).createUser(eq("test"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$"), "Password hash should start with '$2a$10$'"); + + assertEquals("create user ok!", result.getData()); + } + + @Test + void testCreateUserUserAlreadyExists() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(new User()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.createUser("test", "testPass"); + }); + + assertEquals("user 'test' already exist!", exception.getMessage()); + } + + @Test + void testDeleteUserSuccess() { + when(roleService.getRoles("nacos")).thenReturn(new ArrayList<>()); + + Result result = (Result) userControllerV3.deleteUser("nacos"); + + verify(userDetailsService, times(1)).deleteUser("nacos"); + assertEquals("delete user ok!", result.getData()); + } + + @Test + void testDeleteUserCannotDeleteAdmin() { + List roleInfoList = new ArrayList<>(); + RoleInfo adminRole = new RoleInfo(); + adminRole.setRole(AuthConstants.GLOBAL_ADMIN_ROLE); + roleInfoList.add(adminRole); + + when(roleService.getRoles("nacos")).thenReturn(roleInfoList); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.deleteUser("nacos"); + }); + + assertEquals("cannot delete admin: nacos", exception.getMessage()); + } + + @Test + void testUpdateUserSuccess() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + when(userDetailsService.getUserFromDatabase("nacos")).thenReturn(new User()); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.updateUser("nacos", "newPass", response, request); + + verify(userDetailsService, times(1)).updateUserPassword(eq("nacos"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + assertEquals("update user ok!", result.getData()); + } + + @Test + void testLoginSuccess() throws AccessException, IOException { + NacosUser user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + MockHttpServletRequest request = new MockHttpServletRequest(); + when(iAuthenticationManager.authenticate(request)).thenReturn(user); + when(iAuthenticationManager.hasGlobalAdminRole(user)).thenReturn(true); + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(jwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(18000L); + MockHttpServletResponse response = new MockHttpServletResponse(); + Object actual = userControllerV3.login("nacos", "nacos", response, request); + + assertTrue(actual instanceof ObjectNode); + + String actualString = actual.toString(); + + assertTrue(actualString.contains("\"accessToken\":\"1234567890\"")); + assertTrue(actualString.contains("\"tokenTtl\":18000")); + assertTrue(actualString.contains("\"globalAdmin\":true")); + + assertEquals(AuthConstants.TOKEN_PREFIX + "1234567890", response.getHeader(AuthConstants.AUTHORIZATION_HEADER)); + } + + @Test + void testCreateAdminUserSuccess() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(false); + + Result result = (Result) userControllerV3.createAdminUser("testAdminPass"); + + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + verify(userDetailsService, times(1)).createUser(usernameCaptor.capture(), passwordCaptor.capture()); + + assertEquals(AuthConstants.DEFAULT_USER, usernameCaptor.getValue()); + + ObjectNode data = result.getData(); + assertEquals(AuthConstants.DEFAULT_USER, data.get(AuthConstants.PARAM_USERNAME).asText()); + assertEquals("testAdminPass", data.get(AuthConstants.PARAM_PASSWORD).asText()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + } + + @Test + void testCreateAdminUserConflict() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(true); + + Result result = (Result) userControllerV3.createAdminUser("adminPass"); + + assertEquals(HttpStatus.CONFLICT.value(), result.getCode()); + } +} +