22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Collections . Concurrent ;
56using System . IO ;
67using System . Text ;
8+ using System . Threading ;
79using System . Threading . Tasks ;
810using Microsoft . WindowsAzure . Storage ;
911using Microsoft . WindowsAzure . Storage . Auth ;
1012using Microsoft . WindowsAzure . Storage . Blob ;
1113using Moq ;
1214using NuGetGallery . Diagnostics ;
1315using Xunit ;
16+ using Xunit . Abstractions ;
1417
1518namespace NuGetGallery
1619{
@@ -26,6 +29,7 @@ private delegate Task CopyAsync(
2629 string destFileName ) ;
2730
2831 private readonly BlobStorageFixture _fixture ;
32+ private readonly ITestOutputHelper _output ;
2933 private readonly string _testId ;
3034 private readonly string _prefixA ;
3135 private readonly string _prefixB ;
@@ -36,9 +40,10 @@ private delegate Task CopyAsync(
3640 private readonly CloudBlobCoreFileStorageService _targetA ;
3741 private readonly CloudBlobCoreFileStorageService _targetB ;
3842
39- public CloudBlobCoreFileStorageServiceIntegrationTests ( BlobStorageFixture fixture )
43+ public CloudBlobCoreFileStorageServiceIntegrationTests ( BlobStorageFixture fixture , ITestOutputHelper output )
4044 {
4145 _fixture = fixture ?? throw new ArgumentNullException ( nameof ( fixture ) ) ;
46+ _output = output ?? throw new ArgumentNullException ( nameof ( output ) ) ;
4247 _testId = Guid . NewGuid ( ) . ToString ( ) ;
4348 _prefixA = $ "{ _fixture . PrefixA } /{ _testId } ";
4449 _prefixB = $ "{ _fixture . PrefixB } /{ _testId } ";
@@ -53,6 +58,98 @@ public CloudBlobCoreFileStorageServiceIntegrationTests(BlobStorageFixture fixtur
5358 _targetB = new CloudBlobCoreFileStorageService ( _clientB , Mock . Of < IDiagnosticsService > ( ) ) ;
5459 }
5560
61+ [ BlobStorageFact ]
62+ public async Task ReturnsCurrentETagForIfMatch ( )
63+ {
64+ // Arrange
65+ var folderName = CoreConstants . ValidationFolderName ;
66+ var fileName = _prefixA ;
67+ await _targetA . SaveFileAsync ( folderName , fileName , new MemoryStream ( new byte [ 0 ] ) ) ;
68+ var initialReference = await _targetA . GetFileReferenceAsync ( folderName , fileName ) ;
69+ initialReference . OpenRead ( ) . Dispose ( ) ;
70+
71+ // Act
72+ var reference = await _targetA . GetFileReferenceAsync ( folderName , fileName , initialReference . ContentId ) ;
73+
74+ // Assert
75+ Assert . NotNull ( reference ) ;
76+ Assert . Null ( reference . OpenRead ( ) ) ;
77+ Assert . Equal ( initialReference . ContentId , reference . ContentId ) ;
78+ }
79+
80+ [ BlobStorageFact ]
81+ public async Task ReturnsNullForMissingBlob ( )
82+ {
83+ // Arrange
84+ var folderName = CoreConstants . ValidationFolderName ;
85+ var fileName = _prefixA ;
86+
87+ // Act
88+ var reference = await _targetA . GetFileReferenceAsync ( folderName , fileName ) ;
89+
90+ // Assert
91+ Assert . Null ( reference ) ;
92+ }
93+
94+ [ BlobStorageFact ]
95+ public async Task ReturnsTheETagMatchingTheContent ( )
96+ {
97+ // Arrange
98+ var folderName = CoreConstants . ValidationFolderName ;
99+ var fileName = _prefixA ;
100+ var contentToETag = new ConcurrentDictionary < string , string > ( ) ;
101+ var iterations = 20 ;
102+ var cts = new CancellationTokenSource ( ) ;
103+
104+ Func < Task > update = async ( ) =>
105+ {
106+ var container = _blobClientA . GetContainerReference ( folderName ) ;
107+ for ( var i = 1 ; i <= iterations && ! cts . IsCancellationRequested ; i ++ )
108+ {
109+ var blob = container . GetBlockBlobReference ( fileName ) ;
110+ var content = i . ToString ( ) ;
111+ await blob . UploadTextAsync ( content ) ;
112+ contentToETag [ content ] = blob . Properties . ETag ;
113+ _output . WriteLine ( $ "Content '{ content } ' should have etag '{ blob . Properties . ETag } '.") ;
114+ }
115+ } ;
116+
117+ Func < Task > check = async ( ) =>
118+ {
119+ string content = null ;
120+ while ( content != iterations . ToString ( ) )
121+ {
122+ var fileReference = await _targetA . GetFileReferenceAsync ( folderName , fileName ) ;
123+ if ( fileReference == null )
124+ {
125+ continue ;
126+ }
127+
128+ using ( var stream = fileReference . OpenRead ( ) )
129+ using ( var streamReader = new StreamReader ( stream ) )
130+ {
131+ content = await streamReader . ReadToEndAsync ( ) ;
132+ if ( contentToETag . TryGetValue ( content , out var expectedETag ) )
133+ {
134+ _output . WriteLine ( $ "Content '{ content } ' has etag '{ fileReference . ContentId } '.") ;
135+ if ( expectedETag != fileReference . ContentId )
136+ {
137+ cts . Cancel ( ) ;
138+ }
139+
140+ Assert . Equal ( expectedETag , fileReference . ContentId ) ;
141+ }
142+ }
143+ }
144+ } ;
145+
146+ // Act & Assert
147+ var updateTask = update ( ) ;
148+ var checkTask = check ( ) ;
149+ await checkTask ;
150+ await updateTask ;
151+ }
152+
56153 [ BlobStorageFact ]
57154 public async Task CanReadAndDeleteBlobUsingPrivilegedFileUri ( )
58155 {
0 commit comments