feat(smartradius): Implement full RADIUS server and client with RFC 2865/2866 compliance, including packet handling, authenticators, attributes, secrets manager, client APIs, and comprehensive tests and documentation
This commit is contained in:
167
test/client/test.client.ts
Normal file
167
test/client/test.client.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { RadiusClient } from '../../ts_client/index.js';
|
||||
import {
|
||||
RadiusServer,
|
||||
ERadiusCode,
|
||||
EAcctStatusType,
|
||||
} from '../../ts_server/index.js';
|
||||
|
||||
// Test server and client instances
|
||||
let server: RadiusServer;
|
||||
let client: RadiusClient;
|
||||
const TEST_SECRET = 'testing123';
|
||||
const TEST_AUTH_PORT = 18120;
|
||||
const TEST_ACCT_PORT = 18130;
|
||||
|
||||
tap.test('setup - create server and client', async () => {
|
||||
// Create server with authentication handler
|
||||
server = new RadiusServer({
|
||||
authPort: TEST_AUTH_PORT,
|
||||
acctPort: TEST_ACCT_PORT,
|
||||
defaultSecret: TEST_SECRET,
|
||||
authenticationHandler: async (request) => {
|
||||
// Simple handler: accept user/password, reject others
|
||||
if (request.username === 'testuser' && request.password === 'testpass') {
|
||||
return {
|
||||
code: ERadiusCode.AccessAccept,
|
||||
replyMessage: 'Welcome!',
|
||||
sessionTimeout: 3600,
|
||||
};
|
||||
}
|
||||
|
||||
// Test CHAP
|
||||
if (request.username === 'chapuser' && request.chapPassword && request.chapChallenge) {
|
||||
const { RadiusAuthenticator } = await import('../../ts_server/index.js');
|
||||
const isValid = RadiusAuthenticator.verifyChapResponse(
|
||||
request.chapPassword,
|
||||
request.chapChallenge,
|
||||
'chappass'
|
||||
);
|
||||
if (isValid) {
|
||||
return {
|
||||
code: ERadiusCode.AccessAccept,
|
||||
replyMessage: 'CHAP OK',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: ERadiusCode.AccessReject,
|
||||
replyMessage: 'Invalid credentials',
|
||||
};
|
||||
},
|
||||
accountingHandler: async (request) => {
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
|
||||
// Create client
|
||||
client = new RadiusClient({
|
||||
host: '127.0.0.1',
|
||||
authPort: TEST_AUTH_PORT,
|
||||
acctPort: TEST_ACCT_PORT,
|
||||
secret: TEST_SECRET,
|
||||
timeout: 2000,
|
||||
retries: 2,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
});
|
||||
|
||||
tap.test('should authenticate with PAP - valid credentials', async () => {
|
||||
const response = await client.authenticatePap('testuser', 'testpass');
|
||||
|
||||
expect(response.accepted).toBeTruthy();
|
||||
expect(response.rejected).toBeFalsy();
|
||||
expect(response.code).toEqual(ERadiusCode.AccessAccept);
|
||||
expect(response.replyMessage).toEqual('Welcome!');
|
||||
expect(response.sessionTimeout).toEqual(3600);
|
||||
});
|
||||
|
||||
tap.test('should reject PAP - invalid credentials', async () => {
|
||||
const response = await client.authenticatePap('testuser', 'wrongpass');
|
||||
|
||||
expect(response.accepted).toBeFalsy();
|
||||
expect(response.rejected).toBeTruthy();
|
||||
expect(response.code).toEqual(ERadiusCode.AccessReject);
|
||||
expect(response.replyMessage).toEqual('Invalid credentials');
|
||||
});
|
||||
|
||||
tap.test('should authenticate with CHAP - valid credentials', async () => {
|
||||
const response = await client.authenticateChap('chapuser', 'chappass');
|
||||
|
||||
expect(response.accepted).toBeTruthy();
|
||||
expect(response.rejected).toBeFalsy();
|
||||
expect(response.code).toEqual(ERadiusCode.AccessAccept);
|
||||
});
|
||||
|
||||
tap.test('should reject CHAP - invalid credentials', async () => {
|
||||
const response = await client.authenticateChap('chapuser', 'wrongpass');
|
||||
|
||||
expect(response.accepted).toBeFalsy();
|
||||
expect(response.rejected).toBeTruthy();
|
||||
expect(response.code).toEqual(ERadiusCode.AccessReject);
|
||||
});
|
||||
|
||||
tap.test('should send accounting start', async () => {
|
||||
const response = await client.accountingStart('session-001', 'testuser');
|
||||
|
||||
expect(response.success).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should send accounting update', async () => {
|
||||
const response = await client.accountingUpdate('session-001', {
|
||||
username: 'testuser',
|
||||
sessionTime: 1800,
|
||||
inputOctets: 512000,
|
||||
outputOctets: 1024000,
|
||||
});
|
||||
|
||||
expect(response.success).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should send accounting stop', async () => {
|
||||
const response = await client.accountingStop('session-001', {
|
||||
username: 'testuser',
|
||||
sessionTime: 3600,
|
||||
inputOctets: 1024000,
|
||||
outputOctets: 2048000,
|
||||
terminateCause: 1, // User-Request
|
||||
});
|
||||
|
||||
expect(response.success).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should send custom accounting request', async () => {
|
||||
const response = await client.accounting({
|
||||
statusType: EAcctStatusType.Start,
|
||||
sessionId: 'custom-session',
|
||||
username: 'customuser',
|
||||
nasPort: 5060,
|
||||
calledStationId: 'called-001',
|
||||
callingStationId: 'calling-002',
|
||||
});
|
||||
|
||||
expect(response.success).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should handle authentication with custom attributes', async () => {
|
||||
const response = await client.authenticate({
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
nasPort: 1,
|
||||
calledStationId: 'test-station',
|
||||
callingStationId: '192.168.1.100',
|
||||
});
|
||||
|
||||
expect(response.accepted).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('teardown - cleanup server and client', async () => {
|
||||
await client.disconnect();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user