import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:qlyra/core/webpush/max_web_protocol.dart'; Uint8List _hex(String value) { final bytes = Uint8List(value.length ~/ 2); for (var i = 0; i < bytes.length; i++) { bytes[i] = int.parse(value.substring(i * 2, i * 2 + 2), radix: 16); } return bytes; } const _compressedResponseHex = '0a010000000601000018f00782a6726567696f6ea25a5aa973796e746865746963c3'; void main() { test('заголовок кадра совпадает с синтетическим вектором', () { final frame = _hex(_compressedResponseHex); expect(frame[0], MaxWebFraming.protocolVersion); final decoded = MaxWebFraming.decode(frame); expect(decoded.cmd, MaxWebCmd.ok); expect(decoded.opcode, 6); expect(decoded.isOk, isTrue); }); test('распаковка LZ4 и msgpack из синтетического кадра', () { final decoded = MaxWebFraming.decode(_hex(_compressedResponseHex)); final payload = decoded.payload as Map; expect(payload['region'], 'ZZ'); expect(payload['synthetic'], isTrue); expect(payload.length, 2); }); test('кодирование кадра сохраняет контракт заголовка', () { final actual = MaxWebFraming.encode( cmd: 0, seq: 2, opcode: 6, payload: {'synthetic': true}, ); expect( actual.sublist(0, MaxWebFraming.headerSize), _hex('0a00000200060000000c'), ); }); test('msgpack переживает круговой рейс', () { final source = { 'enabled': true, 'count': 40, 'offset': -1, 'nested': { 'a': null, 'b': 3.5, 'c': [1, 'два', false], }, }; final restored = MaxMsgpack.decode(MaxMsgpack.encode(source)) as Map; expect(restored['enabled'], true); expect(restored['count'], 40); expect(restored['offset'], -1); final nested = restored['nested'] as Map; expect(nested['a'], isNull); expect(nested['b'], 3.5); expect((nested['c'] as List)[1], 'два'); }); test('ext(1) разворачивается во вложенное число', () { final pollingInterval = MaxMsgpack.decode( Uint8List.fromList([ 0x81, 0xA8, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6C, 0xC7, 0x03, 0x01, 0xD1, 0x13, 0x88, ]), ) as Map; expect(pollingInterval['interval'], 5000); final expiresAt = MaxMsgpack.decode( Uint8List.fromList([ 0xC7, 0x09, 0x01, 0xD3, 0x00, 0x00, 0x01, 0xA0, 0x25, 0x5B, 0xE9, 0xD2, ]), ); expect(expiresAt, 1787333175762); }); test('LZ4 разворачивает перекрывающиеся совпадения', () { final compressed = Uint8List.fromList([ 0x6E, 0x71, 0x6C, 0x79, 0x72, 0x61, 0x20, 0x06, 0x00, 0x46, 0x70, 0x75, 0x73, 0x68, 0x05, 0x00, 0x50, 0x20, 0x70, 0x75, 0x73, 0x68, ]); final result = Lz4Block.decompress(compressed, 256); expect(utf8.decode(result), 'qlyra qlyra qlyra qlyra push push push push'); }); test('отклоняет кадр с несовпадающей длиной', () { expect( () => MaxWebFraming.decode(_hex('0a000002000600000003aa')), throwsFormatException, ); }); test('ограничивает распаковку LZ4', () { expect( () => Lz4Block.decompress( Uint8List.fromList([0x1f, 0x41, 0x01, 0x00, 0x00]), 8, ), throwsFormatException, ); }); }