Skip to content

Commit c0e3ff4

Browse files
anwarulislamnivedinjamesgeorge007mirarifhasan
authored
fix (common): address mock server issues and improve the UI (hoppscotch#5517)
Co-authored-by: nivedin <[email protected]> Co-authored-by: jamesgeorge007 <[email protected]> Co-authored-by: mirarifhasan <[email protected]>
1 parent 213c543 commit c0e3ff4

File tree

35 files changed

+1133
-318
lines changed

35 files changed

+1133
-318
lines changed

packages/hoppscotch-backend/src/mock-server/mock-server.model.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {
1818
import { WorkspaceType } from 'src/types/WorkspaceTypes';
1919

2020
// Regex pattern for mock server name validation
21-
// Allows letters, numbers, spaces, dots, underscores, and hyphens
22-
const MOCK_SERVER_NAME_PATTERN = /^[a-zA-Z0-9 ._-]+$/;
21+
// Allows letters, numbers, spaces, dots, brackets, underscores, and hyphens
22+
const MOCK_SERVER_NAME_PATTERN = /^[a-zA-Z0-9 .()[\]{}<>_-]+$/;
2323
const MOCK_SERVER_NAME_ERROR_MESSAGE =
24-
'Name can only contain letters, numbers, spaces, dots, underscores, and hyphens';
24+
'Name can only contain letters, numbers, spaces, dots, brackets, underscores, and hyphens';
2525

2626
@ObjectType()
2727
export class MockServer {

packages/hoppscotch-backend/src/mock-server/mock-server.resolver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ export class MockServerResolver {
5555
}
5656

5757
@ResolveField(() => MockServerCollection, {
58+
nullable: true,
5859
description: 'Returns the collection of the mock server',
5960
})
6061
async collection(
6162
@Parent() mockServer: MockServer,
62-
): Promise<MockServerCollection> {
63+
): Promise<MockServerCollection | null> {
6364
const collection = await this.mockServerService.getMockServerCollection(
6465
mockServer.id,
6566
);

packages/hoppscotch-backend/src/mock-server/mock-server.service.spec.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,17 +335,17 @@ describe('MockServerService', () => {
335335
}
336336
});
337337

338-
test('should return error when collection not found', async () => {
338+
test('should return null when collection not found', async () => {
339339
mockPrisma.mockServer.findUnique.mockResolvedValue(dbMockServer);
340340
mockPrisma.userCollection.findUnique.mockResolvedValue(null);
341341

342342
const result = await mockServerService.getMockServerCollection(
343343
dbMockServer.id,
344344
);
345345

346-
expect(E.isLeft(result)).toBe(true);
347-
if (E.isLeft(result)) {
348-
expect(result.left).toBe(MOCK_SERVER_INVALID_COLLECTION);
346+
expect(E.isRight(result)).toBe(true);
347+
if (E.isRight(result)) {
348+
expect(result.right).toBe(null);
349349
}
350350
});
351351
});
@@ -863,6 +863,7 @@ describe('MockServerService', () => {
863863
} as any;
864864

865865
test('should return example by ID header', async () => {
866+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
866867
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
867868
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
868869

@@ -882,6 +883,7 @@ describe('MockServerService', () => {
882883
});
883884

884885
test('should return example by name header', async () => {
886+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
885887
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
886888
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
887889

@@ -915,6 +917,7 @@ describe('MockServerService', () => {
915917
},
916918
};
917919

920+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
918921
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
919922
mockPrisma.userRequest.findMany.mockResolvedValue([
920923
requestWith404,
@@ -936,6 +939,7 @@ describe('MockServerService', () => {
936939
});
937940

938941
test('should match exact path', async () => {
942+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
939943
mockPrisma.userRequest.findMany.mockResolvedValue([userRequest] as any);
940944
mockPrisma.userCollection.findMany.mockResolvedValue([]);
941945

@@ -964,6 +968,7 @@ describe('MockServerService', () => {
964968
},
965969
};
966970

971+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
967972
mockPrisma.userRequest.findMany.mockResolvedValue([
968973
variableRequest,
969974
] as any);
@@ -979,6 +984,7 @@ describe('MockServerService', () => {
979984
});
980985

981986
test('should return error when no examples found', async () => {
987+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
982988
mockPrisma.userRequest.findMany.mockResolvedValue([]);
983989
mockPrisma.userCollection.findMany.mockResolvedValue([]);
984990

@@ -1004,6 +1010,7 @@ describe('MockServerService', () => {
10041010
},
10051011
};
10061012

1013+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
10071014
mockPrisma.userRequest.findMany.mockResolvedValue([
10081015
multipleExamples,
10091016
] as any);
@@ -1036,6 +1043,7 @@ describe('MockServerService', () => {
10361043
},
10371044
};
10381045

1046+
mockPrisma.userCollection.findUnique.mockResolvedValue(userCollection);
10391047
mockPrisma.userCollection.findMany.mockResolvedValue([]); // No child collections
10401048
mockPrisma.userRequest.findMany.mockResolvedValue([simpleRequest] as any);
10411049

@@ -1070,6 +1078,7 @@ describe('MockServerService', () => {
10701078
},
10711079
};
10721080

1081+
mockPrisma.teamCollection.findUnique.mockResolvedValue(teamCollection);
10731082
mockPrisma.teamCollection.findMany.mockResolvedValue([]); // No child collections
10741083
mockPrisma.teamRequest.findMany.mockResolvedValue([teamRequest] as any);
10751084

packages/hoppscotch-backend/src/mock-server/mock-server.service.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ export class MockServerService {
222222
const collection = await this.prisma.userCollection.findUnique({
223223
where: { id: mockServer.collectionID },
224224
});
225-
if (!collection) return E.left(MOCK_SERVER_INVALID_COLLECTION);
225+
if (!collection) return E.right(null);
226226
return E.right({
227227
id: collection.id,
228228
title: collection.title,
@@ -231,7 +231,7 @@ export class MockServerService {
231231
const collection = await this.prisma.teamCollection.findUnique({
232232
where: { id: mockServer.collectionID },
233233
});
234-
if (!collection) return E.left(MOCK_SERVER_INVALID_COLLECTION);
234+
if (!collection) return E.right(null);
235235
return E.right({
236236
id: collection.id,
237237
title: collection.title,
@@ -599,6 +599,12 @@ export class MockServerService {
599599
// This is used by both custom header lookup and candidate fetching
600600
const collectionIds = await this.getCollectionIds(mockServer);
601601

602+
if (collectionIds.length === 0) {
603+
return E.left(
604+
`The collection associated with this mock has been deleted.`,
605+
);
606+
}
607+
602608
// OPTIMIZATION: Fetch all requests with examples once (single DB query)
603609
// This is shared between custom header lookup and candidate matching
604610
const requests = await this.fetchRequestsWithExamples(
@@ -848,6 +854,13 @@ export class MockServerService {
848854
private async getAllUserCollectionIds(
849855
rootCollectionId: string,
850856
): Promise<string[]> {
857+
// First verify the root collection exists
858+
const rootCollection = await this.prisma.userCollection.findUnique({
859+
where: { id: rootCollectionId },
860+
});
861+
862+
if (!rootCollection) return []; // Collection doesn't exist
863+
851864
const ids = [rootCollectionId];
852865
const children = await this.prisma.userCollection.findMany({
853866
where: { parentID: rootCollectionId },
@@ -868,6 +881,13 @@ export class MockServerService {
868881
private async getAllTeamCollectionIds(
869882
rootCollectionId: string,
870883
): Promise<string[]> {
884+
// First verify the root collection exists
885+
const rootCollection = await this.prisma.teamCollection.findUnique({
886+
where: { id: rootCollectionId },
887+
});
888+
889+
if (!rootCollection) return []; // Collection doesn't exist
890+
871891
const ids = [rootCollectionId];
872892
const children = await this.prisma.teamCollection.findMany({
873893
where: { parentID: rootCollectionId },

packages/hoppscotch-common/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@
855855
"authentication": "Authentication"
856856
},
857857
"mock_server": {
858+
"confirm_delete_log": "Are you sure you want to delete this log?",
858859
"create_mock_server": "Configure Mock Server",
859860
"mock_server_configuration": "Mock Server Configuration",
860861
"mock_server_name": "Mock Server Name",
@@ -885,6 +886,7 @@
885886
"private_description": "Only authenticated users can access this mock server",
886887
"select_collection": "Select a collection",
887888
"select_collection_error": "Please select a collection",
889+
"invalid_collection_error": "Failed to create a mock server for the collection.",
888890
"url_copied": "URL copied to clipboard",
889891
"make_public": "Make Public",
890892
"view_logs": "View logs",
@@ -1079,6 +1081,7 @@
10791081
"ai_request_naming_style_custom": "Custom",
10801082
"ai_request_naming_style_custom_placeholder": "Enter your custom naming style template...",
10811083
"experimental_scripting_sandbox": "Experimental scripting sandbox",
1084+
"enable_experimental_mock_servers": "Enable Mock Servers",
10821085
"sync": "Synchronise",
10831086
"sync_collections": "Collections",
10841087
"sync_description": "These settings are synced to cloud.",
@@ -1375,6 +1378,7 @@
13751378
"loading": "Loading...",
13761379
"message_received": "Message: {message} arrived on topic: {topic}",
13771380
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
1381+
"no_content_found": "No content found",
13781382
"none": "None",
13791383
"nothing_found": "Nothing found for",
13801384
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",

packages/hoppscotch-common/src/components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,11 @@ declare module 'vue' {
214214
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
215215
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
216216
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
217+
IconLucideCheck: typeof import('~icons/lucide/check')['default']
217218
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
218219
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
219220
IconLucideCircleCheck: typeof import('~icons/lucide/circle-check')['default']
221+
IconLucideCopy: typeof import('~icons/lucide/copy')['default']
220222
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
221223
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
222224
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
@@ -260,6 +262,7 @@ declare module 'vue' {
260262
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
261263
MockServerCreateMockServer: typeof import('./components/mockServer/CreateMockServer.vue')['default']
262264
MockServerEditMockServer: typeof import('./components/mockServer/EditMockServer.vue')['default']
265+
MockServerLogSection: typeof import('./components/mockServer/LogSection.vue')['default']
263266
MockServerMockServerDashboard: typeof import('./components/mockServer/MockServerDashboard.vue')['default']
264267
MockServerMockServerLogs: typeof import('./components/mockServer/MockServerLogs.vue')['default']
265268
MonacoScriptEditor: typeof import('./components/MonacoScriptEditor.vue')['default']

packages/hoppscotch-common/src/components/collections/Collection.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@
135135
@keyup.p="propertiesAction?.$el.click()"
136136
@keyup.t="runCollectionAction?.$el.click()"
137137
@keyup.s="sortAction?.$el.click()"
138-
@keyup.m="mockServerAction?.$el.click()"
138+
@keyup.m="
139+
ENABLE_EXPERIMENTAL_MOCK_SERVERS &&
140+
mockServerAction?.$el.click()
141+
"
139142
@keyup.escape="hide()"
140143
>
141144
<HoppSmartItem
@@ -177,7 +180,11 @@
177180
"
178181
/>
179182
<HoppSmartItem
180-
v-if="!hasNoTeamAccess && isRootCollection"
183+
v-if="
184+
!hasNoTeamAccess &&
185+
isRootCollection &&
186+
ENABLE_EXPERIMENTAL_MOCK_SERVERS
187+
"
181188
ref="mockServerAction"
182189
:icon="IconServer"
183190
:label="t('mock_server.create_mock_server')"
@@ -321,6 +328,7 @@ import IconArrowUpDown from "~icons/lucide/arrow-up-down"
321328
import { CurrentSortValuesService } from "~/services/current-sort.service"
322329
import { useService } from "dioc/vue"
323330
import { useMockServerStatus } from "~/composables/mockServer"
331+
import { useSetting } from "@composables/settings"
324332
import { platform } from "~/platform"
325333
import { invokeAction } from "~/helpers/actions"
326334
@@ -456,9 +464,16 @@ const isCollectionLoading = computed(() => {
456464
})
457465
458466
// Mock Server Status
467+
const ENABLE_EXPERIMENTAL_MOCK_SERVERS = useSetting(
468+
"ENABLE_EXPERIMENTAL_MOCK_SERVERS"
469+
)
459470
const { getMockServerStatus } = useMockServerStatus()
460471
461472
const mockServerStatus = computed(() => {
473+
if (!ENABLE_EXPERIMENTAL_MOCK_SERVERS.value) {
474+
return { exists: false, isActive: false }
475+
}
476+
462477
const collectionId =
463478
props.collectionsType === "my-collections"
464479
? (props.data as HoppCollection).id

0 commit comments

Comments
 (0)