diff --git a/packages/hoppscotch-backend/src/mock-server/mock-server.service.spec.ts b/packages/hoppscotch-backend/src/mock-server/mock-server.service.spec.ts index 7bab3c052..4f6ba1a77 100644 --- a/packages/hoppscotch-backend/src/mock-server/mock-server.service.spec.ts +++ b/packages/hoppscotch-backend/src/mock-server/mock-server.service.spec.ts @@ -151,6 +151,123 @@ describe('MockServerService', () => { }); }); + describe('cast - server URL generation', () => { + test('should not append /backend to serverUrlDomainBased when ENABLE_SUBPATH_BASED_ACCESS is not set', async () => { + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBe( + 'http://test-subdomain.mock.hopp.io', + ); + }); + + test('should not append /backend to serverUrlDomainBased when ENABLE_SUBPATH_BASED_ACCESS is "false"', async () => { + mockConfigService.get.mockImplementation((key: string) => { + if (key === 'VITE_BACKEND_API_URL') return 'http://localhost:3170/v1'; + if (key === 'INFRA.MOCK_SERVER_WILDCARD_DOMAIN') + return '*.mock.hopp.io'; + if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'false'; + if (key === 'ENABLE_SUBPATH_BASED_ACCESS') return 'false'; + return undefined; + }); + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBe( + 'http://test-subdomain.mock.hopp.io', + ); + }); + + test('should append /backend to serverUrlDomainBased when ENABLE_SUBPATH_BASED_ACCESS is "true"', async () => { + mockConfigService.get.mockImplementation((key: string) => { + if (key === 'VITE_BACKEND_API_URL') return 'http://localhost:3170/v1'; + if (key === 'INFRA.MOCK_SERVER_WILDCARD_DOMAIN') + return '*.mock.hopp.io'; + if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'false'; + if (key === 'ENABLE_SUBPATH_BASED_ACCESS') return 'true'; + return undefined; + }); + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBe( + 'http://test-subdomain.mock.hopp.io/backend', + ); + }); + + test('should use https protocol and append /backend when secure cookies and subpath access are enabled', async () => { + mockConfigService.get.mockImplementation((key: string) => { + if (key === 'VITE_BACKEND_API_URL') return 'https://localhost:3170/v1'; + if (key === 'INFRA.MOCK_SERVER_WILDCARD_DOMAIN') + return '*.mock.hopp.io'; + if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'true'; + if (key === 'ENABLE_SUBPATH_BASED_ACCESS') return 'true'; + return undefined; + }); + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBe( + 'https://test-subdomain.mock.hopp.io/backend', + ); + }); + + test('should leave serverUrlDomainBased null and not append /backend when wildcard domain is not configured', async () => { + mockConfigService.get.mockImplementation((key: string) => { + if (key === 'VITE_BACKEND_API_URL') return 'http://localhost:3170/v1'; + if (key === 'INFRA.MOCK_SERVER_WILDCARD_DOMAIN') return undefined; + if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'false'; + if (key === 'ENABLE_SUBPATH_BASED_ACCESS') return 'true'; + return undefined; + }); + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBeNull(); + }); + + test('should strip trailing slashes from wildcard domain before appending subpath suffix', async () => { + mockConfigService.get.mockImplementation((key: string) => { + if (key === 'VITE_BACKEND_API_URL') return 'http://localhost:3170/v1'; + if (key === 'INFRA.MOCK_SERVER_WILDCARD_DOMAIN') + return '*.mock.hopp.io/'; // Domain with trailing slash + if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'false'; + if (key === 'ENABLE_SUBPATH_BASED_ACCESS') return 'true'; + return undefined; + }); + mockPrisma.mockServer.findMany.mockResolvedValue([dbMockServer]); + + const result = await mockServerService.getUserMockServers(user.uid, { + take: 10, + skip: 0, + }); + + expect(result[0].serverUrlDomainBased).toBe( + 'http://test-subdomain.mock.hopp.io/backend', + ); + }); + }); + describe('getTeamMockServers', () => { test('should return team mock servers with pagination', async () => { const teamMockServer = { diff --git a/packages/hoppscotch-backend/src/mock-server/mock-server.service.ts b/packages/hoppscotch-backend/src/mock-server/mock-server.service.ts index 9a829a713..6efbb8a4e 100644 --- a/packages/hoppscotch-backend/src/mock-server/mock-server.service.ts +++ b/packages/hoppscotch-backend/src/mock-server/mock-server.service.ts @@ -64,8 +64,20 @@ export class MockServerService { const isSecure = this.configService.get('INFRA.ALLOW_SECURE_COOKIES') === 'true'; const protocol = isSecure ? 'https://' : 'http://'; - const serverUrlDomainBased = wildcardDomain - ? protocol + dbMockServer.subdomain + wildcardDomain.substring(1) + + // ENABLE_SUBPATH_BASED_ACCESS is a flat config key (no INFRA. prefix) to support flexible deployment strategies + const SUBPATH_BACKEND_SUFFIX = '/backend'; + const subpathSuffix = + this.configService.get('ENABLE_SUBPATH_BASED_ACCESS') === 'true' + ? SUBPATH_BACKEND_SUFFIX + : ''; + + const domainPart = wildcardDomain + ? dbMockServer.subdomain + wildcardDomain.substring(1) + : null; + + const serverUrlDomainBased = domainPart + ? `${protocol}${domainPart.replace(/\/+$/, '')}${subpathSuffix}` : null; return {