|
2 | 2 | using Amazon.S3.Model; |
3 | 3 | using Amazon.S3.Transfer; |
4 | 4 | using Amazon.S3.Transfer.Internal; |
| 5 | +using Amazon.S3.Util; |
5 | 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; |
6 | 7 | using Moq; |
7 | 8 | using System; |
@@ -1009,6 +1010,139 @@ public async Task ExecuteAsync_SequentialMode_DownloadsOneAtATime() |
1009 | 1010 |
|
1010 | 1011 | #endregion |
1011 | 1012 |
|
| 1013 | + #region Case-Insensitive Key Conflict Tests |
| 1014 | + |
| 1015 | + [TestMethod] |
| 1016 | + public async Task ExecuteAsync_CaseInsensitiveKeyConflict_AbortOnFailure_ThrowsAndNoFilesDownloaded() |
| 1017 | + { |
| 1018 | + var request = CreateDownloadDirectoryRequest(); |
| 1019 | + var isCaseSensitive = FileSystemHelper.IsDirectoryCaseSensitive(request.LocalDirectory); |
| 1020 | + if (isCaseSensitive) |
| 1021 | + { |
| 1022 | + return; |
| 1023 | + } |
| 1024 | + request.FailurePolicy = FailurePolicy.AbortOnFailure; |
| 1025 | + |
| 1026 | + var listResponse = new ListObjectsResponse |
| 1027 | + { |
| 1028 | + S3Objects = new List<S3Object> |
| 1029 | + { |
| 1030 | + new S3Object { Key = "prefix/File.txt", Size = 10 }, |
| 1031 | + new S3Object { Key = "prefix/file.txt", Size = 20 } |
| 1032 | + } |
| 1033 | + }; |
| 1034 | + |
| 1035 | + _mockS3Client.Setup(c => c.ListObjectsAsync( |
| 1036 | + It.IsAny<ListObjectsRequest>(), |
| 1037 | + It.IsAny<CancellationToken>())) |
| 1038 | + .ReturnsAsync(listResponse); |
| 1039 | + |
| 1040 | + var command = new DownloadDirectoryCommand(_mockS3Client.Object, request, _config, useMultipartDownload: false); |
| 1041 | + |
| 1042 | + try |
| 1043 | + { |
| 1044 | + await command.ExecuteAsync(CancellationToken.None); |
| 1045 | + Assert.Fail("Expected an exception due to case-insensitive key conflict."); |
| 1046 | + } |
| 1047 | + catch (Exception ex) |
| 1048 | + { |
| 1049 | + StringAssert.Contains(ex.Message, "conflict", "Exception message should indicate a key path conflict."); |
| 1050 | + } |
| 1051 | + } |
| 1052 | + |
| 1053 | + [TestMethod] |
| 1054 | + public async Task ExecuteAsync_CaseInsensitiveKeyConflict_ContinueOnFailure_ReportsFailureInResponse() |
| 1055 | + { |
| 1056 | + var request = CreateDownloadDirectoryRequest(); |
| 1057 | + var isCaseSensitive = FileSystemHelper.IsDirectoryCaseSensitive(request.LocalDirectory); |
| 1058 | + if (isCaseSensitive) |
| 1059 | + { |
| 1060 | + return; |
| 1061 | + } |
| 1062 | + request.FailurePolicy = FailurePolicy.ContinueOnFailure; |
| 1063 | + |
| 1064 | + var listResponse = new ListObjectsResponse |
| 1065 | + { |
| 1066 | + S3Objects = new List<S3Object> |
| 1067 | + { |
| 1068 | + new S3Object { Key = "prefix/File.txt", Size = 10 }, |
| 1069 | + new S3Object { Key = "prefix/file.txt", Size = 20 } |
| 1070 | + } |
| 1071 | + }; |
| 1072 | + |
| 1073 | + _mockS3Client.Setup(c => c.ListObjectsAsync( |
| 1074 | + It.IsAny<ListObjectsRequest>(), |
| 1075 | + It.IsAny<CancellationToken>())) |
| 1076 | + .ReturnsAsync(listResponse); |
| 1077 | + |
| 1078 | + var command = new DownloadDirectoryCommand(_mockS3Client.Object, request, _config, useMultipartDownload: false); |
| 1079 | + |
| 1080 | + var response = await command.ExecuteAsync(CancellationToken.None); |
| 1081 | + |
| 1082 | + Assert.IsNotNull(response, "Response should not be null."); |
| 1083 | + Assert.AreEqual(0, response.ObjectsDownloaded, "No files should be downloaded when all conflicted."); |
| 1084 | + Assert.IsTrue(response.ObjectsFailed >= 1, "At least one failure should be recorded."); |
| 1085 | + Assert.IsNotNull(response.Errors, "Errors collection should be populated."); |
| 1086 | + Assert.IsTrue(response.Errors.Count >= 1, "Errors collection should contain at least one error."); |
| 1087 | + Assert.IsTrue(response.Result == DirectoryResult.Failure || |
| 1088 | + response.Result == DirectoryResult.PartialSuccess, |
| 1089 | + "Result should indicate failure or partial success due to conflicts."); |
| 1090 | + } |
| 1091 | + |
| 1092 | + [TestMethod] |
| 1093 | + public async Task ExecuteAsync_CaseInsensitiveKeyConflict_SkipCaseCheck_AllowsDownloads() |
| 1094 | + { |
| 1095 | + var request = CreateDownloadDirectoryRequest(); |
| 1096 | + var isCaseSensitive = FileSystemHelper.IsDirectoryCaseSensitive(request.LocalDirectory); |
| 1097 | + if (isCaseSensitive) |
| 1098 | + { |
| 1099 | + // This test validates behavior only on case-insensitive filesystems. |
| 1100 | + // On case-sensitive systems, there is no conflict to detect. |
| 1101 | + return; |
| 1102 | + } |
| 1103 | + |
| 1104 | + request.FailurePolicy = FailurePolicy.AbortOnFailure; |
| 1105 | + |
| 1106 | + var listResponse = new ListObjectsResponse |
| 1107 | + { |
| 1108 | + S3Objects = new List<S3Object> |
| 1109 | + { |
| 1110 | + new S3Object { Key = "prefix/File.txt", Size = 10 }, |
| 1111 | + new S3Object { Key = "prefix/file.txt", Size = 20 } |
| 1112 | + } |
| 1113 | + }; |
| 1114 | + |
| 1115 | + _mockS3Client.Setup(c => c.ListObjectsAsync( |
| 1116 | + It.IsAny<ListObjectsRequest>(), |
| 1117 | + It.IsAny<CancellationToken>())) |
| 1118 | + .ReturnsAsync(listResponse); |
| 1119 | + |
| 1120 | + SetupGetObjectForFile("prefix/File.txt", 10, setupForMultipart: false); |
| 1121 | + SetupGetObjectForFile("prefix/file.txt", 20, setupForMultipart: false); |
| 1122 | + |
| 1123 | + // Enable skipping of directory case-sensitivity checks so that the |
| 1124 | + // conflict detection logic is bypassed. |
| 1125 | + var config = new TransferUtilityConfig |
| 1126 | + { |
| 1127 | + ConcurrentServiceRequests = 4, |
| 1128 | + SkipDirectoryCaseSensitivityCheck = true |
| 1129 | + }; |
| 1130 | + |
| 1131 | + var command = new DownloadDirectoryCommand(_mockS3Client.Object, request, config, useMultipartDownload: false); |
| 1132 | + |
| 1133 | + // Act: with the skip flag enabled, the operation should not throw a |
| 1134 | + // conflict exception and should attempt to download objects. |
| 1135 | + var response = await command.ExecuteAsync(CancellationToken.None); |
| 1136 | + |
| 1137 | + Assert.IsNotNull(response, "Response should not be null when skipping case check."); |
| 1138 | + // Behavior of how many files succeed may vary by implementation |
| 1139 | + // (last one wins on overwrite), but we at least assert that no |
| 1140 | + // conflict exception prevented completion. |
| 1141 | + Assert.IsTrue(response.ObjectsDownloaded >= 1, "At least one object should have been downloaded when skipping case checks."); |
| 1142 | + } |
| 1143 | + |
| 1144 | + #endregion |
| 1145 | + |
1012 | 1146 | #region Helper Methods |
1013 | 1147 |
|
1014 | 1148 | private TransferUtilityDownloadDirectoryRequest CreateDownloadDirectoryRequest( |
|
0 commit comments