mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-10-19 16:17:03 +00:00
* test: remove tests that do not pin user-visible behavior Apply the AGENTS.md test rules to the existing Rust and Flutter suites. A test is kept when it is the only coverage of user- or API-visible behavior, the regression test of a reported or fixed bug, a security boundary (one test per rejection class), or a check against an external reference. Tests that assert private state or the order of internal steps, pin review-round corner cases, or repeat what a kept test already covers are removed. Rust: 679 -> 298 tests. Dart: 118 -> 45 tests. Test-only helpers left without callers go with them (applyLastOnlineStateForTesting, terminalKeyboardRowWidth, debugBufferedInputCount, tokio's test-util dev feature), and cfg gates and visibility that existed only for the deleted tests are narrowed. Runtime behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep tests that pin security and user-visible behavior Restore six tests that review showed are the only coverage of their behavior: - update metadata rejects an artifact file name with path components; - ICE candidates from another session are dropped; - KCP delivers every multi-segment frame queued before a close; - set_files rejects an empty name in a multi-file transfer; - a lopsided elongated cursor keeps its centre hotspot (#16242); - toggling Show hidden during a pending same-path read issues a new read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep offline update and key-exchange downgrade rejections Restore three tests that are the only coverage of their rejection class: - offline update metadata rejects a signed artifact with an http or credential-bearing URL; - offline verification rejects a renamed local file and a version that does not match the release; - a key-exchange pick lowered in transit leaves both sides unable to decrypt, so a v0 fallback after a failed v1 decrypt cannot pass unnoticed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep the decomposed-grapheme and reconnect-cancel tests Each catches a plausible wrong change that passes every kept test: mapping the first scalar under a locked Ctrl turns é into \x05, and a reconnect that does not fail pending reads leaves them hanging until the 30 s timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
210 lines
7.2 KiB
Dart
210 lines
7.2 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_hbb/models/file_model.dart';
|
|
import 'package:flutter_hbb/models/model.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
final _sessionId = UuidValue('00000000-0000-0000-0000-000000000000');
|
|
|
|
class _FakeFFI implements FFI {
|
|
@override
|
|
String id = 'test-peer';
|
|
@override
|
|
UuidValue get sessionId => _sessionId;
|
|
@override
|
|
late final FfiModel ffiModel = FfiModel(WeakReference(this));
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
|
}
|
|
|
|
FileController _createController(FileFetcher fileFetcher) {
|
|
final ffi = _FakeFFI();
|
|
return FileController(
|
|
isLocal: false,
|
|
getSessionID: () => _sessionId,
|
|
rootState: WeakReference(ffi),
|
|
jobController: JobController(() => _sessionId, () => null),
|
|
fileFetcher: fileFetcher,
|
|
getOtherSideDirectoryData: () =>
|
|
DirectoryData(FileDirectory(), DirectoryOptions()),
|
|
);
|
|
}
|
|
|
|
FileDirectory _directory(String path) => FileDirectory()..path = path;
|
|
|
|
String _directoryJson(String path) => jsonEncode({
|
|
'id': 0,
|
|
'path': path,
|
|
'entries': <Object>[],
|
|
});
|
|
|
|
class _SentRead {
|
|
final String path;
|
|
final bool includeHidden;
|
|
|
|
const _SentRead(this.path, this.includeHidden);
|
|
}
|
|
|
|
void main() {
|
|
test('a fast remote response is matched after registration', () async {
|
|
late final FileFetcher fileFetcher;
|
|
fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, path, __) {
|
|
fileFetcher.tryCompleteTask(_directoryJson(path), 'false');
|
|
return Future<void>.value();
|
|
},
|
|
);
|
|
final directory = await fileFetcher.fetchDirectory('/fast', false, false);
|
|
expect(directory.path, '/fast');
|
|
});
|
|
|
|
test('a resolved Home path completes the sole empty-path request', () async {
|
|
final sent = <_SentRead>[];
|
|
final fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, path, includeHidden) async {
|
|
sent.add(_SentRead(path, includeHidden));
|
|
},
|
|
);
|
|
final controller = _createController(fileFetcher);
|
|
controller.directory.value = _directory('/initial');
|
|
|
|
final home = controller.openDirectory('');
|
|
await Future<void>.delayed(Duration.zero);
|
|
final response = _directoryJson('/home/user');
|
|
controller.initDirAndHome({'value': response});
|
|
expect(controller.homePath, '/home/user');
|
|
expect(controller.directory.value.path, '/initial');
|
|
fileFetcher.tryCompleteTask(response, 'false');
|
|
expect(await home, isTrue);
|
|
expect(controller.directory.value.path, '/home/user');
|
|
expect(sent.single.path, isEmpty);
|
|
});
|
|
|
|
test('an automatic response initializes Home without a pending request', () {
|
|
final controller = _createController(FileFetcher(() => _sessionId));
|
|
|
|
controller.initDirAndHome({'value': _directoryJson('/home/user')});
|
|
|
|
expect(controller.homePath, '/home/user');
|
|
expect(controller.directory.value.path, '/home/user');
|
|
});
|
|
|
|
test('a read error completes the sole pending request', () async {
|
|
final fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, __, ___) async {},
|
|
);
|
|
final request = fileFetcher.fetchDirectory('/denied', false, false);
|
|
await Future<void>.delayed(Duration.zero);
|
|
final expectation = expectLater(request, throwsA('permission denied'));
|
|
|
|
fileFetcher.tryCompleteRemoteTaskWithError('permission denied');
|
|
|
|
await expectation;
|
|
expect(fileFetcher.hasPendingRemoteRead('/denied'), isFalse);
|
|
});
|
|
|
|
test('same-path requests share the pending read', () async {
|
|
final sent = <_SentRead>[];
|
|
late final FileFetcher fileFetcher;
|
|
fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, path, includeHidden) async {
|
|
sent.add(_SentRead(path, includeHidden));
|
|
},
|
|
);
|
|
final controller = _createController(fileFetcher);
|
|
controller.directory.value = _directory('/initial');
|
|
|
|
final first = controller.openDirectory('/same');
|
|
final waiting = controller.openDirectory('/same');
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(sent.map((request) => request.path), ['/same']);
|
|
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
|
expect(await first, isTrue);
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(sent.map((request) => request.path), ['/same']);
|
|
expect(await waiting, isTrue);
|
|
expect(controller.directory.value.path, '/same');
|
|
});
|
|
|
|
test('same-path requests with different hidden options are serialized',
|
|
() async {
|
|
final sent = <_SentRead>[];
|
|
final fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, path, includeHidden) async {
|
|
sent.add(_SentRead(path, includeHidden));
|
|
},
|
|
);
|
|
|
|
final first = fileFetcher.fetchDirectory('/same', false, false);
|
|
final second = fileFetcher.fetchDirectory('/same', false, true);
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(sent.map((request) => request.includeHidden), [false]);
|
|
|
|
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
|
expect((await first).path, '/same');
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(sent.map((request) => request.includeHidden), [false, true]);
|
|
|
|
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
|
expect((await second).path, '/same');
|
|
});
|
|
|
|
test('session invalidation cancels active and waiting reads', () async {
|
|
final sent = <_SentRead>[];
|
|
final fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, path, includeHidden) async {
|
|
sent.add(_SentRead(path, includeHidden));
|
|
},
|
|
);
|
|
final first = fileFetcher.fetchDirectory('/same', false, false);
|
|
final waiting = fileFetcher.fetchDirectory('/same', false, true);
|
|
await Future<void>.delayed(Duration.zero);
|
|
final firstError = expectLater(first, throwsA(isA<StateError>()));
|
|
final waitingError = expectLater(waiting, throwsA(isA<StateError>()));
|
|
|
|
fileFetcher.beginRemoteSession();
|
|
|
|
await firstError;
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(sent.map((request) => request.includeHidden), [false]);
|
|
await waitingError;
|
|
expect(fileFetcher.hasPendingRemoteRead('/same'), isFalse);
|
|
final replacement = fileFetcher.fetchDirectory('/same', false, true);
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(sent.map((request) => request.includeHidden), [false, true]);
|
|
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
|
expect((await replacement).path, '/same');
|
|
});
|
|
|
|
test('navigation ignores stale directory responses', () async {
|
|
final fileFetcher = FileFetcher(
|
|
() => _sessionId,
|
|
readRemoteDirectory: (_, __, ___) async {},
|
|
);
|
|
final controller = _createController(fileFetcher);
|
|
controller.directory.value = _directory('/initial');
|
|
|
|
final stale = controller.openDirectory('/stale');
|
|
final latest = controller.openDirectory('/latest');
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
fileFetcher.tryCompleteTask(_directoryJson('/latest'), 'false');
|
|
expect(await latest, isTrue);
|
|
fileFetcher.tryCompleteTask(_directoryJson('/stale'), 'false');
|
|
expect(await stale, isTrue);
|
|
|
|
expect(controller.directory.value.path, '/latest');
|
|
});
|
|
}
|