A todo and personal organisation app
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 112 lines 3.8 kB view raw
1import 'dart:io'; 2import 'package:test/test.dart'; 3import 'package:shelf/shelf.dart'; 4import 'package:toadist_server/routes/files_api.dart'; 5import 'package:toadist_server/middleware/auth_middleware.dart'; 6import 'package:toadist_server/services/storage_service.dart'; 7import 'package:toadist_server/services/database_service.dart'; 8 9/// In-memory fake storage that avoids real database connections. 10/// Overrides only the file-ownership methods needed for security tests. 11class FakeStorageService extends StorageService { 12 final Map<String, String> _fileOwners = {}; 13 14 FakeStorageService() 15 : super( 16 db: DatabaseService( 17 config: const DatabaseConfig( 18 host: 'localhost', 19 port: 5432, 20 database: 'fake', 21 username: 'fake', 22 password: 'fake'))); 23 24 @override 25 Future<void> storeFileOwner( 26 String uniqueFilename, String userId, String originalFilename) async { 27 _fileOwners[uniqueFilename] = userId; 28 } 29 30 @override 31 Future<String?> getFileOwner(String uniqueFilename) async { 32 return _fileOwners[uniqueFilename]; 33 } 34} 35 36/// Build a GET request for the given file name, optionally with a userId in 37/// the auth context (as the auth middleware would populate it). 38Request _buildDownloadRequest(String filename, {String? userId}) { 39 final context = userId != null 40 ? {userContextKey: <String, dynamic>{'userId': userId, 'username': 'testuser'}} 41 : <String, Object>{}; 42 return Request( 43 'GET', 44 Uri.parse('http://localhost/$filename'), 45 context: context, 46 ); 47} 48 49void main() { 50 group('FilesApi – download security', () { 51 late FakeStorageService fakeStorage; 52 late FilesApi filesApi; 53 54 setUp(() { 55 fakeStorage = FakeStorageService(); 56 filesApi = FilesApi(storage: fakeStorage); 57 }); 58 59 test('unauthenticated request returns 401', () async { 60 final request = _buildDownloadRequest('secret.txt'); 61 final response = await filesApi.router(request); 62 expect(response.statusCode, 401); 63 }); 64 65 test('file owned by another user returns 403', () async { 66 await fakeStorage.storeFileOwner( 67 'abc123.txt', 'owner-user-id', 'original.txt'); 68 69 final request = 70 _buildDownloadRequest('abc123.txt', userId: 'attacker-user-id'); 71 final response = await filesApi.router(request); 72 expect(response.statusCode, 403); 73 }); 74 75 test('unknown file returns 404 for authenticated user', () async { 76 // No entry in fakeStorage for this filename 77 final request = _buildDownloadRequest( 78 'no-such-file-${DateTime.now().millisecondsSinceEpoch}.txt', 79 userId: 'some-user-id'); 80 final response = await filesApi.router(request); 81 expect(response.statusCode, 404); 82 }); 83 84 test('file owner receives file contents', () async { 85 const ownerId = 'owner-user-id'; 86 const uniqueFilename = 'owner-test-unit.txt'; 87 const content = 'secret file content'; 88 89 // Create uploads dir relative to test CWD and write a test file 90 final uploadsDir = Directory('uploads'); 91 if (!await uploadsDir.exists()) { 92 await uploadsDir.create(); 93 } 94 final testFile = File('uploads/$uniqueFilename'); 95 await testFile.writeAsString(content); 96 97 try { 98 await fakeStorage.storeFileOwner( 99 uniqueFilename, ownerId, 'original.txt'); 100 101 final request = 102 _buildDownloadRequest(uniqueFilename, userId: ownerId); 103 final response = await filesApi.router(request); 104 105 expect(response.statusCode, 200); 106 expect(await response.readAsString(), content); 107 } finally { 108 if (await testFile.exists()) await testFile.delete(); 109 } 110 }); 111 }); 112}