/* * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gcp.storage; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.concurrent.TimeUnit; import com.google.cloud.WriteChannel; import com.google.cloud.storage.Blob; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Bucket; import com.google.cloud.storage.BucketInfo; import com.google.cloud.storage.HttpMethod; import com.google.cloud.storage.Storage; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.Resource; import org.springframework.core.io.WritableResource; import org.springframework.test.context.junit4.SpringRunner; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Tests for Google Cloud Storage. * * @author Vinicius Carvalho * @author Artem Bilan * @author Mike Eltsufin * @author Chengyuan Zhao */ @SpringBootTest(properties = "spring.main.banner-mode=off") @RunWith(SpringRunner.class) public class GoogleStorageTests { /** * Used to test exception messages and types. */ @Rule public ExpectedException expectedEx = ExpectedException.none(); @Value("gs://test-spring/images/spring.png") private Resource remoteResource; @Value("gs://test_spring/images/spring.png") private Resource remoteResourceWithUnderscore; @Value("gs://test-spring/") private Resource bucketResource; @Autowired private Storage mockStorage; @Test public void testEmptyPath() { this.expectedEx.expect(IllegalArgumentException.class); this.expectedEx.expectMessage("Invalid location: gs://"); new GoogleStorageResource(this.mockStorage, "gs://", false); } @Test public void testSlashPath() { this.expectedEx.expect(IllegalArgumentException.class); this.expectedEx.expectMessage("No bucket specified in the location: gs:///"); new GoogleStorageResource(this.mockStorage, "gs:///", false); } @Test public void testValidObject() throws Exception { BlobId validBlobId = BlobId.of("test-spring", "images/spring.png"); Blob mockedBlob = mock(Blob.class); when(mockedBlob.getSize()).thenReturn(4096L); when(this.mockStorage.get(eq(validBlobId))).thenReturn(mockedBlob); Assert.assertTrue(this.remoteResource.exists()); Assert.assertEquals(4096L, this.remoteResource.contentLength()); } @Test public void testValidObjectWithUnderscore() throws Exception { BlobId validBlobWithUnderscore = BlobId.of("test_spring", "images/spring.png"); Blob mockedBlob = mock(Blob.class); when(mockStorage.get(eq(validBlobWithUnderscore))).thenReturn(mockedBlob); Assert.assertTrue(this.remoteResourceWithUnderscore.exists()); } @Test public void testValidBucket() throws IOException { Assert.assertEquals("gs://test-spring/", this.bucketResource.getDescription()); Assert.assertEquals("test-spring", this.bucketResource.getFilename()); Assert.assertEquals("gs://test-spring/", this.bucketResource.getURI().toString()); String relative = this.bucketResource.createRelative("aaa/bbb").getURI().toString(); Assert.assertEquals("gs://test-spring/aaa/bbb", relative); Assert.assertEquals(relative, this.bucketResource.createRelative("/aaa/bbb").getURI().toString()); Assert.assertNull(((GoogleStorageResource) this.bucketResource).getBlobName()); Assert.assertTrue(((GoogleStorageResource) this.bucketResource).isBucket()); Assert.assertTrue(this.bucketResource.exists()); Assert.assertTrue(((GoogleStorageResource) this.bucketResource).bucketExists()); } @Test public void testBucketNotEndingInSlash() { Assert.assertTrue(new GoogleStorageResource(this.mockStorage, "gs://test-spring").isBucket()); } @Test public void testSpecifyBucketCorrect() { Bucket mockedBucket = mock(Bucket.class); when(this.mockStorage.get("test-spring")).thenReturn(mockedBucket); when(mockedBucket.exists()).thenReturn(true); when(mockedBucket.getName()).thenReturn("test-spring"); GoogleStorageLocation location = GoogleStorageLocation.forBucket("test-spring"); GoogleStorageResource googleStorageResource = new GoogleStorageResource( this.mockStorage, location, false); Assert.assertTrue(googleStorageResource.isBucket()); Assert.assertEquals("test-spring", googleStorageResource.getBucketName()); Assert.assertEquals("test-spring", googleStorageResource.getBucket().getName()); Assert.assertTrue(googleStorageResource.exists()); } @Test public void testSpecifyPathCorrect() { BlobId validBlobId = BlobId.of("test-spring", "images/spring.png"); Blob mockedBlob = mock(Blob.class); when(this.mockStorage.get(eq(validBlobId))).thenReturn(mockedBlob); GoogleStorageResource googleStorageResource = new GoogleStorageResource( this.mockStorage, "gs://test-spring/images/spring.png", false); Assert.assertTrue(googleStorageResource.exists()); } @Test public void testBucketOutputStream() throws IOException { this.expectedEx.expect(IllegalStateException.class); this.expectedEx.expectMessage("Cannot open an output stream to a bucket: 'gs://test-spring/'"); ((WritableResource) this.bucketResource).getOutputStream(); } @Test public void testBucketNoBlobInputStream() throws IOException { this.expectedEx.expect(IllegalStateException.class); this.expectedEx.expectMessage("Cannot open an input stream to a bucket: 'gs://test-spring/'"); this.bucketResource.getInputStream(); } @Test public void testBucketNoBlobContentLength() throws IOException { this.expectedEx.expect(IllegalStateException.class); this.expectedEx.expectMessage("No blob id specified in the location: " + "'gs://test-spring/', and the operation is not allowed on buckets."); this.bucketResource.contentLength(); } @Test public void testBucketNoBlobFile() throws IOException { this.expectedEx.expect(UnsupportedOperationException.class); this.expectedEx.expectMessage("gs://test-spring/ cannot be resolved to absolute file path"); this.bucketResource.getFile(); } @Test public void testBucketNoBlobLastModified() throws IOException { this.expectedEx.expect(IllegalStateException.class); this.expectedEx.expectMessage("No blob id specified in the location: 'gs://test-spring/', " + "and the operation is not allowed on buckets."); this.bucketResource.lastModified(); } @Test public void testBucketNoBlobResourceStatuses() throws IOException { Assert.assertFalse(this.bucketResource.isOpen()); Assert.assertFalse(this.bucketResource.isReadable()); Assert.assertFalse(((WritableResource) this.bucketResource).isWritable()); Assert.assertTrue(this.bucketResource.exists()); } @Test public void testWritable() throws Exception { WriteChannel writeChannel = mock(WriteChannel.class); when(this.mockStorage.writer(any(BlobInfo.class))).thenReturn(writeChannel); Assert.assertTrue(this.remoteResource instanceof WritableResource); WritableResource writableResource = (WritableResource) this.remoteResource; Assert.assertTrue(writableResource.isWritable()); writableResource.getOutputStream(); } @Test public void testWritableOutputStream() throws Exception { String location = "gs://test-spring/test"; Blob blob = mock(Blob.class); WriteChannel writeChannel = mock(WriteChannel.class); when(blob.writer()).thenReturn(writeChannel); when(blob.exists()).thenReturn(true); when(this.mockStorage.get(BlobId.of("test-spring", "test"))).thenReturn(blob); GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); OutputStream os = resource.getOutputStream(); Assert.assertNotNull(os); } @Test public void testWritableOutputStreamNoAutoCreateOnNullBlob() throws Exception { this.expectedEx.expect(FileNotFoundException.class); this.expectedEx.expectMessage("The blob was not found: gs://test-spring/test"); String location = "gs://test-spring/test"; when(this.mockStorage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location, false); resource.getOutputStream(); } @Test public void testWritableOutputStreamWithAutoCreateOnNullBlob() throws Exception { String location = "gs://test-spring/test"; BlobId blobId = BlobId.of("test-spring", "test"); when(this.mockStorage.get(blobId)).thenReturn(null); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); WriteChannel writeChannel = mock(WriteChannel.class); Blob blob = mock(Blob.class); when(blob.writer()).thenReturn(writeChannel); when(this.mockStorage.create(blobInfo)).thenReturn(blob); GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); GoogleStorageResource spyResource = spy(resource); OutputStream os = spyResource.getOutputStream(); Assert.assertNotNull(os); } @Test public void testWritableOutputStreamWithAutoCreateOnNonExistantBlob() throws Exception { String location = "gs://test-spring/test"; BlobId blobId = BlobId.of("test-spring", "test"); Blob nonExistantBlob = mock(Blob.class); when(nonExistantBlob.exists()).thenReturn(false); when(this.mockStorage.get(blobId)).thenReturn(nonExistantBlob); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); WriteChannel writeChannel = mock(WriteChannel.class); Blob blob = mock(Blob.class); when(blob.writer()).thenReturn(writeChannel); when(this.mockStorage.create(blobInfo)).thenReturn(blob); GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); GoogleStorageResource spyResource = spy(resource); OutputStream os = spyResource.getOutputStream(); Assert.assertNotNull(os); } @Test public void testCreateBlobWithContents() { Storage mockStorage = mock(Storage.class); String location = "gs://test-bucket/filename"; byte[] contentBytes = "test contents".getBytes(); GoogleStorageResource resource = new GoogleStorageResource(mockStorage, location); resource.createBlob(contentBytes); verify(mockStorage).create( BlobInfo.newBuilder("test-bucket", "filename").build(), contentBytes); } @Test public void testGetInputStreamOnNullBlob() throws Exception { this.expectedEx.expect(FileNotFoundException.class); this.expectedEx.expectMessage("The blob was not found: gs://test-spring/test"); String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); when(storage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); resource.getInputStream(); } @Test public void testGetFilenameOnNonExistingBlob() throws Exception { String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); when(storage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); Assert.assertEquals("test", resource.getFilename()); } @Test public void testisAutoCreateFilesGetterSetter() { String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); Assert.assertTrue(resource.isAutoCreateFiles()); } @Test public void testCreateRelative() throws IOException { String location = "gs://test-spring/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); GoogleStorageResource relative = (GoogleStorageResource) resource.createRelative("relative.png"); Assert.assertEquals("gs://test-spring/relative.png", relative.getURI().toString()); } @Test public void testCreateRelativeSubdirs() throws IOException { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); GoogleStorageResource relative = (GoogleStorageResource) resource.createRelative("r1/relative.png"); Assert.assertEquals("gs://test-spring/t1/r1/relative.png", relative.getURI().toString()); } @Test public void nullSignedUrlForNullBlob() throws IOException { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); when(storage.get(any(BlobId.class))).thenReturn(null); Assert.assertNull(resource.createSignedUrl(TimeUnit.DAYS, 1)); } @Test public void signedUrlFunctionCalled() throws IOException { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); Blob blob = mock(Blob.class); when(blob.getBucket()).thenReturn("fakeBucket"); when(blob.getName()).thenReturn("fakeObject"); GoogleStorageResource resource = new GoogleStorageResource(storage, location); when(storage.get(any(BlobId.class))).thenReturn(blob); Storage.SignUrlOption option = Storage.SignUrlOption.httpMethod(HttpMethod.PUT); resource.createSignedUrl(TimeUnit.DAYS, 1L, option); verify(storage, times(1)).signUrl(any(), eq(1L), eq(TimeUnit.DAYS), eq(option)); } @Test public void getUrlFails() throws IOException { this.expectedEx.expect(MalformedURLException.class); this.expectedEx.expectMessage("unknown protocol: gs"); this.remoteResource.getURL(); } @Test public void testBucketDoesNotExist() { Bucket mockedBucket = mock(Bucket.class); when(this.mockStorage.create(BucketInfo.newBuilder("non-existing").build())).thenReturn(mockedBucket); when(mockedBucket.getName()).thenReturn("test-spring"); GoogleStorageResource bucket = new GoogleStorageResource(this.mockStorage, "gs://non-existing/"); Assert.assertFalse(bucket.bucketExists()); Assert.assertFalse(bucket.exists()); Assert.assertNotNull(bucket.createBucket()); } @Test public void testBucketExistsButResourceDoesNot() { GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, "gs://test-spring/file1"); Bucket mockedBucket = mock(Bucket.class); when(this.mockStorage.get("test-spring")).thenReturn(mockedBucket); when(mockedBucket.exists()).thenReturn(true); Assert.assertTrue(resource.bucketExists()); Assert.assertFalse(resource.exists()); } /** * Configuration for the tests. */ @Configuration @Import(GoogleStorageProtocolResolver.class) static class StorageApplication { @Bean public static Storage mockStorage() throws Exception { return mock(Storage.class); } @Bean public static GoogleStorageProtocolResolverSettings googleStorageProtocolResolverSettings() { return new GoogleStorageProtocolResolverSettings(); } } }