first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import * as os from 'os';
|
||||
|
||||
import { Facility, Severity, SyslogClient, Transport } from '../src';
|
||||
import { TLS_CERTIFICATE } from './setup';
|
||||
|
||||
describe('SyslogClient - Core', () => {
|
||||
describe('Constructor and defaults', () => {
|
||||
it('should set default options correctly', () => {
|
||||
const client = new SyslogClient();
|
||||
expect(client.target).toBe('127.0.0.1');
|
||||
expect(client.port).toBe(514);
|
||||
expect(client.syslogHostname).toBe(os.hostname());
|
||||
expect(client.tcpTimeout).toBe(10000);
|
||||
expect(client.transport).toBe(Transport.Udp);
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept target parameter', () => {
|
||||
const client = new SyslogClient('127.0.0.2');
|
||||
expect(client.target).toBe('127.0.0.2');
|
||||
expect(client.port).toBe(514);
|
||||
expect(client.syslogHostname).toBe(os.hostname());
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept custom hostname', () => {
|
||||
const client = new SyslogClient('127.0.0.2', {
|
||||
syslogHostname: 'test',
|
||||
});
|
||||
expect(client.target).toBe('127.0.0.2');
|
||||
expect(client.syslogHostname).toBe('test');
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept custom port and timeout', () => {
|
||||
const client = new SyslogClient('127.0.0.2', {
|
||||
syslogHostname: 'test',
|
||||
port: 5555,
|
||||
tcpTimeout: 50,
|
||||
});
|
||||
expect(client.port).toBe(5555);
|
||||
expect(client.tcpTimeout).toBe(50);
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept TCP transport option', () => {
|
||||
const client = new SyslogClient('127.0.0.2', {
|
||||
port: 5555,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
expect(client.transport).toBe(Transport.Tcp);
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept TLS transport with certificate', () => {
|
||||
const client = new SyslogClient('127.0.0.2', {
|
||||
port: 6514,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
expect(client.transport).toBe(Transport.Tls);
|
||||
expect(client.tlsCA).toBe(TLS_CERTIFICATE);
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept facility and severity options', () => {
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
facility: Facility.Mail,
|
||||
severity: Severity.Critical,
|
||||
});
|
||||
expect(client.facility).toBe(Facility.Mail);
|
||||
expect(client.severity).toBe(Severity.Critical);
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept RFC format option', () => {
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
rfc3164: false,
|
||||
});
|
||||
expect(client.rfc3164).toBe(false);
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
# Test Fixtures
|
||||
|
||||
This directory contains test certificates for TLS testing.
|
||||
|
||||
## Required Files
|
||||
|
||||
These files can be regenerated using the following commands:
|
||||
|
||||
- `key.pem` - Private key for test TLS server
|
||||
- `certificate.pem` - Valid certificate for test TLS server
|
||||
- `wrong.pem` - Invalid certificate for testing certificate validation
|
||||
|
||||
## Generate Test Certificates
|
||||
|
||||
You can generate self-signed test certificates using OpenSSL:
|
||||
|
||||
```bash
|
||||
# Generate private key
|
||||
openssl genrsa -out key.pem 2048
|
||||
|
||||
# Generate valid certificate
|
||||
openssl req -new -x509 -key key.pem -out certificate.pem -days 10000 \
|
||||
-subj "/C=US/ST=Test/L=Test/O=Test/CN=localhost"
|
||||
|
||||
# Generate wrong certificate (different key)
|
||||
openssl genrsa -out wrong-key.pem 2048
|
||||
openssl req -new -x509 -key wrong-key.pem -out wrong.pem -days 10000 \
|
||||
-subj "/C=US/ST=Wrong/L=Wrong/O=Wrong/CN=wrong"
|
||||
rm wrong-key.pem
|
||||
```
|
||||
|
||||
These certificates are only for testing and should never be used in production.
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfzCCAmegAwIBAgIUEo4pU1hgsKHlV01QIN3hhDaQ6ggwDQYJKoZIhvcNAQEL
|
||||
BQAwTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx
|
||||
DTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNjAxMDUyMDQ1
|
||||
NDhaGA8yMDUzMDUyMzIwNDU0OFowTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRl
|
||||
c3QxDTALBgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2Fs
|
||||
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzoP33jcgWYJ/mS
|
||||
A8NQm9gABgeOVHlc4lEqHsMo9gZy3GfmanI/XFpTr3Nxeb2AMqJ1B9EgXOtJLBDP
|
||||
J+Z+k3jN9Sv2ZRSClf6ZZUUPFKcjQ6TNYXG/Jgmjoku7k0I9cJluXE8VwlC9mzty
|
||||
iHbZGo/tRtU4LkbaedmL2eTctcBukX4g99uVMJVKot5l8PWxZ1seVZ6T4IiRcaRo
|
||||
NcQqL67xspm3KmlrVx/WzlM41BfGj2k+L4Bgenm/bIg1RPQ+MoETViVFGeKgKjkp
|
||||
kTOjCsZqeK2STCIj1+XI4tLMCxV0B95fw7bcFfEtbnL9XyEjyn+3A33625EJMoiq
|
||||
OX6bb9MCAwEAAaNTMFEwHQYDVR0OBBYEFEB7f6J6nW90lXS8i4gOR/6rya7qMB8G
|
||||
A1UdIwQYMBaAFEB7f6J6nW90lXS8i4gOR/6rya7qMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQELBQADggEBAIXoFrV9saNREtlDkMp5O66IO+UFhr1NcLYmXvhs
|
||||
n3H+OFpg/Uo+/p++vHgf5UKNTdx24pO/88FyPujIcq1mfemrAIgcd8t++aclzSlf
|
||||
G49bZvhYyxsE+qoyl7XWtmYLPydCpYKkL7krLwUVzSeC/f6f/GWEn0vvwWPht5ky
|
||||
dR/Uze7pg/Ux12gztSLPV77n9gBua6lj5HKhXYGoIFuZSw9n1tWqndT28OQ3VwI7
|
||||
ZSpFFJmDyevspC2F18NaTE5VmXv9gckeUmqx8nr0zOmWWREVnFt+d4NNvrNLEChj
|
||||
IJFfkzynUHYTe+1oQkv8Evr0GHU5ocHsqOQ4NqW5gx0XXRA=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDM6D9943IFmCf5
|
||||
kgPDUJvYAAYHjlR5XOJRKh7DKPYGctxn5mpyP1xaU69zcXm9gDKidQfRIFzrSSwQ
|
||||
zyfmfpN4zfUr9mUUgpX+mWVFDxSnI0OkzWFxvyYJo6JLu5NCPXCZblxPFcJQvZs7
|
||||
coh22RqP7UbVOC5G2nnZi9nk3LXAbpF+IPfblTCVSqLeZfD1sWdbHlWek+CIkXGk
|
||||
aDXEKi+u8bKZtyppa1cf1s5TONQXxo9pPi+AYHp5v2yINUT0PjKBE1YlRRnioCo5
|
||||
KZEzowrGanitkkwiI9flyOLSzAsVdAfeX8O23BXxLW5y/V8hI8p/twN9+tuRCTKI
|
||||
qjl+m2/TAgMBAAECggEARqkwe/EbOOKnpmC7+3aox2v6qFBkwRVO1j54LUTK0p55
|
||||
czr6Kju/VUbWkjDnknnK/ErvTyah5GFvWLyXPd0YbehYS7jEUrZVk17ClXYF4T0/
|
||||
7m0E1XzdWSWcqhEdTxJw6fgszPjr8XvxNCbi+FkV19wzOQQOsVBWBLc9hLa31ous
|
||||
CCMmpmJD+KaaN1D7VKDyLOtMQDPM7xft9VcWTU0qATN86HzvTxRF3+wWi1Z8ChV+
|
||||
miMfu4yb4Ip/tFSZfCJQqWupxdXYxw+p6HbcYRxSFa8ux4oZoQwvSVIi3pGKFXij
|
||||
/0oBhx2N1Y0T5LnuUZvne6CjwSyRSCmN68FnUKiogQKBgQD5UuHXcMZiDyZSskpj
|
||||
6rj//Hc3I9eRvB2CIvxVbDecRoCat3r9SZmZJ+DFxyH9Voy0ClrQnjSjPsXR5T89
|
||||
Ya+dFELl0zg7scHMw9VVSBsIjPrBhCnqPII1tfZj5ZzKI0oM1P8DwsoBOMotZIjl
|
||||
eHsUsd3Qw5sogbrwJ5d0cuDcaQKBgQDSZOPLwuEtAc7qyu1lSbifCMYwM4ISac3F
|
||||
2f116BV43/9L8eQv41XmVl1ELmwebahQdIbTtP5bNGDRHLRyIfbsSWxzBamQkKX9
|
||||
INzsiaeCENMTEHo4oSXX89DoZkr50iT62y9DKF5TkzNkA+CMvUuKfkh8YZAnLizA
|
||||
dhrM0NOS2wKBgQD3nJ/ctgzgEEmwMY994fxvhw3i/j62ZswrlZFjSQGFu+M3ROfp
|
||||
q3HXUGqEIbuY/Z7po6sDq0t9oPcX+QQwctbVOu0hkP81Edn2AbvaYa0vdcwH6rkx
|
||||
/3wV2axlFAH+IsQFMHhABPFX+02XfVQUCe6649b24X0z4nuEzN028mxtmQKBgC7q
|
||||
XHmVbUzx+EgXFDTf6ZFdDYS2F60vdrlF0OU36YHYwT8YI4AiO4dvfsCzcVyfq0a1
|
||||
lVMkKwv/dA9tTTeeJ0etX8eOXa9k8f6uE1WTpIy2X7sCk56JIL01G0KXfOSUXbaq
|
||||
pbFeERql2nHVA3+evneVjWhfARwbidMpAvTlJCi9AoGAQxFKi/dKzjZQFkikv9xU
|
||||
QpvtrfjZvYHU56HMDkKA4yIjBpONPWc6TAir5eOroKIKxAF0K+5s/bcQqZagShfZ
|
||||
WhZSdUid4J+gPCYL2v5F8symu9dxP54xRw1886uJUuqyb7tHhdFCOHRclD5wlLg3
|
||||
OrfEUwhYRn7d21UZoGDAvYU=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Generate valid certificate
|
||||
openssl req -new -x509 -key key.pem -out certificate.pem -days 10000 \
|
||||
-subj "/C=US/ST=Test/L=Test/O=Test/CN=localhost"
|
||||
|
||||
# Generate wrong certificate (different key)
|
||||
openssl genrsa -out wrong-key.pem 2048
|
||||
openssl req -new -x509 -key wrong-key.pem -out wrong.pem -days 10000 \
|
||||
-subj "/C=US/ST=Wrong/L=Wrong/O=Wrong/CN=wrong"
|
||||
rm wrong-key.pem
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfTCCAmWgAwIBAgIUfC52wMYMzcoL9c/GY3Ks0beSH2kwDQYJKoZIhvcNAQEL
|
||||
BQAwTTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVdyb25nMQ4wDAYDVQQHDAVXcm9u
|
||||
ZzEOMAwGA1UECgwFV3JvbmcxDjAMBgNVBAMMBXdyb25nMCAXDTI2MDEwNTIwNDU0
|
||||
OFoYDzIwNTMwNTIzMjA0NTQ4WjBNMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFV3Jv
|
||||
bmcxDjAMBgNVBAcMBVdyb25nMQ4wDAYDVQQKDAVXcm9uZzEOMAwGA1UEAwwFd3Jv
|
||||
bmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCj1725lupt3pTvMf2O
|
||||
o931s2l4ObA5qTC6ihlWJX+A7R4RkHrQTlwahEwIgMNSzNURtXOwu3ePyqZE3Luk
|
||||
pVWGMFdgedNuU+4zNUXXamIyIJfsMuEmw6xrWLpdWqawo0G50Y2RxB4MN6/127PL
|
||||
EqogXC+0PHdgBUWtso/sJcGRrdQvGpmH9ZXavVNhER4G3q6f/Tcp1eu0xry1UwGV
|
||||
WN/DghkE5R/9mLm830Lr7gLpEQ6JKUUTnPKaZQW9m9G8gjr6oEXR9QaTcTariHgx
|
||||
HT0wkGRKO54CC3RdBxqwucdkvgUI7OkaGl8tS46QMI4DwQHcUFLkqx29wXHY/Zhu
|
||||
mHxDAgMBAAGjUzBRMB0GA1UdDgQWBBT8E+hqfJTcai2AlqT27R4KL/nGNTAfBgNV
|
||||
HSMEGDAWgBT8E+hqfJTcai2AlqT27R4KL/nGNTAPBgNVHRMBAf8EBTADAQH/MA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQCf38uZYe6+cW7/juw2wCoLObSBoomRuJicwpRbDJpo
|
||||
kzKzlBm4KbAwrz0+2kugZCrqrY1pLZPozQiqqf7UOImNcBi/wCbNk6iksdD9WRpv
|
||||
tooKb2UHY9Z+1VJ7FqBhEekUJnxAoOlmOnSwPW3qDQNaP+niWZEZjgK+m+3/ZI5d
|
||||
oW8lU1UGYJs/pZrZDwPUGHRnPj7Sa6q0S5OCVFx4BN+YMKN9ynvVG0z0pkO3QpA3
|
||||
wOYu/WxINCuFb6Iu469Ov/FoSfnqZRcqZ+PlMDWHr0NCj0DNBP/eGq8VWy6VRabQ
|
||||
SyRRjByPFe8fK1PAbrwIh8w6QIWz28OQZjAz0TsHnK7D
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,285 @@
|
||||
import { SyslogClient, Transport } from '../src';
|
||||
import {
|
||||
awaitUdpMsg,
|
||||
constructRfc5424Regex,
|
||||
constructSyslogRegex,
|
||||
startTestServers,
|
||||
stopTestServers,
|
||||
SYSLOG_UDP_PORT,
|
||||
} from './setup';
|
||||
|
||||
beforeAll(async () => {
|
||||
await startTestServers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
stopTestServers();
|
||||
});
|
||||
|
||||
describe('SyslogClient - Message Formats', () => {
|
||||
describe('RFC 3164 (BSD syslog)', () => {
|
||||
it('should send RFC 3164 format by default', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('RFC 3164 test');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'RFC 3164 test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should send RFC 3164 format when explicitly enabled', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
rfc3164: true,
|
||||
});
|
||||
|
||||
await client.log('RFC 3164 explicit');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'RFC 3164 explicit'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should send back-dated RFC 3164 messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const backdate = new Date(2017, 2, 1);
|
||||
await client.log('Back-dated test', { timestamp: backdate });
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Back-dated test', backdate));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should handle messages with custom options', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Custom options', {
|
||||
rfc3164: true,
|
||||
msgid: '12345', // msgid is ignored in RFC 3164
|
||||
});
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Custom options'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RFC 5424 (modern syslog)', () => {
|
||||
it('should send RFC 5424 format when enabled', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
rfc3164: false,
|
||||
});
|
||||
|
||||
await client.log('RFC 5424 test');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructRfc5424Regex(134, hostname, 'RFC 5424 test', '-'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should use default msgid when not provided', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Default msgid', { rfc3164: false });
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructRfc5424Regex(134, hostname, 'Default msgid', '-'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should use custom msgid when provided', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Custom msgid', {
|
||||
rfc3164: false,
|
||||
msgid: '98765',
|
||||
});
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructRfc5424Regex(134, hostname, 'Custom msgid', '98765'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should accept msgid up to 32 characters (RFC 5424 limit)', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const maxLengthMsgid = '550e8400e29b41d4a716446655440000'; // 32 chars (UUID without hyphens)
|
||||
await client.log('Max length msgid', {
|
||||
rfc3164: false,
|
||||
msgid: maxLengthMsgid,
|
||||
});
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructRfc5424Regex(134, hostname, 'Max length msgid', maxLengthMsgid));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should reject msgid longer than 32 characters (RFC 5424 limit)', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const tooLongMsgid = '550e8400-e29b-41d4-a716-446655440000'; // 36 chars (UUID with hyphens)
|
||||
await expect(
|
||||
client.log('Too long msgid', {
|
||||
rfc3164: false,
|
||||
msgid: tooLongMsgid,
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should send back-dated RFC 5424 messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const backdate = new Date(2017, 2, 1);
|
||||
await client.log('Back-dated RFC 5424', {
|
||||
rfc3164: false,
|
||||
msgid: '98765',
|
||||
timestamp: backdate,
|
||||
});
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(
|
||||
constructRfc5424Regex(134, hostname, 'Back-dated RFC 5424', '98765', backdate),
|
||||
);
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should include process ID in RFC 5424 messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
rfc3164: false,
|
||||
});
|
||||
|
||||
await client.log('Process ID test');
|
||||
const msg = await awaitUdpMsg();
|
||||
|
||||
// Check that message contains a process ID (numeric)
|
||||
expect(msg).toMatch(/\d+ - - Process ID test/);
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Format switching', () => {
|
||||
it('should allow switching format per message', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
rfc3164: true, // Default to RFC 3164
|
||||
});
|
||||
|
||||
// Send RFC 3164 message (default)
|
||||
await client.log('Message 1');
|
||||
const msg1 = await awaitUdpMsg();
|
||||
expect(msg1).toMatch(constructSyslogRegex(134, hostname, 'Message 1'));
|
||||
|
||||
// Send RFC 5424 message (override)
|
||||
await client.log('Message 2', { rfc3164: false });
|
||||
const msg2 = await awaitUdpMsg();
|
||||
expect(msg2).toMatch(constructRfc5424Regex(134, hostname, 'Message 2', '-'));
|
||||
|
||||
// Send RFC 3164 message again (back to default)
|
||||
await client.log('Message 3');
|
||||
const msg3 = await awaitUdpMsg();
|
||||
expect(msg3).toMatch(constructSyslogRegex(134, hostname, 'Message 3'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Message content', () => {
|
||||
it('should preserve newlines in message', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const messageWithNewline = 'Message with\nnewline';
|
||||
await client.log(messageWithNewline);
|
||||
const msg = await awaitUdpMsg();
|
||||
|
||||
expect(msg).toContain(messageWithNewline);
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should handle empty messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('');
|
||||
const msg = await awaitUdpMsg();
|
||||
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, ''));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,221 @@
|
||||
import * as dgram from 'dgram';
|
||||
import * as fs from 'fs';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
import * as readline from 'readline';
|
||||
import * as tls from 'tls';
|
||||
|
||||
// Test ports - dynamically assigned by getAvailablePort
|
||||
export let SYSLOG_UDP_PORT = 0;
|
||||
export let SYSLOG_TCP_PORT = 0;
|
||||
export let SYSLOG_TLS_PORT = 0;
|
||||
|
||||
/**
|
||||
* Get an available port by creating and immediately closing a server.
|
||||
*/
|
||||
const getAvailablePort = async (): Promise<number> =>
|
||||
await new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
server.unref();
|
||||
server.on('error', reject);
|
||||
server.listen(0, () => {
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
server.close();
|
||||
return reject(new Error('Unable to get port'));
|
||||
}
|
||||
const { port } = address;
|
||||
server.close(() => resolve(port));
|
||||
});
|
||||
});
|
||||
|
||||
// Test certificates
|
||||
export const TLS_PRIVATE_KEY = fs.readFileSync(path.join(__dirname, 'fixtures', 'key.pem'), 'utf8');
|
||||
export const TLS_CERTIFICATE = fs.readFileSync(
|
||||
path.join(__dirname, 'fixtures', 'certificate.pem'),
|
||||
'utf8',
|
||||
);
|
||||
export const WRONG_CERTIFICATE = fs.readFileSync(
|
||||
path.join(__dirname, 'fixtures', 'wrong.pem'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// Message queues
|
||||
const queuedUdpMessages: string[] = [];
|
||||
const pendingUdpResolvers: Array<(msg: string) => void> = [];
|
||||
const queuedTcpMessages: string[] = [];
|
||||
const pendingTcpResolvers: Array<(msg: string) => void> = [];
|
||||
const queuedTlsMessages: string[] = [];
|
||||
const pendingTlsResolvers: Array<(msg: string) => void> = [];
|
||||
|
||||
// Test servers
|
||||
let udpServer: dgram.Socket | null = null;
|
||||
let tcpServer: net.Server | null = null;
|
||||
let tlsServer: tls.Server | null = null;
|
||||
|
||||
/**
|
||||
* Helper to await syslog messages from UDP server.
|
||||
*/
|
||||
export const awaitUdpMsg = async (): Promise<string> =>
|
||||
await new Promise((resolve) => {
|
||||
const queued = queuedUdpMessages.shift();
|
||||
if (queued) return resolve(queued);
|
||||
pendingUdpResolvers.push(resolve);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to await syslog messages from TCP server.
|
||||
*/
|
||||
export const awaitTcpMsg = async (): Promise<string> =>
|
||||
await new Promise((resolve) => {
|
||||
const queued = queuedTcpMessages.shift();
|
||||
if (queued) return resolve(queued);
|
||||
pendingTcpResolvers.push(resolve);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to await syslog messages from TLS server.
|
||||
*/
|
||||
export const awaitTlsMsg = async (): Promise<string> =>
|
||||
await new Promise((resolve) => {
|
||||
const queued = queuedTlsMessages.shift();
|
||||
if (queued) return resolve(queued);
|
||||
pendingTlsResolvers.push(resolve);
|
||||
});
|
||||
|
||||
/**
|
||||
* Escape special regex characters.
|
||||
*/
|
||||
const escapeRegExp = (text: string): string => text.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
||||
|
||||
/**
|
||||
* Construct regex to match RFC 3164 syslog message.
|
||||
*/
|
||||
export const constructSyslogRegex = (
|
||||
pri: number,
|
||||
hostname: string,
|
||||
msg: string,
|
||||
timestamp?: Date,
|
||||
): RegExp => {
|
||||
let patDate: string;
|
||||
if (!timestamp) {
|
||||
// RFC-3164 date format: "MMM dd HH:mm:ss"
|
||||
// Month is 3 letters, day is space-padded, time is in 24-hour format
|
||||
patDate = '[A-Z][a-z]{2}\\s{1,2}\\d{1,2}\\s\\d{2}:\\d{2}:\\d{2}';
|
||||
} else {
|
||||
const elements = timestamp.toString().split(/\s+/);
|
||||
const month = elements[1];
|
||||
let day = elements[2];
|
||||
const time = elements[4];
|
||||
|
||||
// Ensure day is space-padded for single digits as per RFC-3164
|
||||
if (day.length === 2 && day[0] === '0') {
|
||||
day = ' ' + day.substring(1);
|
||||
}
|
||||
|
||||
patDate = escapeRegExp(`${month} ${day} ${time}`);
|
||||
}
|
||||
|
||||
// RFC-3164 format: <PRI>TIMESTAMP HOSTNAME MSG
|
||||
// - PRI is enclosed in angle brackets
|
||||
// - Single space between timestamp and hostname
|
||||
// - Single space between hostname and message
|
||||
// - Optional newline at the end
|
||||
return new RegExp(
|
||||
`^<${escapeRegExp(String(pri))}>${patDate}\\s${escapeRegExp(hostname)}\\s${escapeRegExp(msg)}\\n?$`,
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Construct regex to match RFC 5424 syslog message.
|
||||
*/
|
||||
export const constructRfc5424Regex = (
|
||||
pri: number,
|
||||
hostname: string,
|
||||
msg: string,
|
||||
msgid: string | number,
|
||||
timestamp?: Date,
|
||||
): RegExp => {
|
||||
const patDate = !timestamp
|
||||
? '\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z'
|
||||
: escapeRegExp(timestamp.toISOString());
|
||||
|
||||
return new RegExp(
|
||||
`^<${escapeRegExp(String(pri))}>\\d+ ${patDate} ${escapeRegExp(hostname)} \\S{1,48} \\d+ ${escapeRegExp(String(msgid))} - ${escapeRegExp(msg)}\\n?$`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start test syslog servers (UDP, TCP, TLS).
|
||||
*/
|
||||
export const startTestServers = async (): Promise<void> => {
|
||||
// Get available ports
|
||||
SYSLOG_UDP_PORT = await getAvailablePort();
|
||||
SYSLOG_TCP_PORT = await getAvailablePort();
|
||||
SYSLOG_TLS_PORT = await getAvailablePort();
|
||||
|
||||
// Setup UDP server
|
||||
await new Promise<void>((resolve) => {
|
||||
udpServer = dgram.createSocket('udp4');
|
||||
udpServer.on('message', (msg) => {
|
||||
const resolver = pendingUdpResolvers.shift();
|
||||
if (resolver) return resolver(msg.toString());
|
||||
queuedUdpMessages.push(msg.toString());
|
||||
});
|
||||
udpServer.on('listening', () => {
|
||||
console.log(`Started UDP syslog server on port ${SYSLOG_UDP_PORT}`);
|
||||
resolve();
|
||||
});
|
||||
udpServer.bind(SYSLOG_UDP_PORT);
|
||||
});
|
||||
|
||||
// Setup TCP server
|
||||
await new Promise<void>((resolve) => {
|
||||
tcpServer = net.createServer((socket) => {
|
||||
const lines = readline.createInterface({ input: socket, output: socket });
|
||||
lines.on('line', (line) => {
|
||||
const resolver = pendingTcpResolvers.shift();
|
||||
if (resolver) return resolver(line);
|
||||
queuedTcpMessages.push(line);
|
||||
});
|
||||
});
|
||||
tcpServer.listen(SYSLOG_TCP_PORT, () => {
|
||||
console.log(`Started TCP syslog server on port ${SYSLOG_TCP_PORT}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Setup TLS server
|
||||
await new Promise<void>((resolve) => {
|
||||
tlsServer = tls.createServer(
|
||||
{
|
||||
key: TLS_PRIVATE_KEY,
|
||||
cert: TLS_CERTIFICATE,
|
||||
secureProtocol: 'TLSv1_2_method',
|
||||
},
|
||||
(socket) => {
|
||||
const lines = readline.createInterface({ input: socket, output: socket });
|
||||
lines.on('line', (line) => {
|
||||
const resolver = pendingTlsResolvers.shift();
|
||||
if (resolver) return resolver(line);
|
||||
queuedTlsMessages.push(line);
|
||||
});
|
||||
},
|
||||
);
|
||||
tlsServer.listen(SYSLOG_TLS_PORT, () => {
|
||||
console.log(`Started TLS syslog server on port ${SYSLOG_TLS_PORT}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop test syslog servers.
|
||||
*/
|
||||
export const stopTestServers = (): void => {
|
||||
udpServer?.close();
|
||||
tcpServer?.close();
|
||||
tlsServer?.close();
|
||||
udpServer = null;
|
||||
tcpServer = null;
|
||||
tlsServer = null;
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
import { Facility, Severity, SyslogClient, Transport } from '../src';
|
||||
import {
|
||||
awaitTcpMsg,
|
||||
constructSyslogRegex,
|
||||
startTestServers,
|
||||
stopTestServers,
|
||||
SYSLOG_TCP_PORT,
|
||||
} from './setup';
|
||||
|
||||
beforeAll(async () => {
|
||||
await startTestServers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
stopTestServers();
|
||||
});
|
||||
|
||||
describe('SyslogClient - TCP Transport', () => {
|
||||
describe('Basic functionality', () => {
|
||||
it('should connect and send log messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
await client.log('This is a test');
|
||||
const msg1 = await awaitTcpMsg();
|
||||
expect(msg1).toMatch(constructSyslogRegex(134, hostname, 'This is a test'));
|
||||
|
||||
await client.log('This is a second test');
|
||||
const msg2 = await awaitTcpMsg();
|
||||
expect(msg2).toMatch(constructSyslogRegex(134, hostname, 'This is a second test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with promise API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
await client.log('Promise test');
|
||||
const msg = await awaitTcpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Promise test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with callback API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
const callbackPromise = new Promise<void>((resolve) => {
|
||||
void client.log('Callback test', (error) => {
|
||||
expect(error).toBeUndefined();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const msg = await awaitTcpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Callback test'));
|
||||
await callbackPromise;
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Transport reuse', () => {
|
||||
it('should reuse TCP transport for multiple messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
await client.log('Transport reuse test');
|
||||
await awaitTcpMsg();
|
||||
const transport1 = client['transport_'];
|
||||
expect(transport1).toBeDefined();
|
||||
|
||||
await client.log('Transport reuse test 2');
|
||||
await awaitTcpMsg();
|
||||
const transport2 = client['transport_'];
|
||||
expect(transport2).toBe(transport1);
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Close and reconnect', () => {
|
||||
it('should emit close event', async () => {
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
|
||||
await client.log('Test');
|
||||
await awaitTcpMsg();
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
expect(client['transport_']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reconnect after close', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
await client.log('Before close');
|
||||
await awaitTcpMsg();
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
await client.log('After reconnect');
|
||||
const msg = await awaitTcpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'After reconnect'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Log options', () => {
|
||||
it('should accept options with callback', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
const callbackPromise = new Promise<void>((resolve) => {
|
||||
void client.log(
|
||||
'With options',
|
||||
{
|
||||
facility: Facility.System,
|
||||
severity: Severity.Notice,
|
||||
},
|
||||
(error) => {
|
||||
expect(error).toBeUndefined();
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const msg = await awaitTcpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(29, hostname, 'With options'));
|
||||
await callbackPromise;
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should calculate correct PRI value', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
await client.log('Test', {
|
||||
facility: Facility.Local0,
|
||||
severity: Severity.Emergency,
|
||||
});
|
||||
|
||||
const msg = await awaitTcpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(128, hostname, 'Test'));
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
it('should handle connection timeout', async () => {
|
||||
const client = new SyslogClient('203.0.113.1', {
|
||||
port: SYSLOG_TCP_PORT,
|
||||
tcpTimeout: 500,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
|
||||
const errorPromise = new Promise<Error>((resolve) => {
|
||||
client.on('error', resolve);
|
||||
});
|
||||
|
||||
await expect(client.log("Shouldn't work")).rejects.toThrow();
|
||||
const error = await errorPromise;
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('should handle invalid port', () => {
|
||||
expect(() => {
|
||||
new SyslogClient('127.0.0.1', {
|
||||
port: 502342323,
|
||||
tcpTimeout: 2000,
|
||||
transport: Transport.Tcp,
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { ConnectionError, SyslogClient, Transport } from '../src';
|
||||
import {
|
||||
awaitTlsMsg,
|
||||
constructSyslogRegex,
|
||||
startTestServers,
|
||||
stopTestServers,
|
||||
SYSLOG_TLS_PORT,
|
||||
TLS_CERTIFICATE,
|
||||
WRONG_CERTIFICATE,
|
||||
} from './setup';
|
||||
|
||||
beforeAll(async () => {
|
||||
await startTestServers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
stopTestServers();
|
||||
});
|
||||
|
||||
describe('SyslogClient - TLS Transport', () => {
|
||||
describe('Basic functionality', () => {
|
||||
it('should connect and send log messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
await client.log('This is a test');
|
||||
const msg1 = await awaitTlsMsg();
|
||||
expect(msg1).toMatch(constructSyslogRegex(134, hostname, 'This is a test'));
|
||||
|
||||
await client.log('This is a second test');
|
||||
const msg2 = await awaitTlsMsg();
|
||||
expect(msg2).toMatch(constructSyslogRegex(134, hostname, 'This is a second test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with promise API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
await client.log('Promise test');
|
||||
const msg = await awaitTlsMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Promise test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with callback API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
const callbackPromise = new Promise<void>((resolve) => {
|
||||
void client.log('Callback test', (error) => {
|
||||
expect(error).toBeUndefined();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const msg = await awaitTlsMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Callback test'));
|
||||
await callbackPromise;
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Transport reuse', () => {
|
||||
it('should reuse TLS transport for multiple messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
await client.log('Transport reuse test');
|
||||
await awaitTlsMsg();
|
||||
const transport1 = client['transport_'];
|
||||
expect(transport1).toBeDefined();
|
||||
|
||||
await client.log('Transport reuse test 2');
|
||||
await awaitTlsMsg();
|
||||
const transport2 = client['transport_'];
|
||||
expect(transport2).toBe(transport1);
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Close and reconnect', () => {
|
||||
it('should emit close event', async () => {
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
|
||||
await client.log('Test');
|
||||
await awaitTlsMsg();
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
expect(client['transport_']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reconnect after close', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
await client.log('Before close');
|
||||
await awaitTlsMsg();
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
await client.log('After reconnect');
|
||||
const msg = await awaitTlsMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'After reconnect'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Certificate validation', () => {
|
||||
it('should reject invalid certificate', async () => {
|
||||
const client = new SyslogClient('localhost', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: WRONG_CERTIFICATE,
|
||||
});
|
||||
|
||||
const errorPromise = new Promise<Error>((resolve) => {
|
||||
client.on('error', resolve);
|
||||
});
|
||||
|
||||
await expect(client.log('Should error')).rejects.toBeInstanceOf(ConnectionError);
|
||||
|
||||
const error: NodeJS.ErrnoException = await errorPromise;
|
||||
expect(error.code).toBe('DEPTH_ZERO_SELF_SIGNED_CERT');
|
||||
});
|
||||
|
||||
it('should handle connection timeout', async () => {
|
||||
const client = new SyslogClient('203.0.113.1', {
|
||||
port: SYSLOG_TLS_PORT,
|
||||
tcpTimeout: 500,
|
||||
transport: Transport.Tls,
|
||||
tlsCA: TLS_CERTIFICATE,
|
||||
});
|
||||
|
||||
const errorPromise = new Promise<Error>((resolve) => {
|
||||
client.on('error', resolve);
|
||||
});
|
||||
|
||||
await expect(client.log("Shouldn't work")).rejects.toThrow();
|
||||
const error = await errorPromise;
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
import { SyslogClient, Transport } from '../src';
|
||||
import {
|
||||
awaitUdpMsg,
|
||||
constructSyslogRegex,
|
||||
startTestServers,
|
||||
stopTestServers,
|
||||
SYSLOG_UDP_PORT,
|
||||
} from './setup';
|
||||
|
||||
beforeAll(async () => {
|
||||
await startTestServers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
stopTestServers();
|
||||
});
|
||||
|
||||
describe('SyslogClient - UDP Transport', () => {
|
||||
describe('Basic functionality', () => {
|
||||
it('should connect and send log messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('This is a test');
|
||||
const msg1 = await awaitUdpMsg();
|
||||
expect(msg1).toMatch(constructSyslogRegex(134, hostname, 'This is a test'));
|
||||
|
||||
await client.log('This is a second test');
|
||||
const msg2 = await awaitUdpMsg();
|
||||
expect(msg2).toMatch(constructSyslogRegex(134, hostname, 'This is a second test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with promise API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Promise test');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Promise test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should work with callback API', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const callbackPromise = new Promise<void>((resolve) => {
|
||||
void client.log('Callback test', (error) => {
|
||||
expect(error).toBeUndefined();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Callback test'));
|
||||
await callbackPromise;
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Transport reuse', () => {
|
||||
it('should reuse UDP transport for multiple messages', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Transport reuse test');
|
||||
await awaitUdpMsg();
|
||||
const transport1 = client['transport_'];
|
||||
expect(transport1).toBeDefined();
|
||||
|
||||
await client.log('Transport reuse test 2');
|
||||
await awaitUdpMsg();
|
||||
const transport2 = client['transport_'];
|
||||
expect(transport2).toBe(transport1);
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('UDP bind address', () => {
|
||||
it('should bind to specific network address', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
udpBindAddress: '127.0.0.1',
|
||||
});
|
||||
|
||||
await client.log('Bind test');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'Bind test'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
|
||||
it('should handle invalid bind address', () => {
|
||||
expect(() => {
|
||||
new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
transport: Transport.Udp,
|
||||
udpBindAddress: '500.500.500.500',
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Close and reconnect', () => {
|
||||
it('should emit close event', async () => {
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
|
||||
await client.log('Test');
|
||||
await awaitUdpMsg();
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
expect(client['transport_']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reconnect after close', async () => {
|
||||
const hostname = 'testhostname';
|
||||
const client = new SyslogClient('127.0.0.1', {
|
||||
port: SYSLOG_UDP_PORT,
|
||||
syslogHostname: hostname,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
|
||||
await client.log('Before close');
|
||||
await awaitUdpMsg();
|
||||
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
client.once('close', resolve);
|
||||
});
|
||||
client.close();
|
||||
await closePromise;
|
||||
|
||||
await client.log('After reconnect');
|
||||
const msg = await awaitUdpMsg();
|
||||
expect(msg).toMatch(constructSyslogRegex(134, hostname, 'After reconnect'));
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
it('should handle invalid port', () => {
|
||||
expect(() => {
|
||||
new SyslogClient('127.0.0.1', {
|
||||
port: 12378726362,
|
||||
transport: Transport.Udp,
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user