Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]feat(github): Repository file references #439

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using Grynwald.ChangeLog.Integrations.GitHub;
using Xunit;

namespace Grynwald.ChangeLog.Test.Integrations.GitHub
{
/// <summary>
/// Tests for <see cref="GitHubFileReferenceTextElement"/>
/// </summary>
public class GitHubFileReferenceTextElementTest
{
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("\t")]
public void Text_must_not_be_null_or_whitespace(string text)
{
// ARRANGE

// ACT
var ex = Record.Exception(() => new GitHubFileReferenceTextElement(
text: text,
uri: new("http://example.com"),
relativePath: "relativePath"
));

// ASSERT
var argumentException = Assert.IsType<ArgumentException>(ex);
Assert.Equal("text", argumentException.ParamName);
}

[Fact]
public void Uri_must_not_be_null()
{
// ARRANGE

// ACT
var ex = Record.Exception(() => new GitHubFileReferenceTextElement(
text: "some text",
uri: null!,
relativePath: "relativePath"
));

// ASSERT
var argumentNullException = Assert.IsType<ArgumentNullException>(ex);
Assert.Equal("uri", argumentNullException.ParamName);
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("\t")]
public void RelativePath_must_not_be_null_or_whitespace(string relativePath)
{
// ARRANGE

// ACT
var ex = Record.Exception(() => new GitHubFileReferenceTextElement(
text: "Some Text",
uri: new("http://example.com"),
relativePath: relativePath
));

// ASSERT
var argumentException = Assert.IsType<ArgumentException>(ex);
Assert.Equal("relativePath", argumentException.ParamName);
}
}
}
207 changes: 207 additions & 0 deletions src/ChangeLog.Test/Integrations/GitHub/GitHubLinkTaskTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,10 @@ public async Task Run_ignores_footers_which_cannot_be_parsed(string footerText)
.SetupGet()
.ReturnsTestCommit();

m_GithubClientMock.Repository.Content
.SetupGetAllContents()
.ThrowsNotFound();

var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object);

var changeLog = new ApplicationChangeLog()
Expand Down Expand Up @@ -674,6 +678,209 @@ public async Task Run_does_not_add_a_links_to_footers_if_no_issue_or_pull_reques
m_GithubClientMock.PullRequest.Verify(x => x.Get(owner, repo, It.IsAny<int>()), Times.Once);
}

