// Copyright (c) 2025, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. import type {Request, Response} from 'express'; import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {LegacyGoogleUrlHandler, isMobileViewer} from '../../lib/app/url-handlers.js'; describe('Url Handlers', () => { describe('isMobileViewer', () => { it('should return true if CloudFront-Is-Mobile-Viewer header is "true"', () => { const mockRequest = { header: vi.fn().mockImplementation(name => { if (name === 'CloudFront-Is-Mobile-Viewer') return 'true'; return undefined; }), } as unknown as Request; expect(isMobileViewer(mockRequest)).toBe(true); expect(mockRequest.header).toHaveBeenCalledWith('CloudFront-Is-Mobile-Viewer'); }); it('should return false if CloudFront-Is-Mobile-Viewer header is not "true"', () => { const mockRequest = { header: vi.fn().mockImplementation(name => { if (name === 'CloudFront-Is-Mobile-Viewer') return 'false'; return undefined; }), } as unknown as Request; expect(isMobileViewer(mockRequest)).toBe(false); expect(mockRequest.header).toHaveBeenCalledWith('CloudFront-Is-Mobile-Viewer'); }); it('should return false if CloudFront-Is-Mobile-Viewer header is missing', () => { const mockRequest = { header: vi.fn().mockReturnValue(undefined), } as unknown as Request; expect(isMobileViewer(mockRequest)).toBe(false); expect(mockRequest.header).toHaveBeenCalledWith('CloudFront-Is-Mobile-Viewer'); }); }); describe('LegacyGoogleUrlHandler', () => { let handler: LegacyGoogleUrlHandler; let mockCeProps: any; let mockRequest: Partial; let mockResponse: Partial; let mockNext: any; beforeEach(() => { mockCeProps = vi.fn().mockImplementation(key => { if (key === 'allowedShortUrlHostRe') return '.*'; return ''; }); handler = new LegacyGoogleUrlHandler(mockCeProps); // Mock ShortLinkResolver (private property) Object.defineProperty(handler, 'googleShortUrlResolver', { value: { resolve: vi.fn(), }, }); mockRequest = { params: {id: 'abcdef'}, }; mockResponse = { writeHead: vi.fn(), end: vi.fn(), }; mockNext = vi.fn(); }); afterEach(() => { vi.restoreAllMocks(); }); it('should redirect valid URLs', async () => { (handler['googleShortUrlResolver'].resolve as any).mockResolvedValue({ longUrl: 'https://example.com/path', }); await handler.handle(mockRequest as Request, mockResponse as Response, mockNext); expect(handler['googleShortUrlResolver'].resolve).toHaveBeenCalledWith('https://goo.gl/abcdef'); expect(mockResponse.writeHead).toHaveBeenCalledWith(301, { Location: 'https://example.com/path', 'Cache-Control': 'public', }); expect(mockResponse.end).toHaveBeenCalled(); }); it('should reject URLs that do not match allowed hosts', async () => { (handler['googleShortUrlResolver'].resolve as any).mockResolvedValue({ longUrl: 'https://example.com/path', }); mockCeProps.mockImplementation(key => { if (key === 'allowedShortUrlHostRe') return 'allowed\\.com'; return ''; }); await handler.handle(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith({ statusCode: 404, message: 'ID "abcdef" could not be found', }); }); it('should handle errors from URL resolver', async () => { (handler['googleShortUrlResolver'].resolve as any).mockRejectedValue(new Error('Not found')); await handler.handle(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith({ statusCode: 404, message: 'ID "abcdef" could not be found', }); }); }); });