Skip to content

Commit 4c4ee3a

Browse files
committed
[red] specify session-bound consent for recovered board actions
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 80b6053 commit 4c4ee3a

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
4+
import 'dart:async';
5+
import 'package:flutter_riverpod/flutter_riverpod.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:pyble/app/providers.dart';
8+
import 'package:pyble/editor/editor.dart';
9+
import 'package:pyble/editor/board_target_guard.dart';
10+
import 'package:pyble/editor/draft_recovery.dart';
11+
import 'package:pyble/files/files.dart';
12+
import 'package:pyble/pble/pble.dart';
13+
import '../support/recording_connection.dart';
14+
15+
class SessionConnection extends RecordingConnection
16+
implements ConnectionSessionStampSource {
17+
SessionConnection() : super(initial: ConnState.ready);
18+
Object stamp = Object();
19+
@override
20+
Object get connectionSessionStamp => stamp;
21+
}
22+
23+
void main() {
24+
ProviderContainer setup(
25+
SessionConnection connection,
26+
Future<bool> Function(BoardTargetRequest)? confirm,
27+
) {
28+
final c = ProviderContainer(
29+
overrides: [
30+
connectionProvider.overrideWithValue(connection),
31+
boardTargetConfirmationProvider.overrideWithValue(confirm),
32+
],
33+
);
34+
addTearDown(c.dispose);
35+
c
36+
.read(editorDocumentProvider.notifier)
37+
.openGenerated(name: 'restored.py', content: 'print(1)');
38+
c.read(editorRecoveredDraftProvider.notifier).state = true;
39+
return c;
40+
}
41+
42+
test('missing or declined consent sends no Save or Run board verb', () async {
43+
for (final confirm in <Future<bool> Function(BoardTargetRequest)?>[
44+
null,
45+
(_) async => false,
46+
]) {
47+
final connection = SessionConnection();
48+
final c = setup(connection, confirm);
49+
await expectLater(
50+
c.read(saveControllerProvider).save(),
51+
throwsA(isA<BoardActionCancelled>()),
52+
);
53+
await expectLater(
54+
c.read(runControllerProvider).run(),
55+
throwsA(isA<BoardActionCancelled>()),
56+
);
57+
expect(connection.putFileCalls, isEmpty);
58+
expect(connection.runFileCalls, isEmpty);
59+
}
60+
});
61+
test(
62+
'Save confirms actual path; Run confirms write and execution separately',
63+
() async {
64+
final requests = <BoardTargetRequest>[];
65+
final connection = SessionConnection();
66+
final c = setup(connection, (request) async {
67+
requests.add(request);
68+
return true;
69+
});
70+
await c.read(saveControllerProvider).save();
71+
await c.read(runControllerProvider).run();
72+
expect(requests.map((r) => r.path), ['/restored.py', '/restored.py']);
73+
expect(requests.map((r) => r.run), [false, true]);
74+
expect(connection.runFileCalls, ['/restored.py']);
75+
},
76+
);
77+
test(
78+
'session or edit-away/back while confirmation is open revokes consent',
79+
() async {
80+
for (final changeSession in [true, false]) {
81+
final choice = Completer<bool>();
82+
final asked = Completer<void>();
83+
final connection = SessionConnection();
84+
final c = setup(connection, (_) {
85+
asked.complete();
86+
return choice.future;
87+
});
88+
final save = c.read(saveControllerProvider).save();
89+
final result = expectLater(save, throwsA(isA<BoardActionCancelled>()));
90+
await asked.future;
91+
if (changeSession) {
92+
connection.stamp = Object();
93+
} else {
94+
final doc = c.read(editorDocumentProvider.notifier);
95+
doc.setContent('changed');
96+
doc.setContent('print(1)');
97+
}
98+
choice.complete(true);
99+
await result;
100+
expect(connection.putFileCalls, isEmpty);
101+
}
102+
},
103+
);
104+
test(
105+
'Files Upload previews cwd destination and declining sends no PUT',
106+
() async {
107+
final requests = <BoardTargetRequest>[];
108+
final connection = SessionConnection();
109+
final c = setup(connection, (request) async {
110+
requests.add(request);
111+
return false;
112+
});
113+
final files = c.read(fileExplorerProvider.notifier);
114+
await pumpEventQueue();
115+
await connection.mkdir('/lib');
116+
await files.into('lib');
117+
await files.uploadCurrentBuffer();
118+
expect(requests.single.path, '/lib/restored.py');
119+
expect(requests.single.run, isFalse);
120+
expect(connection.putFileCalls, isEmpty);
121+
},
122+
);
123+
}

0 commit comments

Comments
 (0)