From d817f031954d8ef209aad85181f7a53eb1726c57 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Thu, 21 Mar 2024 10:23:37 -0400 Subject: [PATCH] feat(storage): copy and move APIs (#4569) * chore: update copy api * chore: update integ tests for copy * chore: removed unused code * chore: add back test --- .../category/amplify_storage_category.dart | 29 +- .../amplify_storage_plugin_interface.dart | 13 +- .../lib/src/types/storage/copy_request.dart | 4 +- .../integration_test/use_case_test.dart | 101 +------ .../example/bin/example.dart | 62 +--- .../lib/src/amplify_storage_s3_dart_impl.dart | 47 +-- .../service/storage_s3_service_impl.dart | 108 +------ .../test/amplify_storage_s3_dart_test.dart | 153 +--------- .../storage_s3_service_test.dart | 279 +----------------- 9 files changed, 49 insertions(+), 747 deletions(-) diff --git a/packages/amplify_core/lib/src/category/amplify_storage_category.dart b/packages/amplify_core/lib/src/category/amplify_storage_category.dart index e889b70554..87e2913c7e 100644 --- a/packages/amplify_core/lib/src/category/amplify_storage_category.dart +++ b/packages/amplify_core/lib/src/category/amplify_storage_category.dart @@ -194,8 +194,8 @@ class StorageCategory extends AmplifyCategory { /// corresponding [StorageAccessLevel]. /// {@endtemplate} StorageCopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { return identifyCall( @@ -208,31 +208,6 @@ class StorageCategory extends AmplifyCategory { ); } - /// {@template amplify_core.amplify_storage_category.move} - /// Moves [source] to [destination] with optional [StorageMoveOptions], - /// and returns a [StorageMoveOperation]. - /// - /// This API performs two consecutive S3 service calls: - /// 1. copy the source object to destination objection - /// 2. delete the source object - /// - /// {@macro amplify_core.amplify_storage_category.copy_source} - /// {@endtemplate} - StorageMoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - return identifyCall( - StorageCategoryMethod.move, - () => defaultPlugin.move( - source: source, - destination: destination, - options: options, - ), - ); - } - /// {@template amplify_core.amplify_storage_category.remove} /// Removes an object specified by [key] with optional [StorageRemoveOptions], /// and returns a [StorageRemoveOperation]. diff --git a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart index 69167c457c..5dff8be3d6 100644 --- a/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart +++ b/packages/amplify_core/lib/src/plugin/amplify_storage_plugin_interface.dart @@ -79,22 +79,13 @@ abstract class StoragePluginInterface extends AmplifyPluginInterface { /// {@macro amplify_core.amplify_storage_category.copy} StorageCopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { throw UnimplementedError('copy() has not been implemented.'); } - /// {@macro amplify_core.amplify_storage_category.move} - StorageMoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - throw UnimplementedError('move() has not been implemented.'); - } - /// {@macro amplify_core.amplify_storage_category.remove} StorageRemoveOperation remove({ required String key, diff --git a/packages/amplify_core/lib/src/types/storage/copy_request.dart b/packages/amplify_core/lib/src/types/storage/copy_request.dart index d7bc04cee8..0bfddaa1bd 100644 --- a/packages/amplify_core/lib/src/types/storage/copy_request.dart +++ b/packages/amplify_core/lib/src/types/storage/copy_request.dart @@ -15,10 +15,10 @@ class StorageCopyRequest { }); /// Copy source. - final StorageItemWithAccessLevel source; + final StoragePath source; /// Copy destination. - final StorageItemWithAccessLevel destination; + final StoragePath destination; /// Configurable options of the [StorageCopyRequest]. final StorageCopyOptions? options; diff --git a/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart b/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart index 6902ac5ed9..3fbb0cd447 100644 --- a/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart +++ b/packages/storage/amplify_storage_s3/example/integration_test/use_case_test.dart @@ -56,9 +56,7 @@ void main() { testLargeFileBytes[0] = 101; testLargeFileBytes[5 * 1024] = 102; final testObjectKey1 = 'user1-guest-object-${uuid()}'; - final testObject1MoveKey = 'user1-guest-object-move-${uuid()}'; final testObjectKey2 = 'user1-protected-object-${uuid()}'; - final testObject2MoveKey = 'user1-protected-object-move-${uuid()}'; final testObjectKey3 = 'user1-private-object-${uuid()}'; final testObject3CopyKey = 'user1-private-object-copy-${uuid()}'; final testObject3CopyMoveKey = 'user1-private-object-copy-move-${uuid()}'; @@ -407,13 +405,11 @@ void main() { 'copy object with access level private for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.copy( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObjectKey3), - accessLevel: StorageAccessLevel.private, + source: StoragePath.withIdentityId( + (identityId) => '/private/$identityId/$testObjectKey3', ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyKey), - accessLevel: StorageAccessLevel.private, + destination: StoragePath.withIdentityId( + (identityId) => '/private/$identityId/$testObject3CopyKey', ), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions( @@ -426,41 +422,6 @@ void main() { expect(result.copiedItem.eTag, isNotEmpty); }); - testWidgets( - skip: true, - 'move object with access level private for the currently signed in user', - (WidgetTester tester) async { - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyKey), - accessLevel: StorageAccessLevel.private, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject3CopyMoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ), - ).result; - - expect(result.movedItem.key, testObject3CopyMoveKey); - expect(result.movedItem.eTag, isNotEmpty); - - final listedObjects = await Amplify.Storage.list( - options: const StorageListOptions( - accessLevel: StorageAccessLevel.private, - ), - ).result; - - expect( - listedObjects.items.map((item) => item.key), - isNot( - contains(testObject3CopyKey), - ), - ); - }); - testWidgets( 'delete object with access level private for the currently signed in user', (WidgetTester tester) async { @@ -696,41 +657,16 @@ void main() { ); }); - testWidgets( - skip: true, - 'move object with access level guest for the currently signed in user', - (WidgetTester tester) async { - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObjectKey1), - accessLevel: StorageAccessLevel.guest, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject1MoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions( - getProperties: true, - ), - ), - ).result; - - expect(result.movedItem.eTag, isNotEmpty); - }); - testWidgets( skip: true, 'copy object (belongs to other user) with access level protected' ' for the currently signed in user', (WidgetTester tester) async { final result = await Amplify.Storage.copy( - source: S3ItemWithAccessLevel.forIdentity( - user1IdentityId, - storageItem: S3Item(key: testObjectKey2), + source: StoragePath.fromString( + '/protected/$user1IdentityId/$testObjectKey2', ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject2MoveKey), - accessLevel: StorageAccessLevel.private, + destination: StoragePath.withIdentityId( + (identityId) => '/private/$identityId/$testObjectKey2', ), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions( @@ -876,27 +812,6 @@ void main() { await Amplify.Auth.signOut(); }); - testWidgets( - skip: true, - 'move object with access level protected as object owner', - (WidgetTester tester) async { - final result = await Amplify.Storage.move( - source: S3ItemWithAccessLevel.forIdentity( - user1IdentityId, - storageItem: S3Item(key: testObjectKey2), - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: testObject2MoveKey), - accessLevel: StorageAccessLevel.private, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ), - ).result; - - expect(result.movedItem.eTag, isNotEmpty); - }); - testWidgets( skip: true, 'remove many objects belongs to the currently signed user', diff --git a/packages/storage/amplify_storage_s3_dart/example/bin/example.dart b/packages/storage/amplify_storage_s3_dart/example/bin/example.dart index c949181bdf..335667be33 100644 --- a/packages/storage/amplify_storage_s3_dart/example/bin/example.dart +++ b/packages/storage/amplify_storage_s3_dart/example/bin/example.dart @@ -41,8 +41,7 @@ Future main() async { 3. getUrl 4. download data 5. download file 6. upload data url 7. upload file 8. copy - 9. move 10. remove - 0. exit + 9. remove 0. exit '''); final operationNum = int.tryParse(operation); @@ -64,8 +63,6 @@ Future main() async { case 8: await copyOperation(); case 9: - await moveOperation(); - case 10: await removeOperation(); case null: break; @@ -369,24 +366,12 @@ Future uploadFileOperation() async { Future copyOperation() async { final sourceKey = prompt('Enter the key of the source object: '); - final sourceStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the source object: ', - ); final destinationKey = prompt('Enter the key of the destination object: '); - final destinationStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the destination object: ', - ); final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); final copyOperation = s3Plugin.copy( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: sourceKey), - accessLevel: sourceStorageAccessLevel, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: destinationKey), - accessLevel: destinationStorageAccessLevel, - ), + source: StoragePath.fromString(sourceKey), + destination: StoragePath.fromString(destinationKey), options: const StorageCopyOptions( pluginOptions: S3CopyPluginOptions(getProperties: true), ), @@ -408,47 +393,6 @@ Future copyOperation() async { } } -Future moveOperation() async { - final sourceKey = prompt('Enter the key of the source object: '); - final sourceStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the source object: ', - ); - final destinationKey = prompt('Enter the key of the destination object: '); - final destinationStorageAccessLevel = promptStorageAccessLevel( - 'Choose the storage access level associated with the destination object: ', - ); - - final s3Plugin = Amplify.Storage.getPlugin(AmplifyStorageS3Dart.pluginKey); - final moveOperation = s3Plugin.move( - source: S3ItemWithAccessLevel( - storageItem: S3Item(key: sourceKey), - accessLevel: sourceStorageAccessLevel, - ), - destination: S3ItemWithAccessLevel( - storageItem: S3Item(key: destinationKey), - accessLevel: destinationStorageAccessLevel, - ), - options: const StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ), - ); - - try { - final result = await moveOperation.result; - stdout - ..writeln('Copied object: ') - ..writeln('key: ${result.movedItem.key}') - ..writeln('size: ${result.movedItem.size}') - ..writeln('lastModified: ${result.movedItem.lastModified}') - ..writeln('eTag: ${result.movedItem.eTag}') - ..writeln('metadata: ${result.movedItem.metadata}'); - } on Exception catch (error) { - stderr - ..writeln('Something went wrong...') - ..writeln(error); - } -} - Future removeOperation() async { final key = prompt('Enter the object key to remove: '); final accessLevel = promptStorageAccessLevel( diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart index 3ccd4f43e4..c12f56058a 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/amplify_storage_s3_dart_impl.dart @@ -363,13 +363,10 @@ class AmplifyStorageS3Dart extends StoragePluginInterface @override S3CopyOperation copy({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, StorageCopyOptions? options, }) { - final s3Source = S3ItemWithAccessLevel.from(source); - final s3Destination = S3ItemWithAccessLevel.from(destination); - final s3PluginOptions = reifyPluginOptions( pluginOptions: options?.pluginOptions, defaultPluginOptions: const S3CopyPluginOptions(), @@ -381,45 +378,13 @@ class AmplifyStorageS3Dart extends StoragePluginInterface return S3CopyOperation( request: StorageCopyRequest( - source: s3Source, - destination: s3Destination, + source: source, + destination: destination, options: options, ), result: storageS3Service.copy( - source: s3Source, - destination: s3Destination, - options: s3Options, - ), - ); - } - - @override - S3MoveOperation move({ - required StorageItemWithAccessLevel source, - required StorageItemWithAccessLevel destination, - StorageMoveOptions? options, - }) { - final s3PluginOptions = reifyPluginOptions( - pluginOptions: options?.pluginOptions, - defaultPluginOptions: const S3MovePluginOptions(), - ); - - final s3Options = StorageMoveOptions( - pluginOptions: s3PluginOptions, - ); - - final s3Source = S3ItemWithAccessLevel.from(source); - final s3Destination = S3ItemWithAccessLevel.from(destination); - - return S3MoveOperation( - request: StorageMoveRequest( - source: s3Source, - destination: s3Destination, - options: options, - ), - result: storageS3Service.move( - source: s3Source, - destination: s3Destination, + source: source, + destination: destination, options: s3Options, ), ); diff --git a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart index 53661b8aef..c5a8227464 100644 --- a/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart +++ b/packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart @@ -424,36 +424,21 @@ class StorageS3Service { /// /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} Future copy({ - required S3ItemWithAccessLevel source, - required S3ItemWithAccessLevel destination, + required StoragePath source, + required StoragePath destination, required StorageCopyOptions options, }) async { final s3PluginOptions = options.pluginOptions as S3CopyPluginOptions? ?? const S3CopyPluginOptions(); - final resolvedPrefixes = await Future.wait([ - getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: source.accessLevel, - identityId: source.targetIdentityId, - ), - getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: destination.accessLevel, - identityId: destination.targetIdentityId, - ), - ]); - final sourceKey = '${resolvedPrefixes[0]}${source.storageItem.key}'; - final destinationKey = - '${resolvedPrefixes[1]}${destination.storageItem.key}'; + final sourcePath = await _pathResolver.resolvePath(path: source); + final destinationPath = await _pathResolver.resolvePath(path: destination); final copyRequest = s3.CopyObjectRequest.build((builder) { builder ..bucket = _s3PluginConfig.bucket - ..copySource = '${_s3PluginConfig.bucket}/$sourceKey' - ..key = destinationKey + ..copySource = '${_s3PluginConfig.bucket}$sourcePath' + ..key = destinationPath ..metadataDirective = s3.MetadataDirective.copy; }); @@ -472,91 +457,16 @@ class StorageS3Service { await headObject( s3client: _defaultS3Client, bucket: _s3PluginConfig.bucket, - key: destinationKey, + key: destinationPath, ), - key: destination.storageItem.key, - path: destination.storageItem.path, + path: sourcePath, ) : S3Item( - key: destination.storageItem.key, - path: destination.storageItem.path, + path: destinationPath, ), ); } - /// Takes in input from [AmplifyStorageS3Dart.move] API to compose a - /// [s3.CopyObjectRequest] and send to S3 service to copy `source` to - /// `destination`, followed by a [s3.DeleteObjectRequest] to delete the - /// `source`, then returns a [S3MoveResult] based on the `key` of - /// `destination`. - /// - /// When [S3CopyPluginOptions.getProperties] is set to `true`, when both - /// [s3.CopyObjectRequest] and [s3.DeleteObjectRequest] succeed, the API - /// creates a [s3.HeadObjectRequest] with the `key` of the `destination`, - /// and sends to S3 Service, then returns a [S3CopyResult] based on - /// the [s3.HeadObjectOutput] returned by [s3.S3Client.headObject] API. - /// - /// {@macro storage.s3_service.throw_exception_unknown_smithy_exception} - Future move({ - required S3ItemWithAccessLevel source, - required S3ItemWithAccessLevel destination, - required StorageMoveOptions options, - }) async { - final s3PluginOptions = options.pluginOptions as S3MovePluginOptions? ?? - const S3MovePluginOptions(); - - late S3CopyResult copyResult; - - try { - copyResult = await copy( - source: source, - destination: destination, - options: StorageCopyOptions( - pluginOptions: options.pluginOptions != null - ? S3CopyPluginOptions( - getProperties: s3PluginOptions.getProperties, - ) - : const S3CopyPluginOptions(), - ), - ); - } on StorageException catch (error) { - // Normally copy should not fail during moving, if it fails it's a - // "unknown" failure wrapping the underlying exception. - throw UnknownException( - 'Copying the source object failed during the move operation.', - recoverySuggestion: 'Review the underlying exception.', - underlyingException: error, - ); - } - - final resolvedSourcePrefix = await getResolvedPrefix( - prefixResolver: _prefixResolver, - logger: _logger, - accessLevel: source.accessLevel, - identityId: source.targetIdentityId, - ); - - final keyToRemove = '$resolvedSourcePrefix${source.storageItem.key}'; - - try { - await _deleteObject( - s3client: _defaultS3Client, - bucket: _s3PluginConfig.bucket, - key: keyToRemove, - ); - } on StorageException catch (error) { - // Normally delete should not fail during moving, if it fails it's a - // "unknown" failure wrapping the underlying exception. - throw UnknownException( - 'Deleting the source object failed during the move operation.', - recoverySuggestion: 'Review the underlying exception.', - underlyingException: error, - ); - } - - return S3MoveResult(movedItem: copyResult.copiedItem); - } - /// Takes in input from [AmplifyStorageS3Dart.remove] API to compose a /// [s3.DeleteObjectRequest] and send to S3 service, then returns a /// [S3RemoveResult] based on the `key` to be removed. diff --git a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart index 4031d5ad02..c2f812e7ee 100644 --- a/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/amplify_storage_s3_dart_test.dart @@ -10,6 +10,7 @@ import 'package:test/test.dart'; import 'test_utils/mocks.dart'; import 'test_utils/test_custom_prefix_resolver.dart'; +import 'test_utils/test_path_resolver.dart'; import 'test_utils/test_token_provider.dart'; const testPath = StoragePath.fromString('/some/path.txt'); @@ -912,26 +913,19 @@ void main() { }); group('copy() API', () { - const sourceKey = 'source-key'; - const destinationKey = 'destination-key'; - - const testSource = StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ); - const testDestination = StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.protected, + const testSource = StoragePath.fromString('/public/source-key'); + final testDestination = StoragePath.withIdentityId( + (identityId) => '/protected/$identityId/destination-key', ); - final testResult = S3CopyResult(copiedItem: S3Item(key: destinationKey)); + final testResult = S3CopyResult( + copiedItem: S3Item(path: TestPathResolver.path), + ); setUpAll(() { registerFallbackValue(const StorageCopyOptions()); registerFallbackValue( - S3ItemWithAccessLevel( - storageItem: S3Item(key: 'some-key'), - ), + const StoragePath.fromString('/public/source-key'), ); }); @@ -956,9 +950,8 @@ void main() { final captured = verify( () => storageS3Service.copy( - source: captureAny(named: 'source'), - destination: - captureAny(named: 'destination'), + source: captureAny(named: 'source'), + destination: captureAny(named: 'destination'), options: captureAny( named: 'options', ), @@ -968,34 +961,8 @@ void main() { final capturedDestination = captured[1]; final capturedOptions = captured[2]; - expect( - capturedSource, - isA() - .having( - (i) => i.accessLevel, - 'accessLevel', - testSource.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testSource.storageItem.key, - ), - ); - expect( - capturedDestination, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testDestination.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testDestination.storageItem.key, - ), - ); + expect(capturedSource, testSource); + expect(capturedDestination, testDestination); expect( capturedOptions, @@ -1017,102 +984,6 @@ void main() { }); }); - group('move() API', () { - const sourceKey = 'source-key'; - const destinationKey = 'destination-key'; - - const testSource = StorageItemWithAccessLevel( - storageItem: StorageItem(key: sourceKey), - accessLevel: StorageAccessLevel.guest, - ); - - const testDestination = StorageItemWithAccessLevel( - storageItem: StorageItem(key: destinationKey), - accessLevel: StorageAccessLevel.protected, - ); - - final testResult = S3MoveResult(movedItem: S3Item(key: destinationKey)); - - setUpAll(() { - registerFallbackValue(const StorageMoveOptions()); - registerFallbackValue( - S3ItemWithAccessLevel( - storageItem: S3Item(key: 'some-key'), - ), - ); - }); - - test( - 'should forward options with default getProperties value to StorageS3Service.move() API', - () async { - when( - () => storageS3Service.move( - source: any(named: 'source'), - destination: any(named: 'destination'), - options: any(named: 'options'), - ), - ).thenAnswer((_) async => testResult); - - final moveOperation = storageS3Plugin.move( - source: testSource, - destination: testDestination, - ); - - final captured = verify( - () => storageS3Service.move( - source: captureAny(named: 'source'), - destination: - captureAny(named: 'destination'), - options: captureAny( - named: 'options', - ), - ), - ).captured; - final capturedSource = captured[0]; - final capturedDestination = captured[1]; - final capturedOptions = captured[2]; - expect( - capturedSource, - isA() - .having( - (i) => i.accessLevel, - 'accessLevel', - testSource.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testSource.storageItem.key, - ), - ); - expect( - capturedDestination, - isA() - .having( - (o) => o.accessLevel, - 'accessLevel', - testDestination.accessLevel, - ) - .having( - (i) => i.storageItem.key, - 'key', - testDestination.storageItem.key, - ), - ); - expect( - capturedOptions, - isA().having( - (o) => (o.pluginOptions! as S3MovePluginOptions).getProperties, - 'getProperties', - false, - ), - ); - - final result = await moveOperation.result; - expect(result, testResult); - }); - }); - group('remove() API', () { const testKey = 'object-to-remove'; final testResult = S3RemoveResult( diff --git a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart index 6d8f1164ae..547ed4ed16 100644 --- a/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart +++ b/packages/storage/amplify_storage_s3_dart/test/storage_s3_service/storage_s3_service_test.dart @@ -930,11 +930,8 @@ void main() { group('copy() API', () { late S3CopyResult copyResult; - final testSourceItem = S3Item(key: 'source'); - final testDestinationItem = S3Item(key: 'destination'); - final testSource = S3ItemWithAccessLevel(storageItem: testSourceItem); - final testDestination = - S3ItemWithAccessLevel(storageItem: testDestinationItem); + const testSource = StoragePath.fromString('/public/source'); + const testDestination = StoragePath.fromString('/public/destination'); setUpAll(() { registerFallbackValue( @@ -980,16 +977,11 @@ void main() { final request = capturedRequest as CopyObjectRequest; expect(request.bucket, testBucket); - expect( - request.copySource, - '$testBucket/${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); + expect(request.copySource, testBucket + TestPathResolver.path); }); test('should return correct S3CopyResult', () { - expect(copyResult.copiedItem.key, testDestination.storageItem.key); + expect(copyResult.copiedItem.path, TestPathResolver.path); }); test( @@ -1081,268 +1073,7 @@ void main() { final request = headObjectRequest as HeadObjectRequest; expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDestination.accessLevel, - )}${testDestinationItem.key}', - ); - }); - }); - - group('move() API', () { - late S3MoveResult moveResult; - final testSourceItem = S3Item(key: 'source'); - final testDestinationItem = S3Item(key: 'destination'); - final testSource = S3ItemWithAccessLevel(storageItem: testSourceItem); - final testDestination = - S3ItemWithAccessLevel(storageItem: testDestinationItem); - - setUpAll(() { - registerFallbackValue( - CopyObjectRequest( - bucket: 'fake bucket', - copySource: 'dummy source', - key: 'imposing destination', - ), - ); - registerFallbackValue( - DeleteObjectRequest( - bucket: 'fake bucket', - key: 'dummy key', - ), - ); - registerFallbackValue( - HeadObjectRequest( - bucket: 'fake bucket', - key: 'dummy key', - ), - ); - }); - - test( - 'should invoke S3Client.copyObject and S3Client.deleteObject with expected parameters', - () async { - const testOptions = StorageMoveOptions(); - final testCopyObjectOutput = CopyObjectOutput(); - final testDeleteObjectOutput = DeleteObjectOutput(); - final copySmithyOperation = MockSmithyOperation(); - final deleteSmithyOperation = MockSmithyOperation(); - when( - () => copySmithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => deleteSmithyOperation.result, - ).thenAnswer((_) async => testDeleteObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => copySmithyOperation); - - when( - () => s3Client.deleteObject(any()), - ).thenAnswer((_) => deleteSmithyOperation); - - moveResult = await storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ); - - final capturedCopyRequest = verify( - () => s3Client.copyObject(captureAny()), - ).captured.last; - - final capturedDeleteRequest = verify( - () => s3Client.deleteObject(captureAny()), - ).captured.last; - - expect(capturedCopyRequest is CopyObjectRequest, isTrue); - final copyRequest = capturedCopyRequest as CopyObjectRequest; - - expect(capturedDeleteRequest is DeleteObjectRequest, isTrue); - final deleteRequest = capturedDeleteRequest as DeleteObjectRequest; - - expect(copyRequest.bucket, testBucket); - expect( - copyRequest.copySource, - '$testBucket/${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); - - expect(deleteRequest.bucket, testBucket); - expect( - deleteRequest.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testSource.accessLevel, - )}${testSourceItem.key}', - ); - }); - - test('should return correct S3CopyResult', () { - expect(moveResult.movedItem.key, testDestination.storageItem.key); - }); - - test( - 'should throw StorageAccessDeniedException when UnknownSmithyHttpException' - ' with status code 403 returned from service while copying the source', - () async { - const testOptions = StorageMoveOptions(); - const testUnknownException = UnknownSmithyHttpException( - statusCode: 403, - body: 'Access denied.', - ); - - when( - () => s3Client.copyObject( - any(), - ), - ).thenThrow(testUnknownException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test('should handle AWSHttpException and throw NetworkException', - () async { - const testOptions = StorageMoveOptions(); - final testException = AWSHttpException( - AWSHttpRequest(method: AWSHttpMethod.put, uri: Uri()), - ); - - when( - () => s3Client.copyObject(any()), - ).thenThrow(testException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test( - 'should throw StorageHttpStatusException when UnknownSmithyHttpException' - ' with status code 500 returned from service while deleting the source', - () async { - const testOptions = StorageMoveOptions(); - const testUnknownException = UnknownSmithyHttpException( - statusCode: 500, - body: 'Internal error', - ); - final testCopyObjectOutput = CopyObjectOutput(); - final smithyOperation = MockSmithyOperation(); - - when( - () => smithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => smithyOperation); - - when( - () => s3Client.deleteObject( - any(), - ), - ).thenThrow(testUnknownException); - - expect( - storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ), - throwsA( - isA().having( - (o) => o.underlyingException, - 'underlyingException', - isA(), - ), - ), - ); - }); - - test( - 'should invoke S3Client.headObject with correct parameters when' - ' getProperties option is set to true', () async { - const testOptions = StorageMoveOptions( - pluginOptions: S3MovePluginOptions(getProperties: true), - ); - final testCopyObjectOutput = CopyObjectOutput(); - final testDeleteObjectOutput = DeleteObjectOutput(); - final testHeadObjectOutput = HeadObjectOutput(); - final copySmithyOperation = MockSmithyOperation(); - final deleteSmithyOperation = MockSmithyOperation(); - final headSmithyOperation = MockSmithyOperation(); - - when( - () => copySmithyOperation.result, - ).thenAnswer((_) async => testCopyObjectOutput); - - when( - () => deleteSmithyOperation.result, - ).thenAnswer((_) async => testDeleteObjectOutput); - - when( - () => headSmithyOperation.result, - ).thenAnswer((_) async => testHeadObjectOutput); - - when( - () => s3Client.copyObject(any()), - ).thenAnswer((_) => copySmithyOperation); - - when( - () => s3Client.deleteObject(any()), - ).thenAnswer((_) => deleteSmithyOperation); - - when( - () => s3Client.headObject(any()), - ).thenAnswer((_) => headSmithyOperation); - - await storageS3Service.move( - source: testSource, - destination: testDestination, - options: testOptions, - ); - - final headObjectRequest = verify( - () => s3Client.headObject(captureAny()), - ).captured.last; - - expect(headObjectRequest is HeadObjectRequest, isTrue); - final request = headObjectRequest as HeadObjectRequest; - - expect(request.bucket, testBucket); - expect( - request.key, - '${await testPrefixResolver.resolvePrefix( - accessLevel: testDestination.accessLevel, - )}${testDestinationItem.key}', - ); + expect(request.key, TestPathResolver.path); }); });