diff --git a/pom.xml b/pom.xml index 9fb6345..482bd67 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ githubService 21 + 2023.0.0 @@ -42,22 +43,30 @@ 4.2.0 test - + + org.springframework.cloud + spring-cloud-starter-contract-stub-runner + test + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + org.springframework.boot spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - diff --git a/src/main/java/org/example/githubservice/service/impl/GithubServiceImpl.java b/src/main/java/org/example/githubservice/service/impl/GithubServiceImpl.java index ee8042d..e462c47 100644 --- a/src/main/java/org/example/githubservice/service/impl/GithubServiceImpl.java +++ b/src/main/java/org/example/githubservice/service/impl/GithubServiceImpl.java @@ -16,7 +16,7 @@ @Service @Primary public class GithubServiceImpl implements GithubService { - private final String REPOS_OF_USER = "/users/{username}/repos"; + public static final String REPOS_OF_USER = "/users/{username}/repos"; private final WebClient webClient; public GithubServiceImpl(WebClient.Builder webClientBuilder, @Value("${github.api.base-url}") String rootUrl) { diff --git a/src/test/java/org/example/githubservice/controller/GithubControllerIT.java b/src/test/java/org/example/githubservice/controller/GithubControllerIT.java index 33bfcac..5fe2da1 100644 --- a/src/test/java/org/example/githubservice/controller/GithubControllerIT.java +++ b/src/test/java/org/example/githubservice/controller/GithubControllerIT.java @@ -1,50 +1,109 @@ package org.example.githubservice.controller; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.example.githubservice.model.dtos.RepositoryDTO; +import org.example.githubservice.service.api.GithubService; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import reactor.core.publisher.Flux; +import wiremock.org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; + + @SpringBootTest -@AutoConfigureWebTestClient +@WireMockTest(httpPort = 8081) +@ExtendWith(SpringExtension.class) public class GithubControllerIT { - @Autowired - WebTestClient webTestClient; - - @Test - void testListAllRepositoriesOfUser() { - // Given - String username = "Kondziow"; - int page = 1; - int perPage = 2; - - // When/Then - webTestClient.get() - .uri("/api/v1/users/{username}/repos?page={page}&perPage={perPage}", username, page, perPage) - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus() - .isOk() - .expectBody() - .jsonPath("$[0].name").exists() - .jsonPath("$[1].name").exists(); + GithubService githubService; + @DynamicPropertySource + static void configure(DynamicPropertyRegistry registry) { + registry.add("github.api.base-url", () -> "http://localhost:8081"); } @Test - void testUserNotFound() { - // Given - String username = "12i9b12dsblasdklaasd"; - - // When/Then - webTestClient.get() - .uri("/api/v1/users/{username}/repos", username) - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus().isNotFound() - .expectBody() - .jsonPath("$.status").exists() - .jsonPath("$.message").exists(); + void should_return_correct_repositories() throws IOException { + // given + String responseBodyRepositories = IOUtils.resourceToString("/files/correct-response-repositories-from-githubAPI.json", StandardCharsets.UTF_8); + String responseBodyBranches = IOUtils.resourceToString("/files/correct-response-branches-from-githubAPI.json", StandardCharsets.UTF_8); + String username = "jotzet"; + String repositoryName = "gimmemoji"; + + stubFor(get(urlEqualTo("/users/{username}/repos".replace("{username}", username))) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(responseBodyRepositories) + ) + ); + + stubFor(get(urlEqualTo("/repos/{username}/{repository}/branches".replace("{username}", username).replace("{repository}", repositoryName))) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(responseBodyBranches) + ) + ); + //when + Flux response = githubService.listAllRepositoriesOfUser("jotzet"); + + // then + response.doOnNext(repositoryDTO -> { + System.out.println(repositoryDTO.name()); + System.out.println(repositoryDTO.owner().login()); + System.out.println(repositoryDTO.branches()); + }).blockLast(); } + } + + +// @Autowired +// WebTestClient webTestClient; +// +// @Test +// void testListAllRepositoriesOfUser() { +// // Given +// String username = "Kondziow"; +// int page = 1; +// int perPage = 2; +// +// // When/Then +// webTestClient.get() +// .uri("/api/v1/users/{username}/repos?page={page}&perPage={perPage}", username, page, perPage) +// .accept(MediaType.APPLICATION_JSON) +// .exchange() +// .expectStatus() +// .isOk() +// .expectBody() +// .jsonPath("$[0].name").exists() +// .jsonPath("$[1].name").exists(); +// } +// +// @Test +// void testUserNotFound() { +// // Given +// String username = "12i9b12dsblasdklaasd"; +// +// // When/Then +// webTestClient.get() +// .uri("/api/v1/users/{username}/repos", username) +// .accept(MediaType.APPLICATION_JSON) +// .exchange() +// .expectStatus().isNotFound() +// .expectBody() +// .jsonPath("$.status").exists() +// .jsonPath("$.message").exists(); +// } + diff --git a/src/test/java/org/example/githubservice/service/impl/GithubServiceImplTest.java b/src/test/java/org/example/githubservice/service/impl/GithubServiceImplTest.java new file mode 100644 index 0000000..e131eaa --- /dev/null +++ b/src/test/java/org/example/githubservice/service/impl/GithubServiceImplTest.java @@ -0,0 +1,80 @@ +package org.example.githubservice.service.impl; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.example.githubservice.model.dtos.BranchDTO; +import org.example.githubservice.model.dtos.CommitDTO; +import org.example.githubservice.model.dtos.OwnerDTO; +import org.example.githubservice.model.dtos.RepositoryDTO; +import org.example.githubservice.service.api.GithubService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import reactor.core.publisher.Flux; +import wiremock.org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@WireMockTest(httpPort = 8081) +@ExtendWith(SpringExtension.class) +class GithubServiceImplTest { + @Autowired + GithubService githubService; + @DynamicPropertySource + static void configure(DynamicPropertyRegistry registry) { + registry.add("github.api.base-url", () -> "http://localhost:8081"); + } + + @Test + void should_return_correct_repositories_Flux() throws IOException { + // given + String responseBodyRepositories = IOUtils.resourceToString("/files/correct-response-repositories-from-githubAPI.json", StandardCharsets.UTF_8); + String responseBodyBranches = IOUtils.resourceToString("/files/correct-response-branches-from-githubAPI.json", StandardCharsets.UTF_8); + + BranchDTO branchDTO = new BranchDTO("main", new CommitDTO("7155aa7c2d68f7e8ab38abbed9ea22595441b32a")); + RepositoryDTO repositoryTested = new RepositoryDTO("gimmemoji", new OwnerDTO("jotzet"), new LinkedList<>(List.of(branchDTO))); + + String username = "jotzet"; + + stubFor(get(urlEqualTo("/users/{username}/repos".replace("{username}", username))) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(responseBodyRepositories) + ) + ); + + stubFor(get(urlEqualTo("/repos/{username}/{repository}/branches".replace("{username}", username).replace("{repository}", repositoryTested.name()))) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(responseBodyBranches) + ) + ); + //when + Flux response = githubService.listAllRepositoriesOfUser("jotzet"); + + // then + response.doOnNext(repositoryDTO -> { + assertThat(repositoryDTO.name()).isEqualTo(repositoryTested.name()); + assertThat(repositoryDTO.owner()).isEqualTo(repositoryTested.owner()); + assertThat(repositoryDTO.branches().getFirst().name()).isEqualTo(repositoryTested.branches().getFirst().name()); + assertThat(repositoryDTO.branches().getFirst().commit().sha()).isEqualTo(repositoryTested.branches().getFirst().commit().sha()); + }).blockLast(); + } + +} \ No newline at end of file diff --git a/src/test/resources/files/correct-response-branches-from-githubAPI.json b/src/test/resources/files/correct-response-branches-from-githubAPI.json new file mode 100644 index 0000000..d46d0f4 --- /dev/null +++ b/src/test/resources/files/correct-response-branches-from-githubAPI.json @@ -0,0 +1,10 @@ +[ + { + "name": "main", + "commit": { + "sha": "7155aa7c2d68f7e8ab38abbed9ea22595441b32a", + "url": "https://api.github.com/repos/jotzet/gimmemoji/commits/7155aa7c2d68f7e8ab38abbed9ea22595441b32a" + }, + "protected": false + } +] \ No newline at end of file diff --git a/src/test/resources/files/correct-response-full-from-application.json b/src/test/resources/files/correct-response-full-from-application.json new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/files/correct-response-repositories-from-githubAPI.json b/src/test/resources/files/correct-response-repositories-from-githubAPI.json new file mode 100644 index 0000000..e48b2e8 --- /dev/null +++ b/src/test/resources/files/correct-response-repositories-from-githubAPI.json @@ -0,0 +1,202 @@ +[ + { + "id": 751946203, + "node_id": "R_kgDOLNHJ2w", + "name": "gimmemoji", + "full_name": "jotzet/gimmemoji", + "private": false, + "owner": { + "login": "jotzet", + "id": 91730870, + "node_id": "U_kgDOBXeztg", + "avatar_url": "https://avatars.githubusercontent.com/u/91730870?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jotzet", + "html_url": "https://github.com/jotzet", + "followers_url": "https://api.github.com/users/jotzet/followers", + "following_url": "https://api.github.com/users/jotzet/following{/other_user}", + "gists_url": "https://api.github.com/users/jotzet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jotzet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jotzet/subscriptions", + "organizations_url": "https://api.github.com/users/jotzet/orgs", + "repos_url": "https://api.github.com/users/jotzet/repos", + "events_url": "https://api.github.com/users/jotzet/events{/privacy}", + "received_events_url": "https://api.github.com/users/jotzet/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/jotzet/gimmemoji", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/jotzet/gimmemoji", + "forks_url": "https://api.github.com/repos/jotzet/gimmemoji/forks", + "keys_url": "https://api.github.com/repos/jotzet/gimmemoji/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/jotzet/gimmemoji/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/jotzet/gimmemoji/teams", + "hooks_url": "https://api.github.com/repos/jotzet/gimmemoji/hooks", + "issue_events_url": "https://api.github.com/repos/jotzet/gimmemoji/issues/events{/number}", + "events_url": "https://api.github.com/repos/jotzet/gimmemoji/events", + "assignees_url": "https://api.github.com/repos/jotzet/gimmemoji/assignees{/user}", + "branches_url": "https://api.github.com/repos/jotzet/gimmemoji/branches{/branch}", + "tags_url": "https://api.github.com/repos/jotzet/gimmemoji/tags", + "blobs_url": "https://api.github.com/repos/jotzet/gimmemoji/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/jotzet/gimmemoji/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/jotzet/gimmemoji/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/jotzet/gimmemoji/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/jotzet/gimmemoji/statuses/{sha}", + "languages_url": "https://api.github.com/repos/jotzet/gimmemoji/languages", + "stargazers_url": "https://api.github.com/repos/jotzet/gimmemoji/stargazers", + "contributors_url": "https://api.github.com/repos/jotzet/gimmemoji/contributors", + "subscribers_url": "https://api.github.com/repos/jotzet/gimmemoji/subscribers", + "subscription_url": "https://api.github.com/repos/jotzet/gimmemoji/subscription", + "commits_url": "https://api.github.com/repos/jotzet/gimmemoji/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/jotzet/gimmemoji/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/jotzet/gimmemoji/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/jotzet/gimmemoji/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/jotzet/gimmemoji/contents/{+path}", + "compare_url": "https://api.github.com/repos/jotzet/gimmemoji/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/jotzet/gimmemoji/merges", + "archive_url": "https://api.github.com/repos/jotzet/gimmemoji/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/jotzet/gimmemoji/downloads", + "issues_url": "https://api.github.com/repos/jotzet/gimmemoji/issues{/number}", + "pulls_url": "https://api.github.com/repos/jotzet/gimmemoji/pulls{/number}", + "milestones_url": "https://api.github.com/repos/jotzet/gimmemoji/milestones{/number}", + "notifications_url": "https://api.github.com/repos/jotzet/gimmemoji/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/jotzet/gimmemoji/labels{/name}", + "releases_url": "https://api.github.com/repos/jotzet/gimmemoji/releases{/id}", + "deployments_url": "https://api.github.com/repos/jotzet/gimmemoji/deployments", + "created_at": "2024-02-02T17:12:33Z", + "updated_at": "2024-02-07T16:58:16Z", + "pushed_at": "2024-02-07T16:57:30Z", + "git_url": "git://github.com/jotzet/gimmemoji.git", + "ssh_url": "git@github.com:jotzet/gimmemoji.git", + "clone_url": "https://github.com/jotzet/gimmemoji.git", + "svn_url": "https://github.com/jotzet/gimmemoji", + "homepage": null, + "size": 212, + "stargazers_count": 0, + "watchers_count": 0, + "language": "JavaScript", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main" + }, + { + "id": 711467689, + "node_id": "R_kgDOKmgiqQ", + "name": "pianoroll-frontend-challenge", + "full_name": "jotzet/pianoroll-frontend-challenge", + "private": false, + "owner": { + "login": "jotzet", + "id": 91730870, + "node_id": "U_kgDOBXeztg", + "avatar_url": "https://avatars.githubusercontent.com/u/91730870?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jotzet", + "html_url": "https://github.com/jotzet", + "followers_url": "https://api.github.com/users/jotzet/followers", + "following_url": "https://api.github.com/users/jotzet/following{/other_user}", + "gists_url": "https://api.github.com/users/jotzet/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jotzet/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jotzet/subscriptions", + "organizations_url": "https://api.github.com/users/jotzet/orgs", + "repos_url": "https://api.github.com/users/jotzet/repos", + "events_url": "https://api.github.com/users/jotzet/events{/privacy}", + "received_events_url": "https://api.github.com/users/jotzet/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/jotzet/pianoroll-frontend-challenge", + "description": null, + "fork": true, + "url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge", + "forks_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/forks", + "keys_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/teams", + "hooks_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/hooks", + "issue_events_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/issues/events{/number}", + "events_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/events", + "assignees_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/assignees{/user}", + "branches_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/branches{/branch}", + "tags_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/tags", + "blobs_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/statuses/{sha}", + "languages_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/languages", + "stargazers_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/stargazers", + "contributors_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/contributors", + "subscribers_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/subscribers", + "subscription_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/subscription", + "commits_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/contents/{+path}", + "compare_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/merges", + "archive_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/downloads", + "issues_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/issues{/number}", + "pulls_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/pulls{/number}", + "milestones_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/milestones{/number}", + "notifications_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/labels{/name}", + "releases_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/releases{/id}", + "deployments_url": "https://api.github.com/repos/jotzet/pianoroll-frontend-challenge/deployments", + "created_at": "2023-10-29T11:09:03Z", + "updated_at": "2023-10-30T14:57:16Z", + "pushed_at": "2023-11-06T11:02:21Z", + "git_url": "git://github.com/jotzet/pianoroll-frontend-challenge.git", + "ssh_url": "git@github.com:jotzet/pianoroll-frontend-challenge.git", + "clone_url": "https://github.com/jotzet/pianoroll-frontend-challenge.git", + "svn_url": "https://github.com/jotzet/pianoroll-frontend-challenge", + "homepage": null, + "size": 335, + "stargazers_count": 0, + "watchers_count": 0, + "language": "JavaScript", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + } +] \ No newline at end of file