247 lines
9.4 KiB
TypeScript
247 lines
9.4 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import {
|
|
RadiusPacket,
|
|
RadiusAuthenticator,
|
|
ERadiusCode,
|
|
ERadiusAttributeType,
|
|
EAcctStatusType,
|
|
EAcctTerminateCause,
|
|
EAcctAuthentic,
|
|
} from '../../ts_server/index.js';
|
|
|
|
tap.test('should create Accounting-Request packet with Start status', async () => {
|
|
const identifier = 1;
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingRequest(identifier, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.Start },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-001' },
|
|
{ type: ERadiusAttributeType.UserName, value: 'testuser' },
|
|
{ type: ERadiusAttributeType.NasIpAddress, value: '192.168.1.1' },
|
|
]);
|
|
|
|
const decoded = RadiusPacket.decode(packet);
|
|
expect(decoded.code).toEqual(ERadiusCode.AccountingRequest);
|
|
expect(decoded.identifier).toEqual(identifier);
|
|
});
|
|
|
|
tap.test('should create Accounting-Request packet with Stop status', async () => {
|
|
const identifier = 2;
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingRequest(identifier, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.Stop },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-001' },
|
|
{ type: ERadiusAttributeType.UserName, value: 'testuser' },
|
|
{ type: ERadiusAttributeType.AcctSessionTime, value: 3600 }, // 1 hour
|
|
{ type: ERadiusAttributeType.AcctInputOctets, value: 1024000 },
|
|
{ type: ERadiusAttributeType.AcctOutputOctets, value: 2048000 },
|
|
{ type: ERadiusAttributeType.AcctInputPackets, value: 1000 },
|
|
{ type: ERadiusAttributeType.AcctOutputPackets, value: 2000 },
|
|
{ type: ERadiusAttributeType.AcctTerminateCause, value: EAcctTerminateCause.UserRequest },
|
|
]);
|
|
|
|
const decoded = RadiusPacket.decodeAndParse(packet);
|
|
expect(decoded.code).toEqual(ERadiusCode.AccountingRequest);
|
|
|
|
const statusType = decoded.parsedAttributes.find(
|
|
(a) => a.type === ERadiusAttributeType.AcctStatusType
|
|
);
|
|
expect(statusType?.value).toEqual(EAcctStatusType.Stop);
|
|
});
|
|
|
|
tap.test('should create Accounting-Request packet with Interim-Update status', async () => {
|
|
const identifier = 3;
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingRequest(identifier, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.InterimUpdate },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-001' },
|
|
{ type: ERadiusAttributeType.UserName, value: 'testuser' },
|
|
{ type: ERadiusAttributeType.AcctSessionTime, value: 1800 }, // 30 min
|
|
{ type: ERadiusAttributeType.AcctInputOctets, value: 512000 },
|
|
{ type: ERadiusAttributeType.AcctOutputOctets, value: 1024000 },
|
|
]);
|
|
|
|
const decoded = RadiusPacket.decodeAndParse(packet);
|
|
expect(decoded.code).toEqual(ERadiusCode.AccountingRequest);
|
|
|
|
const statusType = decoded.parsedAttributes.find(
|
|
(a) => a.type === ERadiusAttributeType.AcctStatusType
|
|
);
|
|
expect(statusType?.value).toEqual(EAcctStatusType.InterimUpdate);
|
|
});
|
|
|
|
tap.test('should verify Accounting-Request authenticator', async () => {
|
|
const identifier = 1;
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingRequest(identifier, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.Start },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-001' },
|
|
]);
|
|
|
|
// Verify the authenticator
|
|
const isValid = RadiusAuthenticator.verifyAccountingRequestAuthenticator(packet, secret);
|
|
expect(isValid).toBeTruthy();
|
|
|
|
// Should fail with wrong secret
|
|
const isInvalid = RadiusAuthenticator.verifyAccountingRequestAuthenticator(packet, 'wrongsecret');
|
|
expect(isInvalid).toBeFalsy();
|
|
});
|
|
|
|
tap.test('should create Accounting-Response packet', async () => {
|
|
const identifier = 1;
|
|
const requestAuthenticator = Buffer.alloc(16, 0x42);
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingResponse(
|
|
identifier,
|
|
requestAuthenticator,
|
|
secret,
|
|
[] // Usually no attributes in response
|
|
);
|
|
|
|
const decoded = RadiusPacket.decode(packet);
|
|
expect(decoded.code).toEqual(ERadiusCode.AccountingResponse);
|
|
expect(decoded.identifier).toEqual(identifier);
|
|
});
|
|
|
|
tap.test('should verify Accounting-Response authenticator', async () => {
|
|
const identifier = 1;
|
|
const requestAuthenticator = Buffer.alloc(16, 0x42);
|
|
const secret = 'testing123';
|
|
|
|
const response = RadiusPacket.createAccountingResponse(
|
|
identifier,
|
|
requestAuthenticator,
|
|
secret,
|
|
[]
|
|
);
|
|
|
|
// Verify response authenticator
|
|
const isValid = RadiusAuthenticator.verifyResponseAuthenticator(
|
|
response,
|
|
requestAuthenticator,
|
|
secret
|
|
);
|
|
expect(isValid).toBeTruthy();
|
|
});
|
|
|
|
tap.test('should parse all accounting attributes correctly', async () => {
|
|
const identifier = 1;
|
|
const secret = 'testing123';
|
|
|
|
const packet = RadiusPacket.createAccountingRequest(identifier, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.Stop },
|
|
{ type: ERadiusAttributeType.AcctDelayTime, value: 5 },
|
|
{ type: ERadiusAttributeType.AcctInputOctets, value: 1000000 },
|
|
{ type: ERadiusAttributeType.AcctOutputOctets, value: 2000000 },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'sess-123' },
|
|
{ type: ERadiusAttributeType.AcctAuthentic, value: EAcctAuthentic.Radius },
|
|
{ type: ERadiusAttributeType.AcctSessionTime, value: 7200 },
|
|
{ type: ERadiusAttributeType.AcctInputPackets, value: 5000 },
|
|
{ type: ERadiusAttributeType.AcctOutputPackets, value: 10000 },
|
|
{ type: ERadiusAttributeType.AcctTerminateCause, value: EAcctTerminateCause.SessionTimeout },
|
|
{ type: ERadiusAttributeType.AcctMultiSessionId, value: 'multi-sess-456' },
|
|
{ type: ERadiusAttributeType.AcctLinkCount, value: 2 },
|
|
]);
|
|
|
|
const decoded = RadiusPacket.decodeAndParse(packet);
|
|
|
|
// Check each attribute
|
|
const attrs = decoded.parsedAttributes;
|
|
|
|
const statusType = attrs.find((a) => a.type === ERadiusAttributeType.AcctStatusType);
|
|
expect(statusType?.value).toEqual(EAcctStatusType.Stop);
|
|
|
|
const delayTime = attrs.find((a) => a.type === ERadiusAttributeType.AcctDelayTime);
|
|
expect(delayTime?.value).toEqual(5);
|
|
|
|
const inputOctets = attrs.find((a) => a.type === ERadiusAttributeType.AcctInputOctets);
|
|
expect(inputOctets?.value).toEqual(1000000);
|
|
|
|
const outputOctets = attrs.find((a) => a.type === ERadiusAttributeType.AcctOutputOctets);
|
|
expect(outputOctets?.value).toEqual(2000000);
|
|
|
|
const sessionId = attrs.find((a) => a.type === ERadiusAttributeType.AcctSessionId);
|
|
expect(sessionId?.value).toEqual('sess-123');
|
|
|
|
const authentic = attrs.find((a) => a.type === ERadiusAttributeType.AcctAuthentic);
|
|
expect(authentic?.value).toEqual(EAcctAuthentic.Radius);
|
|
|
|
const sessionTime = attrs.find((a) => a.type === ERadiusAttributeType.AcctSessionTime);
|
|
expect(sessionTime?.value).toEqual(7200);
|
|
|
|
const terminateCause = attrs.find((a) => a.type === ERadiusAttributeType.AcctTerminateCause);
|
|
expect(terminateCause?.value).toEqual(EAcctTerminateCause.SessionTimeout);
|
|
});
|
|
|
|
tap.test('should handle Accounting-On/Off status types', async () => {
|
|
const secret = 'testing123';
|
|
|
|
// Accounting-On (NAS restart/reboot notification)
|
|
const acctOnPacket = RadiusPacket.createAccountingRequest(1, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.AccountingOn },
|
|
{ type: ERadiusAttributeType.NasIpAddress, value: '192.168.1.1' },
|
|
]);
|
|
|
|
let decoded = RadiusPacket.decodeAndParse(acctOnPacket);
|
|
let statusType = decoded.parsedAttributes.find(
|
|
(a) => a.type === ERadiusAttributeType.AcctStatusType
|
|
);
|
|
expect(statusType?.value).toEqual(EAcctStatusType.AccountingOn);
|
|
|
|
// Accounting-Off
|
|
const acctOffPacket = RadiusPacket.createAccountingRequest(2, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.AccountingOff },
|
|
{ type: ERadiusAttributeType.NasIpAddress, value: '192.168.1.1' },
|
|
]);
|
|
|
|
decoded = RadiusPacket.decodeAndParse(acctOffPacket);
|
|
statusType = decoded.parsedAttributes.find(
|
|
(a) => a.type === ERadiusAttributeType.AcctStatusType
|
|
);
|
|
expect(statusType?.value).toEqual(EAcctStatusType.AccountingOff);
|
|
});
|
|
|
|
tap.test('should handle all termination causes', async () => {
|
|
const secret = 'testing123';
|
|
const terminationCauses = [
|
|
EAcctTerminateCause.UserRequest,
|
|
EAcctTerminateCause.LostCarrier,
|
|
EAcctTerminateCause.LostService,
|
|
EAcctTerminateCause.IdleTimeout,
|
|
EAcctTerminateCause.SessionTimeout,
|
|
EAcctTerminateCause.AdminReset,
|
|
EAcctTerminateCause.AdminReboot,
|
|
EAcctTerminateCause.PortError,
|
|
EAcctTerminateCause.NasError,
|
|
EAcctTerminateCause.NasRequest,
|
|
EAcctTerminateCause.NasReboot,
|
|
EAcctTerminateCause.PortUnneeded,
|
|
EAcctTerminateCause.PortPreempted,
|
|
EAcctTerminateCause.PortSuspended,
|
|
EAcctTerminateCause.ServiceUnavailable,
|
|
EAcctTerminateCause.Callback,
|
|
EAcctTerminateCause.UserError,
|
|
EAcctTerminateCause.HostRequest,
|
|
];
|
|
|
|
for (const cause of terminationCauses) {
|
|
const packet = RadiusPacket.createAccountingRequest(1, secret, [
|
|
{ type: ERadiusAttributeType.AcctStatusType, value: EAcctStatusType.Stop },
|
|
{ type: ERadiusAttributeType.AcctSessionId, value: 'session-001' },
|
|
{ type: ERadiusAttributeType.AcctTerminateCause, value: cause },
|
|
]);
|
|
|
|
const decoded = RadiusPacket.decodeAndParse(packet);
|
|
const termCause = decoded.parsedAttributes.find(
|
|
(a) => a.type === ERadiusAttributeType.AcctTerminateCause
|
|
);
|
|
expect(termCause?.value).toEqual(cause);
|
|
}
|
|
});
|
|
|
|
export default tap.start();
|