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