[Theory]
[InlineData("some-file.md", "some-file.md", "some-file.md", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("./some-file.md", "./some-file.md", "./some-file.md", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("dir/some-file.md", "dir/some-file.md", "dir/some-file.md", "https://example.com/owner/repo/default-branch/dir/some-file.md")]
[InlineData("dir/../some-file.md", "dir/../some-file.md", "dir/../some-file.md", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("[Link Text](some-file.md)", "some-file.md", "Link Text", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("[Link Text](./dir/../some-file.md)", "./dir/../some-file.md", "Link Text", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("[](some-file.md)", "some-file.md", "some-file.md", "https://example.com/owner/repo/default-branch/some-file.md")]
[InlineData("[ ](some-file.md)", "some-file.md", "some-file.md", "https://example.com/owner/repo/default-branch/some-file.md")]
public async Task Run_adds_link_to_repository_file_references(string footerText, string filePath, string expectedText, string expectedLink)
{
// ARRANGE
var owner = "owner";
var repoName = "repo";
var repoMock = new Mock<IGitRepository>(MockBehavior.Strict);
repoMock.SetupRemotes("origin", $"http://github.com/{owner}/{repoName}.git");

m_GithubClientMock.Repository.Content
.Setup(x => x.GetAllContents(owner, repoName, filePath))
.ReturnsTestRepositoryContent(
new TestRepositoryContent(expectedLink, "File Content")
);

var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object);

var changeLog = new ApplicationChangeLog()
{
GetSingleVersionChangeLog(
"1.2.3",
null,
GetChangeLogEntry(summary: "Entry1", commit: TestGitIds.Id1, footers: new []
{
new ChangeLogEntryFooter(new CommitMessageFooterName("See-Also"), new PlainTextElement(footerText))
})
)
};

// ACT
var result = await sut.RunAsync(changeLog);

// ASSERT
Assert.Equal(ChangeLogTaskResult.Success, result);

var entries = changeLog.ChangeLogs.SelectMany(x => x.AllEntries).ToArray();
Assert.All(entries, entry =>
{
Assert.All(entry.Footers.Where(x => x.Name == new CommitMessageFooterName("See-Also")), footer =>
{
var fileReferenceElement = Assert.IsType<GitHubFileReferenceTextElement>(footer.Value);
Assert.Equal(expectedText, fileReferenceElement.Text);
Assert.Equal(expectedLink, fileReferenceElement.Uri.ToString());
});

});

m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents(owner, repoName, filePath), Times.Once);
}

[Theory]
[InlineData("https://example.com/some-file.md")]
public async Task Run_does_not_add_link_to_absolute_paths(string footerText)
{
// ARRANGE
var repoMock = new Mock<IGitRepository>(MockBehavior.Strict);
repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git");

m_GithubClientMock.Repository.Content
.Setup(x => x.GetAllContents("owner", "repo", footerText))
.ThrowsNotFound();

var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object);

var originalFooterValue = new PlainTextElement(footerText);
var changeLog = new ApplicationChangeLog()
{
GetSingleVersionChangeLog(
"1.2.3",
null,
GetChangeLogEntry(summary: "Entry1", commit: TestGitIds.Id1, footers: new []
{
new ChangeLogEntryFooter(new CommitMessageFooterName("See-Also"), originalFooterValue)
})
)
};

// ACT
var result = await sut.RunAsync(changeLog);

// ASSERT
Assert.Equal(ChangeLogTaskResult.Success, result);

var entries = changeLog.ChangeLogs.SelectMany(x => x.AllEntries).ToArray();
Assert.All(entries, entry =>
{
Assert.All(entry.Footers.Where(x => x.Name == new CommitMessageFooterName("See-Also")), footer =>
{
Assert.Same(originalFooterValue, footer.Value);
});

});

m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
}

[Theory]
[InlineData("some-file.md")]
public async Task Run_does_not_add_link_to_repository_file_references_if_file_does_not_exist(string footerText)
{
// ARRANGE
var repoMock = new Mock<IGitRepository>(MockBehavior.Strict);
repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git");

m_GithubClientMock.Repository.Content
.Setup(x => x.GetAllContents("owner", "repo", footerText))
.ThrowsNotFound();

var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object);

var originalFooterValue = new PlainTextElement(footerText);
var changeLog = new ApplicationChangeLog()
{
GetSingleVersionChangeLog(
"1.2.3",
null,
GetChangeLogEntry(summary: "Entry1", commit: TestGitIds.Id1, footers: new []
{
new ChangeLogEntryFooter(new CommitMessageFooterName("See-Also"), originalFooterValue)
})
)
};

// ACT
var result = await sut.RunAsync(changeLog);

// ASSERT
Assert.Equal(ChangeLogTaskResult.Success, result);

var entries = changeLog.ChangeLogs.SelectMany(x => x.AllEntries).ToArray();
Assert.All(entries, entry =>
{
Assert.All(entry.Footers.Where(x => x.Name == new CommitMessageFooterName("See-Also")), footer =>
{
Assert.Same(originalFooterValue, footer.Value);
});

});

m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents("owner", "repo", footerText), Times.Once);
}

[Theory]
[InlineData("some-directory", 0)]
[InlineData("some-directory", 1)]
[InlineData("some-directory", 5)]
public async Task Run_does_not_add_link_to_repository_file_references_if_path_is_a_directory(string footerText, int fileCount)
{
// ARRANGE
var repoMock = new Mock<IGitRepository>(MockBehavior.Strict);
repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git");

m_GithubClientMock.Repository.Content
.Setup(x => x.GetAllContents("owner", "repo", footerText))
.ReturnsTestRepositoryContent(
Enumerable.Range(0, fileCount).Select(x => new TestRepositoryContent("https://example.com", content: null))
);

var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object);

