Files
smartradius/test/server/test.attributes.ts

212 lines
8.0 KiB
TypeScript

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