Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): multi bucket remove #5598

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
tyllark marked this conversation as resolved.
Show resolved Hide resolved
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
Loading