From 22b7190d5f7801fd19f0759348ed59b8eee001f0 Mon Sep 17 00:00:00 2001 From: devatherock Date: Sun, 15 Nov 2020 10:21:49 -0600 Subject: [PATCH] Added more unit tests - connects to #24 --- build.gradle | 22 ++- .../artifactory/service/ShieldsIOClient.java | 2 +- src/main/resources/application.yml | 6 +- .../artifactory/config/AppConfigSpec.groovy | 2 +- .../controllers/DockerControllerSpec.groovy | 167 ++++++++++++++++-- src/test/resources/application-test.yml | 5 +- 6 files changed, 183 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index 5c65a26..2ae4991 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id "net.ltgt.apt-eclipse" version "0.21" id "com.github.johnrengelman.shadow" version "5.0.0" id "application" + id 'java' id 'groovy' id 'jacoco' id 'org.sonarqube' version '3.0' @@ -57,6 +58,7 @@ dependencies { testImplementation group: 'org.spockframework', name: 'spock-core', version: '1.3-groovy-2.5' testImplementation group: 'cglib', name: 'cglib-nodep', version: '3.3.0' testImplementation group: 'org.objenesis', name: 'objenesis', version: '3.1' + testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.27.2' } test.classpath += configurations.developmentOnly @@ -87,10 +89,22 @@ run { ext.jacoco = [ exclusions: [ 'io/github/devatherock/Application.class', - 'io/github/devatherock/test/ArtifactoryController.class', - // Excluded for now till tests are written - 'io/github/devatherock/artifactory/service/DockerBadgeService.class', - 'io/github/devatherock/artifactory/util/BadgeGenerator.class' + 'io/github/devatherock/test/ArtifactoryController.class' + ], + // Should be removed when all tests have been written + coverageThresholds: [ + 'io.github.devatherock.artifactory.service.DockerBadgeService': [ + 'BRANCH': 0.28, + 'COMPLEXITY': 0.32, + 'INSTRUCTION': 0.70, + 'LINE': 0.70 + ], + 'io.github.devatherock.artifactory.util.BadgeGenerator': [ + 'BRANCH': 0.50, + 'COMPLEXITY': 0.50, + 'INSTRUCTION': 0.10, + 'LINE': 0.14 + ] ] ] apply from: '../gradle-includes/checks.gradle' \ No newline at end of file diff --git a/src/main/java/io/github/devatherock/artifactory/service/ShieldsIOClient.java b/src/main/java/io/github/devatherock/artifactory/service/ShieldsIOClient.java index 9541f67..dc88750 100644 --- a/src/main/java/io/github/devatherock/artifactory/service/ShieldsIOClient.java +++ b/src/main/java/io/github/devatherock/artifactory/service/ShieldsIOClient.java @@ -4,7 +4,7 @@ import io.micronaut.http.annotation.QueryValue; import io.micronaut.http.client.annotation.Client; -@Client(value = "https://img.shields.io") +@Client(value = "${artifactory.badge.shields-io.url}") public interface ShieldsIOClient { @Get(value = "/static/v1") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bf2e5e6..eaa24a1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -29,4 +29,8 @@ micronaut: mapping: /swagger/** swagger-ui: paths: classpath:META-INF/swagger/views/swagger-ui - mapping: /swagger-ui/** \ No newline at end of file + mapping: /swagger-ui/** +artifactory: + badge: + shields-io: + url: https://img.shields.io \ No newline at end of file diff --git a/src/test/groovy/io/github/devatherock/artifactory/config/AppConfigSpec.groovy b/src/test/groovy/io/github/devatherock/artifactory/config/AppConfigSpec.groovy index 3757aa3..6c85f0a 100644 --- a/src/test/groovy/io/github/devatherock/artifactory/config/AppConfigSpec.groovy +++ b/src/test/groovy/io/github/devatherock/artifactory/config/AppConfigSpec.groovy @@ -12,7 +12,7 @@ class AppConfigSpec extends Specification { @Subject AppConfig appConfig = new AppConfig() - def 'test initialize http client'() { + void 'test initialize http client'() { given: HttpClient httpClient = Mock() BlockingHttpClient outputClient = Mock() diff --git a/src/test/groovy/io/github/devatherock/artifactory/controllers/DockerControllerSpec.groovy b/src/test/groovy/io/github/devatherock/artifactory/controllers/DockerControllerSpec.groovy index 119c236..7737551 100644 --- a/src/test/groovy/io/github/devatherock/artifactory/controllers/DockerControllerSpec.groovy +++ b/src/test/groovy/io/github/devatherock/artifactory/controllers/DockerControllerSpec.groovy @@ -1,40 +1,72 @@ package io.github.devatherock.artifactory.controllers -import io.github.devatherock.artifactory.service.DockerBadgeService +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock import io.micronaut.http.HttpRequest import io.micronaut.http.client.HttpClient import io.micronaut.http.client.annotation.Client import io.micronaut.http.uri.UriBuilder -import io.micronaut.test.annotation.MockBean import io.micronaut.test.extensions.spock.annotation.MicronautTest +import spock.lang.Shared import spock.lang.Specification import javax.inject.Inject +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; + /** * Test class for {@link DockerController} */ @MicronautTest(propertySources = 'classpath:application-test.yml') class DockerControllerSpec extends Specification { - @Inject - DockerBadgeService badgeService + @Shared + WireMockServer mockServer = new WireMockServer(8081) @Inject @Client('/') HttpClient httpClient + void setupSpec() { + WireMock.configureFor(8081) + mockServer.start() + } + + void cleanupSpec() { + mockServer.stop() + } + + void cleanup() { + mockServer.resetRequests() + } + void 'test get image pull count - default label'() { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}") + .willReturn(WireMock.okJson(getFoldersResponse()))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/1.1.0/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('1.1.0', 10)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/1.1.2/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('1.1.2', 20)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/latest/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('latest', 30)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/abcdefgh/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('abcdefgh', 40)))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('docker pulls')) + .withQueryParam('message', equalTo('100')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/pulls') .queryParam('package', packageName).build())) then: - 1 * badgeService.getPullsCountBadge(packageName, 'docker pulls') >> 'dummyBadge' badge == 'dummyBadge' } @@ -42,6 +74,23 @@ class DockerControllerSpec extends Specification { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}") + .willReturn(WireMock.okJson(getFoldersResponse()))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/1.1.0/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('1.1.0', 10)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/1.1.2/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('1.1.2', 20)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/latest/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('latest', 30)))) + WireMock.givenThat(WireMock.get("/artifactory/api/storage/${packageName}/abcdefgh/manifest.json?stats") + .willReturn(WireMock.okJson(getManifestStats('abcdefgh', 40)))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('downloads')) + .withQueryParam('message', equalTo('100')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/pulls') @@ -49,7 +98,6 @@ class DockerControllerSpec extends Specification { .queryParam('label', 'downloads').build())) then: - 1 * badgeService.getPullsCountBadge(packageName, 'downloads') >> 'dummyBadge' badge == 'dummyBadge' } @@ -57,13 +105,21 @@ class DockerControllerSpec extends Specification { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/${packageName}/latest/manifest.json") + .willReturn(WireMock.okJson(getManifest()))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('image size')) + .withQueryParam('message', equalTo('11 MB')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/image-size') .queryParam('package', packageName).build())) then: - 1 * badgeService.getImageSizeBadge(packageName, 'latest', 'image size') >> 'dummyBadge' badge == 'dummyBadge' } @@ -71,6 +127,15 @@ class DockerControllerSpec extends Specification { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/${packageName}/1.2.0/manifest.json") + .willReturn(WireMock.okJson(getManifest()))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('size')) + .withQueryParam('message', equalTo('11 MB')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/image-size') @@ -79,7 +144,6 @@ class DockerControllerSpec extends Specification { .queryParam('label', 'size').build())) then: - 1 * badgeService.getImageSizeBadge(packageName, '1.2.0', 'size') >> 'dummyBadge' badge == 'dummyBadge' } @@ -87,13 +151,21 @@ class DockerControllerSpec extends Specification { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/${packageName}/latest/manifest.json") + .willReturn(WireMock.okJson(getManifest()))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('layers')) + .withQueryParam('message', equalTo('2')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/layers') .queryParam('package', packageName).build())) then: - 1 * badgeService.getImageLayersBadge(packageName, 'latest', 'layers') >> 'dummyBadge' badge == 'dummyBadge' } @@ -101,6 +173,15 @@ class DockerControllerSpec extends Specification { given: String packageName = 'docker/devatherock/simple-slack' + and: + WireMock.givenThat(WireMock.get("/artifactory/${packageName}/1.2.0/manifest.json") + .willReturn(WireMock.okJson(getManifest()))) + WireMock.givenThat(WireMock.get(WireMock.urlPathEqualTo('/static/v1')) + .withQueryParam('label', equalTo('number of layers')) + .withQueryParam('message', equalTo('2')) + .withQueryParam('color', equalTo('blue')) + .willReturn(WireMock.okXml('dummyBadge'))) + when: String badge = httpClient.toBlocking().retrieve( HttpRequest.GET(UriBuilder.of('/docker/layers') @@ -109,12 +190,72 @@ class DockerControllerSpec extends Specification { .queryParam('label', 'number of layers').build())) then: - 1 * badgeService.getImageLayersBadge(packageName, '1.2.0', 'number of layers') >> 'dummyBadge' badge == 'dummyBadge' } - @MockBean(DockerBadgeService) - DockerBadgeService badgeService() { - Mock(DockerBadgeService) + String getFoldersResponse() { + """{ + "repo": "docker", + "path": "/devatherock/simple-slack", + "created": "2018-09-23T18:02:56.147Z", + "createdBy": "devatherock", + "lastModified": "2018-09-23T18:02:56.147Z", + "modifiedBy": "devatherock", + "lastUpdated": "2018-09-23T18:02:56.147Z", + "children": [ + { + "uri": "/1.1.0", + "folder": true + }, + { + "uri": "/1.1.2", + "folder": true + }, + { + "uri": "/latest", + "folder": true + }, + { + "uri": "/abcdefgh", + "folder": true + } + ], + "uri": "http://localhost:8081/artifactory/api/storage/docker/devatherock/simple-slack" + }""" + } + + String getManifestStats(String tag, int downloadCount) { + """{ + "uri": "http://localhost:8081/artifactory/docker/devatherock/simple-slack/${tag}/manifest.json", + "downloadCount": ${downloadCount}, + "lastDownloaded": 1602863958001, + "lastDownloadedBy": "devatherock", + "remoteDownloadCount": 0, + "remoteLastDownloaded": 0 + }""" + } + + String getManifest() { + """{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 3027, + "digest": "sha256:9fe1c24da9391a4d7346200a997c06c7c900466181081af7953a2a15c9fffd7c" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 10485760, + "digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 1048576, + "digest": "sha256:f910a506b6cb1dbec766725d70356f695ae2bf2bea6224dbe8c7c6ad4f3664a2" + } + ] + }""" } } diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 97801ec..cecbd45 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -1,3 +1,6 @@ artifactory: url: http://localhost:8081 - api-key: dummyKey \ No newline at end of file + api-key: dummyKey + badge: + shields-io: + url: http://localhost:8081 \ No newline at end of file