This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 5ff8cc337 fix(cluster): align the NameServer update body with the DTO 
(#5882)
5ff8cc337 is described below

commit 5ff8cc33741b1f471af4010c3486da5c4b458b62
Author: Wang1rrr <[email protected]>
AuthorDate: Mon Oct 12 16:15:38 2026 +0800

    fix(cluster): align the NameServer update body with the DTO (#5882)
    
    The frontend and the backend disagreed about what `POST 
/api/nameservers/update` accepts, and only mock mode honoured the frontend's 
version.
    
    - #5882 `UpdateNameServerDTO.java:29-37` carries exactly `clusterId`, 
`addr` and `version` — there is no `newAddr` anywhere in the contract — yet 
`web/src/api/cluster.ts:324-329` declared the field and forwarded the whole 
object, and `web/src/services/clusterService.ts:410-428` implemented a 
mock-mode rename complete with a duplicate-address check, so mock mode really 
did change a NameServer's address while a live backend silently dropped the 
unknown property (Boot does not fail on un [...]
    
    Tests: `cluster.test.ts` goes 12 → 13 with an `it.each` over `version` 
undefined and `'5.4.0'` that asserts the exact JSON body placed on the wire, so 
reintroducing a pass-through adds `newAddr` and fails, and 
`clusterService.test.ts` goes 7 → 8 asserting the mock leaves `nameServers` 
equal to the original, so restoring the mock rename fails.
---
 docs/api-spec.md                        |  2 +-
 web/src/api/cluster.test.ts             | 28 ++++++++++++++++++---
 web/src/api/cluster.ts                  |  5 ++--
 web/src/services/clusterService.test.ts | 43 ++++++++++++++++++---------------
 web/src/services/clusterService.ts      |  9 +------
 5 files changed, 53 insertions(+), 34 deletions(-)

diff --git a/docs/api-spec.md b/docs/api-spec.md
index c411ab0a2..e5b0c7fd2 100644
--- a/docs/api-spec.md
+++ b/docs/api-spec.md
@@ -575,7 +575,7 @@ POST /api/nameservers/update
 |------|------|------|------|
 | `clusterId` | `string` | 是 | 所属集群 ID |
 | `addr` | `string` | 是 | 原地址 |
-| `newAddr` | `string` | 否 | 新地址(不传则不修改) |
+| `version` | `string` | 否 | NameServer 版本(不传则不修改) |
 
 **Response `data`:** `NameServerInfo`
 
diff --git a/web/src/api/cluster.test.ts b/web/src/api/cluster.test.ts
index f0e8dbef5..5c3fa4e65 100644
--- a/web/src/api/cluster.test.ts
+++ b/web/src/api/cluster.test.ts
@@ -37,6 +37,9 @@ import {
   upgradeNameServer,
 } from './cluster';
 import type { K8sCertInfo } from './cluster';
+import { updateNameServer as updateNameServerService } from 
'../services/clusterService';
+
+vi.mock('../services/dataMode', () => ({ isMockMode: () => false }));
 
 const mock = new MockAdapter(client);
 
@@ -173,7 +176,7 @@ describe('K8s certificate API', () => {
       ['/nameservers/restart', target],
       ['/nameservers/upgrade', { ...target, version: '5.4.0' }],
       ['/nameservers/create', target],
-      ['/nameservers/update', { ...target, newAddr: '127.0.0.2:9876' }],
+      ['/nameservers/update', { ...target, version: '5.4.0' }],
       ['/nameservers/delete', target],
     ] as const;
     requests.forEach(([url, body]) => {
@@ -186,12 +189,29 @@ describe('K8s certificate API', () => {
     await expect(restartNameServer(target)).resolves.toBeUndefined();
     await expect(upgradeNameServer({ ...target, version: '5.4.0' 
})).resolves.toBeUndefined();
     await expect(createNameServer(target)).resolves.toBeUndefined();
-    await expect(
-      updateNameServer({ ...target, newAddr: '127.0.0.2:9876' }),
-    ).resolves.toBeUndefined();
+    await expect(updateNameServer({ ...target, version: '5.4.0' 
})).resolves.toBeUndefined();
     await expect(deleteNameServer(target)).resolves.toBeUndefined();
   });
 
+  it.each([
+    ['API', updateNameServer],
+    ['live service', updateNameServerService],
+  ] as const)('sends only the NameServer update DTO fields through the %s', 
async (_, update) => {
+    const target = { clusterId: 'cluster-1', addr: '127.0.0.1:9876' };
+    mock.onPost('/nameservers/update').reply(200, { code: 200, data: null });
+
+    for (const version of [undefined, '5.4.0']) {
+      const request = { ...target, version, newAddr: '127.0.0.2:9876' };
+      await update(request);
+
+      const lastRequest = mock.history.post[mock.history.post.length - 1];
+      expect(JSON.parse(lastRequest.data)).toEqual({
+        ...target,
+        ...(version === undefined ? {} : { version }),
+      });
+    }
+  });
+
   it('loads NameServer configuration drift for the selected cluster', async () 
=> {
     const result = {
       cluster: 'cluster-1',
diff --git a/web/src/api/cluster.ts b/web/src/api/cluster.ts
index e83f09782..28a8907cc 100644
--- a/web/src/api/cluster.ts
+++ b/web/src/api/cluster.ts
@@ -324,9 +324,10 @@ export async function createNameServer(data: { clusterId: 
string; addr: string }
 export async function updateNameServer(data: {
   clusterId: string;
   addr: string;
-  newAddr?: string;
+  version?: string;
 }) {
-  await client.post('/nameservers/update', data);
+  const { clusterId, addr, version } = data;
+  await client.post('/nameservers/update', { clusterId, addr, version });
 }
 
 export async function getNameServerConfigDiff(clusterId: string, instanceId?: 
string) {
diff --git a/web/src/services/clusterService.test.ts 
b/web/src/services/clusterService.test.ts
index 614915b77..fd2af0972 100644
--- a/web/src/services/clusterService.test.ts
+++ b/web/src/services/clusterService.test.ts
@@ -23,7 +23,9 @@ vi.mock('../config', () => ({
 }));
 
 import {
+  createNameServer,
   createK8sCert,
+  deleteNameServer,
   deleteK8sCert,
   getCluster,
   getNameServerConfigDiff,
@@ -138,36 +140,39 @@ describe('clusterService mock clusters', () => {
     }
   });
 
-  it('rejects NameServer edits that would duplicate an address in the same 
cluster', async () => {
+  it('accepts the update DTO without renaming a mock NameServer', async () => {
     const clusterId = 'cluster-prod';
+    const addr = 'nameserver-update.example:9876';
+    const newAddr = 'nameserver-renamed.example:9876';
+    await createNameServer({ clusterId, addr });
     const original = await getCluster(clusterId);
-    const [first, second] = original.nameServers;
 
     try {
-      await expect(
-        updateNameServer({
-          clusterId,
-          addr: first.addr,
-          newAddr: second.addr,
-        }),
-      ).rejects.toThrow(`NameServer already exists: ${second.addr}`);
-
-      const fresh = await getCluster(clusterId);
-      expect(fresh.nameServers.map((item) => item.addr)).toEqual(
-        original.nameServers.map((item) => item.addr),
-      );
+      for (const version of [undefined, '5.4.0']) {
+        const request = { clusterId, addr, version, newAddr };
+        await expect(updateNameServer(request)).resolves.toBeUndefined();
+        expect((await 
getCluster(clusterId)).nameServers).toEqual(original.nameServers);
+      }
     } finally {
       const current = await getCluster(clusterId);
-      for (let index = 0; index < original.nameServers.length; index += 1) {
-        const currentAddr = current.nameServers[index]?.addr;
-        const originalAddr = original.nameServers[index].addr;
-        if (currentAddr && currentAddr !== originalAddr) {
-          await updateNameServer({ clusterId, addr: currentAddr, newAddr: 
originalAddr });
+      for (const candidate of [addr, newAddr]) {
+        if (current.nameServers.some((item) => item.addr === candidate)) {
+          await deleteNameServer({ clusterId, addr: candidate });
         }
       }
     }
   });
 
+  it('rejects a NameServer update when its target is absent from the selected 
cluster', async () => {
+    const clusterId = 'cluster-prod';
+    const before = await getCluster(clusterId);
+
+    await expect(
+      updateNameServer({ clusterId, addr: 'missing.example:9876', version: 
'5.4.0' }),
+    ).rejects.toThrow('NameServer not found: missing.example:9876');
+    expect((await 
getCluster(clusterId)).nameServers).toEqual(before.nameServers);
+  });
+
   it('copies certificate SAN arrays before writing them into the mock store', 
async () => {
     const san = ['proxy.example.com'];
     const created = await createK8sCert({
diff --git a/web/src/services/clusterService.ts 
b/web/src/services/clusterService.ts
index 0f46640eb..e8f55f4fe 100644
--- a/web/src/services/clusterService.ts
+++ b/web/src/services/clusterService.ts
@@ -410,19 +410,12 @@ export async function createNameServer(data: { clusterId: 
string; addr: string }
 export async function updateNameServer(data: {
   clusterId: string;
   addr: string;
-  newAddr?: string;
+  version?: string;
 }): Promise<void> {
   if (isMockMode()) {
     const nameServers = getMockCluster(data.clusterId).nameServers;
     const nameServer = nameServers.find((item) => item.addr === data.addr);
     if (!nameServer) throw new Error(`NameServer not found: ${data.addr}`);
-    if (data.newAddr && data.newAddr !== data.addr) {
-      const duplicate = nameServers.some(
-        (item) => item !== nameServer && item.addr === data.newAddr,
-      );
-      if (duplicate) throw new Error(`NameServer already exists: 
${data.newAddr}`);
-    }
-    if (data.newAddr) nameServer.addr = data.newAddr;
     return;
   }
   return clusterApi.updateNameServer(data);

Reply via email to