Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
33db113
Add CreateBbsMigrationSource.
synthead Aug 16, 2022
ea02e45
Update RootCommand for BbsToGithub with BBS description.
synthead Aug 17, 2022
4305aec
Merge branch 'main' into start-bbs-migrations
synthead Aug 17, 2022
0a1bae7
Update BBS migrate-repo command for BBS migrations.
synthead Aug 18, 2022
d936df5
Refer to bbs2gh as bbs2gh in Program.cs.
synthead Aug 18, 2022
5d9f3f4
Rearrange options in bbs migrate-repo.
synthead Aug 18, 2022
f5cce52
Add bbs2gh reference to OctoshiftCLI.Tests.csproj.
synthead Aug 18, 2022
dd8873d
Ensure BBS migrate-repo command options are correct.
synthead Aug 18, 2022
8e10829
Use MigrateRepoCommandArgs in BBS migrate-repo.
synthead Aug 19, 2022
7fba448
Raise ArgumentNullException if args is null in BBS migrate-repo Invoke.
synthead Aug 19, 2022
d69017e
Add happy path tests for BBS migrate-repo.
synthead Aug 19, 2022
7496173
Remove ADO logic from BBS EnvironmentVariableProvider.
synthead Aug 19, 2022
af69cf4
Add existing repo test for BBS migrate-repo.
synthead Aug 19, 2022
f66270e
Simplify happy path test in BBS migrate-repo.
synthead Aug 19, 2022
712ed91
Add test for PAT option for BBS migrate-repo.
synthead Aug 19, 2022
fbf38c5
Remove some args from existing repo test for BBS migrate-repo.
synthead Aug 19, 2022
dfb4ed6
Indicate unused variables in BBS migrate-repo tests.
synthead Aug 19, 2022
64bcec7
Ensure GitHub PAT is being passed to GithubApiFactory for BBS.
synthead Aug 19, 2022
d195816
Add test to ensure GitHub API URL is used with BBS.
synthead Aug 19, 2022
f5afcc6
Remove unused constant in BBS migrate-repo tests.
synthead Aug 19, 2022
4257969
Add release notes for bbs2gh migrate-repo command.
synthead Aug 19, 2022
52f63c1
Fix type in param description for BBS migrate-repo.
synthead Aug 19, 2022
273316e
Remove --github-api-url option from BBS migrate-repo.
synthead Aug 19, 2022
a2c57ef
Remove reference to GEI in bbs2gh Program.cs.
synthead Aug 19, 2022
b6f8dce
Use "bbs2gh extension" phrasing in bbs2gh Program.cs.
synthead Aug 19, 2022
0c3096f
Remove VerifyNoOtherCalls in BBS migrate-repo tests.
synthead Aug 19, 2022
24d9952
Remove console log tests for BBS migrate-repo.
synthead Aug 19, 2022
b0e916b
Only verify StartMigration calls in "happy" BBS migrate-repo tests.
synthead Aug 19, 2022
17ca947
Rearrange some mocks in BBS migrate-repo tests.
synthead Aug 19, 2022
138ea8b
Don't test console output in "existing repo" BBS migrate-repo test.
synthead Aug 19, 2022
36c4731
Remove using System.Collections.Generic from BBS migrate-repo tests.
synthead Aug 19, 2022
88113db
Add StartBbsMigration to GithubApi.
synthead Aug 19, 2022
cce2213
Call StartBbsMigration from BBS MigrateRepoCommand.
synthead Aug 19, 2022
553391c
Fix indentation level in StartBbsMigration.
synthead Aug 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@

- Add `migrate-repo` command to `bbs2gh`.
40 changes: 40 additions & 0 deletions src/Octoshift/GithubApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,32 @@ public virtual async Task<string> CreateAdoMigrationSource(string orgId, string
return (string)data["data"]["createMigrationSource"]["migrationSource"]["id"];
}

public virtual async Task<string> CreateBbsMigrationSource(string orgId)
{
var url = $"{_apiUrl}/graphql";

var query = "mutation createMigrationSource($name: String!, $url: String!, $ownerId: ID!, $type: MigrationSourceType!)";
var gql = "createMigrationSource(input: {name: $name, url: $url, ownerId: $ownerId, type: $type}) { migrationSource { id, name, url, type } }";

var payload = new
{
query = $"{query} {{ {gql} }}",
variables = new
{
name = "Bitbucket Server Source",
url = "https://not-used",
ownerId = orgId,
type = "BITBUCKET_SERVER"
},
operationName = "createMigrationSource"
};

var response = await _client.PostAsync(url, payload);
var data = JObject.Parse(response);

return (string)data["data"]["createMigrationSource"]["migrationSource"]["id"];
}

