fix: improve endpoint parsing in parseExample method (#5762)
This commit is contained in:
parent
194c9496aa
commit
992579e285
3 changed files with 465 additions and 17 deletions
|
|
@ -67,8 +67,8 @@ services:
|
|||
target: app
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
# depends_on:
|
||||
# - hoppscotch-backend
|
||||
ports:
|
||||
- "3080:80"
|
||||
- "3000:3000"
|
||||
|
|
@ -86,8 +86,8 @@ services:
|
|||
target: sh_admin
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
# depends_on:
|
||||
# - hoppscotch-backend
|
||||
ports:
|
||||
- "3280:80"
|
||||
- "3100:3100"
|
||||
|
|
@ -177,7 +177,7 @@ services:
|
|||
depends_on:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
command: sh -c "pnpx prisma migrate deploy"
|
||||
command: sh -c "pnpm exec prisma migrate deploy"
|
||||
|
||||
# All the services listed below are deprecated
|
||||
# These services are kept for backward compatibility but should not be used for new deployments
|
||||
|
|
|
|||
|
|
@ -1453,4 +1453,437 @@ describe('MockServerService', () => {
|
|||
expect(hasAccess).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseExample (private method)', () => {
|
||||
const requestId = 'req123';
|
||||
|
||||
test('should parse basic example with path only', () => {
|
||||
const exampleData = {
|
||||
key: 'example1',
|
||||
name: 'Get Users',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{"success": true}',
|
||||
responseHeaders: [{ key: 'content-type', value: 'application/json' }],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.id).toBe('example1');
|
||||
expect(result.name).toBe('Get Users');
|
||||
expect(result.method).toBe('GET');
|
||||
expect(result.endpoint).toBe('http://api.example.com/users');
|
||||
expect(result.path).toBe('/users');
|
||||
expect(result.queryParams).toEqual({});
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.statusText).toBe('OK');
|
||||
expect(result.responseBody).toBe('{"success": true}');
|
||||
expect(result.responseHeaders).toHaveLength(1);
|
||||
expect(result.requestHeaders).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should parse example with query parameters', () => {
|
||||
const exampleData = {
|
||||
key: 'example2',
|
||||
name: 'Search Users',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users?page=1&limit=10&sort=name',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users');
|
||||
expect(result.queryParams).toEqual({
|
||||
page: '1',
|
||||
limit: '10',
|
||||
sort: 'name',
|
||||
});
|
||||
});
|
||||
|
||||
test('should parse example with path variables', () => {
|
||||
const exampleData = {
|
||||
key: 'example3',
|
||||
name: 'Get User By ID',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users/<<userId>>',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{"id": "123"}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users/<<userId>>');
|
||||
expect(result.queryParams).toEqual({});
|
||||
});
|
||||
|
||||
test('should parse example with path variables and query params', () => {
|
||||
const exampleData = {
|
||||
key: 'example4',
|
||||
name: 'Update User',
|
||||
method: 'PUT',
|
||||
endpoint: 'http://api.example.com/users/<<userId>>?notify=true',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{"updated": true}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users/<<userId>>');
|
||||
expect(result.queryParams).toEqual({ notify: 'true' });
|
||||
});
|
||||
|
||||
test('should handle endpoint starting with <<', () => {
|
||||
const exampleData = {
|
||||
key: 'example5',
|
||||
name: 'Dynamic Base',
|
||||
method: 'GET',
|
||||
endpoint: '<<baseUrl>>/api/users',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/api/users');
|
||||
});
|
||||
|
||||
test('should handle endpoint without domain', () => {
|
||||
const exampleData = {
|
||||
key: 'example6',
|
||||
name: 'Relative Path',
|
||||
method: 'POST',
|
||||
endpoint: '/api/users',
|
||||
statusCode: 201,
|
||||
statusText: 'Created',
|
||||
responseBody: '{"id": "new"}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/api/users');
|
||||
});
|
||||
|
||||
test('should remove domain from endpoint', () => {
|
||||
const exampleData = {
|
||||
key: 'example7',
|
||||
name: 'Full URL',
|
||||
method: 'GET',
|
||||
endpoint: 'https://subdomain.example.com/api/v1/users',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/api/v1/users');
|
||||
});
|
||||
|
||||
test('should use default values when fields are missing', () => {
|
||||
const exampleData = {
|
||||
endpoint: '/users',
|
||||
responseBody: '{}',
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.id).toBe(`${requestId}-undefined`);
|
||||
expect(result.method).toBe('GET');
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.statusText).toBe('OK');
|
||||
expect(result.responseHeaders).toEqual([]);
|
||||
expect(result.requestHeaders).toEqual([]);
|
||||
});
|
||||
|
||||
test('should generate ID from requestId and name when key is missing', () => {
|
||||
const exampleData = {
|
||||
name: 'Test Example',
|
||||
endpoint: '/test',
|
||||
responseBody: '{}',
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.id).toBe(`${requestId}-Test Example`);
|
||||
});
|
||||
|
||||
test('should handle complex path with multiple segments', () => {
|
||||
const exampleData = {
|
||||
key: 'example8',
|
||||
name: 'Nested Resource',
|
||||
method: 'GET',
|
||||
endpoint:
|
||||
'http://api.example.com/organizations/<<orgId>>/teams/<<teamId>>/members',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe(
|
||||
'/organizations/<<orgId>>/teams/<<teamId>>/members',
|
||||
);
|
||||
});
|
||||
|
||||
test('should preserve special characters in query parameters', () => {
|
||||
const exampleData = {
|
||||
key: 'example9',
|
||||
name: 'Special Chars',
|
||||
method: 'GET',
|
||||
endpoint:
|
||||
'http://api.example.com/search?q=hello+world&filter=name:john',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.queryParams.q).toBe('hello world');
|
||||
expect(result.queryParams.filter).toBe('name:john');
|
||||
});
|
||||
|
||||
test('should handle root path', () => {
|
||||
const exampleData = {
|
||||
key: 'example10',
|
||||
name: 'Root',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{"status": "ok"}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/');
|
||||
});
|
||||
|
||||
test('should handle empty endpoint gracefully', () => {
|
||||
const exampleData = {
|
||||
key: 'example11',
|
||||
name: 'Empty Endpoint',
|
||||
method: 'GET',
|
||||
endpoint: '',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/');
|
||||
});
|
||||
|
||||
test('should handle encoded characters in path', () => {
|
||||
const exampleData = {
|
||||
key: 'example12',
|
||||
name: 'Encoded Path',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users/%3C%3CuserId%3E%3E',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users/<<userId>>');
|
||||
});
|
||||
|
||||
test('should handle multiple query parameters with same key', () => {
|
||||
const exampleData = {
|
||||
key: 'example13',
|
||||
name: 'Multiple Query Values',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users?id=1&id=2&id=3',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
// URLSearchParams keeps last value when keys duplicate
|
||||
expect(result.queryParams.id).toBe('3');
|
||||
});
|
||||
|
||||
test('should handle POST method with request body', () => {
|
||||
const exampleData = {
|
||||
key: 'example14',
|
||||
name: 'Create User',
|
||||
method: 'POST',
|
||||
endpoint: 'http://api.example.com/users',
|
||||
statusCode: 201,
|
||||
statusText: 'Created',
|
||||
responseBody: '{"id": "123", "name": "John"}',
|
||||
responseHeaders: [{ key: 'location', value: '/users/123' }],
|
||||
headers: [{ key: 'content-type', value: 'application/json' }],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.method).toBe('POST');
|
||||
expect(result.statusCode).toBe(201);
|
||||
expect(result.requestHeaders).toHaveLength(1);
|
||||
expect(result.responseHeaders).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should return null on parsing error', () => {
|
||||
// Create an object that will cause URL parsing to fail
|
||||
const exampleData = {
|
||||
key: 'bad-example',
|
||||
name: 'Invalid',
|
||||
endpoint: 'http://a bc.com', // This should cause an error
|
||||
responseBody: '{}',
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('should handle endpoint with port number', () => {
|
||||
const exampleData = {
|
||||
key: 'example15',
|
||||
name: 'With Port',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com:8080/users',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users');
|
||||
});
|
||||
|
||||
test('should handle different HTTP methods', () => {
|
||||
const methods = [
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'DELETE',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
];
|
||||
|
||||
methods.forEach((method) => {
|
||||
const exampleData = {
|
||||
key: `example-${method}`,
|
||||
name: `Test ${method}`,
|
||||
method: method,
|
||||
endpoint: 'http://api.example.com/test',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](
|
||||
exampleData,
|
||||
requestId,
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.method).toBe(method);
|
||||
});
|
||||
});
|
||||
|
||||
test('should preserve endpoint in result', () => {
|
||||
const endpoint = 'http://api.example.com/users/<<id>>?page=1';
|
||||
const exampleData = {
|
||||
key: 'example16',
|
||||
name: 'Preserve Endpoint',
|
||||
method: 'GET',
|
||||
endpoint: endpoint,
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '{}',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.endpoint).toBe(endpoint);
|
||||
});
|
||||
|
||||
test('should handle empty query string', () => {
|
||||
const exampleData = {
|
||||
key: 'example17',
|
||||
name: 'Empty Query',
|
||||
method: 'GET',
|
||||
endpoint: 'http://api.example.com/users?',
|
||||
statusCode: 200,
|
||||
statusText: 'OK',
|
||||
responseBody: '[]',
|
||||
responseHeaders: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const result = mockServerService['parseExample'](exampleData, requestId);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.path).toBe('/users');
|
||||
expect(result.queryParams).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1016,14 +1016,30 @@ export class MockServerService {
|
|||
private parseExample(exampleData: any, requestId: string) {
|
||||
try {
|
||||
// Parse endpoint to extract path and query parameters
|
||||
let endpointString = String(exampleData.endpoint ?? '');
|
||||
let path = '/';
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
if (exampleData.endpoint) {
|
||||
// If endpoint starts with '<<', then cut the string after '>>'
|
||||
if (endpointString.startsWith('<<')) {
|
||||
const endIndex = endpointString.indexOf('>>');
|
||||
if (endIndex !== -1) {
|
||||
endpointString = endpointString.slice(endIndex + 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove domain if present
|
||||
endpointString = endpointString.replace(
|
||||
/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}/,
|
||||
'',
|
||||
);
|
||||
|
||||
// Use URL to parse path and query parameters
|
||||
const url = new URL(
|
||||
exampleData.endpoint,
|
||||
endpointString,
|
||||
'http://dummy.com', // Base URL for parsing
|
||||
);
|
||||
|
||||
// Decode the pathname to preserve Hoppscotch variable syntax (<<variable>>)
|
||||
path = decodeURIComponent(url.pathname);
|
||||
|
||||
|
|
@ -1031,7 +1047,6 @@ export class MockServerService {
|
|||
url.searchParams.forEach((value, key) => {
|
||||
queryParams[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: exampleData.key || `${requestId}-${exampleData.name}`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue