@@ -55,17 +55,20 @@ final class _FakeGithubApi extends Fake implements GithubApi {
5555}
5656
5757final class _SessionRecordingConnection extends RecordingConnection
58- implements ConnectionSessionStampSource {
58+ implements ConnectionSessionStampSource , ConnectionDirectoryListingSource {
5959 _SessionRecordingConnection ({
6060 List <List <RemoteEntry >>? directoryListings,
61+ List <bool >? listingTruncation,
6162 this .failPutAt,
6263 this .holdPutAt,
6364 }) : directoryListings =
6465 directoryListings ?? < List <RemoteEntry >> [< RemoteEntry > []],
66+ listingTruncation = listingTruncation ?? < bool > [false ],
6567 _sessionStamp = Object (),
6668 super (initial: ConnState .ready);
6769
6870 final List <List <RemoteEntry >> directoryListings;
71+ final List <bool > listingTruncation;
6972 final int ? failPutAt;
7073 final int ? holdPutAt;
7174
@@ -82,14 +85,28 @@ final class _SessionRecordingConnection extends RecordingConnection
8285 void advanceSession () => _sessionStamp = Object ();
8386
8487 @override
85- Future <List < RemoteEntry >> listDir (String path) async {
88+ Future <DirectoryListing > listDirWithMetadata (String path) async {
8689 operationLog.add ('list:$path ' );
87- if (directoryListings.isEmpty) return < RemoteEntry > [];
90+ if (directoryListings.isEmpty) {
91+ return const DirectoryListing (entries: < RemoteEntry > [], truncated: false );
92+ }
8893 final int index = _listIndex < directoryListings.length
8994 ? _listIndex
9095 : directoryListings.length - 1 ;
9196 _listIndex += 1 ;
92- return List <RemoteEntry >.of (directoryListings[index]);
97+ final int truncationIndex = index < listingTruncation.length
98+ ? index
99+ : listingTruncation.length - 1 ;
100+ return DirectoryListing (
101+ entries: directoryListings[index],
102+ truncated: listingTruncation[truncationIndex],
103+ );
104+ }
105+
106+ @override
107+ Future <List <RemoteEntry >> listDir (String path) async {
108+ final DirectoryListing listing = await listDirWithMetadata (path);
109+ return listing.entries;
93110 }
94111
95112 @override
@@ -133,13 +150,15 @@ final class _Harness {
133150 factory _Harness ({
134151 String cwd = '/flash' ,
135152 List <List <RemoteEntry >>? directoryListings,
153+ List <bool >? listingTruncation,
136154 int ? failPutAt,
137155 int ? holdPutAt,
138156 }) {
139157 // Share one chronological log so tests can prove that no PUT races ahead
140158 // of a pending or unvalidated Git blob.
141159 final _SessionRecordingConnection connection = _SessionRecordingConnection (
142160 directoryListings: directoryListings,
161+ listingTruncation: listingTruncation,
143162 failPutAt: failPutAt,
144163 holdPutAt: holdPutAt,
145164 );
@@ -217,12 +236,14 @@ void main() {
217236 _Harness harness ({
218237 String cwd = '/flash' ,
219238 List <List <RemoteEntry >>? directoryListings,
239+ List <bool >? listingTruncation,
220240 int ? failPutAt,
221241 int ? holdPutAt,
222242 }) {
223243 final _Harness value = _Harness (
224244 cwd: cwd,
225245 directoryListings: directoryListings,
246+ listingTruncation: listingTruncation,
226247 failPutAt: failPutAt,
227248 holdPutAt: holdPutAt,
228249 );
@@ -368,6 +389,25 @@ void main() {
368389 expect (h.connection.putFileCalls, isEmpty);
369390 });
370391
392+ test (
393+ 'a truncated board listing blocks review before any download' ,
394+ () async {
395+ final _Harness h = harness (
396+ directoryListings: < List <RemoteEntry >> [
397+ < RemoteEntry > [_remoteFile ('visible.py' )],
398+ ],
399+ listingTruncation: < bool > [true ],
400+ );
401+
402+ await expectLater (
403+ h.importer.review (_repository, < GithubEntry > [_file ('hidden.py' )]),
404+ throwsA (_failure (GithubFailureKind .incompleteBoardListing)),
405+ );
406+ expect (h.api.fetchedRemotePaths, isEmpty);
407+ expect (h.connection.putFileCalls, isEmpty);
408+ },
409+ );
410+
371411 test (
372412 'an existing file requires separate explicit overwrite consent' ,
373413 () async {
0 commit comments