import { expect, tap } from '@git.zone/tstest/tapbundle'; import { RadiusAttributes, ERadiusAttributeType, } from '../../ts_server/index.js'; tap.test('should get attribute definitions by type', async () => { const userNameDef = RadiusAttributes.getDefinition(ERadiusAttributeType.UserName); expect(userNameDef).toBeDefined(); expect(userNameDef!.name).toEqual('User-Name'); expect(userNameDef!.valueType).toEqual('text'); const nasIpDef = RadiusAttributes.getDefinition(ERadiusAttributeType.NasIpAddress); expect(nasIpDef).toBeDefined(); expect(nasIpDef!.name).toEqual('NAS-IP-Address'); expect(nasIpDef!.valueType).toEqual('address'); const nasPortDef = RadiusAttributes.getDefinition(ERadiusAttributeType.NasPort); expect(nasPortDef).toBeDefined(); expect(nasPortDef!.name).toEqual('NAS-Port'); expect(nasPortDef!.valueType).toEqual('integer'); }); tap.test('should get attribute type by name', async () => { expect(RadiusAttributes.getTypeByName('User-Name')).toEqual(ERadiusAttributeType.UserName); expect(RadiusAttributes.getTypeByName('NAS-IP-Address')).toEqual(ERadiusAttributeType.NasIpAddress); expect(RadiusAttributes.getTypeByName('NAS-Port')).toEqual(ERadiusAttributeType.NasPort); expect(RadiusAttributes.getTypeByName('Unknown-Attribute')).toBeUndefined(); }); tap.test('should get attribute name by type', async () => { expect(RadiusAttributes.getNameByType(ERadiusAttributeType.UserName)).toEqual('User-Name'); expect(RadiusAttributes.getNameByType(ERadiusAttributeType.UserPassword)).toEqual('User-Password'); expect(RadiusAttributes.getNameByType(255)).toInclude('Unknown-Attribute'); }); tap.test('should parse text attributes', async () => { const textBuffer = Buffer.from('testuser', 'utf8'); const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.UserName, textBuffer); expect(parsed).toEqual('testuser'); }); tap.test('should parse address attributes', async () => { const addressBuffer = Buffer.from([192, 168, 1, 100]); const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.NasIpAddress, addressBuffer); expect(parsed).toEqual('192.168.1.100'); }); tap.test('should parse integer attributes', async () => { const intBuffer = Buffer.allocUnsafe(4); intBuffer.writeUInt32BE(12345, 0); const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.NasPort, intBuffer); expect(parsed).toEqual(12345); }); tap.test('should encode text attributes', async () => { const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.UserName, 'testuser'); expect(encoded.toString('utf8')).toEqual('testuser'); }); tap.test('should encode address attributes', async () => { const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.NasIpAddress, '10.20.30.40'); expect(encoded.length).toEqual(4); expect(encoded[0]).toEqual(10); expect(encoded[1]).toEqual(20); expect(encoded[2]).toEqual(30); expect(encoded[3]).toEqual(40); }); tap.test('should encode integer attributes', async () => { const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.NasPort, 65535); expect(encoded.length).toEqual(4); expect(encoded.readUInt32BE(0)).toEqual(65535); }); tap.test('should encode complete attribute with TLV format', async () => { const encoded = RadiusAttributes.encodeAttribute(ERadiusAttributeType.UserName, 'test'); expect(encoded[0]).toEqual(ERadiusAttributeType.UserName); // Type expect(encoded[1]).toEqual(6); // Length (2 + 4) expect(encoded.subarray(2).toString('utf8')).toEqual('test'); }); tap.test('should handle attribute value too long', async () => { const longValue = Buffer.alloc(254); // Max is 253 bytes let error: Error | undefined; try { RadiusAttributes.encodeAttribute(ERadiusAttributeType.UserName, longValue); } catch (e) { error = e as Error; } expect(error).toBeDefined(); expect(error!.message).toInclude('too long'); }); tap.test('should parse raw attribute', async () => { const rawAttr = { type: ERadiusAttributeType.UserName, value: Buffer.from('john.doe', 'utf8'), }; const parsed = RadiusAttributes.parseAttribute(rawAttr); expect(parsed.type).toEqual(ERadiusAttributeType.UserName); expect(parsed.name).toEqual('User-Name'); expect(parsed.value).toEqual('john.doe'); }); tap.test('should handle Vendor-Specific Attributes', async () => { // Vendor-Id: 9 (Cisco), Vendor-Type: 1, Value: 'test' const vendorId = 9; const vendorType = 1; const vendorValue = Buffer.from('cisco-av-pair', 'utf8'); const vsaBuffer = RadiusAttributes.encodeVSA({ vendorId, vendorType, vendorValue }); // Parse it back const parsed = RadiusAttributes.parseVSA(vsaBuffer); expect(parsed).toBeDefined(); expect(parsed!.vendorId).toEqual(vendorId); expect(parsed!.vendorType).toEqual(vendorType); expect(parsed!.vendorValue.toString('utf8')).toEqual('cisco-av-pair'); }); tap.test('should create complete Vendor-Specific attribute', async () => { const vendorId = 311; // Microsoft const vendorType = 1; const vendorValue = Buffer.from('test-value', 'utf8'); const attr = RadiusAttributes.createVendorAttribute(vendorId, vendorType, vendorValue); // First byte should be type 26 (Vendor-Specific) expect(attr[0]).toEqual(ERadiusAttributeType.VendorSpecific); // Second byte is total length expect(attr[1]).toBeGreaterThan(6); }); tap.test('should check if attribute is encrypted', async () => { expect(RadiusAttributes.isEncrypted(ERadiusAttributeType.UserPassword)).toBeTruthy(); expect(RadiusAttributes.isEncrypted(ERadiusAttributeType.UserName)).toBeFalsy(); }); tap.test('should handle all standard RFC 2865 attributes', async () => { // Test that all standard attributes have definitions const standardAttributes = [ ERadiusAttributeType.UserName, ERadiusAttributeType.UserPassword, ERadiusAttributeType.ChapPassword, ERadiusAttributeType.NasIpAddress, ERadiusAttributeType.NasPort, ERadiusAttributeType.ServiceType, ERadiusAttributeType.FramedProtocol, ERadiusAttributeType.FramedIpAddress, ERadiusAttributeType.FramedIpNetmask, ERadiusAttributeType.FramedRouting, ERadiusAttributeType.FilterId, ERadiusAttributeType.FramedMtu, ERadiusAttributeType.FramedCompression, ERadiusAttributeType.LoginIpHost, ERadiusAttributeType.LoginService, ERadiusAttributeType.LoginTcpPort, ERadiusAttributeType.ReplyMessage, ERadiusAttributeType.CallbackNumber, ERadiusAttributeType.CallbackId, ERadiusAttributeType.FramedRoute, ERadiusAttributeType.FramedIpxNetwork, ERadiusAttributeType.State, ERadiusAttributeType.Class, ERadiusAttributeType.VendorSpecific, ERadiusAttributeType.SessionTimeout, ERadiusAttributeType.IdleTimeout, ERadiusAttributeType.TerminationAction, ERadiusAttributeType.CalledStationId, ERadiusAttributeType.CallingStationId, ERadiusAttributeType.NasIdentifier, ERadiusAttributeType.ProxyState, ERadiusAttributeType.ChapChallenge, ERadiusAttributeType.NasPortType, ERadiusAttributeType.PortLimit, ]; for (const attrType of standardAttributes) { const def = RadiusAttributes.getDefinition(attrType); expect(def).toBeDefined(); expect(def!.name).toBeDefined(); expect(def!.valueType).toBeDefined(); } }); tap.test('should handle all RFC 2866 accounting attributes', async () => { const accountingAttributes = [ ERadiusAttributeType.AcctStatusType, ERadiusAttributeType.AcctDelayTime, ERadiusAttributeType.AcctInputOctets, ERadiusAttributeType.AcctOutputOctets, ERadiusAttributeType.AcctSessionId, ERadiusAttributeType.AcctAuthentic, ERadiusAttributeType.AcctSessionTime, ERadiusAttributeType.AcctInputPackets, ERadiusAttributeType.AcctOutputPackets, ERadiusAttributeType.AcctTerminateCause, ERadiusAttributeType.AcctMultiSessionId, ERadiusAttributeType.AcctLinkCount, ]; for (const attrType of accountingAttributes) { const def = RadiusAttributes.getDefinition(attrType); expect(def).toBeDefined(); expect(def!.name).toBeDefined(); } }); export default tap.start();