From 4e09a94e00c91e56d88b2c9333c9d1d4390338e8 Mon Sep 17 00:00:00 2001 From: Arin Ghazarian Date: Fri, 10 Dec 2021 15:06:09 -0800 Subject: [PATCH] Add unit tests for GitHubApi (#172) Co-authored-by: Dylan Smith --- src/OctoshiftCLI.Tests/GithubApiTests.cs | 613 +++++++++++++++++- .../OctoshiftCLI.Tests.csproj | 1 + .../Commands/GrantMigratorRoleCommand.cs | 7 +- .../Commands/RevokeMigratorRoleCommand.cs | 4 +- src/OctoshiftCLI/GithubApi.cs | 52 +- src/OctoshiftCLI/GithubClient.cs | 26 +- 6 files changed, 645 insertions(+), 58 deletions(-) diff --git a/src/OctoshiftCLI.Tests/GithubApiTests.cs b/src/OctoshiftCLI.Tests/GithubApiTests.cs index 060866e90..7ffbe5a88 100644 --- a/src/OctoshiftCLI.Tests/GithubApiTests.cs +++ b/src/OctoshiftCLI.Tests/GithubApiTests.cs @@ -1,5 +1,6 @@ using System.Net.Http; using System.Threading.Tasks; +using FluentAssertions; using Moq; using Xunit; @@ -8,7 +9,236 @@ namespace OctoshiftCLI.Tests public class GithubApiTests { [Fact] - public async Task Create_Migration_Source_Returns_New_Migration_Source_Id() + public async Task AddAutoLink_Calls_The_Right_Endpoint_With_Payload() + { + // Arrange + const string org = "ORG"; + const string repo = "REPO"; + const string adoOrg = "ADO_ORG"; + const string adoTeamProject = "ADO_TEAM_PROJECT"; + + var url = $"https://api.github.com/repos/{org}/{repo}/autolinks"; + + var payload = + $"{{ \"key_prefix\": \"AB#\", \"url_template\": \"https://dev.azure.com/{adoOrg}/{adoTeamProject}/_workitems/edit//\" }}"; + + var githubClientMock = new Mock(null, null); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + await githubApi.AddAutoLink(org, repo, adoOrg, adoTeamProject); + + // Assert + githubClientMock.Verify(m => m.PostAsync(url, payload), Times.Once); + } + + [Fact] + public async Task CreateTeam_Returns_Created_Team_Id() + { + // Arrange + const string org = "ORG"; + const string teamName = "TEAM_NAME"; + + var url = $"https://api.github.com/orgs/{org}/teams"; + var payload = $"{{ \"name\": \"{teamName}\", \"privacy\": \"closed\" }}"; + + const string teamId = "TEAM_ID"; + var response = $"{{\"id\": \"{teamId}\"}}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var result = await githubApi.CreateTeam(org, teamName); + + // Assert + result.Should().Be(teamId); + } + + [Fact] + public async Task GetTeamMembers_Returns_Team_Members() + { + // Arrange + const string org = "ORG"; + const string teamName = "TEAM_NAME"; + + var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/members"; + const string teamMember1 = "TEAM_MEMBER_1"; + const string teamMember2 = "TEAM_MEMBER_2"; + var response = $@" + [ + {{ + ""login"": ""{teamMember1}"", + ""id"": 1 + }}, + {{ + ""login"": ""{teamMember2}"", + ""id"": 2 + }} + ]"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.GetAsync(url)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var result = await githubApi.GetTeamMembers(org, teamName); + + // Assert + result.Should().Equal(teamMember1, teamMember2); + } + + [Fact] + public async Task RemoveTeamMember_Calls_The_Right_Endpoint() + { + // Arrange + const string org = "ORG"; + const string teamName = "TEAM_NAME"; + const string member = "MEMBER"; + + var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/memberships/{member}"; + + var githubClinetMock = new Mock(null, null); + githubClinetMock.Setup(m => m.DeleteAsync(url)); + + // Act + using var githubApi = new GithubApi(githubClinetMock.Object); + await githubApi.RemoveTeamMember(org, teamName, member); + + // Assert + githubClinetMock.Verify(m => m.DeleteAsync(url)); + } + + [Fact] + public async Task GetIdpGroup_Returns_Id_Name_And_Description_Of_Idp_Group() + { + // Arrange + const string org = "ORG"; + const string idpGroupName = "IDP_GROUP_NAME"; + const string groupId = "123"; + const string groupDescription = "GROUP_DESCRIPTION"; + + var url = $"https://api.github.com/orgs/{org}/team-sync/groups"; + var response = $@" + {{ + ""groups"": [ + {{ + ""group_id"": ""{groupId}"", + ""group_name"": ""{idpGroupName}"", + ""group_description"": ""{groupDescription}"" + }}, + {{ + ""group_id"": ""123"", + ""group_name"": ""Octocat admins"", + ""group_description"": ""The people who configure your octoworld."" + }} + ] + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.GetAsync(url)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var (id, name, description) = await githubApi.GetIdpGroup(org, idpGroupName); + + // Assert + id.Should().Be(groupId); + name.Should().Be(idpGroupName); + description.Should().Be(groupDescription); + } + + [Fact] + public async Task AddTeamSync_Calls_The_Right_Endpoint_With_Payload() + { + // Arrange + const string org = "ORG"; + const string teamName = "TEAM_NAME"; + const string groupId = "GROUP_ID"; + const string groupName = "GROUP_NAME"; + const string groupDesc = "GROUP_DESC"; + + var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/team-sync/group-mappings"; + var payload = + $"{{ \"groups\": [{{ \"group_id\":\"{groupId}\", \"group_name\":\"{groupName}\", \"group_description\":\"{groupDesc}\" }}] }}"; + + var githubClientMock = new Mock(null, null); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + await githubApi.AddTeamSync(org, teamName, groupId, groupName, groupDesc); + + // Assert + githubClientMock.Verify(m => m.PatchAsync(url, payload)); + } + + [Fact] + public async Task AddTeamToRepo_Calls_The_Right_Endpoint_With_Payload() + { + // Arrange + const string org = "ORG"; + const string repo = "REPO"; + const string teamName = "TEAM_NAME"; + const string role = "ROLE"; + + var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/repos/{org}/{repo}"; + var payload = $"{{ \"permission\":\"{role}\" }}"; + + var githubClientMock = new Mock(null, null); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + await githubApi.AddTeamToRepo(org, repo, teamName, role); + + // Assert + githubClientMock.Verify(m => m.PutAsync(url, payload)); + } + + [Fact] + public async Task GetOrganizationId_Returns_The_Org_Id() + { + // Arrange + const string org = "ORG"; + const string orgId = "ORG_ID"; + + var url = $"https://api.github.com/graphql"; + var payload = + $"{{\"query\":\"query($login: String!){{organization(login: $login) {{ login, id, name }} }}\",\"variables\":{{\"login\":\"{org}\"}}}}"; + var response = $@" + {{ + ""data"": + {{ + ""organization"": + {{ + ""login"": ""{org}"", + ""id"": ""{orgId}"", + ""name"": ""github"" + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var result = await githubApi.GetOrganizationId(org); + + // Assert + result.Should().Be(orgId); + } + + [Fact] + public async Task CreateMigrationSource_Returns_New_Migration_Source_Id() { // Arrange const string url = "https://api.github.com/graphql"; @@ -19,20 +249,389 @@ public async Task Create_Migration_Source_Returns_New_Migration_Source_Id() "{\"query\":\"mutation createMigrationSource($name: String!, $url: String!, $ownerId: ID!, $accessToken: String!, $type: MigrationSourceType!, $githubPat: String!) " + "{ createMigrationSource(input: {name: $name, url: $url, ownerId: $ownerId, accessToken: $accessToken, type: $type, githubPat: $githubPat}) { migrationSource { id, name, url, type } } }\"" + $",\"variables\":{{\"name\":\"Azure DevOps Source\",\"url\":\"https://dev.azure.com\",\"ownerId\":\"{orgId}\",\"type\":\"AZURE_DEVOPS\",\"accessToken\":\"{adoToken}\", \"githubPat\":\"{githubPat}\"}},\"operationName\":\"createMigrationSource\"}}"; - const string migrationSourceId = "MS_kgC4NjFhOTVjOTc4ZTRhZjEwMDA5NjNhOTdm"; - var result = $"{{\"data\":{{\"createMigrationSource\":{{\"migrationSource\": {{\"id\":\"{migrationSourceId}\",\"name\":\"Azure Devops Source\",\"url\":\"https://dev.azure.com\",\"type\":\"AZURE_DEVOPS\"}}}}}}}}"; + const string actualMigrationSourceId = "MS_kgC4NjFhOTVjOTc4ZTRhZjEwMDA5NjNhOTdm"; + var response = $@" + {{ + ""data"": {{ + ""createMigrationSource"": {{ + ""migrationSource"": {{ + ""id"": ""{actualMigrationSourceId}"", + ""name"": ""Azure Devops Source"", + ""url"": ""https://dev.azure.com"", + ""type"": ""AZURE_DEVOPS"" + }} + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var expectedMigrationSourceId = await githubApi.CreateMigrationSource(orgId, adoToken, githubPat); + + // Assert + expectedMigrationSourceId.Should().Be(actualMigrationSourceId); + } + + [Fact] + public async Task StartMigration_Returns_New_Repository_Migration_Id() + { + // Arrange + const string migrationSourceId = "MIGRATION_SOURCE_ID"; + const string adoRepoUrl = "ADO_REPO_URL"; + const string orgId = "ORG_ID"; + const string repo = "REPO"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"mutation startRepositoryMigration($sourceId: ID!, $ownerId: ID!, $sourceRepositoryUrl: URI!, $repositoryName: String!, $continueOnError: Boolean!) " + + "{ startRepositoryMigration(input: { sourceId: $sourceId, ownerId: $ownerId, sourceRepositoryUrl: $sourceRepositoryUrl, repositoryName: $repositoryName, continueOnError: $continueOnError }) " + + "{ repositoryMigration { id, migrationSource { id, name, type }, sourceUrl, state, failureReason } } }\"" + + $",\"variables\":{{\"sourceId\":\"{migrationSourceId}\",\"ownerId\":\"{orgId}\",\"sourceRepositoryUrl\":\"{adoRepoUrl}\",\"repositoryName\":\"{repo}\",\"continueOnError\":true}}," + + "\"operationName\":\"startRepositoryMigration\"}"; + const string actualRepositoryMigrationId = "RM_kgC4NjFhNmE2NGU2ZWE1YTQwMDA5ODliZjhi"; + var response = $@" + {{ + ""data"": {{ + ""startRepositoryMigration"": {{ + ""repositoryMigration"": {{ + ""id"": ""{actualRepositoryMigrationId}"", + ""migrationSource"": {{ + ""id"": ""MS_kgC4NjFhNmE2NDViNWZmOTEwMDA5MTZiMGQw"", + ""name"": ""Azure Devops Source"", + ""type"": ""AZURE_DEVOPS"" + }}, + ""sourceUrl"": ""https://dev.azure.com/github-inside-msft/Team-Demos/_git/Tiny"", + ""state"": ""QUEUED"", + ""failureReason"": """" + }} + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var expectedRepositoryMigrationId = + await githubApi.StartMigration(migrationSourceId, adoRepoUrl, orgId, repo); + + // Assert + expectedRepositoryMigrationId.Should().Be(actualRepositoryMigrationId); + } + + [Fact] + public async Task GetMigrationState_Returns_The_Migration_State() + { + // Arrange + const string migrationId = "MIGRATION_ID"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"query($id: ID!) { node(id: $id) { ... on Migration { id, sourceUrl, migrationSource { name }, state, failureReason } } }\"" + + $",\"variables\":{{\"id\":\"{migrationId}\"}}}}"; + const string actualMigrationState = "SUCCEEDED"; + var response = $@" + {{ + ""data"": {{ + ""node"": {{ + ""id"": ""RM_kgC4NjFhNmE2ZWY1NmE4MjAwMDA4NjA5NTZi"", + ""sourceUrl"": ""https://github.com/import-testing/archive-export-testing"", + ""migrationSource"": {{ + ""name"": ""GHEC Archive Source"" + }}, + ""state"": ""{actualMigrationState}"", + ""failureReason"": """" + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var expectedMigrationState = await githubApi.GetMigrationState(migrationId); + + // Assert + expectedMigrationState.Should().Be(actualMigrationState); + } + + [Fact] + public async Task GetMigrationFailureReason_Returns_The_Migration_Failure_Reason() + { + // Arrange + const string migrationId = "MIGRATION_ID"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"query($id: ID!) { node(id: $id) { ... on Migration { id, sourceUrl, migrationSource { name }, state, failureReason } } }\"" + + $",\"variables\":{{\"id\":\"{migrationId}\"}}}}"; + const string actualFailureReason = "FAILURE_REASON"; + var response = $@" + {{ + ""data"": {{ + ""node"": {{ + ""id"": ""RM_kgC4NjFhNmE2ZWY1NmE4MjAwMDA4NjA5NTZi"", + ""sourceUrl"": ""https://github.com/import-testing/archive-export-testing"", + ""migrationSource"": {{ + ""name"": ""GHEC Archive Source"" + }}, + ""state"": ""FAILED"", + ""failureReason"": ""{actualFailureReason}"" + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var expectedFailureReason = await githubApi.GetMigrationFailureReason(migrationId); + + // Assert + expectedFailureReason.Should().Be(actualFailureReason); + } + + [Fact] + public async Task GetIdpGroupId_Returns_The_Idp_Group_Id() + { + // Arrange + const string org = "ORG"; + const string groupName = "GROUP_NAME"; + + var url = $"https://api.github.com/orgs/{org}/external-groups"; + const int expectedGroupId = 123; + var response = $@" + {{ + ""groups"": [ + {{ + ""group_id"": ""{expectedGroupId}"", + ""group_name"": ""{groupName}"", + ""updated_at"": ""2021-01-24T11:31:04-06:00"" + }}, + {{ + ""group_id"": ""456"", + ""group_name"": ""Octocat admins"", + ""updated_at"": ""2021-03-24T11:31:04-06:00"" + }}, + ] + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.GetAsync(url)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var actualGroupId = await githubApi.GetIdpGroupId(org, groupName); + + // Assert + actualGroupId.Should().Be(expectedGroupId); + } + + [Fact] + public async Task GetTeamSlug_Returns_The_Team_Slug() + { + // Arrange + const string org = "ORG"; + const string teamName = "TEAM_NAME"; + + var url = $"https://api.github.com/orgs/{org}/teams"; + const string expectedTeamSlug = "justice-league"; + var response = $@" + [ + {{ + ""id"": 1, + ""node_id"": ""MDQ6VGVhbTE="", + ""url"": ""https://api.github.com/teams/1"", + ""html_url"": ""https://github.com/orgs/github/teams/justice-league"", + ""name"": ""{teamName}"", + ""slug"": ""{expectedTeamSlug}"", + ""description"": ""A great team."", + ""privacy"": ""closed"", + ""permission"": ""admin"", + ""members_url"": ""https://api.github.com/teams/1/members/membber"", + ""repositories_url"": ""https://api.github.com/teams/1/repos"", + ""parent"": null + }} + ]"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.GetAsync(url)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var actualTeamSlug = await githubApi.GetTeamSlug(org, teamName); + + // Assert + actualTeamSlug.Should().Be(expectedTeamSlug); + } + + [Fact] + public async Task AddEmuGroupToTeam_Calls_The_Right_Endpoint_With_Payload() + { + // Arrange + const string org = "ORG"; + const string teamSlug = "TEAM_SLUG"; + const int groupId = 1; + + var url = $"https://api.github.com/orgs/{org}/teams/{teamSlug}/external-groups"; + var payload = $"{{ \"group_id\": {groupId} }}"; + + var githubClientMock = new Mock(null, null); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + await githubApi.AddEmuGroupToTeam(org, teamSlug, groupId); + + // Assert + githubClientMock.Verify(m => m.PatchAsync(url, payload)); + } + + [Fact] + public async Task GrantMigratorRole_Returns_True_On_Success() + { + // Arrange + const string org = "ORG"; + const string actor = "ACTOR"; + const string actorType = "ACTOR_TYPE"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"mutation grantMigratorRole ( $organizationId: ID!, $actor: String!, $actor_type: ActorType! ) " + + "{ grantMigratorRole( input: {organizationId: $organizationId, actor: $actor, actorType: $actor_type }) { success } }\"" + + $",\"variables\":{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}," + + "\"operationName\":\"grantMigratorRole\"}"; + const bool expectedSuccessState = true; + var response = $@" + {{ + ""data"": {{ + ""grantMigratorRole"": {{ + ""success"": {expectedSuccessState.ToString().ToLower()} + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var actualSuccessState = await githubApi.GrantMigratorRole(org, actor, actorType); + + // Assert + actualSuccessState.Should().BeTrue(); + } + + [Fact] + public async Task GrantMigratorRole_Returns_False_On_HttpRequestException() + { + // Arrange + const string org = "ORG"; + const string actor = "ACTOR"; + const string actorType = "ACTOR_TYPE"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"mutation grantMigratorRole ( $organizationId: ID!, $actor: String!, $actor_type: ActorType! ) " + + "{ grantMigratorRole( input: {organizationId: $organizationId, actor: $actor, actorType: $actor_type }) { success } }\"" + + $",\"variables\":{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}," + + "\"operationName\":\"grantMigratorRole\"}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .Throws(); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var actualSuccessState = await githubApi.GrantMigratorRole(org, actor, actorType); + + // Assert + actualSuccessState.Should().BeFalse(); + } + + [Fact] + public async Task RevokeMigratorRole_Returns_True_On_Success() + { + // Arrange + const string org = "ORG"; + const string actor = "ACTOR"; + const string actorType = "ACTOR_TYPE"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"mutation revokeMigratorRole ( $organizationId: ID!, $actor: String!, $actor_type: ActorType! ) " + + "{ revokeMigratorRole( input: {organizationId: $organizationId, actor: $actor, actorType: $actor_type }) { success } }\"" + + $",\"variables\":{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}," + + "\"operationName\":\"revokeMigratorRole\"}"; + const bool expectedSuccessState = true; + var response = $@" + {{ + ""data"": {{ + ""revokeMigratorRole"": {{ + ""success"": {expectedSuccessState.ToString().ToLower()} + }} + }} + }}"; + + var githubClientMock = new Mock(null, null); + githubClientMock + .Setup(m => m.PostAsync(url, payload)) + .ReturnsAsync(response); + + // Act + using var githubApi = new GithubApi(githubClientMock.Object); + var actualSuccessState = await githubApi.RevokeMigratorRole(org, actor, actorType); + + // Assert + actualSuccessState.Should().BeTrue(); + } + + [Fact] + public async Task RevokeMigratorRole_Returns_False_On_HttpRequestException() + { + // Arrange + const string org = "ORG"; + const string actor = "ACTOR"; + const string actorType = "ACTOR_TYPE"; + const string url = "https://api.github.com/graphql"; + + var payload = + "{\"query\":\"mutation revokeMigratorRole ( $organizationId: ID!, $actor: String!, $actor_type: ActorType! ) " + + "{ revokeMigratorRole( input: {organizationId: $organizationId, actor: $actor, actorType: $actor_type }) { success } }\"" + + $",\"variables\":{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}," + + "\"operationName\":\"revokeMigratorRole\"}"; var githubClientMock = new Mock(null, null); githubClientMock - .Setup(m => m.PostAsync(url, It.Is(x => x.ReadAsStringAsync().Result == payload))) - .ReturnsAsync(result); + .Setup(m => m.PostAsync(url, payload)) + .Throws(); // Act using var githubApi = new GithubApi(githubClientMock.Object); - var id = await githubApi.CreateMigrationSource(orgId, adoToken, githubPat); + var actualSuccessState = await githubApi.RevokeMigratorRole(org, actor, actorType); // Assert - Assert.Equal(migrationSourceId, id); + actualSuccessState.Should().BeFalse(); } } } \ No newline at end of file diff --git a/src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj b/src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj index 465ed6548..0be6fcfac 100644 --- a/src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj +++ b/src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/src/OctoshiftCLI/Commands/GrantMigratorRoleCommand.cs b/src/OctoshiftCLI/Commands/GrantMigratorRoleCommand.cs index 89d8fa2f1..982a40542 100644 --- a/src/OctoshiftCLI/Commands/GrantMigratorRoleCommand.cs +++ b/src/OctoshiftCLI/Commands/GrantMigratorRoleCommand.cs @@ -1,4 +1,3 @@ -using System; using System.CommandLine; using System.CommandLine.Invocation; using System.Threading.Tasks; @@ -66,11 +65,9 @@ public async Task Invoke(string githubOrg, string actor, string actorType, bool using var github = _githubFactory.Create(); var githubOrgId = await github.GetOrganizationId(githubOrg); - var grantMigratorRoleState = await github.GrantMigratorRole(githubOrgId, actor, actorType); + var success = await github.GrantMigratorRole(githubOrgId, actor, actorType); - Console.WriteLine(grantMigratorRoleState); - - if (grantMigratorRoleState?.Trim().ToUpper() == "TRUE") + if (success) { _log.LogSuccess($"Migrator role successfully set for the {actorType} \"{actor}\""); } diff --git a/src/OctoshiftCLI/Commands/RevokeMigratorRoleCommand.cs b/src/OctoshiftCLI/Commands/RevokeMigratorRoleCommand.cs index 44f4d317c..44a726671 100644 --- a/src/OctoshiftCLI/Commands/RevokeMigratorRoleCommand.cs +++ b/src/OctoshiftCLI/Commands/RevokeMigratorRoleCommand.cs @@ -66,9 +66,9 @@ public async Task Invoke(string githubOrg, string actor, string actorType, bool using var github = _githubFactory.Create(); var githubOrgId = await github.GetOrganizationId(githubOrg); - var revokeMigratorRoleState = await github.RevokeMigratorRole(githubOrgId, actor, actorType); + var success = await github.RevokeMigratorRole(githubOrgId, actor, actorType); - if (revokeMigratorRoleState?.Trim().ToUpper() == "TRUE") + if (success) { _log.LogSuccess($"Migrator role successfully revoked for the {actorType} \"{actor}\""); } diff --git a/src/OctoshiftCLI/GithubApi.cs b/src/OctoshiftCLI/GithubApi.cs index d67458f2e..a270a98f1 100644 --- a/src/OctoshiftCLI/GithubApi.cs +++ b/src/OctoshiftCLI/GithubApi.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -20,18 +19,16 @@ public virtual async Task AddAutoLink(string org, string repo, string adoOrg, st var url = $"https://api.github.com/repos/{org}/{repo}/autolinks"; var payload = $"{{ \"key_prefix\": \"AB#\", \"url_template\": \"https://dev.azure.com/{adoOrg}/{adoTeamProject}/_workitems/edit//\" }}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - await _client.PostAsync(url, body); + await _client.PostAsync(url, payload); } public virtual async Task CreateTeam(string org, string teamName) { var url = $"https://api.github.com/orgs/{org}/teams"; var payload = $"{{ \"name\": \"{teamName}\", \"privacy\": \"closed\" }}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["id"]; @@ -69,19 +66,17 @@ public virtual async Task RemoveTeamMember(string org, string teamName, string m public virtual async Task AddTeamSync(string org, string teamName, string groupId, string groupName, string groupDesc) { var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/team-sync/group-mappings"; - var payload = $"{{ 'groups': [{{ 'group_id':'{groupId}', 'group_name':'{groupName}','group_description':'{groupDesc}' }}] }}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); + var payload = $"{{ \"groups\": [{{ \"group_id\":\"{groupId}\", \"group_name\":\"{groupName}\", \"group_description\":\"{groupDesc}\" }}] }}"; - await _client.PatchAsync(url, body); + await _client.PatchAsync(url, payload); } public virtual async Task AddTeamToRepo(string org, string repo, string teamName, string role) { var url = $"https://api.github.com/orgs/{org}/teams/{teamName}/repos/{org}/{repo}"; var payload = $"{{ \"permission\":\"{role}\" }}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - await _client.PutAsync(url, body); + await _client.PutAsync(url, payload); } public virtual async Task GetOrganizationId(string org) @@ -91,9 +86,7 @@ public virtual async Task GetOrganizationId(string org) // TODO: this is super ugly, need to find a graphql library to make this code nicer var payload = $"{{\"query\":\"query($login: String!){{organization(login: $login) {{ login, id, name }} }}\",\"variables\":{{\"login\":\"{org}\"}}}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["data"]["organization"]["id"]; @@ -108,9 +101,8 @@ public virtual async Task CreateMigrationSource(string orgId, string ado var variables = $"{{\"name\":\"Azure DevOps Source\",\"url\":\"https://dev.azure.com\",\"ownerId\":\"{orgId}\",\"type\":\"AZURE_DEVOPS\",\"accessToken\":\"{adoToken}\", \"githubPat\":\"{githubPat}\"}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables},\"operationName\":\"createMigrationSource\"}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["data"]["createMigrationSource"]["migrationSource"]["id"]; @@ -125,9 +117,8 @@ public virtual async Task StartMigration(string migrationSourceId, strin var variables = $"{{\"sourceId\":\"{migrationSourceId}\",\"ownerId\":\"{orgId}\",\"sourceRepositoryUrl\":\"{adoRepoUrl}\",\"repositoryName\":\"{repo}\",\"continueOnError\":true}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables},\"operationName\":\"startRepositoryMigration\"}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["data"]["startRepositoryMigration"]["repositoryMigration"]["id"]; @@ -142,9 +133,8 @@ public virtual async Task GetMigrationState(string migrationId) var variables = $"{{\"id\":\"{migrationId}\"}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables}}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["data"]["node"]["state"]; @@ -159,9 +149,8 @@ public virtual async Task GetMigrationFailureReason(string migrationId) var variables = $"{{\"id\":\"{migrationId}\"}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables}}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); return (string)data["data"]["node"]["failureReason"]; @@ -193,12 +182,11 @@ public virtual async Task AddEmuGroupToTeam(string org, string teamSlug, int gro { var url = $"https://api.github.com/orgs/{org}/teams/{teamSlug}/external-groups"; var payload = $"{{ \"group_id\": {groupId} }}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); - await _client.PatchAsync(url, body); + await _client.PatchAsync(url, payload); } - public virtual async Task GrantMigratorRole(string org, string actor, string actorType) + public virtual async Task GrantMigratorRole(string org, string actor, string actorType) { var url = $"https://api.github.com/graphql"; @@ -207,22 +195,21 @@ public virtual async Task GrantMigratorRole(string org, string actor, st var variables = $"{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables},\"operationName\":\"grantMigratorRole\"}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); try { - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); - return (string)data["data"]["grantMigratorRole"]["success"]; + return (bool)data["data"]["grantMigratorRole"]["success"]; } catch (HttpRequestException) { - return "False"; + return false; } } - public virtual async Task RevokeMigratorRole(string org, string actor, string actorType) + public virtual async Task RevokeMigratorRole(string org, string actor, string actorType) { var url = $"https://api.github.com/graphql"; @@ -231,18 +218,17 @@ public virtual async Task RevokeMigratorRole(string org, string actor, s var variables = $"{{\"organizationId\":\"{org}\", \"actor\":\"{actor}\", \"actor_type\":\"{actorType}\"}}"; var payload = $"{{\"query\":\"{query} {{ {gql} }}\",\"variables\":{variables},\"operationName\":\"revokeMigratorRole\"}}"; - using var body = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"); try { - var response = await _client.PostAsync(url, body); + var response = await _client.PostAsync(url, payload); var data = JObject.Parse(response); - return (string)data["data"]["revokeMigratorRole"]["success"]; + return (bool)data["data"]["revokeMigratorRole"]["success"]; } catch (HttpRequestException) { - return "False"; + return false; } } diff --git a/src/OctoshiftCLI/GithubClient.cs b/src/OctoshiftCLI/GithubClient.cs index 3cd4f8c48..aaaeaaa1e 100644 --- a/src/OctoshiftCLI/GithubClient.cs +++ b/src/OctoshiftCLI/GithubClient.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; namespace OctoshiftCLI @@ -22,7 +23,7 @@ public GithubClient(OctoLogger log, string githubToken) _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", githubToken); } - public async Task GetAsync(string url) + public virtual async Task GetAsync(string url) { url = url?.Replace(" ", "%20"); @@ -35,13 +36,14 @@ public async Task GetAsync(string url) return content; } - public virtual async Task PostAsync(string url, HttpContent body) + public virtual async Task PostAsync(string url, string body) { url = url?.Replace(" ", "%20"); _log.LogVerbose($"HTTP GET: {url}"); - _log.LogVerbose($"HTTP BODY: {await body?.ReadAsStringAsync()}"); - var response = await _httpClient.PostAsync(url, body); + _log.LogVerbose($"HTTP BODY: {body}"); + using var payload = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync(url, payload); var content = await response.Content.ReadAsStringAsync(); _log.LogVerbose($"RESPONSE ({response.StatusCode}): {content}"); response.EnsureSuccessStatusCode(); @@ -49,13 +51,14 @@ public virtual async Task PostAsync(string url, HttpContent body) return content; } - public async Task PutAsync(string url, HttpContent body) + public virtual async Task PutAsync(string url, string body) { url = url?.Replace(" ", "%20"); _log.LogVerbose($"HTTP GET: {url}"); - _log.LogVerbose($"HTTP BODY: {await body?.ReadAsStringAsync()}"); - var response = await _httpClient.PutAsync(url, body); + _log.LogVerbose($"HTTP BODY: {body}"); + using var payload = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await _httpClient.PutAsync(url, payload); var content = await response.Content.ReadAsStringAsync(); _log.LogVerbose($"RESPONSE ({response.StatusCode}): {content}"); response.EnsureSuccessStatusCode(); @@ -63,13 +66,14 @@ public async Task PutAsync(string url, HttpContent body) return content; } - public async Task PatchAsync(string url, HttpContent body) + public virtual async Task PatchAsync(string url, string body) { url = url?.Replace(" ", "%20"); _log.LogVerbose($"HTTP GET: {url}"); - _log.LogVerbose($"HTTP BODY: {await body?.ReadAsStringAsync()}"); - var response = await _httpClient.PatchAsync(url, body); + _log.LogVerbose($"HTTP BODY: {body}"); + using var payload = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await _httpClient.PatchAsync(url, payload); var content = await response.Content.ReadAsStringAsync(); _log.LogVerbose($"RESPONSE ({response.StatusCode}): {content}"); response.EnsureSuccessStatusCode(); @@ -77,7 +81,7 @@ public async Task PatchAsync(string url, HttpContent body) return content; } - public async Task DeleteAsync(string url) + public virtual async Task DeleteAsync(string url) { url = url?.Replace(" ", "%20");