Files
smartradius/test/server/test.accounting.ts

247 lines
9.4 KiB
TypeScript
Raw Permalink Normal View History

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();