From c20fc1b3eb1e686b7adbe5070f152b1a6753297f Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 18 Nov 2019 18:43:52 +0100 Subject: [PATCH 01/13] :pushpin: : pins antlr4 version in properties --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 259a107bd..03231a50a 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ + 4.7.2 11 2.27 5.5.2 @@ -215,7 +216,7 @@ org.antlr antlr4-runtime - 4.7.2 + ${antlr4.version} @@ -313,7 +314,7 @@ org.antlr antlr4-maven-plugin - 4.7.2 + ${antlr4.version} From f8313736c35b20af7299de0f7b83df2e7714a3a5 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Wed, 6 Nov 2019 19:12:37 +0100 Subject: [PATCH 02/13] :bug: : handle parameter injection in RestTemplate URL When passing parameters to the RestTemplate exchange method, spring url-encodes the parameter. This behaviour is not desirable for GithubRawContent, as the project ID contains a '/' --- .../gaia/registries/RegistryRawContent.kt | 5 ++-- .../github/GitHubRawContentTest.java | 30 ++++++++----------- .../gitlab/GitLabRawContentTest.java | 10 +++---- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt b/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt index 939b3c246..c3edef0a5 100644 --- a/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt +++ b/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt @@ -35,11 +35,10 @@ abstract class RegistryRawContent(private val registryType: RegistryType, privat val requestEntity = HttpEntity(headers) val response = restTemplate.exchange( - this.registryType.readmeUrl, + this.registryType.readmeUrl.replace("{id}", module.registryDetails.projectId), HttpMethod.GET, requestEntity, - RegistryFile::class.java, - module.registryDetails.projectId) + RegistryFile::class.java) if(response.statusCode == HttpStatus.OK) { return Optional.of(String(Base64.getDecoder().decode(response.body?.content))) diff --git a/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java b/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java index a4419c11b..78aef29a2 100644 --- a/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java +++ b/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java @@ -54,7 +54,7 @@ void matches_shouldReturnFalseForInvalidUrl() { void getReadmeContent_shouldCallTheApi_andServeDecodedContent(){ // given var module = new TerraformModule(); - module.setRegistryDetails(new RegistryDetails(RegistryType.GITLAB, "group/project")); + module.setRegistryDetails(new RegistryDetails(RegistryType.GITHUB, "Apophis/Chulak")); var jack = new User("Jack", null); jack.setOAuth2User(new OAuth2User("GITHUB","TOKENSTRING", null)); @@ -66,11 +66,10 @@ void getReadmeContent_shouldCallTheApi_andServeDecodedContent(){ var response = new ResponseEntity<>(githubFile, HttpStatus.OK); when(restTemplate.exchange( - eq("https://api.github.com/repos/{id}/contents/README.md?ref=master"), + eq("https://api.github.com/repos/Apophis/Chulak/contents/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("group/project"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitHubRawContent.getReadme(module); @@ -86,7 +85,7 @@ void getReadmeContent_shouldCallTheApi_andServeDecodedContent(){ void getReadmeContent_shouldCallTheApiWithoutAuth_ifNoToken_andServeDecodedContent(){ // given var module = new TerraformModule(); - module.setRegistryDetails(new RegistryDetails(RegistryType.GITLAB, "group/project")); + module.setRegistryDetails(new RegistryDetails(RegistryType.GITHUB, "Apophis/Chulak")); var jack = new User("Jack", null); module.setCreatedBy(jack); @@ -97,11 +96,10 @@ void getReadmeContent_shouldCallTheApiWithoutAuth_ifNoToken_andServeDecodedConte var response = new ResponseEntity<>(githubFile, HttpStatus.OK); when(restTemplate.exchange( - eq("https://api.github.com/repos/{id}/contents/README.md?ref=master"), + eq("https://api.github.com/repos/Apophis/Chulak/contents/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("group/project"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitHubRawContent.getReadme(module); @@ -117,7 +115,7 @@ void getReadmeContent_shouldCallTheApiWithoutAuth_ifNoToken_andServeDecodedConte void getReadmeContent_shouldCallTheApiWithoutAuth_ifNoOwner_andServeDecodedContent(){ // given var module = new TerraformModule(); - module.setRegistryDetails(new RegistryDetails(RegistryType.GITLAB, "group/project")); + module.setRegistryDetails(new RegistryDetails(RegistryType.GITHUB, "Apophis/Chulak")); var requestCaptor = ArgumentCaptor.forClass(HttpEntity.class); @@ -125,11 +123,10 @@ void getReadmeContent_shouldCallTheApiWithoutAuth_ifNoOwner_andServeDecodedConte var response = new ResponseEntity<>(githubFile, HttpStatus.OK); when(restTemplate.exchange( - eq(RegistryType.GITHUB.getReadmeUrl()), + eq("https://api.github.com/repos/Apophis/Chulak/contents/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("group/project"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitHubRawContent.getReadme(module); @@ -157,10 +154,10 @@ void getReadmeContent_withNoRegistryDetails_returnsEmptyContent(){ void getReadmeContent_shouldReturnEmpty_whenReadmeDoesntExists(){ // given var module = new TerraformModule(); - module.setRegistryDetails(new RegistryDetails(RegistryType.GITLAB, "123")); + module.setRegistryDetails(new RegistryDetails(RegistryType.GITHUB, "Apophis/Chulak")); var jack = new User("Jack", null); - jack.setOAuth2User(new OAuth2User("GITLAB","TOKENSTRING", null)); + jack.setOAuth2User(new OAuth2User("GITHUB","TOKENSTRING", null)); module.setCreatedBy(jack); var requestCaptor = ArgumentCaptor.forClass(HttpEntity.class); @@ -168,11 +165,10 @@ void getReadmeContent_shouldReturnEmpty_whenReadmeDoesntExists(){ var response = new ResponseEntity(HttpStatus.NOT_FOUND); when(restTemplate.exchange( - eq(RegistryType.GITHUB.getReadmeUrl()), + eq("https://api.github.com/repos/Apophis/Chulak/contents/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("123"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitHubRawContent.getReadme(module); diff --git a/src/test/java/io/codeka/gaia/registries/gitlab/GitLabRawContentTest.java b/src/test/java/io/codeka/gaia/registries/gitlab/GitLabRawContentTest.java index b4522f211..4c96af8a7 100644 --- a/src/test/java/io/codeka/gaia/registries/gitlab/GitLabRawContentTest.java +++ b/src/test/java/io/codeka/gaia/registries/gitlab/GitLabRawContentTest.java @@ -66,11 +66,10 @@ void getReadmeContent_shouldCallTheApi_andServeDecodedContent(){ var response = new ResponseEntity<>(gitlabFile, HttpStatus.OK); when(restTemplate.exchange( - eq(RegistryType.GITLAB.getReadmeUrl()), + eq("https://gitlab.com/api/v4/projects/123/repository/files/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("123"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitLabRawContent.getReadme(module); @@ -109,11 +108,10 @@ void getReadmeContent_shouldReturnEmpty_whenReadmeDoesntExists(){ var response = new ResponseEntity(HttpStatus.NOT_FOUND); when(restTemplate.exchange( - eq(RegistryType.GITLAB.getReadmeUrl()), + eq("https://gitlab.com/api/v4/projects/123/repository/files/README.md?ref=master"), eq(HttpMethod.GET), requestCaptor.capture(), - eq(RegistryFile.class), - eq("123"))).thenReturn(response); + eq(RegistryFile.class))).thenReturn(response); // when var result = gitLabRawContent.getReadme(module); From 42d531c2b902de4d84c013163f69a78e0725ca6a Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Wed, 6 Nov 2019 19:30:12 +0100 Subject: [PATCH 03/13] :bug: : remove line feeds for large README.md files Github serves raw content in base 64 with line feeds for large files. We have to remove them before decoding the content. --- src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt | 2 +- .../io/codeka/gaia/registries/github/GitHubRawContentTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt b/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt index c3edef0a5..45547d132 100644 --- a/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt +++ b/src/main/java/io/codeka/gaia/registries/RegistryRawContent.kt @@ -41,7 +41,7 @@ abstract class RegistryRawContent(private val registryType: RegistryType, privat RegistryFile::class.java) if(response.statusCode == HttpStatus.OK) { - return Optional.of(String(Base64.getDecoder().decode(response.body?.content))) + return Optional.of(String(Base64.getDecoder().decode(response.body?.content?.replace("\n","")))) } return Optional.empty() } diff --git a/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java b/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java index 78aef29a2..29e46e251 100644 --- a/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java +++ b/src/test/java/io/codeka/gaia/registries/github/GitHubRawContentTest.java @@ -62,7 +62,7 @@ void getReadmeContent_shouldCallTheApi_andServeDecodedContent(){ var requestCaptor = ArgumentCaptor.forClass(HttpEntity.class); - var githubFile = new RegistryFile(Base64.encode("# Module Readme".getBytes())); + var githubFile = new RegistryFile(Base64.encode("# Module Readme".getBytes()) + "\n"); var response = new ResponseEntity<>(githubFile, HttpStatus.OK); when(restTemplate.exchange( From a96ac1f7e27ecb9f0a1c60070500aec67a4ee1ae Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Sat, 9 Nov 2019 16:35:28 +0100 Subject: [PATCH 04/13] :heavy_plus_sign: : add pitest configuration --- pom.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pom.xml b/pom.xml index 03231a50a..b824e9e94 100644 --- a/pom.xml +++ b/pom.xml @@ -327,6 +327,25 @@ + + + org.pitest + pitest-maven + 1.4.10 + + + org.pitest + pitest-junit5-plugin + 0.10 + + + + *IT + + kotlin.jvm.internal + + + From 3f555835ecd30f24bfcd4b8140746360801a7afd Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Sat, 9 Nov 2019 16:36:37 +0100 Subject: [PATCH 05/13] :white_check_mark: : corrects test based on mutation tests --- .../teams/controller/TeamsRestControllerTest.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/codeka/gaia/teams/controller/TeamsRestControllerTest.java b/src/test/java/io/codeka/gaia/teams/controller/TeamsRestControllerTest.java index 40d7a8fb4..4cb72c306 100644 --- a/src/test/java/io/codeka/gaia/teams/controller/TeamsRestControllerTest.java +++ b/src/test/java/io/codeka/gaia/teams/controller/TeamsRestControllerTest.java @@ -1,5 +1,6 @@ package io.codeka.gaia.teams.controller; +import io.codeka.gaia.teams.Team; import io.codeka.gaia.teams.repository.TeamRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -7,7 +8,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class TeamsRestControllerTest { @@ -20,8 +25,15 @@ class TeamsRestControllerTest { @Test void teams_shouldReturnAllTeams() { - teamsRestController.teams(); + // given + var a = new Team("A"); + when(teamRepository.findAll()).thenReturn(List.of(a)); + + // when + var teams = teamsRestController.teams(); + // then + assertThat(teams).contains(a); verify(teamRepository).findAll(); } } \ No newline at end of file From 70103f46df803c6c5e52f482de9fe5a1b8f291aa Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Sat, 9 Nov 2019 17:37:20 +0100 Subject: [PATCH 06/13] :rotating_light: : correct depreciation warning `Mockito.verifyZeroInteractions` is deprecated since 3.x.x. Using `verifyNoInteraction` instead --- .../gaia/modules/controller/ModulesMVCControllerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java b/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java index ee493b03a..17d6e5225 100644 --- a/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java +++ b/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java @@ -100,7 +100,7 @@ void readme_shouldThrowExceptionIfModuleNotFound() { // then verify(moduleRepository).findById("TEST"); - verifyZeroInteractions(moduleGitRepository); + verifyNoInteractions(moduleGitRepository); } @Test @@ -144,7 +144,7 @@ void module_shouldThrowException_forUnauthorizedUser(){ // then verify(moduleRepository).findById("12"); - verifyZeroInteractions(model); + verifyNoInteractions(model); } @Test @@ -158,7 +158,7 @@ void module_shouldThrowException_forUnexistingModule(){ // then verify(moduleRepository).findById("12"); - verifyZeroInteractions(model); + verifyNoInteractions(model); } } \ No newline at end of file From 4ceaed3264168a78d97781a9cd98e3c4af4ed811 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 18 Nov 2019 18:59:58 +0100 Subject: [PATCH 07/13] :speech_balloon: : correct error pages titles --- src/main/resources/templates/error/403.html | 2 +- src/main/resources/templates/error/500.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/error/403.html b/src/main/resources/templates/error/403.html index ca22ac07b..221b2ad9a 100644 --- a/src/main/resources/templates/error/403.html +++ b/src/main/resources/templates/error/403.html @@ -2,7 +2,7 @@ - Gaia - Page not found + Gaia - Access Forbidden diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html index 599eaffdf..a754d8dab 100644 --- a/src/main/resources/templates/error/500.html +++ b/src/main/resources/templates/error/500.html @@ -2,7 +2,7 @@ - Gaia - Page not found + Gaia - Internal Server Error From 572ba290b1510e7ee248a0896ade86135f826dad Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Fri, 27 Dec 2019 18:42:38 +0100 Subject: [PATCH 08/13] :recycle: : move MongoRepositories configuration to the right class --- src/main/java/io/codeka/gaia/Gaia.java | 2 -- src/main/java/io/codeka/gaia/config/MongoConfig.java | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codeka/gaia/Gaia.java b/src/main/java/io/codeka/gaia/Gaia.java index 6721bab4c..a61e80df7 100644 --- a/src/main/java/io/codeka/gaia/Gaia.java +++ b/src/main/java/io/codeka/gaia/Gaia.java @@ -2,10 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication -@EnableMongoRepositories public class Gaia { public static void main(String[] args) { diff --git a/src/main/java/io/codeka/gaia/config/MongoConfig.java b/src/main/java/io/codeka/gaia/config/MongoConfig.java index 1555707be..dc90b7c79 100644 --- a/src/main/java/io/codeka/gaia/config/MongoConfig.java +++ b/src/main/java/io/codeka/gaia/config/MongoConfig.java @@ -3,8 +3,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @Configuration +@EnableMongoRepositories(basePackages = "io.codeka.gaia") public class MongoConfig { @Autowired From 1a6647e08a99a55890b14305105019b3e668601e Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Fri, 27 Dec 2019 19:05:06 +0100 Subject: [PATCH 09/13] :sparkles: : import module from github --- .../gaia/config/security/SecurityConfig.java | 2 +- src/main/java/io/codeka/gaia/hcl/HclParser.kt | 2 + .../controller/ModuleRestController.java | 10 ++ .../io/codeka/gaia/registries/RegistryApi.kt | 10 ++ .../controller/GithubRegistryController.kt | 56 +++++++++++ .../registries/github/GitHubRawContent.kt | 4 +- .../registries/github/GithubRegistryApi.kt | 79 +++++++++++++++ src/main/resources/application.properties | 3 +- .../controller/ModuleRestControllerIT.java | 13 ++- .../controller/ModuleRestControllerTest.java | 14 +++ .../GithubRegistryControllerTest.kt | 87 +++++++++++++++++ .../github/GithubRegistryApiTest.kt | 97 +++++++++++++++++++ .../org.mockito.plugins.MockMaker | 1 + 13 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/codeka/gaia/registries/RegistryApi.kt create mode 100644 src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt create mode 100644 src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt create mode 100644 src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt create mode 100644 src/test/java/io/codeka/gaia/registries/github/GithubRegistryApiTest.kt create mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java index ad85f1975..37655ca5b 100644 --- a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java +++ b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java @@ -35,7 +35,7 @@ protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() - .antMatchers("/api/**").permitAll() + .antMatchers("/api/state/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/css/**", "/js/**", "/favicon.ico", "/images/**").permitAll() .antMatchers("/**").authenticated() diff --git a/src/main/java/io/codeka/gaia/hcl/HclParser.kt b/src/main/java/io/codeka/gaia/hcl/HclParser.kt index 758fa35ad..9f53d0c52 100644 --- a/src/main/java/io/codeka/gaia/hcl/HclParser.kt +++ b/src/main/java/io/codeka/gaia/hcl/HclParser.kt @@ -6,7 +6,9 @@ import io.codeka.gaia.modules.bo.Output import io.codeka.gaia.modules.bo.Variable import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CommonTokenStream +import org.springframework.stereotype.Service +@Service class HclParser { private fun parseContent(content: String): HclVisitor { diff --git a/src/main/java/io/codeka/gaia/modules/controller/ModuleRestController.java b/src/main/java/io/codeka/gaia/modules/controller/ModuleRestController.java index 619e1b925..86577f6ef 100644 --- a/src/main/java/io/codeka/gaia/modules/controller/ModuleRestController.java +++ b/src/main/java/io/codeka/gaia/modules/controller/ModuleRestController.java @@ -4,11 +4,13 @@ import io.codeka.gaia.modules.repository.TerraformModuleRepository; import io.codeka.gaia.teams.User; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; +import java.util.UUID; /** * Rest controller for the module API @@ -45,6 +47,14 @@ public TerraformModule findModule(@PathVariable String id, User user){ return module; } + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public TerraformModule createModule(@RequestBody TerraformModule module, User user){ + module.setId(UUID.randomUUID().toString()); + module.setCreatedBy(user); + return moduleRepository.save(module); + } + @PutMapping("/{id}") public TerraformModule saveModule(@PathVariable String id, @RequestBody @Valid TerraformModule module, User user){ var existingModule = moduleRepository.findById(id).orElseThrow(ModuleNotFoundException::new); diff --git a/src/main/java/io/codeka/gaia/registries/RegistryApi.kt b/src/main/java/io/codeka/gaia/registries/RegistryApi.kt new file mode 100644 index 000000000..327425a87 --- /dev/null +++ b/src/main/java/io/codeka/gaia/registries/RegistryApi.kt @@ -0,0 +1,10 @@ +package io.codeka.gaia.registries + +import io.codeka.gaia.teams.User +import org.springframework.web.client.RestTemplate + +abstract class RegistryApi(private val registryType: RegistryType, val restTemplate: RestTemplate) { + + abstract fun getRepositories(user: User) : List + +} \ No newline at end of file diff --git a/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt new file mode 100644 index 000000000..6982ceecb --- /dev/null +++ b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt @@ -0,0 +1,56 @@ +package io.codeka.gaia.registries.controller + +import io.codeka.gaia.hcl.HclParser +import io.codeka.gaia.modules.bo.TerraformModule +import io.codeka.gaia.modules.repository.TerraformCLIRepository +import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.registries.RegistryDetails +import io.codeka.gaia.registries.RegistryType +import io.codeka.gaia.registries.github.GitHubRawContent +import io.codeka.gaia.registries.github.GithubRegistryApi +import io.codeka.gaia.registries.github.GithubRepository +import io.codeka.gaia.teams.User +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpStatus +import org.springframework.security.access.annotation.Secured +import org.springframework.web.bind.annotation.* +import java.util.* + +@RestController +@RequestMapping("/api/registries/github") +@Secured +class GithubRegistryController( + val githubRegistryApi: GithubRegistryApi, + val hclParser: HclParser, + val cliRepository: TerraformCLIRepository, + val moduleRepository: TerraformModuleRepository) { + + @GetMapping("/repositories") + fun getRepositories(user: User): List { + return this.githubRegistryApi.getRepositories(user); + } + + @GetMapping("/repositories/{owner}/{repo}/import") + @ResponseStatus(HttpStatus.CREATED) + fun importRepository(@PathVariable owner: String, @PathVariable repo: String, user: User): TerraformModule { + val module = TerraformModule() + module.id = UUID.randomUUID().toString() + + val githubRepository = githubRegistryApi.getRepository(user, owner, repo) + + module.gitRepositoryUrl = githubRepository.htmlUrl + module.gitBranch = "master" + module.name = githubRepository.fullName + module.cliVersion = cliRepository.listCLIVersion().first() + + module.registryDetails = RegistryDetails(RegistryType.GITHUB, githubRepository.fullName) + module.createdBy = user + + // get variables + val variablesFile = githubRegistryApi.getFileContent(user, "$owner/$repo", "variables.tf") + module.variables = hclParser.parseVariables(variablesFile) + + // saving module ! + return moduleRepository.save(module) + } +} \ No newline at end of file diff --git a/src/main/java/io/codeka/gaia/registries/github/GitHubRawContent.kt b/src/main/java/io/codeka/gaia/registries/github/GitHubRawContent.kt index e97329966..6a473dec2 100644 --- a/src/main/java/io/codeka/gaia/registries/github/GitHubRawContent.kt +++ b/src/main/java/io/codeka/gaia/registries/github/GitHubRawContent.kt @@ -2,11 +2,13 @@ package io.codeka.gaia.registries.github import io.codeka.gaia.registries.RegistryType import io.codeka.gaia.registries.RegistryRawContent +import org.springframework.stereotype.Component import org.springframework.web.client.RestTemplate import java.util.regex.Pattern +@Component class GitHubRawContent(restTemplate: RestTemplate) : RegistryRawContent(RegistryType.GITHUB, restTemplate) { - override val pattern: Pattern = Pattern.compile("^http[s]?://[www.]?github.com(.*).git$") + override val pattern: Pattern = Pattern.compile("^https?:\\/\\/(w{3}\\.)?github\\.com(.*)(.git)?\$") } diff --git a/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt b/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt new file mode 100644 index 000000000..7c040ecab --- /dev/null +++ b/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt @@ -0,0 +1,79 @@ +package io.codeka.gaia.registries.github + +import com.fasterxml.jackson.annotation.JsonAlias +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.databind.annotation.JsonNaming +import io.codeka.gaia.registries.RegistryApi +import io.codeka.gaia.registries.RegistryFile +import io.codeka.gaia.registries.RegistryType +import io.codeka.gaia.teams.User +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Service +import org.springframework.web.client.RestTemplate +import java.util.* + +@Service +class GithubRegistryApi(restTemplate: RestTemplate): RegistryApi(RegistryType.GITHUB, restTemplate){ + + fun callWithAuth(url: String, token: String, responseType: Class): T{ + val headers = HttpHeaders() + if(token != null) { + headers.add("Authorization", "Bearer $token") + } + + val requestEntity = HttpEntity(headers) + + val response = restTemplate.exchange( + url, + HttpMethod.GET, + requestEntity, + responseType) + if(response.statusCode == HttpStatus.OK) { + return response.body + } + else { + TODO("error code mgmt") + } + } + + override fun getRepositories(user: User): List { + // fetching repositories + val url = "https://api.github.com/user/repos?visibility=public" + + val token = user.oAuth2User?.token!! + + val repos = callWithAuth(url, token, Array::class.java) + + return repos.map { it.fullName }.toList() + } + + fun getRepository(user: User, owner: String, repo: String): GithubRepository { + // fetching repositories + val url = "https://api.github.com/repos/$owner/$repo" + + val token = user.oAuth2User?.token!! + + return callWithAuth(url, token, GithubRepository::class.java) + } + + fun getFileContent(user: User, projectId: String, filename: String): String { + val url = "https://api.github.com/repos/$projectId/contents/$filename?ref=master"; + + val token = user.oAuth2User?.token!!; + + val file = callWithAuth(url, token, RegistryFile::class.java) + + // removing trailing \n + println(file.content.replace("\n", "")) + return String(Base64.getDecoder().decode(file.content.replace("\n", ""))) + } + +} + +data class GithubRepository( + @JsonProperty("full_name") val fullName: String, + @JsonProperty("html_url") val htmlUrl: String) \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2fdcb4393..bae8ba1e4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ -logging.level.org.springframework.web=TRACE +logging.level.org.springframework.web=INFO +logging.level.org.springframework.data.mongodb=DEBUG spring.data.rest.basePath=/api diff --git a/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerIT.java b/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerIT.java index e099fd142..475750cf2 100644 --- a/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerIT.java +++ b/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerIT.java @@ -14,8 +14,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -132,4 +131,14 @@ void saveModule_shouldValidateModuleVariables_forBlankFields() throws Exception .andExpect(jsonPath("$.message", containsString("variables[0].name must not be blank"))); } + @Test + void createModule_shouldSaveAModule() throws Exception { + mockMvc.perform(post("/api/modules") + .contentType(MediaType.APPLICATION_JSON) + // empty variable name + .content("{\"name\":\"new-module\"}")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.name", is("new-module"))); + } + } \ No newline at end of file diff --git a/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerTest.java b/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerTest.java index 02aad6ad7..6a58c9e2c 100644 --- a/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerTest.java +++ b/src/test/java/io/codeka/gaia/modules/controller/ModuleRestControllerTest.java @@ -180,4 +180,18 @@ void save_shouldThrowAndExecption_whenTheUserIsUnauthorized(){ assertThrows(ModuleNotFoundException.class, () -> moduleRestController.saveModule("12", module, bob)); } + @Test + void createModule_shouldSaveTheModule_forTheGivenUser(){ + // given + var module = new TerraformModule(); + module.setName("test-creation"); + // when + moduleRestController.createModule(module, bob); + + // then + verify(moduleRepository).save(module); + assertThat(module.getCreatedBy()).isEqualTo(bob); + assertThat(module.getId()).isNotBlank(); + } + } \ No newline at end of file diff --git a/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt new file mode 100644 index 000000000..78b96ace9 --- /dev/null +++ b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt @@ -0,0 +1,87 @@ +package io.codeka.gaia.registries.controller + +import io.codeka.gaia.hcl.HclParser +import io.codeka.gaia.modules.bo.TerraformModule +import io.codeka.gaia.modules.bo.Variable +import io.codeka.gaia.modules.repository.TerraformCLIRepository +import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.registries.RegistryType +import io.codeka.gaia.registries.github.GitHubRawContent +import io.codeka.gaia.registries.github.GithubRegistryApi +import io.codeka.gaia.registries.github.GithubRepository +import io.codeka.gaia.teams.User +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.* +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.* +import org.mockito.junit.jupiter.MockitoExtension + +@ExtendWith(MockitoExtension::class) +class GithubRegistryControllerTest{ + + @Mock + lateinit var githubRegistryApi: GithubRegistryApi + + @Mock + lateinit var hclParser: HclParser + + @Mock + lateinit var terraformCLIRepository: TerraformCLIRepository + + @Mock + lateinit var moduleRepository: TerraformModuleRepository + + @InjectMocks + lateinit var githubRegistryController: GithubRegistryController + + @Test + fun `getRepositories() should call the github registry api`() { + // given + val john = User("john", null) + // when + githubRegistryController.getRepositories(john) + // then + verify(githubRegistryApi).getRepositories(john) + } + + @Test + fun `importRepository() should call the github registry and create a module`() { + // returns saved module as first arg + `when`(moduleRepository.save(any(TerraformModule::class.java))).then { it.arguments[0] } + + `when`(terraformCLIRepository.listCLIVersion()).thenReturn(listOf("1.12.8", "1.12.7")) + + val user = User("juwit", null) + + val githubRepository = GithubRepository("juwit/terraform-docker-mongo","https://github.com/juwit/terraform-docker-mongo") + `when`(githubRegistryApi.getRepository(user, "juwit", "terraform-docker-mongo")).thenReturn(githubRepository) + + val variablesFileContent = "mock file content" + `when`(githubRegistryApi.getFileContent(user, "juwit/terraform-docker-mongo", "variables.tf")).thenReturn(variablesFileContent) + `when`(hclParser.parseVariables(variablesFileContent)).thenReturn(listOf(Variable("dummy"))) + + val module = githubRegistryController.importRepository("juwit", "terraform-docker-mongo", user) + + verify(githubRegistryApi).getRepository(user, "juwit", "terraform-docker-mongo") + verify(githubRegistryApi).getFileContent(user, "juwit/terraform-docker-mongo", "variables.tf") + + verifyNoMoreInteractions(githubRegistryApi) + + assertThat(module.id).isNotBlank() + + assertThat(module.name).isEqualTo("juwit/terraform-docker-mongo") + assertThat(module.gitRepositoryUrl).isEqualTo("https://github.com/juwit/terraform-docker-mongo") + assertThat(module.gitBranch).isEqualTo("master") + + assertThat(module.registryDetails.registryType).isEqualTo(RegistryType.GITHUB) + assertThat(module.registryDetails.projectId).isEqualTo("juwit/terraform-docker-mongo") + + assertThat(module.cliVersion).isEqualTo("1.12.8") + assertThat(module.createdBy).isEqualTo(user) + + assertThat(module.variables).containsExactly(Variable("dummy")) + + } +} \ No newline at end of file diff --git a/src/test/java/io/codeka/gaia/registries/github/GithubRegistryApiTest.kt b/src/test/java/io/codeka/gaia/registries/github/GithubRegistryApiTest.kt new file mode 100644 index 000000000..284dbad0d --- /dev/null +++ b/src/test/java/io/codeka/gaia/registries/github/GithubRegistryApiTest.kt @@ -0,0 +1,97 @@ +package io.codeka.gaia.registries.github + +import com.fasterxml.jackson.databind.ObjectMapper +import io.codeka.gaia.registries.RegistryFile +import io.codeka.gaia.teams.OAuth2User +import io.codeka.gaia.teams.User +import org.assertj.core.api.Assertions.assertThat +import org.bson.internal.Base64 +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient +import org.springframework.boot.test.autoconfigure.web.client.RestClientTest +import org.springframework.http.MediaType +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers.header +import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo +import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess +import java.nio.charset.Charset + + +@RestClientTest(GithubRegistryApi::class) +@AutoConfigureWebClient(registerRestTemplate = true) +class GithubRegistryApiTest{ + + @Autowired + lateinit var githubRegistryApi: GithubRegistryApi + + @Autowired + lateinit var server: MockRestServiceServer + + @Autowired + lateinit var objectMapper: ObjectMapper + + @Test + fun `getRepositories() should call the user repos github api`() { + // given + val sampleResult = GithubRepository("terraform-aws-modules/terraform-aws-rds", "https://github.com/terraform-aws-modules/terraform-aws-rds") + val sampleListResult = listOf(sampleResult) + val listDetailsString = objectMapper.writeValueAsString(sampleListResult) + + server.expect(requestTo("https://api.github.com/user/repos?visibility=public")) + .andExpect(header("Authorization", "Bearer johnstoken")) + .andRespond(withSuccess(listDetailsString, MediaType.APPLICATION_JSON)) + + val john = User("john", null) + john.oAuth2User = OAuth2User("github", "johnstoken", null) + + // when + val repositories = githubRegistryApi.getRepositories(john) + + // then + assertThat(repositories).containsExactly("terraform-aws-modules/terraform-aws-rds") + } + + @Test + fun `getRepository() should call the repos github api`() { + // given + val sampleResult = GithubRepository("terraform-aws-modules/terraform-aws-rds", "https://github.com/terraform-aws-modules/terraform-aws-rds") + val detailsString = objectMapper.writeValueAsString(sampleResult) + server.expect(requestTo("https://api.github.com/repos/terraform-aws-modules/terraform-aws-rds")) + .andExpect(header("Authorization", "Bearer johnstoken")) + .andRespond(withSuccess(detailsString, MediaType.APPLICATION_JSON)) + + val john = User("john", null) + john.oAuth2User = OAuth2User("github", "johnstoken", null) + + // when + val repositories = githubRegistryApi.getRepository(john, "terraform-aws-modules", "terraform-aws-rds") + + // then + assertThat(repositories).isEqualTo(GithubRepository("terraform-aws-modules/terraform-aws-rds", "https://github.com/terraform-aws-modules/terraform-aws-rds")) + } + + @Test + fun `getFileContent() should call the contents github api`() { + // given + val readmeContent = """ + # Sample README.md + """ + val sampleResult = RegistryFile(content = Base64.encode(readmeContent.toByteArray(Charset.defaultCharset()))) + val detailsString = objectMapper.writeValueAsString(sampleResult) + server.expect(requestTo("https://api.github.com/repos/terraform-aws-modules/terraform-aws-rds/contents/README.md?ref=master")) + .andExpect(header("Authorization", "Bearer johnstoken")) + .andRespond(withSuccess(detailsString, MediaType.APPLICATION_JSON)) + + val john = User("john", null) + john.oAuth2User = OAuth2User("github", "johnstoken", null) + + // when + val content = githubRegistryApi.getFileContent(john, "terraform-aws-modules/terraform-aws-rds", "README.md") + + // then + assertThat(content).isEqualTo(readmeContent) + } + +} diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..ca6ee9cea --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file From e2075edbf37e3dfee75ad639777216516568f847 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 30 Dec 2019 19:30:14 +0100 Subject: [PATCH 10/13] :sparkles: : add import module screen --- .../controller/ModulesMVCController.java | 5 + src/main/resources/static/css/style.css | 2 +- src/main/resources/templates/modules.html | 11 +- .../resources/templates/modules_import.html | 235 ++++++++++++++++++ .../templates/vue_templates/breadcrumb.vue | 1 + .../controller/ModulesMVCControllerTest.java | 14 +- 6 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/templates/modules_import.html diff --git a/src/main/java/io/codeka/gaia/modules/controller/ModulesMVCController.java b/src/main/java/io/codeka/gaia/modules/controller/ModulesMVCController.java index 099e4629d..ada6efa74 100644 --- a/src/main/java/io/codeka/gaia/modules/controller/ModulesMVCController.java +++ b/src/main/java/io/codeka/gaia/modules/controller/ModulesMVCController.java @@ -35,6 +35,11 @@ public String modulesList(){ return "modules"; } + @GetMapping("/modules/import") + public String importModule() { + return "modules_import"; + } + @GetMapping("/modules/{id}") public String module(@PathVariable String id, Model model, User user){ var module = terraformModuleRepository.findById(id).orElseThrow(ModuleNotFoundException::new); diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 12f533a29..24ecf2931 100755 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -2274,7 +2274,7 @@ i.icon { /** button section **/ .btn { - font-size: 14px; + /*font-size: 14px;*/ } .button_sction { diff --git a/src/main/resources/templates/modules.html b/src/main/resources/templates/modules.html index f0adc5c09..1dc8a6113 100644 --- a/src/main/resources/templates/modules.html +++ b/src/main/resources/templates/modules.html @@ -27,9 +27,16 @@ -
- +
+
+ +
+
diff --git a/src/main/resources/templates/modules_import.html b/src/main/resources/templates/modules_import.html new file mode 100644 index 000000000..b230fee25 --- /dev/null +++ b/src/main/resources/templates/modules_import.html @@ -0,0 +1,235 @@ + + + + + Gaia - Import Module + + + + +
+ +
+
+ + + + +
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/vue_templates/breadcrumb.vue b/src/main/resources/templates/vue_templates/breadcrumb.vue index 8d9c30f0f..7fa22d1ac 100644 --- a/src/main/resources/templates/vue_templates/breadcrumb.vue +++ b/src/main/resources/templates/vue_templates/breadcrumb.vue @@ -12,6 +12,7 @@ new_stack: [{text: 'Modules', href: '/modules'}, {text: 'Stack creation'}], module: [{text: 'Modules', href: '/modules'}, {text: 'Module edition'}], module_description: [{text: 'Modules', href: '/modules'}, {text: 'Module description'},], + module_import: [{text: 'Modules', href: '/modules'}, {text: 'Module import'},], stacks: [{text: 'Stacks'}], stack: [{text: 'Stacks', href: '/stacks'}, {text: 'Stack'}], users: [{text: 'Users'}], diff --git a/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java b/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java index 17d6e5225..df480db19 100644 --- a/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java +++ b/src/test/java/io/codeka/gaia/modules/controller/ModulesMVCControllerTest.java @@ -92,8 +92,6 @@ void readme_shouldReturnReadmeContent() { @Test void readme_shouldThrowExceptionIfModuleNotFound() { - // given - // when when(moduleRepository.findById(anyString())).thenReturn(Optional.empty()); assertThrows(NoSuchElementException.class, () -> controller.readme("TEST")); @@ -105,9 +103,6 @@ void readme_shouldThrowExceptionIfModuleNotFound() { @Test void modulesList_shouldShowModulesView(){ - // given - var model = mock(Model.class); - // when var res = controller.modulesList(); @@ -115,6 +110,15 @@ void modulesList_shouldShowModulesView(){ assertEquals("modules", res); } + @Test + void importModule_shouldShowImportModuleView(){ + // when + var res = controller.importModule(); + + // then + assertEquals("modules_import", res); + } + @Test void module_shouldShowModule_forAuthorizedUser(){ // given From 3324659cfe680fb2d1fb670c1ec5472d7fe8314e Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 6 Jan 2020 17:31:45 +0100 Subject: [PATCH 11/13] :recycle: : use interfaces instead of implementations to remote the need of mockito inline --- .../hcl/{HclParser.kt => HclParserImpl.kt} | 14 ++++++--- .../io/codeka/gaia/registries/RegistryApi.kt | 9 ++++-- .../controller/GithubRegistryController.kt | 6 ++-- .../registries/github/GithubRegistryApi.kt | 6 ++-- .../io/codeka/gaia/hcl/HCLParserTest.java | 2 +- .../GithubRegistryControllerTest.kt | 30 ++++++++++++------- .../org.mockito.plugins.MockMaker | 1 - 7 files changed, 43 insertions(+), 25 deletions(-) rename src/main/java/io/codeka/gaia/hcl/{HclParser.kt => HclParserImpl.kt} (71%) delete mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/main/java/io/codeka/gaia/hcl/HclParser.kt b/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt similarity index 71% rename from src/main/java/io/codeka/gaia/hcl/HclParser.kt rename to src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt index 9f53d0c52..130ebba7e 100644 --- a/src/main/java/io/codeka/gaia/hcl/HclParser.kt +++ b/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt @@ -8,10 +8,16 @@ import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CommonTokenStream import org.springframework.stereotype.Service +interface HclParser { + fun parseContent(content: String): HclVisitor + fun parseVariables(content: String): List + fun parseOutputs(content: String): List +} + @Service -class HclParser { +class HclParserImpl : HclParser { - private fun parseContent(content: String): HclVisitor { + override fun parseContent(content: String): HclVisitor { // loading test file val charStream = CharStreams.fromString(content) @@ -30,12 +36,12 @@ class HclParser { return hclVisitor } - fun parseVariables(content:String): List { + override fun parseVariables(content:String): List { val hclVisitor = parseContent(content) return hclVisitor.variables } - fun parseOutputs(content:String): List { + override fun parseOutputs(content:String): List { val hclVisitor = parseContent(content) return hclVisitor.outputs } diff --git a/src/main/java/io/codeka/gaia/registries/RegistryApi.kt b/src/main/java/io/codeka/gaia/registries/RegistryApi.kt index 327425a87..bb00da7b5 100644 --- a/src/main/java/io/codeka/gaia/registries/RegistryApi.kt +++ b/src/main/java/io/codeka/gaia/registries/RegistryApi.kt @@ -1,10 +1,15 @@ package io.codeka.gaia.registries +import io.codeka.gaia.registries.github.GithubRepository import io.codeka.gaia.teams.User import org.springframework.web.client.RestTemplate -abstract class RegistryApi(private val registryType: RegistryType, val restTemplate: RestTemplate) { +interface RegistryApi { - abstract fun getRepositories(user: User) : List + fun getRepositories(user: User) : List + + fun getRepository(user: User, owner: String, repo: String): T + + fun getFileContent(user: User, projectId: String, filename: String): String } \ No newline at end of file diff --git a/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt index 6982ceecb..afbbf3168 100644 --- a/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt +++ b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt @@ -4,13 +4,11 @@ import io.codeka.gaia.hcl.HclParser import io.codeka.gaia.modules.bo.TerraformModule import io.codeka.gaia.modules.repository.TerraformCLIRepository import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.registries.RegistryApi import io.codeka.gaia.registries.RegistryDetails import io.codeka.gaia.registries.RegistryType -import io.codeka.gaia.registries.github.GitHubRawContent -import io.codeka.gaia.registries.github.GithubRegistryApi import io.codeka.gaia.registries.github.GithubRepository import io.codeka.gaia.teams.User -import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* @@ -20,7 +18,7 @@ import java.util.* @RequestMapping("/api/registries/github") @Secured class GithubRegistryController( - val githubRegistryApi: GithubRegistryApi, + val githubRegistryApi: RegistryApi, val hclParser: HclParser, val cliRepository: TerraformCLIRepository, val moduleRepository: TerraformModuleRepository) { diff --git a/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt b/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt index 7c040ecab..49981034f 100644 --- a/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt +++ b/src/main/java/io/codeka/gaia/registries/github/GithubRegistryApi.kt @@ -17,7 +17,7 @@ import org.springframework.web.client.RestTemplate import java.util.* @Service -class GithubRegistryApi(restTemplate: RestTemplate): RegistryApi(RegistryType.GITHUB, restTemplate){ +class GithubRegistryApi(val restTemplate: RestTemplate): RegistryApi { fun callWithAuth(url: String, token: String, responseType: Class): T{ val headers = HttpHeaders() @@ -51,7 +51,7 @@ class GithubRegistryApi(restTemplate: RestTemplate): RegistryApi(RegistryType.GI return repos.map { it.fullName }.toList() } - fun getRepository(user: User, owner: String, repo: String): GithubRepository { + override fun getRepository(user: User, owner: String, repo: String): GithubRepository { // fetching repositories val url = "https://api.github.com/repos/$owner/$repo" @@ -60,7 +60,7 @@ class GithubRegistryApi(restTemplate: RestTemplate): RegistryApi(RegistryType.GI return callWithAuth(url, token, GithubRepository::class.java) } - fun getFileContent(user: User, projectId: String, filename: String): String { + override fun getFileContent(user: User, projectId: String, filename: String): String { val url = "https://api.github.com/repos/$projectId/contents/$filename?ref=master"; val token = user.oAuth2User?.token!!; diff --git a/src/test/java/io/codeka/gaia/hcl/HCLParserTest.java b/src/test/java/io/codeka/gaia/hcl/HCLParserTest.java index 60b26b80d..d9eeacffe 100644 --- a/src/test/java/io/codeka/gaia/hcl/HCLParserTest.java +++ b/src/test/java/io/codeka/gaia/hcl/HCLParserTest.java @@ -13,7 +13,7 @@ class HCLParserTest { - private HclParser hclParser = new HclParser(); + private HclParserImpl hclParser = new HclParserImpl(); @Test void parsing_variables_shouldWorkWithVisitor() throws IOException { diff --git a/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt index 78b96ace9..6f44a2bed 100644 --- a/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt +++ b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt @@ -5,24 +5,28 @@ import io.codeka.gaia.modules.bo.TerraformModule import io.codeka.gaia.modules.bo.Variable import io.codeka.gaia.modules.repository.TerraformCLIRepository import io.codeka.gaia.modules.repository.TerraformModuleRepository +import io.codeka.gaia.registries.RegistryApi import io.codeka.gaia.registries.RegistryType -import io.codeka.gaia.registries.github.GitHubRawContent -import io.codeka.gaia.registries.github.GithubRegistryApi import io.codeka.gaia.registries.github.GithubRepository import io.codeka.gaia.teams.User import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.* import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.* +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.stubbing.OngoingStubbing +import org.mockito.stubbing.Stubber @ExtendWith(MockitoExtension::class) class GithubRegistryControllerTest{ @Mock - lateinit var githubRegistryApi: GithubRegistryApi + lateinit var githubRegistryApi: RegistryApi @Mock lateinit var hclParser: HclParser @@ -49,18 +53,18 @@ class GithubRegistryControllerTest{ @Test fun `importRepository() should call the github registry and create a module`() { // returns saved module as first arg - `when`(moduleRepository.save(any(TerraformModule::class.java))).then { it.arguments[0] } + whenever(moduleRepository.save(any(TerraformModule::class.java))).then { it.arguments[0] } - `when`(terraformCLIRepository.listCLIVersion()).thenReturn(listOf("1.12.8", "1.12.7")) + whenever(terraformCLIRepository.listCLIVersion()).thenReturn(listOf("1.12.8", "1.12.7")) val user = User("juwit", null) val githubRepository = GithubRepository("juwit/terraform-docker-mongo","https://github.com/juwit/terraform-docker-mongo") - `when`(githubRegistryApi.getRepository(user, "juwit", "terraform-docker-mongo")).thenReturn(githubRepository) + whenever(githubRegistryApi.getRepository(user, "juwit", "terraform-docker-mongo")).thenReturn(githubRepository) val variablesFileContent = "mock file content" - `when`(githubRegistryApi.getFileContent(user, "juwit/terraform-docker-mongo", "variables.tf")).thenReturn(variablesFileContent) - `when`(hclParser.parseVariables(variablesFileContent)).thenReturn(listOf(Variable("dummy"))) + whenever(githubRegistryApi.getFileContent(user, "juwit/terraform-docker-mongo", "variables.tf")).thenReturn(variablesFileContent) + whenever(hclParser.parseVariables(variablesFileContent)).thenReturn(listOf(Variable("dummy"))) val module = githubRegistryController.importRepository("juwit", "terraform-docker-mongo", user) @@ -84,4 +88,10 @@ class GithubRegistryControllerTest{ assertThat(module.variables).containsExactly(Variable("dummy")) } +} + +fun Stubber.whenever(mock: T) = `when`(mock) + +inline fun whenever(methodCall: T): OngoingStubbing { + return Mockito.`when`(methodCall)!! } \ No newline at end of file diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker deleted file mode 100644 index ca6ee9cea..000000000 --- a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ /dev/null @@ -1 +0,0 @@ -mock-maker-inline \ No newline at end of file From c8b7e6eecab61c624c8529282343ae8d76881652 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 6 Jan 2020 19:00:28 +0100 Subject: [PATCH 12/13] :truck: : rename integration test --- ...iExtensionsScannerTest.kt => UiExtensionsScannerIT.kt} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename src/test/java/io/codeka/gaia/dashboard/controller/{UiExtensionsScannerTest.kt => UiExtensionsScannerIT.kt} (88%) diff --git a/src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerTest.kt b/src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerIT.kt similarity index 88% rename from src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerTest.kt rename to src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerIT.kt index 24746d96d..592b68769 100644 --- a/src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerTest.kt +++ b/src/test/java/io/codeka/gaia/dashboard/controller/UiExtensionsScannerIT.kt @@ -5,13 +5,13 @@ import org.junit.jupiter.api.Test import org.rnorth.visibleassertions.VisibleAssertions.assertThrows import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.ApplicationContext -import java.util.NoSuchElementException +import java.util.* @SpringBootTest(classes = [UIExtensionsConfig::class]) -class UiExtensionsScannerTest(val applicationContext: ApplicationContext) { +class UiExtensionsScannerIT(private val applicationContext: ApplicationContext) { @Test - fun `scan should find all exsting extensions`() { + fun `scan should find all existing extensions`() { val scanner = UiExtensionsScanner(applicationContext) val locations = arrayOf( @@ -35,7 +35,7 @@ class UiExtensionsScannerTest(val applicationContext: ApplicationContext) { val uiExtension = scanner.scan(*locations) - assertThat(uiExtension.size).isEqualTo(locations.size); + assertThat(uiExtension.size).isEqualTo(locations.size) } @Test From 86eecd38b6b601b3fbc451137520a88bb19ce6bc Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 6 Jan 2020 19:09:45 +0100 Subject: [PATCH 13/13] :rotating_light: : make attributes private --- .../registries/controller/GithubRegistryController.kt | 10 +++++----- .../controller/GithubRegistryControllerTest.kt | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt index afbbf3168..62349e500 100644 --- a/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt +++ b/src/main/java/io/codeka/gaia/registries/controller/GithubRegistryController.kt @@ -18,14 +18,14 @@ import java.util.* @RequestMapping("/api/registries/github") @Secured class GithubRegistryController( - val githubRegistryApi: RegistryApi, - val hclParser: HclParser, - val cliRepository: TerraformCLIRepository, - val moduleRepository: TerraformModuleRepository) { + private val githubRegistryApi: RegistryApi, + private val hclParser: HclParser, + private val cliRepository: TerraformCLIRepository, + private val moduleRepository: TerraformModuleRepository) { @GetMapping("/repositories") fun getRepositories(user: User): List { - return this.githubRegistryApi.getRepositories(user); + return this.githubRegistryApi.getRepositories(user) } @GetMapping("/repositories/{owner}/{repo}/import") diff --git a/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt index 6f44a2bed..178ec7950 100644 --- a/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt +++ b/src/test/java/io/codeka/gaia/registries/controller/GithubRegistryControllerTest.kt @@ -90,8 +90,6 @@ class GithubRegistryControllerTest{ } } -fun Stubber.whenever(mock: T) = `when`(mock) - inline fun whenever(methodCall: T): OngoingStubbing { return Mockito.`when`(methodCall)!! } \ No newline at end of file