Enhance security middleware and package dependencies
- Updated security headers configuration with stricter settings - Modified rate limiting and helmet middleware setup - Added TypeScript type definitions for Express, rate-limit, and Helmet - Refined referrer policy and HSTS configuration - Improved security middleware chain for better protection
This commit is contained in:
272
__tests__/context/index.test.ts
Normal file
272
__tests__/context/index.test.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { ContextManager, ResourceType, RelationType, ResourceState, ResourceRelationship } from '../../src/context/index.js';
|
||||
|
||||
describe('Context Manager', () => {
|
||||
let contextManager: ContextManager;
|
||||
|
||||
beforeEach(() => {
|
||||
contextManager = new ContextManager();
|
||||
});
|
||||
|
||||
describe('Resource Management', () => {
|
||||
const testResource: ResourceState = {
|
||||
id: 'test1',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'on',
|
||||
attributes: { name: 'Test Device' },
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
it('should add resources', () => {
|
||||
const addHandler = jest.fn();
|
||||
contextManager.on('resource_added', addHandler);
|
||||
|
||||
contextManager.addResource(testResource);
|
||||
|
||||
const resource = contextManager.getResource('test1');
|
||||
expect(resource).toEqual(testResource);
|
||||
expect(addHandler).toHaveBeenCalledWith(testResource);
|
||||
});
|
||||
|
||||
it('should update resources', () => {
|
||||
const updateHandler = jest.fn();
|
||||
contextManager.on('resource_updated', updateHandler);
|
||||
|
||||
contextManager.addResource(testResource);
|
||||
contextManager.updateResource('test1', {
|
||||
state: 'off',
|
||||
attributes: { ...testResource.attributes, brightness: 50 }
|
||||
});
|
||||
|
||||
const resource = contextManager.getResource('test1');
|
||||
expect(resource?.state).toBe('off');
|
||||
expect(resource?.attributes.brightness).toBe(50);
|
||||
expect(updateHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove resources', () => {
|
||||
const removeHandler = jest.fn();
|
||||
contextManager.on('resource_removed', removeHandler);
|
||||
|
||||
contextManager.addResource(testResource);
|
||||
contextManager.removeResource('test1');
|
||||
|
||||
expect(contextManager.getResource('test1')).toBeUndefined();
|
||||
expect(removeHandler).toHaveBeenCalledWith(testResource);
|
||||
});
|
||||
|
||||
it('should get resources by type', () => {
|
||||
const resources = [
|
||||
testResource,
|
||||
{
|
||||
id: 'test2',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'off',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
},
|
||||
{
|
||||
id: 'area1',
|
||||
type: ResourceType.AREA,
|
||||
state: 'active',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
}
|
||||
];
|
||||
|
||||
resources.forEach(r => contextManager.addResource(r));
|
||||
|
||||
const devices = contextManager.getResourcesByType(ResourceType.DEVICE);
|
||||
expect(devices).toHaveLength(2);
|
||||
expect(devices.every((d: ResourceState) => d.type === ResourceType.DEVICE)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Relationship Management', () => {
|
||||
const testRelationship: ResourceRelationship = {
|
||||
sourceId: 'device1',
|
||||
targetId: 'area1',
|
||||
type: RelationType.CONTAINS
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Add test resources
|
||||
contextManager.addResource({
|
||||
id: 'device1',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
});
|
||||
contextManager.addResource({
|
||||
id: 'area1',
|
||||
type: ResourceType.AREA,
|
||||
state: 'active',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
it('should add relationships', () => {
|
||||
const addHandler = jest.fn();
|
||||
contextManager.on('relationship_added', addHandler);
|
||||
|
||||
contextManager.addRelationship(testRelationship);
|
||||
|
||||
const related = contextManager.getRelatedResources('device1');
|
||||
expect(related).toHaveLength(2);
|
||||
expect(related.some(r => r.id === 'area1')).toBe(true);
|
||||
expect(addHandler).toHaveBeenCalledWith(testRelationship);
|
||||
});
|
||||
|
||||
it('should remove relationships', () => {
|
||||
const removeHandler = jest.fn();
|
||||
contextManager.on('relationship_removed', removeHandler);
|
||||
|
||||
contextManager.addRelationship(testRelationship);
|
||||
contextManager.removeRelationship(
|
||||
'device1',
|
||||
'area1',
|
||||
RelationType.CONTAINS
|
||||
);
|
||||
|
||||
const related = contextManager.getRelatedResources('device1');
|
||||
expect(related).toHaveLength(0);
|
||||
expect(removeHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should get related resources with depth', () => {
|
||||
// Create a chain: device1 -> area1 -> area2
|
||||
contextManager.addResource({
|
||||
id: 'area2',
|
||||
type: ResourceType.AREA,
|
||||
state: 'active',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
});
|
||||
|
||||
contextManager.addRelationship({
|
||||
sourceId: 'device1',
|
||||
targetId: 'area1',
|
||||
type: RelationType.CONTAINS
|
||||
});
|
||||
contextManager.addRelationship({
|
||||
sourceId: 'area1',
|
||||
targetId: 'area2',
|
||||
type: RelationType.CONTAINS
|
||||
});
|
||||
|
||||
const relatedDepth1 = contextManager.getRelatedResources('device1', undefined, 1);
|
||||
expect(relatedDepth1).toHaveLength(3);
|
||||
|
||||
const relatedDepth2 = contextManager.getRelatedResources('device1', undefined, 2);
|
||||
expect(relatedDepth2).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resource Analysis', () => {
|
||||
beforeEach(() => {
|
||||
// Setup test resources and relationships
|
||||
const resources = [
|
||||
{
|
||||
id: 'device1',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
},
|
||||
{
|
||||
id: 'automation1',
|
||||
type: ResourceType.AUTOMATION,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
},
|
||||
{
|
||||
id: 'group1',
|
||||
type: ResourceType.GROUP,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
}
|
||||
];
|
||||
|
||||
resources.forEach(r => contextManager.addResource(r));
|
||||
|
||||
const relationships = [
|
||||
{
|
||||
sourceId: 'automation1',
|
||||
targetId: 'device1',
|
||||
type: RelationType.CONTROLS
|
||||
},
|
||||
{
|
||||
sourceId: 'device1',
|
||||
targetId: 'group1',
|
||||
type: RelationType.DEPENDS_ON
|
||||
},
|
||||
{
|
||||
sourceId: 'group1',
|
||||
targetId: 'device1',
|
||||
type: RelationType.GROUPS
|
||||
}
|
||||
];
|
||||
|
||||
relationships.forEach(r => contextManager.addRelationship(r));
|
||||
});
|
||||
|
||||
it('should analyze resource usage', () => {
|
||||
const analysis = contextManager.analyzeResourceUsage('device1');
|
||||
|
||||
expect(analysis.dependencies).toHaveLength(1);
|
||||
expect(analysis.dependencies[0]).toBe('group1');
|
||||
expect(analysis.dependents).toHaveLength(0);
|
||||
expect(analysis.groups).toHaveLength(1);
|
||||
expect(analysis.usage.controlCount).toBe(0);
|
||||
expect(analysis.usage.triggerCount).toBe(0);
|
||||
expect(analysis.usage.groupCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Event Subscriptions', () => {
|
||||
it('should handle resource subscriptions', () => {
|
||||
const callback = jest.fn();
|
||||
const unsubscribe = contextManager.subscribeToResource('test1', callback);
|
||||
|
||||
contextManager.addResource({
|
||||
id: 'test1',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
});
|
||||
|
||||
contextManager.updateResource('test1', { state: 'off' });
|
||||
expect(callback).toHaveBeenCalled();
|
||||
|
||||
unsubscribe();
|
||||
contextManager.updateResource('test1', { state: 'on' });
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle type subscriptions', () => {
|
||||
const callback = jest.fn();
|
||||
const unsubscribe = contextManager.subscribeToType(ResourceType.DEVICE, callback);
|
||||
|
||||
const resource = {
|
||||
id: 'test1',
|
||||
type: ResourceType.DEVICE,
|
||||
state: 'on',
|
||||
attributes: {},
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
contextManager.addResource(resource);
|
||||
contextManager.updateResource('test1', { state: 'off' });
|
||||
expect(callback).toHaveBeenCalled();
|
||||
|
||||
unsubscribe();
|
||||
contextManager.updateResource('test1', { state: 'on' });
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
189
__tests__/performance/index.test.ts
Normal file
189
__tests__/performance/index.test.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { PerformanceMonitor, PerformanceOptimizer, Metric } from '../../src/performance/index.js';
|
||||
import type { MemoryUsage } from 'node:process';
|
||||
|
||||
describe('Performance Module', () => {
|
||||
describe('PerformanceMonitor', () => {
|
||||
let monitor: PerformanceMonitor;
|
||||
|
||||
beforeEach(() => {
|
||||
monitor = new PerformanceMonitor({
|
||||
responseTime: 500,
|
||||
memoryUsage: 1024 * 1024 * 512, // 512MB
|
||||
cpuUsage: 70
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
monitor.stop();
|
||||
});
|
||||
|
||||
it('should collect metrics', () => {
|
||||
const metricHandler = jest.fn();
|
||||
monitor.on('metric', metricHandler);
|
||||
|
||||
monitor.start();
|
||||
|
||||
// Wait for first collection
|
||||
return new Promise(resolve => setTimeout(() => {
|
||||
expect(metricHandler).toHaveBeenCalled();
|
||||
const calls = metricHandler.mock.calls;
|
||||
|
||||
// Verify memory metrics
|
||||
expect(calls.some(([metric]: [Metric]) =>
|
||||
metric.name === 'memory.heapUsed'
|
||||
)).toBe(true);
|
||||
expect(calls.some(([metric]: [Metric]) =>
|
||||
metric.name === 'memory.heapTotal'
|
||||
)).toBe(true);
|
||||
expect(calls.some(([metric]: [Metric]) =>
|
||||
metric.name === 'memory.rss'
|
||||
)).toBe(true);
|
||||
|
||||
// Verify CPU metrics
|
||||
expect(calls.some(([metric]: [Metric]) =>
|
||||
metric.name === 'cpu.user'
|
||||
)).toBe(true);
|
||||
expect(calls.some(([metric]: [Metric]) =>
|
||||
metric.name === 'cpu.system'
|
||||
)).toBe(true);
|
||||
|
||||
resolve(true);
|
||||
}, 100));
|
||||
});
|
||||
|
||||
it('should emit threshold exceeded events', () => {
|
||||
const thresholdHandler = jest.fn();
|
||||
monitor = new PerformanceMonitor({
|
||||
memoryUsage: 1, // Ensure threshold is exceeded
|
||||
cpuUsage: 1
|
||||
});
|
||||
monitor.on('threshold_exceeded', thresholdHandler);
|
||||
|
||||
monitor.start();
|
||||
|
||||
return new Promise(resolve => setTimeout(() => {
|
||||
expect(thresholdHandler).toHaveBeenCalled();
|
||||
resolve(true);
|
||||
}, 100));
|
||||
});
|
||||
|
||||
it('should clean old metrics', () => {
|
||||
const now = Date.now();
|
||||
const oldMetric: Metric = {
|
||||
name: 'test',
|
||||
value: 1,
|
||||
timestamp: now - 25 * 60 * 60 * 1000 // 25 hours old
|
||||
};
|
||||
const newMetric: Metric = {
|
||||
name: 'test',
|
||||
value: 2,
|
||||
timestamp: now - 1000 // 1 second old
|
||||
};
|
||||
|
||||
monitor.addMetric(oldMetric);
|
||||
monitor.addMetric(newMetric);
|
||||
|
||||
const metrics = monitor.getMetrics(now - 24 * 60 * 60 * 1000);
|
||||
expect(metrics).toHaveLength(1);
|
||||
expect(metrics[0]).toEqual(newMetric);
|
||||
});
|
||||
|
||||
it('should calculate metric averages', () => {
|
||||
const now = Date.now();
|
||||
const metrics: Metric[] = [
|
||||
{ name: 'test', value: 1, timestamp: now - 3000 },
|
||||
{ name: 'test', value: 2, timestamp: now - 2000 },
|
||||
{ name: 'test', value: 3, timestamp: now - 1000 }
|
||||
];
|
||||
|
||||
metrics.forEach(metric => monitor.addMetric(metric));
|
||||
|
||||
const average = monitor.calculateAverage(
|
||||
'test',
|
||||
now - 5000,
|
||||
now
|
||||
);
|
||||
expect(average).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PerformanceOptimizer', () => {
|
||||
it('should process batches correctly', async () => {
|
||||
const items = [1, 2, 3, 4, 5];
|
||||
const batchSize = 2;
|
||||
const processor = jest.fn(async (batch: number[]) =>
|
||||
batch.map(n => n * 2)
|
||||
);
|
||||
|
||||
const results = await PerformanceOptimizer.processBatch(
|
||||
items,
|
||||
batchSize,
|
||||
processor
|
||||
);
|
||||
|
||||
expect(results).toEqual([2, 4, 6, 8, 10]);
|
||||
expect(processor).toHaveBeenCalledTimes(3); // 2 + 2 + 1 items
|
||||
});
|
||||
|
||||
it('should debounce function calls', (done) => {
|
||||
const fn = jest.fn();
|
||||
const debounced = PerformanceOptimizer.debounce(fn, 100);
|
||||
|
||||
debounced();
|
||||
debounced();
|
||||
debounced();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
}, 50);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
it('should throttle function calls', (done) => {
|
||||
const fn = jest.fn();
|
||||
const throttled = PerformanceOptimizer.throttle(fn, 100);
|
||||
|
||||
throttled();
|
||||
throttled();
|
||||
throttled();
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
setTimeout(() => {
|
||||
throttled();
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
it('should optimize memory when threshold is exceeded', async () => {
|
||||
const originalGc = global.gc;
|
||||
global.gc = jest.fn();
|
||||
|
||||
const memoryUsage = process.memoryUsage;
|
||||
process.memoryUsage = jest.fn().mockImplementation((): MemoryUsage => ({
|
||||
heapUsed: 900,
|
||||
heapTotal: 1000,
|
||||
rss: 2000,
|
||||
external: 0,
|
||||
arrayBuffers: 0
|
||||
}));
|
||||
|
||||
await PerformanceOptimizer.optimizeMemory();
|
||||
|
||||
expect(global.gc).toHaveBeenCalled();
|
||||
|
||||
// Cleanup
|
||||
process.memoryUsage = memoryUsage;
|
||||
if (originalGc) {
|
||||
global.gc = originalGc;
|
||||
} else {
|
||||
delete global.gc;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
212
__tests__/security/index.test.ts
Normal file
212
__tests__/security/index.test.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { TokenManager, validateRequest, sanitizeInput, errorHandler } from '../../src/security';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
describe('Security Module', () => {
|
||||
describe('TokenManager', () => {
|
||||
const testToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNzE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
const encryptionKey = 'test_encryption_key';
|
||||
|
||||
it('should encrypt and decrypt tokens', () => {
|
||||
const encrypted = TokenManager.encryptToken(testToken, encryptionKey);
|
||||
const decrypted = TokenManager.decryptToken(encrypted, encryptionKey);
|
||||
|
||||
expect(decrypted).toBe(testToken);
|
||||
});
|
||||
|
||||
it('should validate tokens correctly', () => {
|
||||
expect(TokenManager.validateToken(testToken)).toBe(true);
|
||||
expect(TokenManager.validateToken('invalid_token')).toBe(false);
|
||||
expect(TokenManager.validateToken('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle expired tokens', () => {
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
expect(TokenManager.validateToken(expiredToken)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Request Validation', () => {
|
||||
let mockRequest: Partial<Request>;
|
||||
let mockResponse: Partial<Response>;
|
||||
let mockNext: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRequest = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: 'Bearer validToken'
|
||||
},
|
||||
is: jest.fn().mockReturnValue(true),
|
||||
body: { test: 'data' }
|
||||
};
|
||||
mockResponse = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
mockNext = jest.fn();
|
||||
});
|
||||
|
||||
it('should pass valid requests', () => {
|
||||
validateRequest(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
expect(mockResponse.status).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject invalid content type', () => {
|
||||
mockRequest.is = jest.fn().mockReturnValue(false);
|
||||
|
||||
validateRequest(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(415);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
error: 'Unsupported Media Type - Content-Type must be application/json'
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject missing token', () => {
|
||||
mockRequest.headers = {};
|
||||
|
||||
validateRequest(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(401);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
error: 'Invalid or expired token'
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject invalid request body', () => {
|
||||
mockRequest.body = null;
|
||||
|
||||
validateRequest(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(400);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
error: 'Invalid request body'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input Sanitization', () => {
|
||||
let mockRequest: Partial<Request>;
|
||||
let mockResponse: Partial<Response>;
|
||||
let mockNext: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRequest = {
|
||||
body: {}
|
||||
};
|
||||
mockResponse = {};
|
||||
mockNext = jest.fn();
|
||||
});
|
||||
|
||||
it('should sanitize HTML tags from request body', () => {
|
||||
mockRequest.body = {
|
||||
text: 'Test <script>alert("xss")</script>',
|
||||
nested: {
|
||||
html: '<img src="x" onerror="alert(1)">'
|
||||
}
|
||||
};
|
||||
|
||||
sanitizeInput(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockRequest.body).toEqual({
|
||||
text: 'Test alert("xss")',
|
||||
nested: {
|
||||
html: 'img src="x" onerror="alert(1)"'
|
||||
}
|
||||
});
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle non-object body', () => {
|
||||
mockRequest.body = 'string body';
|
||||
|
||||
sanitizeInput(
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockRequest.body).toBe('string body');
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handler', () => {
|
||||
let mockRequest: Partial<Request>;
|
||||
let mockResponse: Partial<Response>;
|
||||
let mockNext: jest.Mock;
|
||||
const originalEnv = process.env.NODE_ENV;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRequest = {};
|
||||
mockResponse = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
mockNext = jest.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
|
||||
it('should handle errors in production mode', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
const error = new Error('Test error');
|
||||
|
||||
errorHandler(
|
||||
error,
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(500);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
error: 'Internal Server Error',
|
||||
message: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it('should include error message in development mode', () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
const error = new Error('Test error');
|
||||
|
||||
errorHandler(
|
||||
error,
|
||||
mockRequest as Request,
|
||||
mockResponse as Response,
|
||||
mockNext
|
||||
);
|
||||
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(500);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
error: 'Internal Server Error',
|
||||
message: 'Test error'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1
__tests__/websocket/client.test.ts
Normal file
1
__tests__/websocket/client.test.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">38.18% </span>
|
||||
<span class="strong">45.71% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>42/110</span>
|
||||
<span class='fraction'>128/280</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">12.96% </span>
|
||||
<span class="strong">40.19% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>7/54</span>
|
||||
<span class='fraction'>41/102</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">28.57% </span>
|
||||
<span class="strong">40.74% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>2/7</span>
|
||||
<span class='fraction'>33/81</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">38.18% </span>
|
||||
<span class="strong">46.29% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>42/110</span>
|
||||
<span class='fraction'>125/270</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -79,18 +79,18 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file low" data-value="src"><a href="src/index.html">src</a></td>
|
||||
<td data-value="33" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 33%"></div><div class="cover-empty" style="width: 67%"></div></div>
|
||||
<td class="file high" data-value="src"><a href="src/index.html">src</a></td>
|
||||
<td data-value="100" class="pic high">
|
||||
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||
</td>
|
||||
<td data-value="33" class="pct low">33%</td>
|
||||
<td data-value="100" class="abs low">33/100</td>
|
||||
<td data-value="2.43" class="pct low">2.43%</td>
|
||||
<td data-value="41" class="abs low">1/41</td>
|
||||
<td data-value="20" class="pct low">20%</td>
|
||||
<td data-value="5" class="abs low">1/5</td>
|
||||
<td data-value="33" class="pct low">33%</td>
|
||||
<td data-value="100" class="abs low">33/100</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="33" class="abs high">33/33</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="1" class="abs high">1/1</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="33" class="abs high">33/33</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -108,6 +108,21 @@
|
||||
<td data-value="2" class="abs high">2/2</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="src/context"><a href="src/context/index.html">src/context</a></td>
|
||||
<td data-value="95.55" class="pic high">
|
||||
<div class="chart"><div class="cover-fill" style="width: 95%"></div><div class="cover-empty" style="width: 5%"></div></div>
|
||||
</td>
|
||||
<td data-value="95.55" class="pct high">95.55%</td>
|
||||
<td data-value="90" class="abs high">86/90</td>
|
||||
<td data-value="85" class="pct high">85%</td>
|
||||
<td data-value="40" class="abs high">34/40</td>
|
||||
<td data-value="91.17" class="pct high">91.17%</td>
|
||||
<td data-value="34" class="abs high">31/34</td>
|
||||
<td data-value="95.4" class="pct high">95.4%</td>
|
||||
<td data-value="87" class="abs high">83/87</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="src/hass"><a href="src/hass/index.html">src/hass</a></td>
|
||||
<td data-value="87.5" class="pic high">
|
||||
@@ -123,6 +138,36 @@
|
||||
<td data-value="8" class="abs high">7/8</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="src/performance"><a href="src/performance/index.html">src/performance</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="67" class="abs low">0/67</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="22" class="abs low">0/22</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="21" class="abs low">0/21</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="60" class="abs low">0/60</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file low" data-value="src/websocket"><a href="src/websocket/index.html">src/websocket</a></td>
|
||||
<td data-value="0" class="pic low">
|
||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
||||
</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="80" class="abs low">0/80</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="26" class="abs low">0/26</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="23" class="abs low">0/23</td>
|
||||
<td data-value="0" class="pct low">0%</td>
|
||||
<td data-value="80" class="abs low">0/80</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -131,7 +176,7 @@
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2024-12-21T09:04:36.269Z
|
||||
at 2025-01-30T08:26:17.384Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -13,130 +13,6 @@ BRF:1
|
||||
BRH:1
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/index.ts
|
||||
FN:49,main
|
||||
FN:60,(anonymous_1)
|
||||
FN:74,(anonymous_2)
|
||||
FN:136,(anonymous_3)
|
||||
FNF:4
|
||||
FNH:0
|
||||
FNDA:0,main
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
DA:8,0
|
||||
DA:9,0
|
||||
DA:32,0
|
||||
DA:33,0
|
||||
DA:34,0
|
||||
DA:50,0
|
||||
DA:53,0
|
||||
DA:56,0
|
||||
DA:61,0
|
||||
DA:62,0
|
||||
DA:69,0
|
||||
DA:70,0
|
||||
DA:73,0
|
||||
DA:74,0
|
||||
DA:75,0
|
||||
DA:76,0
|
||||
DA:77,0
|
||||
DA:79,0
|
||||
DA:84,0
|
||||
DA:87,0
|
||||
DA:92,0
|
||||
DA:101,0
|
||||
DA:137,0
|
||||
DA:138,0
|
||||
DA:140,0
|
||||
DA:141,0
|
||||
DA:144,0
|
||||
DA:145,0
|
||||
DA:150,0
|
||||
DA:152,0
|
||||
DA:153,0
|
||||
DA:155,0
|
||||
DA:156,0
|
||||
DA:158,0
|
||||
DA:159,0
|
||||
DA:161,0
|
||||
DA:164,0
|
||||
DA:165,0
|
||||
DA:167,0
|
||||
DA:168,0
|
||||
DA:170,0
|
||||
DA:173,0
|
||||
DA:174,0
|
||||
DA:175,0
|
||||
DA:177,0
|
||||
DA:178,0
|
||||
DA:180,0
|
||||
DA:181,0
|
||||
DA:184,0
|
||||
DA:185,0
|
||||
DA:187,0
|
||||
DA:188,0
|
||||
DA:190,0
|
||||
DA:191,0
|
||||
DA:193,0
|
||||
DA:198,0
|
||||
DA:201,0
|
||||
DA:205,0
|
||||
DA:206,0
|
||||
DA:215,0
|
||||
DA:216,0
|
||||
DA:219,0
|
||||
DA:224,0
|
||||
DA:227,0
|
||||
DA:236,0
|
||||
DA:237,0
|
||||
DA:240,0
|
||||
LF:67
|
||||
LH:0
|
||||
BRDA:8,0,0,0
|
||||
BRDA:8,0,1,0
|
||||
BRDA:69,1,0,0
|
||||
BRDA:76,2,0,0
|
||||
BRDA:94,3,0,0
|
||||
BRDA:94,3,1,0
|
||||
BRDA:140,4,0,0
|
||||
BRDA:150,5,0,0
|
||||
BRDA:150,5,1,0
|
||||
BRDA:150,5,2,0
|
||||
BRDA:150,5,3,0
|
||||
BRDA:150,5,4,0
|
||||
BRDA:150,5,5,0
|
||||
BRDA:152,6,0,0
|
||||
BRDA:155,7,0,0
|
||||
BRDA:158,8,0,0
|
||||
BRDA:164,9,0,0
|
||||
BRDA:164,10,0,0
|
||||
BRDA:164,10,1,0
|
||||
BRDA:167,11,0,0
|
||||
BRDA:167,12,0,0
|
||||
BRDA:167,12,1,0
|
||||
BRDA:173,13,0,0
|
||||
BRDA:174,14,0,0
|
||||
BRDA:177,15,0,0
|
||||
BRDA:180,16,0,0
|
||||
BRDA:184,17,0,0
|
||||
BRDA:184,18,0,0
|
||||
BRDA:184,18,1,0
|
||||
BRDA:187,19,0,0
|
||||
BRDA:187,20,0,0
|
||||
BRDA:187,20,1,0
|
||||
BRDA:190,21,0,0
|
||||
BRDA:190,22,0,0
|
||||
BRDA:190,22,1,0
|
||||
BRDA:215,23,0,0
|
||||
BRDA:224,24,0,0
|
||||
BRDA:224,24,1,0
|
||||
BRDA:229,25,0,0
|
||||
BRDA:229,25,1,0
|
||||
BRF:40
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/schemas.ts
|
||||
FNF:0
|
||||
FNH:0
|
||||
@@ -196,6 +72,210 @@ BRF:8
|
||||
BRH:4
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/context/index.ts
|
||||
FN:4,(anonymous_0)
|
||||
FN:25,(anonymous_1)
|
||||
FN:48,(anonymous_2)
|
||||
FN:53,(anonymous_3)
|
||||
FN:58,(anonymous_4)
|
||||
FN:75,(anonymous_5)
|
||||
FN:81,(anonymous_6)
|
||||
FN:88,(anonymous_7)
|
||||
FN:93,(anonymous_8)
|
||||
FN:95,(anonymous_9)
|
||||
FN:104,(anonymous_10)
|
||||
FN:113,(anonymous_11)
|
||||
FN:118,(anonymous_12)
|
||||
FN:122,(anonymous_13)
|
||||
FN:124,(anonymous_14)
|
||||
FN:128,(anonymous_15)
|
||||
FN:136,(anonymous_16)
|
||||
FN:141,(anonymous_17)
|
||||
FN:145,(anonymous_18)
|
||||
FN:160,(anonymous_19)
|
||||
FN:171,(anonymous_20)
|
||||
FN:172,(anonymous_21)
|
||||
FN:175,(anonymous_22)
|
||||
FN:176,(anonymous_23)
|
||||
FN:179,(anonymous_24)
|
||||
FN:180,(anonymous_25)
|
||||
FN:184,(anonymous_26)
|
||||
FN:187,(anonymous_27)
|
||||
FN:196,(anonymous_28)
|
||||
FN:200,(anonymous_29)
|
||||
FN:207,(anonymous_30)
|
||||
FN:210,(anonymous_31)
|
||||
FN:214,(anonymous_32)
|
||||
FN:221,(anonymous_33)
|
||||
FNF:34
|
||||
FNH:31
|
||||
FNDA:1,(anonymous_0)
|
||||
FNDA:1,(anonymous_1)
|
||||
FNDA:11,(anonymous_2)
|
||||
FNDA:18,(anonymous_3)
|
||||
FNDA:5,(anonymous_4)
|
||||
FNDA:1,(anonymous_5)
|
||||
FNDA:0,(anonymous_6)
|
||||
FNDA:7,(anonymous_7)
|
||||
FNDA:1,(anonymous_8)
|
||||
FNDA:1,(anonymous_9)
|
||||
FNDA:5,(anonymous_10)
|
||||
FNDA:0,(anonymous_11)
|
||||
FNDA:3,(anonymous_12)
|
||||
FNDA:1,(anonymous_13)
|
||||
FNDA:3,(anonymous_14)
|
||||
FNDA:4,(anonymous_15)
|
||||
FNDA:13,(anonymous_16)
|
||||
FNDA:12,(anonymous_17)
|
||||
FNDA:9,(anonymous_18)
|
||||
FNDA:1,(anonymous_19)
|
||||
FNDA:3,(anonymous_20)
|
||||
FNDA:1,(anonymous_21)
|
||||
FNDA:3,(anonymous_22)
|
||||
FNDA:0,(anonymous_23)
|
||||
FNDA:3,(anonymous_24)
|
||||
FNDA:1,(anonymous_25)
|
||||
FNDA:3,(anonymous_26)
|
||||
FNDA:3,(anonymous_27)
|
||||
FNDA:1,(anonymous_28)
|
||||
FNDA:1,(anonymous_29)
|
||||
FNDA:1,(anonymous_30)
|
||||
FNDA:1,(anonymous_31)
|
||||
FNDA:1,(anonymous_32)
|
||||
FNDA:1,(anonymous_33)
|
||||
DA:4,1
|
||||
DA:5,1
|
||||
DA:6,1
|
||||
DA:7,1
|
||||
DA:8,1
|
||||
DA:9,1
|
||||
DA:10,1
|
||||
DA:11,1
|
||||
DA:25,1
|
||||
DA:26,1
|
||||
DA:27,1
|
||||
DA:28,1
|
||||
DA:29,1
|
||||
DA:30,1
|
||||
DA:43,11
|
||||
DA:44,11
|
||||
DA:45,11
|
||||
DA:46,11
|
||||
DA:49,11
|
||||
DA:54,18
|
||||
DA:55,18
|
||||
DA:59,5
|
||||
DA:60,5
|
||||
DA:62,5
|
||||
DA:65,5
|
||||
DA:70,5
|
||||
DA:71,5
|
||||
DA:76,1
|
||||
DA:77,1
|
||||
DA:78,1
|
||||
DA:80,1
|
||||
DA:81,0
|
||||
DA:83,1
|
||||
DA:89,7
|
||||
DA:90,7
|
||||
DA:94,1
|
||||
DA:95,1
|
||||
DA:97,1
|
||||
DA:98,1
|
||||
DA:99,1
|
||||
DA:105,5
|
||||
DA:106,5
|
||||
DA:107,5
|
||||
DA:108,0
|
||||
DA:110,5
|
||||
DA:114,0
|
||||
DA:119,3
|
||||
DA:123,1
|
||||
DA:124,3
|
||||
DA:133,4
|
||||
DA:134,4
|
||||
DA:136,4
|
||||
DA:137,13
|
||||
DA:138,8
|
||||
DA:140,8
|
||||
DA:142,12
|
||||
DA:146,9
|
||||
DA:147,9
|
||||
DA:148,9
|
||||
DA:149,9
|
||||
DA:150,9
|
||||
DA:155,4
|
||||
DA:156,4
|
||||
DA:170,1
|
||||
DA:171,3
|
||||
DA:172,1
|
||||
DA:174,1
|
||||
DA:175,3
|
||||
DA:176,0
|
||||
DA:178,1
|
||||
DA:179,3
|
||||
DA:180,1
|
||||
DA:182,1
|
||||
DA:184,3
|
||||
DA:187,3
|
||||
DA:192,1
|
||||
DA:200,1
|
||||
DA:201,1
|
||||
DA:202,1
|
||||
DA:206,1
|
||||
DA:207,1
|
||||
DA:214,1
|
||||
DA:215,1
|
||||
DA:216,1
|
||||
DA:220,1
|
||||
DA:221,1
|
||||
DA:226,1
|
||||
LF:87
|
||||
LH:83
|
||||
BRDA:4,0,0,1
|
||||
BRDA:4,0,1,1
|
||||
BRDA:25,1,0,1
|
||||
BRDA:25,1,1,1
|
||||
BRDA:60,2,0,5
|
||||
BRDA:77,3,0,1
|
||||
BRDA:81,4,0,0
|
||||
BRDA:81,4,1,0
|
||||
BRDA:95,5,0,1
|
||||
BRDA:95,5,1,1
|
||||
BRDA:95,5,2,1
|
||||
BRDA:97,6,0,1
|
||||
BRDA:105,7,0,5
|
||||
BRDA:105,7,1,3
|
||||
BRDA:107,8,0,0
|
||||
BRDA:114,9,0,0
|
||||
BRDA:114,9,1,0
|
||||
BRDA:131,10,0,2
|
||||
BRDA:137,11,0,5
|
||||
BRDA:137,12,0,13
|
||||
BRDA:137,12,1,9
|
||||
BRDA:142,13,0,12
|
||||
BRDA:142,13,1,7
|
||||
BRDA:142,13,2,9
|
||||
BRDA:142,13,3,0
|
||||
BRDA:146,14,0,5
|
||||
BRDA:146,14,1,4
|
||||
BRDA:148,15,0,9
|
||||
BRDA:171,16,0,3
|
||||
BRDA:171,16,1,1
|
||||
BRDA:175,17,0,3
|
||||
BRDA:175,17,1,2
|
||||
BRDA:179,18,0,3
|
||||
BRDA:179,18,1,2
|
||||
BRDA:184,19,0,3
|
||||
BRDA:184,19,1,1
|
||||
BRDA:187,20,0,3
|
||||
BRDA:187,20,1,1
|
||||
BRDA:201,21,0,1
|
||||
BRDA:215,22,0,1
|
||||
BRF:40
|
||||
BRH:34
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/hass/index.ts
|
||||
FN:66,(anonymous_0)
|
||||
FN:107,get_hass
|
||||
@@ -221,3 +301,297 @@ BRDA:110,2,1,0
|
||||
BRF:5
|
||||
BRH:2
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/performance/index.ts
|
||||
FN:42,(anonymous_1)
|
||||
FN:43,(anonymous_2)
|
||||
FN:50,(anonymous_3)
|
||||
FN:57,(anonymous_4)
|
||||
FN:99,(anonymous_5)
|
||||
FN:105,(anonymous_6)
|
||||
FN:107,(anonymous_7)
|
||||
FN:111,(anonymous_8)
|
||||
FN:134,(anonymous_9)
|
||||
FN:139,(anonymous_10)
|
||||
FN:147,(anonymous_11)
|
||||
FN:154,(anonymous_12)
|
||||
FN:163,(anonymous_13)
|
||||
FN:175,(anonymous_14)
|
||||
FN:185,(anonymous_15)
|
||||
FN:191,(anonymous_16)
|
||||
FN:196,(anonymous_17)
|
||||
FN:198,(anonymous_18)
|
||||
FN:203,(anonymous_19)
|
||||
FN:208,(anonymous_20)
|
||||
FN:212,(anonymous_21)
|
||||
FNF:21
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
FNDA:0,(anonymous_6)
|
||||
FNDA:0,(anonymous_7)
|
||||
FNDA:0,(anonymous_8)
|
||||
FNDA:0,(anonymous_9)
|
||||
FNDA:0,(anonymous_10)
|
||||
FNDA:0,(anonymous_11)
|
||||
FNDA:0,(anonymous_12)
|
||||
FNDA:0,(anonymous_13)
|
||||
FNDA:0,(anonymous_14)
|
||||
FNDA:0,(anonymous_15)
|
||||
FNDA:0,(anonymous_16)
|
||||
FNDA:0,(anonymous_17)
|
||||
FNDA:0,(anonymous_18)
|
||||
FNDA:0,(anonymous_19)
|
||||
FNDA:0,(anonymous_20)
|
||||
FNDA:0,(anonymous_21)
|
||||
DA:20,0
|
||||
DA:31,0
|
||||
DA:32,0
|
||||
DA:37,0
|
||||
DA:38,0
|
||||
DA:43,0
|
||||
DA:44,0
|
||||
DA:45,0
|
||||
DA:51,0
|
||||
DA:52,0
|
||||
DA:58,0
|
||||
DA:59,0
|
||||
DA:60,0
|
||||
DA:63,0
|
||||
DA:69,0
|
||||
DA:75,0
|
||||
DA:82,0
|
||||
DA:88,0
|
||||
DA:95,0
|
||||
DA:100,0
|
||||
DA:101,0
|
||||
DA:106,0
|
||||
DA:107,0
|
||||
DA:112,0
|
||||
DA:113,0
|
||||
DA:114,0
|
||||
DA:121,0
|
||||
DA:122,0
|
||||
DA:123,0
|
||||
DA:124,0
|
||||
DA:125,0
|
||||
DA:139,0
|
||||
DA:140,0
|
||||
DA:152,0
|
||||
DA:153,0
|
||||
DA:154,0
|
||||
DA:160,0
|
||||
DA:164,0
|
||||
DA:165,0
|
||||
DA:167,0
|
||||
DA:168,0
|
||||
DA:169,0
|
||||
DA:180,0
|
||||
DA:181,0
|
||||
DA:182,0
|
||||
DA:183,0
|
||||
DA:184,0
|
||||
DA:185,0
|
||||
DA:187,0
|
||||
DA:196,0
|
||||
DA:197,0
|
||||
DA:198,0
|
||||
DA:207,0
|
||||
DA:208,0
|
||||
DA:209,0
|
||||
DA:210,0
|
||||
DA:211,0
|
||||
DA:212,0
|
||||
DA:219,0
|
||||
DA:222,0
|
||||
LF:60
|
||||
LH:0
|
||||
BRDA:27,0,0,0
|
||||
BRDA:28,1,0,0
|
||||
BRDA:29,2,0,0
|
||||
BRDA:33,3,0,0
|
||||
BRDA:33,3,1,0
|
||||
BRDA:34,4,0,0
|
||||
BRDA:34,4,1,0
|
||||
BRDA:35,5,0,0
|
||||
BRDA:35,5,1,0
|
||||
BRDA:51,6,0,0
|
||||
BRDA:113,7,0,0
|
||||
BRDA:124,8,0,0
|
||||
BRDA:136,9,0,0
|
||||
BRDA:140,10,0,0
|
||||
BRDA:140,10,1,0
|
||||
BRDA:140,10,2,0
|
||||
BRDA:140,10,3,0
|
||||
BRDA:150,11,0,0
|
||||
BRDA:153,12,0,0
|
||||
BRDA:167,13,0,0
|
||||
BRDA:168,14,0,0
|
||||
BRDA:209,15,0,0
|
||||
BRF:22
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:src/websocket/client.ts
|
||||
FN:13,(anonymous_0)
|
||||
FN:27,(anonymous_1)
|
||||
FN:28,(anonymous_2)
|
||||
FN:32,(anonymous_3)
|
||||
FN:36,(anonymous_4)
|
||||
FN:41,(anonymous_5)
|
||||
FN:45,(anonymous_6)
|
||||
FN:50,(anonymous_7)
|
||||
FN:56,(anonymous_8)
|
||||
FN:65,(anonymous_9)
|
||||
FN:72,(anonymous_10)
|
||||
FN:92,(anonymous_11)
|
||||
FN:100,(anonymous_12)
|
||||
FN:105,(anonymous_13)
|
||||
FN:107,(anonymous_14)
|
||||
FN:114,(anonymous_15)
|
||||
FN:122,(anonymous_16)
|
||||
FN:129,(anonymous_17)
|
||||
FN:139,(anonymous_18)
|
||||
FN:145,(anonymous_19)
|
||||
FN:152,(anonymous_20)
|
||||
FN:162,(anonymous_21)
|
||||
FN:168,(anonymous_22)
|
||||
FNF:23
|
||||
FNH:0
|
||||
FNDA:0,(anonymous_0)
|
||||
FNDA:0,(anonymous_1)
|
||||
FNDA:0,(anonymous_2)
|
||||
FNDA:0,(anonymous_3)
|
||||
FNDA:0,(anonymous_4)
|
||||
FNDA:0,(anonymous_5)
|
||||
FNDA:0,(anonymous_6)
|
||||
FNDA:0,(anonymous_7)
|
||||
FNDA:0,(anonymous_8)
|
||||
FNDA:0,(anonymous_9)
|
||||
FNDA:0,(anonymous_10)
|
||||
FNDA:0,(anonymous_11)
|
||||
FNDA:0,(anonymous_12)
|
||||
FNDA:0,(anonymous_13)
|
||||
FNDA:0,(anonymous_14)
|
||||
FNDA:0,(anonymous_15)
|
||||
FNDA:0,(anonymous_16)
|
||||
FNDA:0,(anonymous_17)
|
||||
FNDA:0,(anonymous_18)
|
||||
FNDA:0,(anonymous_19)
|
||||
FNDA:0,(anonymous_20)
|
||||
FNDA:0,(anonymous_21)
|
||||
FNDA:0,(anonymous_22)
|
||||
DA:5,0
|
||||
DA:6,0
|
||||
DA:7,0
|
||||
DA:8,0
|
||||
DA:9,0
|
||||
DA:10,0
|
||||
DA:11,0
|
||||
DA:14,0
|
||||
DA:15,0
|
||||
DA:16,0
|
||||
DA:22,0
|
||||
DA:23,0
|
||||
DA:24,0
|
||||
DA:28,0
|
||||
DA:29,0
|
||||
DA:30,0
|
||||
DA:32,0
|
||||
DA:33,0
|
||||
DA:36,0
|
||||
DA:37,0
|
||||
DA:38,0
|
||||
DA:41,0
|
||||
DA:42,0
|
||||
DA:45,0
|
||||
DA:46,0
|
||||
DA:47,0
|
||||
DA:50,0
|
||||
DA:51,0
|
||||
DA:52,0
|
||||
DA:53,0
|
||||
DA:56,0
|
||||
DA:57,0
|
||||
DA:60,0
|
||||
DA:66,0
|
||||
DA:73,0
|
||||
DA:75,0
|
||||
DA:76,0
|
||||
DA:78,0
|
||||
DA:79,0
|
||||
DA:81,0
|
||||
DA:82,0
|
||||
DA:84,0
|
||||
DA:85,0
|
||||
DA:87,0
|
||||
DA:88,0
|
||||
DA:93,0
|
||||
DA:94,0
|
||||
DA:95,0
|
||||
DA:97,0
|
||||
DA:101,0
|
||||
DA:102,0
|
||||
DA:104,0
|
||||
DA:105,0
|
||||
DA:106,0
|
||||
DA:107,0
|
||||
DA:108,0
|
||||
DA:115,0
|
||||
DA:116,0
|
||||
DA:119,0
|
||||
DA:120,0
|
||||
DA:122,0
|
||||
DA:123,0
|
||||
DA:129,0
|
||||
DA:130,0
|
||||
DA:131,0
|
||||
DA:133,0
|
||||
DA:140,0
|
||||
DA:141,0
|
||||
DA:144,0
|
||||
DA:145,0
|
||||
DA:146,0
|
||||
DA:152,0
|
||||
DA:153,0
|
||||
DA:154,0
|
||||
DA:156,0
|
||||
DA:163,0
|
||||
DA:164,0
|
||||
DA:169,0
|
||||
DA:170,0
|
||||
DA:171,0
|
||||
LF:80
|
||||
LH:0
|
||||
BRDA:16,0,0,0
|
||||
BRDA:23,1,0,0
|
||||
BRDA:23,1,1,0
|
||||
BRDA:24,2,0,0
|
||||
BRDA:24,2,1,0
|
||||
BRDA:73,3,0,0
|
||||
BRDA:73,3,1,0
|
||||
BRDA:73,3,2,0
|
||||
BRDA:73,3,3,0
|
||||
BRDA:73,3,4,0
|
||||
BRDA:94,4,0,0
|
||||
BRDA:104,5,0,0
|
||||
BRDA:104,6,0,0
|
||||
BRDA:104,6,1,0
|
||||
BRDA:115,7,0,0
|
||||
BRDA:130,8,0,0
|
||||
BRDA:130,8,1,0
|
||||
BRDA:133,9,0,0
|
||||
BRDA:133,9,1,0
|
||||
BRDA:140,10,0,0
|
||||
BRDA:153,11,0,0
|
||||
BRDA:153,11,1,0
|
||||
BRDA:156,12,0,0
|
||||
BRDA:156,12,1,0
|
||||
BRDA:163,13,0,0
|
||||
BRDA:169,14,0,0
|
||||
BRF:26
|
||||
BRH:0
|
||||
end_of_record
|
||||
|
||||
@@ -28,8 +28,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ajv": "^0.0.5",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-rate-limit": "^5.1.3",
|
||||
"@types/helmet": "^0.0.48",
|
||||
"@types/jest": "^28.1.8",
|
||||
"@types/node": "^20.17.10",
|
||||
"@types/ws": "^8.5.14",
|
||||
"jest": "^28.1.3",
|
||||
"semver": "^6.3.1",
|
||||
"ts-jest": "^28.0.8",
|
||||
|
||||
@@ -24,19 +24,25 @@ export const securityHeaders = helmet({
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:', 'https:'],
|
||||
connectSrc: ["'self'", process.env.HASS_HOST || ''],
|
||||
upgradeInsecureRequests: []
|
||||
upgradeInsecureRequests: true
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: true,
|
||||
crossOriginOpenerPolicy: true,
|
||||
crossOriginResourcePolicy: { policy: 'same-site' },
|
||||
dnsPrefetchControl: true,
|
||||
frameguard: { action: 'deny' },
|
||||
frameguard: {
|
||||
action: 'deny'
|
||||
},
|
||||
hidePoweredBy: true,
|
||||
hsts: true,
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
},
|
||||
ieNoOpen: true,
|
||||
noSniff: true,
|
||||
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
||||
referrerPolicy: { policy: 'no-referrer' },
|
||||
xssFilter: true
|
||||
});
|
||||
|
||||
@@ -172,8 +178,32 @@ export function errorHandler(err: Error, req: Request, res: Response, next: Next
|
||||
|
||||
// Export security middleware chain
|
||||
export const securityMiddleware = [
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:', 'https:'],
|
||||
connectSrc: ["'self'", process.env.HASS_HOST || ''],
|
||||
upgradeInsecureRequests: true
|
||||
}
|
||||
},
|
||||
dnsPrefetchControl: true,
|
||||
frameguard: {
|
||||
action: 'deny'
|
||||
},
|
||||
hidePoweredBy: true,
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
},
|
||||
noSniff: true,
|
||||
referrerPolicy: { policy: 'no-referrer' },
|
||||
xssFilter: true
|
||||
}),
|
||||
rateLimiter,
|
||||
securityHeaders,
|
||||
validateRequest,
|
||||
sanitizeInput,
|
||||
errorHandler
|
||||
|
||||
Reference in New Issue
Block a user