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 bf2cbb213 fix(mock): isolate consumer groups by instance (#2880)
bf2cbb213 is described below
commit bf2cbb213e7a47a54abe77ff0b3c6c03112c5f9b
Author: btlqql <[email protected]>
AuthorDate: Wed Sep 2 17:15:21 2026 +0800
fix(mock): isolate consumer groups by instance (#2880)
---
web/src/services/consumerService.test.ts | 40 ++++++++++++++++++++++++++++++++
web/src/services/consumerService.ts | 22 +++++++++++++++---
2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/web/src/services/consumerService.test.ts
b/web/src/services/consumerService.test.ts
index 44463d470..7e5d59597 100644
--- a/web/src/services/consumerService.test.ts
+++ b/web/src/services/consumerService.test.ts
@@ -18,6 +18,7 @@
import { describe, expect, it, vi } from 'vitest';
import {
createConsumerGroup,
+ deleteConsumerGroup,
getConsumerGroup,
getConsumerProgress,
getConsumerStack,
@@ -26,6 +27,7 @@ import {
listConsumerGroupPage,
listConsumerGroups,
previewConsumerOffsetReset,
+ refreshConsumerGroup,
} from './consumerService';
const { mode, metadataApi } = vi.hoisted(() => ({
@@ -271,6 +273,44 @@ describe('consumer service mock data', () => {
expect(detail).not.toBe(created);
});
+ it('isolates mock consumer groups with the same name by instance', async ()
=> {
+ const name = 'cg-shared-instance-scope-test';
+ await createConsumerGroup({ name, instanceId: 'instance-a', namespace:
'namespace-a' });
+ await createConsumerGroup({ name, instanceId: 'instance-b', namespace:
'namespace-b' });
+
+ try {
+ await expect(
+ createConsumerGroup({ name, instanceId: 'instance-a' }),
+ ).rejects.toThrow(`Consumer group already exists: ${name}`);
+
+ const instanceAGroups = await listConsumerGroups({ instanceId:
'instance-a', search: name });
+ const instanceBGroups = await listConsumerGroups({ instanceId:
'instance-b', search: name });
+ expect(instanceAGroups).toHaveLength(1);
+ expect(instanceAGroups[0].namespace).toBe('namespace-a');
+ expect(instanceBGroups).toHaveLength(1);
+ expect(instanceBGroups[0].namespace).toBe('namespace-b');
+ await expect(getConsumerGroup(name,
'instance-a')).resolves.toMatchObject({
+ instanceId: 'instance-a',
+ namespace: 'namespace-a',
+ });
+ await expect(refreshConsumerGroup(name,
'instance-b')).resolves.toMatchObject({
+ instanceId: 'instance-b',
+ namespace: 'namespace-b',
+ });
+
+ await deleteConsumerGroup(name, 'instance-a');
+ await expect(getConsumerGroup(name, 'instance-a')).rejects.toThrow(
+ `Consumer group not found: ${name}`,
+ );
+ await expect(getConsumerGroup(name,
'instance-b')).resolves.toMatchObject({
+ instanceId: 'instance-b',
+ });
+ } finally {
+ await deleteConsumerGroup(name, 'instance-a');
+ await deleteConsumerGroup(name, 'instance-b');
+ }
+ });
+
it('forwards the selected instance when loading consumer group details in
API mode', async () => {
mode.mock = false;
const detail = {
diff --git a/web/src/services/consumerService.ts
b/web/src/services/consumerService.ts
index 098b0de67..54ba4a3e0 100644
--- a/web/src/services/consumerService.ts
+++ b/web/src/services/consumerService.ts
@@ -81,6 +81,9 @@ const normalizeConsumerGroup = <T extends
ConsumerGroup>(group: T): T => ({
function filterConsumerGroups(params?: ConsumerGroupQuery): ConsumerGroup[] {
let result = [...consumerGroupsState];
+ if (params?.instanceId) {
+ result = result.filter((group) => group.instanceId === params.instanceId);
+ }
if (params?.clusterId) result = result.filter((group) => group.clusterId ===
params.clusterId);
if (params?.search) {
const kw = params.search.trim().toLowerCase();
@@ -164,7 +167,9 @@ export async function getConsumerGroup(
instanceId?: string,
): Promise<ConsumerGroupDetail> {
if (isMockMode()) {
- const group = mockConsumerGroups.find((item) => item.name === name);
+ const group = mockConsumerGroups.find(
+ (item) => item.name === name && (!instanceId || item.instanceId ===
instanceId),
+ );
if (!group) throw new Error(`Consumer group not found: ${name}`);
return copyConsumerGroup(group as unknown as ConsumerGroupDetail) as
ConsumerGroupDetail;
}
@@ -196,7 +201,9 @@ export async function refreshConsumerGroup(
instanceId?: string,
): Promise<ConsumerGroup | null> {
if (isMockMode()) {
- const group = mockConsumerGroups.find((item) => item.name === name);
+ const group = mockConsumerGroups.find(
+ (item) => item.name === name && (!instanceId || item.instanceId ===
instanceId),
+ );
return group ? copyConsumerGroup(group) : null;
}
const data = await metadataApi.refreshConsumerGroup(name, instanceId);
@@ -234,9 +241,16 @@ export async function getConsumerStack(
export async function createConsumerGroup(data: Partial<ConsumerGroup>):
Promise<ConsumerGroup> {
if (isMockMode()) {
+ const instanceId = data.instanceId ?? '';
+ const duplicate = consumerGroupsState.some(
+ (group) => group.name === data.name && group.instanceId === instanceId,
+ );
+ if (duplicate) throw new Error(`Consumer group already exists:
${data.name}`);
+
const now = new Date().toISOString();
const group = {
name: data.name ?? '',
+ instanceId,
namespace: data.namespace ?? 'default',
clusterId: data.clusterId ?? '',
subscriptionMode: data.subscriptionMode ?? 'Push',
@@ -294,7 +308,9 @@ export async function exportConsumerGroups(params:
ConsumerGroupExportQuery = {}
export async function deleteConsumerGroup(name: string, instanceId?: string):
Promise<void> {
if (isMockMode()) {
- const idx = consumerGroupsState.findIndex((group) => group.name === name);
+ const idx = consumerGroupsState.findIndex(
+ (group) => group.name === name && (!instanceId || group.instanceId ===
instanceId),
+ );
if (idx >= 0) consumerGroupsState.splice(idx, 1);
return;
}