Files
flutter_server_box/test/windows_test.dart
lollipopkit🏳️‍⚧️ 3a615449e3 feat: Windows compatibility (#836)
* feat: win compatibility

* fix

* fix: uptime parse

* opt.: linux uptime accuracy

* fix: windows temperature fetching

* opt.

* opt.: powershell exec

* refactor: address PR review feedback and improve code quality

### Major Improvements:
- **Refactored Windows status parsing**: Broke down large `_getWindowsStatus` method into 13 smaller, focused helper methods for better maintainability and readability
- **Extracted system detection logic**: Created dedicated `SystemDetector` helper class to separate OS detection concerns from ServerProvider
- **Improved concurrency handling**: Implemented proper synchronization for server updates using Future-based locks to prevent race conditions

### Bug Fixes:
- **Fixed CPU percentage parsing**: Removed incorrect '*100' multiplication in BSD CPU parsing (values were already percentages)
- **Enhanced memory parsing**: Added validation and error handling to BSD memory fallback parsing with proper logging
- **Improved uptime parsing**: Added support for multiple Windows date formats and robust error handling with validation
- **Fixed division by zero**: Added safety checks in Swap.usedPercent getter

### Code Quality Enhancements:
- **Added comprehensive documentation**: Documented Windows CPU counter limitations and approach
- **Strengthened error handling**: Added detailed logging and validation throughout parsing methods
- **Improved robustness**: Enhanced BSD CPU parsing with percentage validation and warnings
- **Better separation of concerns**: Each parsing method now has single responsibility

### Files Changed:
- `lib/data/helper/system_detector.dart` (new): System detection helper
- `lib/data/model/server/cpu.dart`: Fixed percentage parsing and added validation
- `lib/data/model/server/memory.dart`: Enhanced fallback parsing and division-by-zero protection
- `lib/data/model/server/server_status_update_req.dart`: Refactored into 13 focused parsing methods
- `lib/data/provider/server.dart`: Improved synchronization and extracted system detection

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: parse & shell fn struct

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-08 16:56:36 +08:00

474 lines
15 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/server_status_update_req.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/res/status.dart';
void main() {
group('Windows System Tests', () {
test('should verify Windows segments length matches command types', () {
final systemType = SystemType.windows;
final expectedLength = WindowsStatusCmdType.values.length;
expect(systemType.segmentsLen, equals(expectedLength));
expect(systemType.isSegmentsLenMatch(expectedLength), isTrue);
});
test('should generate Windows PowerShell script correctly', () {
final script = ShellFunc.allScript({'custom_cmd': 'echo "test"'}, systemType: SystemType.windows);
expect(script, contains('PowerShell script for ServerBox'));
expect(script, contains('function SbStatus'));
expect(script, contains('function SbProcess'));
expect(script, contains('function SbShutdown'));
expect(script, contains('function SbReboot'));
expect(script, contains('function SbSuspend'));
expect(script, contains('switch (\$args[0])'));
expect(script, contains('"-s" { SbStatus }'));
expect(script, contains('echo "test"'));
});
test('should handle Windows system parsing with real data', () async {
final segments = _windowsStatusSegments;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
// Verify system information was parsed
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
// Verify CPU information
expect(result.cpu.now, isNotEmpty);
expect(result.cpu.brand.keys.first, contains('12th Gen Intel(R) Core(TM) i5-12490F'));
// Verify memory information
expect(result.mem, isNotNull);
expect(result.mem.total, equals(66943944));
expect(result.mem.free, equals(58912812));
// Verify disk information
expect(result.disk, isNotEmpty);
final cDrive = result.disk.firstWhere((disk) => disk.path == 'C:');
expect(cDrive.fsTyp, equals('NTFS'));
expect(cDrive.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
expect(cDrive.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
// Verify TCP connections
expect(result.tcp, isNotNull);
expect(result.tcp.active, equals(2));
});
test('should parse Windows CPU data correctly', () async {
const cpuJson = '''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''';
final segments = ['__windows', '1754151483', '', '', cpuJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.cpu.now, hasLength(1));
expect(result.cpu.now.first.user, equals(42));
expect(result.cpu.now.first.idle, equals(58));
});
test('should parse Windows memory data correctly', () async {
const memoryJson = '''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''';
final segments = ['__windows', '1754151483', '', '', '', '', '', '', memoryJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.mem, isNotNull);
expect(result.mem.total, equals(66943944));
expect(result.mem.free, equals(58912812));
expect(result.mem.avail, equals(58912812));
});
test('should parse Windows disk data correctly', () async {
const diskJson = '''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''';
final segments = ['__windows', '1754151483', '', '', '', '', '', diskJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.disk, hasLength(1));
final disk = result.disk.first;
expect(disk.path, equals('C:'));
expect(disk.mount, equals('C:'));
expect(disk.fsTyp, equals('NTFS'));
expect(disk.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
expect(disk.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
expect(disk.usedPercent, equals(61));
});
test('should parse Windows battery data correctly', () async {
const batteryJson = '''
{
"EstimatedChargeRemaining": 85,
"BatteryStatus": 6
}
''';
// Create segments with enough elements to reach battery position
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.battery.index] = batteryJson;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.batteries, hasLength(1));
final battery = result.batteries.first;
expect(battery.name, equals('Battery'));
expect(battery.percent, equals(85));
expect(battery.status.name, equals('charging'));
});
test('should handle Windows uptime parsing correctly', () async {
// Test new format with date line + uptime days
const uptimeNewFormat = 'Friday, July 25, 2025 2:26:42 PM\n2';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.uptime.index] = uptimeNewFormat;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.more[StatusCmdType.uptime], isNotNull);
});
test('should handle Windows uptime parsing with old format', () async {
const uptimeDateTime = 'Friday, July 25, 2025 2:26:42 PM';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.uptime.index] = uptimeDateTime;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.more[StatusCmdType.uptime], isNotNull);
});
test('should handle Windows script path generation', () {
const serverId = 'test-server';
final scriptPath = ShellFunc.getScriptPath(serverId, systemType: SystemType.windows);
expect(scriptPath, contains('.ps1'));
expect(scriptPath, contains('\\'));
final installCmd = ShellFunc.getInstallShellCmd(serverId, systemType: SystemType.windows);
expect(installCmd, contains('New-Item'));
expect(installCmd, contains('Set-Content'));
// No longer contains 'powershell' prefix as commands now run in PowerShell session
});
test('should execute Windows commands correctly', () {
const serverId = 'test-server';
final statusCmd = ShellFunc.status.exec(serverId, systemType: SystemType.windows);
expect(statusCmd, contains('powershell'));
expect(statusCmd, contains('-ExecutionPolicy Bypass'));
expect(statusCmd, contains('-s'));
final processCmd = ShellFunc.process.exec(serverId, systemType: SystemType.windows);
expect(processCmd, contains('powershell'));
expect(processCmd, contains('-p'));
});
test('should handle GPU detection on Windows', () async {
const nvidiaNotFound = 'NVIDIA driver not found';
const amdNotFound = 'AMD driver not found';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.nvidia.index] = nvidiaNotFound;
segments[WindowsStatusCmdType.amd.index] = amdNotFound;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
// Should not throw errors even when GPU drivers are not found
expect(result.nvidia, anyOf(isNull, isEmpty));
expect(result.amd, anyOf(isNull, isEmpty));
});
test('should handle Windows error conditions gracefully', () async {
// Test with malformed JSON and error messages
final segments = [
'__windows',
'1754151483',
'Network adapter error',
'Microsoft Windows 11 Pro for Workstations',
'invalid json {',
'uptime error',
'connection error',
'disk error',
'memory error',
'temp error',
'LKH6',
'diskio error',
'battery error',
'NVIDIA driver not found',
'AMD driver not found',
'sensor error',
'smart error',
'12th Gen Intel(R) Core(TM) i5-12490F',
];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
// Should not throw exceptions
expect(() async => await getStatus(req), returnsNormally);
final result = await getStatus(req);
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
});
test('should handle Windows temperature error output gracefully', () async {
// Test with actual error output from win_raw.txt
final segments = [
'__windows',
'1754151483',
'', // network
'Microsoft Windows 11 Pro for Workstations', // system
'''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''', // cpu
'Friday, July 25, 2025 2:26:42 PM', // uptime
'2', // connections
'''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''', // disk
'''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''', // memory
'''
The string is missing the terminator: ".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
''', // temp (error output)
'LKH6', // host
'', // diskio
'', // battery
'NVIDIA driver not found', // nvidia
'AMD driver not found', // amd
'', // sensors
'''
{
"DeviceId": "0",
"Temperature": 41,
"TemperatureMax": 70,
"Wear": 0,
"PowerOnHours": null
}
''', // smart
'12th Gen Intel(R) Core(TM) i5-12490F', // cpu brand
];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
// Should not throw exceptions even with error output in temperature values
expect(() async => await getStatus(req), returnsNormally);
final result = await getStatus(req);
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
// Temperature should be empty since we got error output
expect(result.temps.isEmpty, isTrue);
});
});
}
// Sample Windows status segments based on real PowerShell output
final _windowsStatusSegments = [
'__windows', // System type marker
'1754151483', // Unix timestamp
'', // Network data (empty for now)
'Microsoft Windows 11 Pro for Workstations', // System name
'''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''', // CPU data
'Friday, July 25, 2025 2:26:42 PM', // Uptime (boot time)
'2', // Connection count
'''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''', // Disk data
'''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''', // Memory data
'', // Temperature (combined command - empty due to OpenHardwareMonitor error)
'LKH6', // Hostname
'', // Disk I/O (empty for now)
'', // Battery data (empty)
'NVIDIA driver not found', // NVIDIA GPU
'AMD driver not found', // AMD GPU
'', // Sensors (empty due to OpenHardwareMonitor error)
'''
{
"CimClass": {
"CimSuperClassName": "MSFT_StorageObject",
"CimSuperClass": {
"CimSuperClassName": null,
"CimSuperClass": null,
"CimClassProperties": "ObjectId PassThroughClass PassThroughIds PassThroughNamespace PassThroughServer UniqueId",
"CimClassQualifiers": "Abstract = True locale = 1033",
"CimClassMethods": "",
"CimSystemProperties": "Microsoft.Management.Infrastructure.CimSystemProperties"
},
"CimClassProperties": [
"ObjectId",
"PassThroughClass",
"PassThroughIds",
"PassThroughNamespace",
"PassThroughServer",
"UniqueId",
"DeviceId",
"FlushLatencyMax",
"LoadUnloadCycleCount",
"LoadUnloadCycleCountMax",
"ManufactureDate",
"PowerOnHours",
"ReadErrorsCorrected",
"ReadErrorsTotal",
"ReadErrorsUncorrected",
"ReadLatencyMax",
"StartStopCycleCount",
"StartStopCycleCountMax",
"Temperature",
"TemperatureMax",
"Wear",
"WriteErrorsCorrected",
"WriteErrorsTotal",
"WriteErrorsUncorrected",
"WriteLatencyMax"
]
},
"Temperature": 46,
"TemperatureMax": 70,
"Wear": 0,
"ReadLatencyMax": 1930,
"WriteLatencyMax": 1903,
"FlushLatencyMax": 262
}
''', // Disk SMART data
'12th Gen Intel(R) Core(TM) i5-12490F', // CPU brand
];