var originalFooterValue = new PlainTextElement(footerText);
var changeLog = new ApplicationChangeLog()
{
GetSingleVersionChangeLog(
"1.2.3",
null,
GetChangeLogEntry(summary: "Entry1", commit: TestGitIds.Id1, footers: new []
{
new ChangeLogEntryFooter(new CommitMessageFooterName("See-Also"), originalFooterValue)
})
)
};

// ACT
var result = await sut.RunAsync(changeLog);

// ASSERT
Assert.Equal(ChangeLogTaskResult.Success, result);

var entries = changeLog.ChangeLogs.SelectMany(x => x.AllEntries).ToArray();
Assert.All(entries, entry =>
{
Assert.All(entry.Footers.Where(x => x.Name == new CommitMessageFooterName("See-Also")), footer =>
{
Assert.Same(originalFooterValue, footer.Value);
});

});

m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
m_GithubClientMock.Repository.Content.Verify(x => x.GetAllContents("owner", "repo", footerText), Times.Once);
}

[Theory]
[InlineData("github.com")]
[InlineData("github.example.com")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ public class RepositoriesClientMock

public Mock<IRepositoryCommitsClient> Commit { get; } = new(MockBehavior.Strict);

public Mock<IRepositoryContentsClient> Content { get; } = new(MockBehavior.Strict);


public RepositoriesClientMock()
{
m_Mock.Setup(x => x.Commit).Returns(Commit.Object);

m_Mock.Setup(x => x.Content).Returns(Content.Object);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Moq;
using Moq.Language.Flow;
Expand Down Expand Up @@ -57,5 +59,29 @@ public static IReturnsResult<IPullRequestsClient> ThrowsNotFound(this ISetup<IPu
{
return setup.ThrowsAsync(new NotFoundException("Not found", HttpStatusCode.NotFound));
}

public static ISetup<IRepositoryContentsClient, Task<IReadOnlyList<RepositoryContent>>> SetupGetAllContents(this Mock<IRepositoryContentsClient> mock)
{
return mock.Setup(x => x.GetAllContents(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()));
}

public static IReturnsResult<IRepositoryContentsClient> ThrowsNotFound(this ISetup<IRepositoryContentsClient, Task<IReadOnlyList<RepositoryContent>>> setup)
{
return setup.ThrowsAsync(new NotFoundException("Not found", HttpStatusCode.NotFound));
}

public static IReturnsResult<IRepositoryContentsClient> ReturnsTestRepositoryContent(
this ISetup<IRepositoryContentsClient, Task<IReadOnlyList<RepositoryContent>>> setup,
params TestRepositoryContent[] files)
{
return setup.ReturnsAsync((string _, string _, string _) => files);
}

public static IReturnsResult<IRepositoryContentsClient> ReturnsTestRepositoryContent(
this ISetup<IRepositoryContentsClient, Task<IReadOnlyList<RepositoryContent>>> setup,
IEnumerable<TestRepositoryContent> files)
{
return setup.ReturnsAsync((string _, string _, string _) => files.ToList());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Octokit;

namespace Grynwald.ChangeLog.Test.Integrations.GitHub
{
public class TestRepositoryContent : RepositoryContent
{
public Uri HtmlUri => new Uri(HtmlUrl);

public TestRepositoryContent(string htmlUrl, string? content)
{
HtmlUrl = htmlUrl;

var encodedContent = content is null
? null
: Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(content)); ;

// No really a good idea but probably okay for test code
typeof(RepositoryContent)
.GetProperty(nameof(EncodedContent))!
.SetMethod!
.Invoke(this, new[] { encodedContent });
}
}
}
Loading