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 1 commit
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,91 @@ 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 {
// upload to secondary bucket
await Amplify.Storage.uploadData(
data: StorageDataPayload.bytes('data'.codeUnits),
path: storagePath,
bucket: secondaryBucket,
).result;
expect(
await objectExistsInBuckets(
storagePath,
[mainBucket, secondaryBucket],
),
true,
);

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

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

expect(
await objectExistsInBuckets(
storagePath,
[mainBucket, secondaryBucket],
),
false,
);
});

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

await expectLater(
Amplify.Storage.remove(
path: storagePath,
options: StorageRemoveOptions(bucket: secondaryBucket),
).result,
completes,
);
});
});

testWidgets('unauthorized path', (_) async {
await expectLater(
() => Amplify.Storage.remove(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,29 @@ import 'package:amplify_core/amplify_core.dart';
/// Returns true if an object exists at the given [path].
Future<bool> objectExists(StoragePath path) async {
try {
await Amplify.Storage.getProperties(path: path).result;
await Amplify.Storage.getProperties(
path: path,
).result;
return true;
} on StorageNotFoundException {
return false;
}
}

/// Returns true if an object exists in multiple [buckets] at [path].
Future<bool> objectExistsInBuckets(
StoragePath path,
List<StorageBucket> buckets,
) async {
try {
await Future.wait(
buckets.map(
(bucket) => 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 @@ -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