Skip to content

Commit

Permalink
feat(storage): multi bucket remove (#5598)
Browse files Browse the repository at this point in the history
  • Loading branch information
Equartey authored and Nika Hassani committed Nov 20, 2024
1 parent e65ee84 commit 6af829c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:aws_common/aws_common.dart';
import 'package:amplify_core/amplify_core.dart';

/// {@template amplify_core.storage.remove_options}
/// Configurable options for `Amplify.Storage.remove`.
Expand All @@ -14,20 +14,25 @@ class StorageRemoveOptions
/// {@macro amplify_core.storage.remove_options}
const StorageRemoveOptions({
this.pluginOptions,
this.bucket,
});

/// {@macro amplify_core.storage.remove_plugin_options}
final StorageRemovePluginOptions? pluginOptions;

/// Optionally specify which bucket to target
final StorageBucket? bucket;

@override
List<Object?> get props => [pluginOptions];
List<Object?> get props => [pluginOptions, bucket];

@override
String get runtimeTypeName => 'StorageRemoveOptions';

@override
Map<String, Object?> toJson() => {
'pluginOptions': pluginOptions?.toJson(),
'bucket': bucket,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,116 @@ void main() {
});
});

group('Multi-bucket', () {
final mainBucket = StorageBucket.fromOutputs(
'Storage Integ Test main bucket',
);
final secondaryBucket = StorageBucket.fromOutputs(
'Storage Integ Test secondary bucket',
);
final path = 'public/multi-bucket-remove-${uuid()}';
final storagePath = StoragePath.fromString(path);
setUp(() async {
// upload to main bucket
await Amplify.Storage.uploadData(
data: StorageDataPayload.bytes('data'.codeUnits),
path: storagePath,
bucket: mainBucket,
).result;
});

testWidgets('removes from multiple buckets', (_) async {
expect(
await objectExists(
storagePath,
bucket: mainBucket,
),
true,
);

// upload to secondary bucket
await Amplify.Storage.uploadData(
data: StorageDataPayload.bytes('data'.codeUnits),
path: storagePath,
bucket: secondaryBucket,
).result;

expect(
await objectExists(
storagePath,
bucket: secondaryBucket,
),
true,
);

final mainResult = await Amplify.Storage.remove(
path: storagePath,
options: StorageRemoveOptions(bucket: mainBucket),
).result;
expect(mainResult.removedItem.path, path);

// Assert path was only removed from the main bucket
expect(
await objectExists(
storagePath,
bucket: mainBucket,
),
false,
);
expect(
await objectExists(
storagePath,
bucket: secondaryBucket,
),
true,
);

final secondaryResult = await Amplify.Storage.remove(
path: storagePath,
options: StorageRemoveOptions(bucket: secondaryBucket),
).result;
expect(secondaryResult.removedItem.path, path);
expect(
await objectExists(
storagePath,
bucket: secondaryBucket,
),
false,
);
});

testWidgets('removes when present in bucket', (_) async {
expect(
await objectExists(
storagePath,
bucket: mainBucket,
),
true,
);
final mainResult = await Amplify.Storage.remove(
path: storagePath,
options: StorageRemoveOptions(bucket: mainBucket),
).result;
expect(mainResult.removedItem.path, path);
expect(
await objectExists(
storagePath,
bucket: mainBucket,
),
false,
);

await expectLater(
Amplify.Storage.remove(
path: storagePath,
options: StorageRemoveOptions(bucket: secondaryBucket),
).result,
completes,
reason: 'non existent path does not throw',
);
});
});

testWidgets('unauthorized path', (_) async {
await expectLater(
() => Amplify.Storage.remove(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:amplify_core/amplify_core.dart';

/// Returns true if an object exists at the given [path].
Future<bool> objectExists(StoragePath path) async {
Future<bool> objectExists(StoragePath path, {StorageBucket? bucket}) async {
try {
await Amplify.Storage.getProperties(path: path).result;
await Amplify.Storage.getProperties(
path: path,
options: StorageGetPropertiesOptions(bucket: bucket),
).result;
return true;
} on StorageNotFoundException {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ void addTearDownPaths(List<StoragePath> paths) {
);
}

/// Adds a tear down to remove the same object in multiple [buckets].
void addTearDownMultiBucket(StoragePath path, List<StorageBucket> buckets) {
addTearDown(
() {
try {
return Future.wait(
buckets.map(
(bucket) => Amplify.Storage.remove(
path: path,
options: StorageRemoveOptions(bucket: bucket),
).result,
),
);
} on Exception catch (e) {
_logger.warn('Failed to remove files after test', e);
rethrow;
}
},
);
}

/// Adds a tear down to delete the current user.
void addTearDownCurrentUser() {
addTearDown(() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ class AmplifyStorageS3Dart extends StoragePluginInterface

final s3Options = StorageRemoveOptions(
pluginOptions: s3PluginOptions,
bucket: options?.bucket,
);

return S3RemoveOperation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class StorageS3Service {
return S3GetPropertiesResult(
storageItem: S3Item.fromHeadObjectOutput(
await headObject(
s3client: _defaultS3Client,
s3client: s3ClientInfo.client,
bucket: s3ClientInfo.bucketName,
key: resolvedPath,
),
Expand Down Expand Up @@ -456,11 +456,12 @@ class StorageS3Service {
required StoragePath path,
required StorageRemoveOptions options,
}) async {
final s3ClientInfo = getS3ClientInfo(storageBucket: options.bucket);
final resolvedPath = await _pathResolver.resolvePath(path: path);

await _deleteObject(
s3client: _defaultS3Client,
bucket: _storageOutputs.bucketName,
s3client: s3ClientInfo.client,
bucket: s3ClientInfo.bucketName,
key: resolvedPath,
);

Expand Down

0 comments on commit 6af829c

Please sign in to comment.