nostr-deploy-server
Version:
Node.js server for hosting static websites under npub subdomains using Nostr protocol and Blossom servers
260 lines (220 loc) • 8.41 kB
text/typescript
import { SimpleSSRHelper } from '../../helpers/ssr-simple';
import { ConfigManager } from '../../utils/config';
// Mock dependencies
jest.mock('puppeteer', () => ({
launch: jest.fn(),
}));
jest.mock('../../utils/config');
jest.mock('../../utils/logger', () => ({
logger: {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
describe('SimpleSSRHelper', () => {
let ssrHelper: SimpleSSRHelper;
let mockBrowser: any;
let mockPage: any;
let mockConfigManager: jest.Mocked<ConfigManager>;
beforeEach(() => {
// Mock ConfigManager
mockConfigManager = {
getConfig: jest.fn().mockReturnValue({
ssrEnabled: true,
ssrTimeoutMs: 30000,
ssrViewportWidth: 1920,
ssrViewportHeight: 1080,
}),
} as any;
(ConfigManager.getInstance as jest.Mock).mockReturnValue(mockConfigManager);
// Mock Puppeteer
mockPage = {
setViewport: jest.fn().mockResolvedValue(undefined),
setUserAgent: jest.fn().mockResolvedValue(undefined),
setBypassCSP: jest.fn().mockResolvedValue(undefined),
on: jest.fn(),
setRequestInterception: jest.fn().mockResolvedValue(undefined),
setDefaultTimeout: jest.fn(),
goto: jest.fn().mockResolvedValue(undefined),
waitForFunction: jest.fn().mockResolvedValue(undefined),
evaluate: jest.fn(),
content: jest
.fn()
.mockResolvedValue('<html><body><div id="root">Rendered Content</div></body></html>'),
isClosed: jest.fn().mockReturnValue(false),
close: jest.fn().mockResolvedValue(undefined),
};
mockBrowser = {
newPage: jest.fn().mockResolvedValue(mockPage),
close: jest.fn().mockResolvedValue(undefined),
};
const puppeteer = require('puppeteer');
puppeteer.launch.mockResolvedValue(mockBrowser);
ssrHelper = new SimpleSSRHelper();
});
afterEach(async () => {
await ssrHelper.close();
jest.clearAllMocks();
});
describe('shouldRenderSSR', () => {
it('should return true for HTML content with regular user agent', () => {
const result = ssrHelper.shouldRenderSSR('text/html', '/', 'Mozilla/5.0');
expect(result).toBe(true);
});
it('should return false for SSR disabled', () => {
const disabledConfig = {
port: 3000,
baseDomain: 'test.com',
defaultRelays: [],
defaultBlossomServers: [],
cacheTtlSeconds: 300,
maxCacheSize: 100,
rateLimitWindowMs: 60000,
rateLimitMaxRequests: 100,
logLevel: 'info',
corsOrigin: '*',
trustProxy: false,
requestTimeoutMs: 30000,
maxFileSizeMB: 50,
ssrEnabled: false,
ssrTimeoutMs: 30000,
ssrCacheTtlSeconds: 1800,
ssrViewportWidth: 1920,
ssrViewportHeight: 1080,
ssrMaxConcurrentPages: 3,
wsConnectionTimeoutMs: 3600000,
wsCleanupIntervalMs: 300000,
// Cache TTL Configuration
negativeCacheTtlMs: 10000,
positiveCacheTtlMs: 300000,
fileContentCacheTtlMs: 1800000,
errorCacheTtlMs: 60000,
// Query Timeout Configuration
relayQueryTimeoutMs: 10000,
// Advanced Cache Configuration
cacheTime: 3600,
maxFileSize: 52428800, // 50MB in bytes
// Real-time Cache Invalidation Configuration
realtimeCacheInvalidation: false,
invalidationRelays: [],
invalidationTimeoutMs: 30000,
invalidationReconnectDelayMs: 5000,
// Sliding Expiration Configuration
slidingExpiration: false,
};
mockConfigManager.getConfig.mockReturnValue(disabledConfig);
// Create a new instance with disabled config
const disabledSSRHelper = new SimpleSSRHelper();
const result = disabledSSRHelper.shouldRenderSSR('text/html', '/', 'Mozilla/5.0');
expect(result).toBe(false);
});
it('should return false for NostrSSRBot user agent (prevent recursion)', () => {
const result = ssrHelper.shouldRenderSSR('text/html', '/', 'NostrSSRBot/1.0');
expect(result).toBe(false);
});
it('should return false for non-HTML content', () => {
const result = ssrHelper.shouldRenderSSR('application/json', '/', 'Mozilla/5.0');
expect(result).toBe(false);
});
it('should return false for API endpoints', () => {
const result = ssrHelper.shouldRenderSSR('text/html', '/api/test', 'Mozilla/5.0');
expect(result).toBe(false);
});
it('should return false for admin endpoints', () => {
const result = ssrHelper.shouldRenderSSR('text/html', '/admin/stats', 'Mozilla/5.0');
expect(result).toBe(false);
});
});
describe('renderPage', () => {
const sampleHtml = `
<!DOCTYPE html>
<html>
<head><title>Test</title></head>
<body><div id="root">Loading...</div></body>
</html>
`;
it('should render page successfully', async () => {
const result = await ssrHelper.renderPage(
'http://test.example.com/',
Buffer.from(sampleHtml),
'text/html'
);
expect(result.html).toContain('Rendered Content');
expect(result.contentType).toBe('text/html; charset=utf-8');
expect(result.html).toContain('Nostr Static Server SSR');
expect(result.html).toContain('og:url');
expect(result.html).toContain('twitter:url');
expect(result.html).toContain('canonical');
});
it('should add SSR meta tags', async () => {
const result = await ssrHelper.renderPage(
'http://test.example.com/',
Buffer.from(sampleHtml),
'text/html'
);
expect(result.html).toContain('name="generator" content="Nostr Static Server SSR"');
expect(result.html).toContain('property="og:url" content="http://test.example.com/"');
expect(result.html).toContain('name="twitter:url" content="http://test.example.com/"');
expect(result.html).toContain('rel="canonical" href="http://test.example.com/"');
});
it('should handle browser errors gracefully', async () => {
mockBrowser.newPage.mockRejectedValue(new Error('Browser error'));
const result = await ssrHelper.renderPage(
'http://test.example.com/',
Buffer.from(sampleHtml),
'text/html'
);
// Should fallback to original content
expect(result.html).toContain('Loading...');
expect(result.contentType).toBe('text/html'); // Original content type without charset
});
it('should handle page timeout gracefully', async () => {
mockPage.goto.mockRejectedValue(new Error('Navigation timeout'));
const result = await ssrHelper.renderPage(
'http://test.example.com/',
Buffer.from(sampleHtml),
'text/html'
);
// Should fallback to original content
expect(result.html).toContain('Loading...');
});
it('should set correct browser configuration', async () => {
await ssrHelper.renderPage('http://test.example.com/', Buffer.from(sampleHtml), 'text/html');
expect(mockPage.setViewport).toHaveBeenCalledWith({
width: 1920,
height: 1080,
});
expect(mockPage.setUserAgent).toHaveBeenCalledWith('NostrSSRBot/1.0 (Internal SSR Request)');
expect(mockPage.setBypassCSP).toHaveBeenCalledWith(true);
expect(mockPage.setRequestInterception).toHaveBeenCalledWith(true);
});
});
describe('getBrowserStats', () => {
it('should return browser statistics', async () => {
const stats = await ssrHelper.getBrowserStats();
expect(stats).toHaveProperty('isConnected');
expect(stats).toHaveProperty('pagesCount');
expect(typeof stats.isConnected).toBe('boolean');
expect(typeof stats.pagesCount).toBe('number');
});
});
describe('close', () => {
it('should close browser gracefully', async () => {
// Initialize browser first
await ssrHelper.renderPage(
'http://test.example.com/',
Buffer.from('<html></html>'),
'text/html'
);
await ssrHelper.close();
expect(mockBrowser.close).toHaveBeenCalled();
});
it('should handle close errors gracefully', async () => {
mockBrowser.close.mockRejectedValue(new Error('Close error'));
// Should not throw
await expect(ssrHelper.close()).resolves.toBeUndefined();
});
});
});