fix: add basePath to swagger UI requests, curl display (#3316)

* fix: add basePath to swagger UI requests, curl display

* refactor: separate coreApiRequestInterceptorFactory

* test: add unit test for coreApiRequestInterceptorFactory

* Update ui/apiDocs/utils.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
wnhlee
2026-03-23 11:38:28 +01:00
committed by GitHub
co-authored by coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
parent 6da3b4756b
commit 83802e33f2
2 changed files with 131 additions and 37 deletions
+92
View File
@@ -0,0 +1,92 @@
import type { ApiPropsFull } from 'configs/app/apis';
import { describe, test, expect } from 'vitest';
import { coreApiRequestInterceptorFactory } from './utils';
const api: ApiPropsFull = {
endpoint: 'https://example.com',
basePath: '/blockscout',
host: 'example.com',
protocol: 'https',
socketEndpoint: 'wss://example.com',
};
const apiWithoutBasePath: ApiPropsFull = {
endpoint: 'https://example.com',
basePath: '',
host: 'example.com',
protocol: 'https',
socketEndpoint: 'wss://example.com',
};
const apiWithPort: ApiPropsFull = {
endpoint: 'https://example.com:8080',
basePath: '/blockscout',
host: 'example.com',
protocol: 'https',
port: '8080',
socketEndpoint: 'wss://example.com:8080',
};
describe('coreApiRequestInterceptorFactory', () => {
describe('DEFAULT_SERVER (blockscout.com/poa/core)', () => {
test('replaces host and adds basePath', () => {
const interceptor = coreApiRequestInterceptorFactory(api);
const req = { url: 'http://blockscout.com/poa/core/api/v2/search?q=test' };
const result = interceptor(req);
expect(result.url).toBe('https://example.com/blockscout/api/v2/search?q=test');
});
test('does not duplicate basePath if already present', () => {
const interceptor = coreApiRequestInterceptorFactory(api);
const req = { url: 'http://blockscout.com/poa/core/blockscout/api/v2/search' };
const result = interceptor(req);
expect(result.url).toContain('/blockscout/api/v2/search');
expect(result.url).not.toContain('/blockscout/blockscout');
});
test('skips basePath when empty', () => {
const interceptor = coreApiRequestInterceptorFactory(apiWithoutBasePath);
const req = { url: 'http://blockscout.com/poa/core/api/v2/search' };
const result = interceptor(req);
expect(result.url).toBe('https://example.com/api/v2/search');
});
test('applies port when configured', () => {
const interceptor = coreApiRequestInterceptorFactory(apiWithPort);
const req = { url: 'http://blockscout.com/poa/core/api/v2/search' };
const result = interceptor(req);
expect(result.url).toBe('https://example.com:8080/blockscout/api/v2/search');
});
});
describe('DEFAULT_SERVER_NEW (http://localhost/api)', () => {
test('replaces with endpoint + basePath', () => {
const interceptor = coreApiRequestInterceptorFactory(api);
const req = { url: 'http://localhost/api/v2/search?q=test' };
const result = interceptor(req);
expect(result.url).toBe('https://example.com/blockscout/api/v2/search?q=test');
});
test('works without basePath', () => {
const interceptor = coreApiRequestInterceptorFactory(apiWithoutBasePath);
const req = { url: 'http://localhost/api/v2/search' };
const result = interceptor(req);
expect(result.url).toBe('https://example.com/api/v2/search');
});
});
test('does not modify loadSpec requests', () => {
const interceptor = coreApiRequestInterceptorFactory(api);
const req = { url: 'http://blockscout.com/poa/core/api/v2/search', loadSpec: true };
const result = interceptor(req);
expect(result.url).toBe('http://blockscout.com/poa/core/api/v2/search');
});
test('does not modify unmatched URLs', () => {
const interceptor = coreApiRequestInterceptorFactory(api);
const req = { url: 'https://other-server.com/api/v2/search' };
const result = interceptor(req);
expect(result.url).toBe('https://other-server.com/api/v2/search');
});
});
+39 -37
View File
@@ -1,7 +1,44 @@
import type { SwaggerRequest } from './types';
import config from 'configs/app';
import type { ApiPropsBase } from 'configs/app/apis';
import type { ApiPropsBase, ApiPropsFull } from 'configs/app/apis';
const DEFAULT_SERVER = 'blockscout.com/poa/core';
const DEFAULT_SERVER_NEW = 'http://localhost/api';
export const coreApiRequestInterceptorFactory = (api: ApiPropsFull) => (req: SwaggerRequest): SwaggerRequest => {
if (!req.loadSpec) {
const newUrl = (() => {
try {
if (req.url.includes(DEFAULT_SERVER)) {
const url = new URL(req.url.replace(DEFAULT_SERVER, api.host));
url.protocol = api.protocol + ':';
if (api.port) {
url.port = api.port;
}
if (api.basePath && !url.pathname.includes(api.basePath)) {
url.pathname = api.basePath + url.pathname;
}
return url;
}
if (req.url.includes(DEFAULT_SERVER_NEW)) {
return new URL(req.url.replace(DEFAULT_SERVER_NEW, `${ api.endpoint }${ api.basePath ?? '' }/api`));
}
} catch (error) {}
})();
if (newUrl) {
req.url = newUrl.toString();
}
}
return req;
};
const microserviceRequestInterceptorFactory = (api: ApiPropsBase) => (req: SwaggerRequest) => {
try {
@@ -23,42 +60,7 @@ export const REST_API_SECTIONS = [
swagger: {
// default swagger URL, will be replaced with an URL constructed from the backend version and the openapi spec folder name
url: 'https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml',
requestInterceptor: (req: SwaggerRequest) => {
if (!config.apis.general) {
return req;
}
if (!req.loadSpec) {
const newUrl = (() => {
try {
const DEFAULT_SERVER = 'blockscout.com/poa/core';
const DEFAULT_SERVER_NEW = 'http://localhost/api';
if (req.url.includes(DEFAULT_SERVER)) {
const url = new URL(req.url.replace(DEFAULT_SERVER, config.apis.general.host));
url.protocol = config.apis.general.protocol + ':';
if (config.apis.general.port) {
url.port = config.apis.general.port;
}
return url;
}
if (req.url.includes(DEFAULT_SERVER_NEW)) {
return new URL(req.url.replace(DEFAULT_SERVER_NEW, `${ config.apis.general.endpoint }${ config.apis.general.basePath }/api`));
}
} catch (error) {}
})();
if (newUrl) {
req.url = newUrl.toString();
}
}
return req;
},
requestInterceptor: coreApiRequestInterceptorFactory(config.apis.general),
},
},
config.apis.stats && {