import 'package:flutter_test/flutter_test.dart'; import 'package:yx_tracking_flutter/src/config/analytics_config.dart'; import 'package:yx_tracking_flutter/src/config/config_manager.dart'; import 'package:yx_tracking_flutter/src/core/validator.dart'; import 'package:yx_tracking_flutter/src/model/device_info.dart'; import 'package:yx_tracking_flutter/src/model/event.dart'; import 'package:yx_tracking_flutter/src/model/system_dim_info.dart'; class StaticConfigManager extends ConfigManager { StaticConfigManager({ required super.config, required SystemDimInfo configValue, }) : _configValue = configValue, super(refreshInterval: Duration.zero); final SystemDimInfo _configValue; @override SystemDimInfo? get currentConfig => _configValue; @override Future init() async {} @override Future fetchAndCacheConfig({bool force = false}) async {} @override Future forceRefresh() async {} @override Future dispose() async {} } const DeviceInfo _device = DeviceInfo( os: 'test', model: 'test', screenResolution: '1x1', ); AnalyticsConfig _config() { return AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', clientType: 3, enableMetrics: false, ); } Event _event({ required String type, Map? customTags, }) { final now = DateTime.now(); final ts = now.millisecondsSinceEpoch; return Event( systemCode: 'TEST_APP', eventType: type, userInfo: null, clientType: 3, clientTimestamp: ts, timestamp: now.toUtc().toIso8601String(), deviceInfo: _device, eventParams: null, customTags: customTags, createTime: now, ); } SystemDimInfo _dimInfo() { return SystemDimInfo( systemInfo: const SystemInfo(raw: {}), eventDefinitions: const [ EventDefinition(eventCode: 'KNOWN'), ], tagDefinitions: const [ TagDefinition(tagName: 'tenantId', tagType: 'string', isRequired: true), TagDefinition(tagName: 'count', tagType: 'int', isRequired: false), ], sdkStrategy: null, lastFetchedAt: DateTime.now(), ); } SystemDimInfo _dimInfoWithTypes() { return SystemDimInfo( systemInfo: const SystemInfo(raw: {}), eventDefinitions: const [ EventDefinition(eventCode: 'TYPED'), ], tagDefinitions: const [ TagDefinition(tagName: 'intNum', tagType: 'int', isRequired: true), TagDefinition(tagName: 'intStr', tagType: 'integer', isRequired: true), TagDefinition(tagName: 'boolNum', tagType: 'bool', isRequired: true), TagDefinition(tagName: 'boolStr', tagType: 'boolean', isRequired: true), TagDefinition(tagName: 'doubleStr', tagType: 'double', isRequired: true), TagDefinition(tagName: 'floatStr', tagType: 'float', isRequired: true), TagDefinition(tagName: 'numStr', tagType: 'num', isRequired: true), TagDefinition(tagName: 'unknown', tagType: 'mystery', isRequired: false), ], sdkStrategy: null, lastFetchedAt: DateTime.now(), ); } bool _isTypeMismatch(ValidationIssue issue) => issue.code == Validator.typeMismatch; void main() { group('Validator', () { test('已配置事件且必填 tag 满足时无告警', () { final manager = StaticConfigManager( config: _config(), configValue: _dimInfo(), ); final validator = Validator(manager); final result = validator.validate( _event( type: 'KNOWN', customTags: const {'tenantId': 't1'}, ), ); expect(result.isEmpty, true); }); test('未知事件会产生 error', () { final manager = StaticConfigManager( config: _config(), configValue: _dimInfo(), ); final validator = Validator(manager); final result = validator.validate(_event(type: 'UNKNOWN')); expect(result.hasErrors, true); expect(result.errors.first.code, Validator.unknownEventType); }); test('缺失必填 tag 会产生 warning', () { final manager = StaticConfigManager( config: _config(), configValue: _dimInfo(), ); final validator = Validator(manager); final result = validator.validate(_event(type: 'KNOWN')); expect(result.hasWarnings, true); expect(result.warnings.first.code, Validator.missingRequiredTag); }); test('类型不匹配会产生 warning', () { final manager = StaticConfigManager( config: _config(), configValue: _dimInfo(), ); final validator = Validator(manager); final result = validator.validate( _event( type: 'KNOWN', customTags: const { 'tenantId': 't1', 'count': 'not-an-int', }, ), ); expect(result.hasWarnings, true); expect( result.warnings.any(_isTypeMismatch), true, ); }); test('多类型匹配路径均可通过', () { final manager = StaticConfigManager( config: _config(), configValue: _dimInfoWithTypes(), ); final validator = Validator(manager); final result = validator.validate( _event( type: 'TYPED', customTags: const { 'intNum': 1.0, 'intStr': '2', 'boolNum': 0, 'boolStr': 'no', 'doubleStr': '3.14', 'floatStr': '2.72', 'numStr': '42', 'unknown': {'any': 'value'}, }, ), ); expect(result.hasErrors, false); expect(result.hasWarnings, false); }); }); }