public virtual async Task<string> CreateGhecMigrationSource(string orgId)
{
var url = $"{_apiUrl}/graphql";
Expand Down Expand Up @@ -268,6 +294,20 @@ mutation startRepositoryMigration(
return (string)data["data"]["startRepositoryMigration"]["repositoryMigration"]["id"];
}

public virtual async Task<string> StartBbsMigration(string migrationSourceId, string orgId, string repo, string targetToken, string archiveUrl)
{
return await StartMigration(
migrationSourceId,
"https://not-used", // source repository URL
orgId,
repo,
"not-used", // source access token
targetToken,
archiveUrl,
"https://not-used" // metadata archive URL
);
}

public virtual async Task<(string State, string RepositoryName, string FailureReason)> GetMigration(string migrationId)
{
var url = $"{_apiUrl}/graphql";
Expand Down
138 changes: 138 additions & 0 deletions src/OctoshiftCLI.Tests/GithubApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,42 @@ public async Task CreateAdoMigrationSource_Uses_Ado_Server_Url()
expectedMigrationSourceId.Should().Be(actualMigrationSourceId);
}

[Fact]
public async Task CreateBbsMigrationSource_Returns_New_Migration_Source_Id()
{
// Arrange
const string url = "https://api.github.com/graphql";
const string orgId = "ORG_ID";
var payload =
"{\"query\":\"mutation createMigrationSource($name: String!, $url: String!, $ownerId: ID!, $type: MigrationSourceType!) " +
"{ createMigrationSource(input: {name: $name, url: $url, ownerId: $ownerId, type: $type}) { migrationSource { id, name, url, type } } }\"" +
$",\"variables\":{{\"name\":\"Bitbucket Server Source\",\"url\":\"https://not-used\",\"ownerId\":\"{orgId}\",\"type\":\"BITBUCKET_SERVER\"}},\"operationName\":\"createMigrationSource\"}}";
const string actualMigrationSourceId = "MS_kgC4NjFhOTVjOTc4ZTRhZjEwMDA5NjNhOTdm";
var response = $@"
{{
""data"": {{
""createMigrationSource"": {{
""migrationSource"": {{
""id"": ""{actualMigrationSourceId}"",
""name"": ""Bitbucket Server Source"",
""url"": ""https://not-used"",
""type"": ""BITBUCKET_SERVER""
}}
}}
}}
}}";

_githubClientMock
.Setup(m => m.PostAsync(url, It.Is<object>(x => x.ToJson() == payload)))
.ReturnsAsync(response);

// Act
var expectedMigrationSourceId = await _githubApi.CreateBbsMigrationSource(orgId);

// Assert
expectedMigrationSourceId.Should().Be(actualMigrationSourceId);
}

[Fact]
public async Task CreateGhecMigrationSource_Returns_New_Migration_Source_Id()
{
Expand Down Expand Up @@ -609,6 +645,108 @@ mutation startRepositoryMigration(
expectedRepositoryMigrationId.Should().Be(actualRepositoryMigrationId);
}

[Fact]
public async Task StartBbsMigration_Returns_New_Repository_Migration_Id()
{
// Arrange
const string migrationSourceId = "MIGRATION_SOURCE_ID";
const string orgId = "ORG_ID";
const string url = "https://api.github.com/graphql";
const string gitArchiveUrl = "GIT_ARCHIVE_URL";
const string targetToken = "TARGET_TOKEN";

const string unusedSourceRepoUrl = "https://not-used";
const string unusedSourceToken = "not-used";
const string unusedMetadataArchiveUrl = "https://not-used";

const string query = @"
mutation startRepositoryMigration(
$sourceId: ID!,
$ownerId: ID!,
$sourceRepositoryUrl: URI!,
$repositoryName: String!,
$continueOnError: Boolean!,
$gitArchiveUrl: String,
$metadataArchiveUrl: String,
$accessToken: String!,
$githubPat: String,
$skipReleases: Boolean)";
const string gql = @"
startRepositoryMigration(
input: {
sourceId: $sourceId,
ownerId: $ownerId,
sourceRepositoryUrl: $sourceRepositoryUrl,
repositoryName: $repositoryName,
continueOnError: $continueOnError,
gitArchiveUrl: $gitArchiveUrl,
metadataArchiveUrl: $metadataArchiveUrl,
accessToken: $accessToken,
githubPat: $githubPat,
skipReleases: $skipReleases
}
) {
repositoryMigration {
id,
migrationSource {
id,
name,
type
},
sourceUrl,
state,
failureReason
}
}";
var payload = new
{
query = $"{query} {{ {gql} }}",
variables = new
{
sourceId = migrationSourceId,
ownerId = orgId,
sourceRepositoryUrl = unusedSourceRepoUrl,
repositoryName = GITHUB_REPO,
continueOnError = true,
gitArchiveUrl,
metadataArchiveUrl = unusedMetadataArchiveUrl,
accessToken = unusedSourceToken,
githubPat = targetToken,
skipReleases = false
},
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"": """"
}}
}}
}}
}}";

_githubClientMock
.Setup(m => m.PostAsync(url, It.Is<object>(x => x.ToJson() == payload.ToJson())))
.ReturnsAsync(response);

// Act
var expectedRepositoryMigrationId = await _githubApi.StartBbsMigration(migrationSourceId, orgId, GITHUB_REPO, targetToken, gitArchiveUrl);

// Assert
expectedRepositoryMigrationId.Should().Be(actualRepositoryMigrationId);
}

[Fact]
public async Task StartMigration_Throws_When_GraphQL_Response_Has_Errors()
{
Expand Down
1 change: 1 addition & 0 deletions src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<ProjectReference Include="..\ado2gh\ado2gh.csproj" />
<ProjectReference Include="..\bbs2gh\bbs2gh.csproj" />
<ProjectReference Include="..\gei\gei.csproj" />
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="JunitXml.TestLogger" Version="3.0.98" />
Expand Down
144 changes: 144 additions & 0 deletions src/OctoshiftCLI.Tests/bbs2gh/Commands/MigrateRepoCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using OctoshiftCLI.BbsToGithub;
using OctoshiftCLI.BbsToGithub.Commands;
using Xunit;

namespace OctoshiftCLI.Tests.BbsToGithub.Commands
{
public class MigrateRepoCommandTests
{
private readonly Mock<GithubApi> _mockGithubApi = TestHelpers.CreateMock<GithubApi>();
private readonly Mock<GithubApiFactory> _mockGithubApiFactory = TestHelpers.CreateMock<GithubApiFactory>();
private readonly Mock<OctoLogger> _mockOctoLogger = TestHelpers.CreateMock<OctoLogger>();
private readonly Mock<EnvironmentVariableProvider> _mockEnvironmentVariableProvider = TestHelpers.CreateMock<EnvironmentVariableProvider>();

private readonly MigrateRepoCommand _command;

private const string ARCHIVE_URL = "https://archive-url/bbs-archive.tar";
private const string GITHUB_ORG = "target-org";
private const string GITHUB_REPO = "target-repo";
private const string GITHUB_PAT = "github pat";

private const string GITHUB_ORG_ID = "github-org-id";
private const string MIGRATION_SOURCE_ID = "migration-source-id";
private const string MIGRATION_ID = "migration-id";

public MigrateRepoCommandTests()
{
_command = new MigrateRepoCommand(
_mockOctoLogger.Object,
_mockGithubApiFactory.Object,
_mockEnvironmentVariableProvider.Object
);
}

[Fact]
public void Should_Have_Options()
{
_command.Should().NotBeNull();
_command.Name.Should().Be("migrate-repo");
_command.Options.Count.Should().Be(6);

TestHelpers.VerifyCommandOption(_command.Options, "archive-url", true);
TestHelpers.VerifyCommandOption(_command.Options, "github-org", true);
TestHelpers.VerifyCommandOption(_command.Options, "github-repo", true);
TestHelpers.VerifyCommandOption(_command.Options, "github-pat", false);
TestHelpers.VerifyCommandOption(_command.Options, "wait", false);
TestHelpers.VerifyCommandOption(_command.Options, "verbose", false);
}

[Fact]
public async Task Happy_Path()
{
// Arrange
_mockEnvironmentVariableProvider.Setup(m => m.GithubPersonalAccessToken()).Returns(GITHUB_PAT);
_mockGithubApiFactory.Setup(m => m.Create(It.IsAny<string>(), It.IsAny<string>())).Returns(_mockGithubApi.Object);

_mockGithubApi.Setup(x => x.GetOrganizationId(GITHUB_ORG).Result).Returns(GITHUB_ORG_ID);
_mockGithubApi.Setup(x => x.CreateBbsMigrationSource(GITHUB_ORG_ID).Result).Returns(MIGRATION_SOURCE_ID);
_mockGithubApi
.Setup(x => x.StartBbsMigration(MIGRATION_SOURCE_ID, GITHUB_ORG_ID, GITHUB_REPO, GITHUB_PAT, ARCHIVE_URL).Result)
.Returns(MIGRATION_ID);

// Act
var args = new MigrateRepoCommandArgs
{
ArchiveUrl = ARCHIVE_URL,
GithubOrg = GITHUB_ORG,
GithubRepo = GITHUB_REPO
};
await _command.Invoke(args);

// Assert
_mockGithubApi.Verify(m => m.StartBbsMigration(
MIGRATION_SOURCE_ID,
GITHUB_ORG_ID,
GITHUB_REPO,
GITHUB_PAT,
ARCHIVE_URL
));
}

[Fact]
public async Task Uses_GitHub_Pat_When_Provided_As_Option()
{
// Arrange
var githubPat = "specific github pat";

_mockGithubApiFactory.Setup(m => m.Create(It.IsAny<string>(), githubPat)).Returns(_mockGithubApi.Object);

_mockGithubApi.Setup(x => x.GetOrganizationId(GITHUB_ORG).Result).Returns(GITHUB_ORG_ID);
_mockGithubApi.Setup(x => x.CreateBbsMigrationSource(GITHUB_ORG_ID).Result).Returns(MIGRATION_SOURCE_ID);
_mockGithubApi
.Setup(x => x.StartBbsMigration(MIGRATION_SOURCE_ID, GITHUB_ORG_ID, GITHUB_REPO, GITHUB_PAT, ARCHIVE_URL).Result)
.Returns(MIGRATION_ID);

// Act
var args = new MigrateRepoCommandArgs
{
ArchiveUrl = ARCHIVE_URL,
GithubOrg = GITHUB_ORG,
GithubRepo = GITHUB_REPO,
GithubPat = githubPat
};
await _command.Invoke(args);

// Assert
_mockGithubApi.Verify(m => m.StartBbsMigration(
MIGRATION_SOURCE_ID,
GITHUB_ORG_ID,
GITHUB_REPO,
githubPat,
ARCHIVE_URL
));
}

[Fact]
public async Task Skip_Migration_If_Target_Repo_Exists()
{
// Arrange
_mockEnvironmentVariableProvider.Setup(m => m.GithubPersonalAccessToken()).Returns(GITHUB_PAT);
_mockGithubApiFactory.Setup(m => m.Create(It.IsAny<string>(), It.IsAny<string>())).Returns(_mockGithubApi.Object);

_mockGithubApi.Setup(x => x.GetOrganizationId(GITHUB_ORG).Result).Returns(GITHUB_ORG_ID);
_mockGithubApi.Setup(x => x.CreateBbsMigrationSource(GITHUB_ORG_ID).Result).Returns(MIGRATION_SOURCE_ID);
_mockGithubApi
.Setup(x => x.StartBbsMigration(MIGRATION_SOURCE_ID, GITHUB_ORG_ID, GITHUB_REPO, GITHUB_PAT, ARCHIVE_URL).Result)
.Throws(new OctoshiftCliException($"A repository called {GITHUB_ORG}/{GITHUB_REPO} already exists"));

// Act
var args = new MigrateRepoCommandArgs
{
ArchiveUrl = ARCHIVE_URL,
GithubOrg = GITHUB_ORG,
GithubRepo = GITHUB_REPO
};
await _command.Invoke(args);

// Assert
_mockOctoLogger.Verify(m => m.LogWarning(It.IsAny<string>()), Times.Exactly(1));
}
}
}
Loading