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

Allow users to access unsupported Activty event payloads as Json strings #1645

Closed
wants to merge 5 commits into from
Closed
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
167 changes: 126 additions & 41 deletions Octokit.Tests.Integration/Clients/EventsClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -295,77 +296,161 @@ public async Task ReturnsDistinctEventsBasedOnStartPageWithRepositoryId()

public class EventPayloads
{
readonly IEnumerable<Activity> _events;
readonly IGitHubClient _github;

public EventPayloads()
{
var github = Helper.GetAuthenticatedClient();
_events = github.Activity.Events.GetAllUserPerformed("shiftkey").Result;
_github = Helper.GetAuthenticatedClient();
}

[IntegrationTest]
public void AllEventsHavePayloads()
{
Assert.True(_events.All(e => e.Payload != null));
var events = _github.Activity.Events.GetAllUserPerformed("shiftkey", new ApiOptions { PageSize = 100, PageCount = 3 }).Result;
Assert.True(events.All(e => e.Payload != null));
}

[IntegrationTest]
public void AllEventsHaveJsonPayloads()
{
var events = _github.Activity.Events.GetAllUserPerformed("shiftkey", new ApiOptions { PageSize = 100, PageCount = 3 }).Result;
Assert.True(events.All(e => !string.IsNullOrEmpty(e.PayloadJson)));
}

[IntegrationTest(Skip = "no longer able to access this event")]
public void IssueCommentPayloadEventDeserializesCorrectly()
[IntegrationTest]
public void CommitCommentPayloadDeserializesCorrectly()
{
var commentEvent = _events.FirstOrDefault(e => e.Id == "2628548686");
var commitCommentEvent = FindEventOfType("CommitCommentEvent");
Assert.NotNull(commitCommentEvent);
Assert.Equal(typeof(CommitCommentPayload), commitCommentEvent.Payload.GetType());
var commentPayload = commitCommentEvent.Payload as CommitCommentPayload;
Assert.NotNull(commentPayload);
Assert.NotNull(commentPayload.Comment);
Assert.True(!string.IsNullOrEmpty(commentPayload.Comment.Body));
}

[IntegrationTest]
public void ForkEventPayloadDeserializesCorrectly()
{
var forkEvent = FindEventOfType("ForkEvent");
Assert.NotNull(forkEvent);
Assert.Equal(typeof(ForkEventPayload), forkEvent.Payload.GetType());
var forkPayload = forkEvent.Payload as ForkEventPayload;
Assert.NotNull(forkPayload);
Assert.NotNull(forkPayload.Forkee);
Assert.True(!string.IsNullOrEmpty(forkPayload.Forkee.FullName));
}

[IntegrationTest]
public void IssueCommentPayloadDeserializesCorrectly()
{
var commentEvent = FindEventOfType("IssueCommentEvent");
Assert.NotNull(commentEvent);
Assert.Equal("IssueCommentEvent", commentEvent.Type);
Assert.Equal(typeof(IssueCommentPayload), commentEvent.Payload.GetType());
var commentPayload = commentEvent.Payload as IssueCommentPayload;
Assert.NotNull(commentPayload);
Assert.Equal("created", commentPayload.Action);
Assert.NotNull(commentPayload.Comment);
Assert.Equal("@joshvera just going to give this a once-over to ensure it matches up with our other conventions before merging", commentPayload.Comment.Body);
Assert.NotNull(commentPayload.Comment.Body);
Assert.NotNull(commentPayload.Issue);
Assert.Equal(742, commentPayload.Issue.Number);
Assert.True(commentPayload.Issue.Number > 0);
}

[IntegrationTest]
public void IssueEventPayloadDeserializesCorrectly()
{
var issueEvent = FindEventOfType("IssuesEvent");
Assert.NotNull(issueEvent);
Assert.Equal(typeof(IssueEventPayload), issueEvent.Payload.GetType());
var issuePayload = issueEvent.Payload as IssueEventPayload;
Assert.NotNull(issuePayload);
Assert.Contains(issuePayload.Action, new[] { "created", "closed" });
Assert.NotNull(issuePayload.Issue);
Assert.True(issuePayload.Issue.Number > 0);
}

[IntegrationTest]
public void PullRequestCommentPayloadDeserializesCorrectly()
{
var prrcEvent = FindEventOfType("PullRequestReviewCommentEvent");
Assert.NotNull(prrcEvent);
Assert.Equal(typeof(PullRequestCommentPayload), prrcEvent.Payload.GetType());
var prrcPayload = prrcEvent.Payload as PullRequestCommentPayload;
Assert.NotNull(prrcPayload);
Assert.Equal("created", prrcPayload.Action);
Assert.NotNull(prrcPayload.Comment);
Assert.True(!string.IsNullOrEmpty(prrcPayload.Comment.Body));
Assert.NotNull(prrcPayload.PullRequest);
Assert.True(prrcPayload.PullRequest.Number > 0);
}

[IntegrationTest(Skip = "no longer able to access this event")]
[IntegrationTest]
public void PullRequestEventPayloadDeserializesCorrectly()
{
var prEvent = FindEventOfType("PullRequestEvent");
Assert.NotNull(prEvent);
Assert.Equal(typeof(PullRequestEventPayload), prEvent.Payload.GetType());
var prPayload = prEvent.Payload as PullRequestEventPayload;
Assert.NotNull(prPayload);
Assert.Contains(prPayload.Action, new[] { "opened", "closed" });
Assert.True(prPayload.Number > 0);
Assert.NotNull(prPayload.PullRequest);
Assert.True(prPayload.PullRequest.Number > 0);
}

[IntegrationTest]
public void PushEventPayloadDeserializesCorrectly()
{
var pushEvent = _events.FirstOrDefault(e => e.Id == "2628858765");
var pushEvent = FindEventOfType("PushEvent");
Assert.NotNull(pushEvent);
Assert.Equal("PushEvent", pushEvent.Type);
Assert.Equal(typeof(PushEventPayload), pushEvent.Payload.GetType());
var pushPayload = pushEvent.Payload as PushEventPayload;
Assert.NotNull(pushPayload);
Assert.True(!string.IsNullOrEmpty(pushPayload.Head));
Assert.True(!string.IsNullOrEmpty(pushPayload.Ref));
Assert.True(pushPayload.Size > 0);
Assert.NotNull(pushPayload.Commits);
Assert.Equal(1, pushPayload.Commits.Count);
Assert.Equal("3cdcba0ccbea0e6d13ae94249fbb294d71648321", pushPayload.Commits.FirstOrDefault().Sha);
Assert.Equal("3cdcba0ccbea0e6d13ae94249fbb294d71648321", pushPayload.Head);
Assert.Equal("refs/heads/release-candidate", pushPayload.Ref);
Assert.Equal(1, pushPayload.Size);
Assert.True(pushPayload.Commits.Count > 0);
Assert.True(!string.IsNullOrEmpty(pushPayload.Commits.FirstOrDefault().Sha));
}

[IntegrationTest(Skip = "no longer able to access this event")]
public void PREventPayloadDeserializesCorrectly()
[IntegrationTest]
public void StarredEventPayloadDeserializesCorrectly()
{
var prEvent = _events.FirstOrDefault(e => e.Id == "2628718313");
Assert.NotNull(prEvent);
Assert.Equal("PullRequestEvent", prEvent.Type);
var prPayload = prEvent.Payload as PullRequestEventPayload;
Assert.NotNull(prPayload);
Assert.Equal("opened", prPayload.Action);
Assert.Equal(743, prPayload.Number);
Assert.NotNull(prPayload.PullRequest);
Assert.Equal(743, prPayload.PullRequest.Number);
var starredEvent = FindEventOfType("WatchEvent");
Assert.NotNull(starredEvent);
Assert.Equal(typeof(StarredEventPayload), starredEvent.Payload.GetType());
var starredPayload = starredEvent.Payload as StarredEventPayload;
Assert.NotNull(starredPayload);
Assert.Equal("started", starredPayload.Action);
}

[IntegrationTest(Skip = "no longer able to access this event")]
public void PRReviewCommentEventPayloadDeserializesCorrectly()
[IntegrationTest]
public void UnsupportedEventPayloadDeserializesCorrectly()
{
var prrcEvent = _events.First(e => e.Id == "2623246246");
Assert.NotNull(prrcEvent);
Assert.Equal("PullRequestReviewCommentEvent", prrcEvent.Type);
var prrcPayload = prrcEvent.Payload as PullRequestCommentPayload;
Assert.NotNull(prrcPayload);
Assert.Equal("created", prrcPayload.Action);
Assert.NotNull(prrcPayload.Comment);
Assert.Equal("Suuuuuuuuure :P", prrcPayload.Comment.Body);
Assert.NotNull(prrcPayload.PullRequest);
Assert.Equal(737, prrcPayload.PullRequest.Number);
var unsupportedEvent = FindEventOfType("DeleteEvent");
Assert.NotNull(unsupportedEvent);
Assert.Equal(typeof(ActivityPayload), unsupportedEvent.Payload.GetType());
var unsupportedPayload = unsupportedEvent.Payload;
Assert.NotNull(unsupportedPayload);
Assert.True(unsupportedEvent.PayloadJson.Contains("ref"));
Assert.True(unsupportedEvent.PayloadJson.Contains("ref_type"));
Assert.True(unsupportedEvent.PayloadJson.Contains("pusher_type"));
}

public Activity FindEventOfType(string eventType)
{
var page = 1;
Activity foundEvent = null;

while (foundEvent == null)
{
var events = _github.Activity.Events.GetAllForRepository("octokit", "octokit.net", new ApiOptions { PageSize = 100, PageCount = 1, StartPage = page }).Result;
foundEvent = events.FirstOrDefault(x => x.Type == eventType);
page++;
}

return foundEvent;
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Octokit/Helpers/ParameterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@ public sealed class ParameterAttribute : Attribute
/// The name to use in place of the enum's value
/// </summary>
public string Value { get; set; }

/// <summary>
/// Whether to allow more than one response parameter to map to this same API value
/// </summary>
public bool AllowDuplicates { get; set; }
}
}
21 changes: 16 additions & 5 deletions Octokit/Helpers/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,26 @@ internal static class ReflectionExtensions
{
public static string GetJsonFieldName(this MemberInfo memberInfo)
{
var memberName = memberInfo.Name;
var paramAttr = memberInfo.GetCustomAttribute<ParameterAttribute>();
// Default to the member name, converted to "ruby case"
var memberName = memberInfo.Name.ToRubyCase();

if (paramAttr != null && !string.IsNullOrEmpty(paramAttr.Key))
// If a [Parameter(Key = "new_name")] attribute exists, use it instead of member name
var paramAttr = memberInfo.GetCustomAttribute<ParameterAttribute>();
if (!string.IsNullOrEmpty(paramAttr?.Key))
{
memberName = paramAttr.Key;
if (paramAttr.AllowDuplicates)
{
// De-dupe parameter key by appending unique "octokit" suffix
memberName = $"{paramAttr.Key}_octokit_{memberName}";
}
else
{
// Take the Parameter key as is
memberName = paramAttr.Key;
}
}

return memberName.ToRubyCase();
return memberName;
}

public static IEnumerable<PropertyOrField> GetPropertiesAndFields(this Type type)
Expand Down
4 changes: 4 additions & 0 deletions Octokit/Models/Response/Activity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Octokit.Internal;

namespace Octokit
{
Expand Down Expand Up @@ -65,6 +66,9 @@ public Activity(string type, bool @public, Repository repo, User actor, Organiza
/// </summary>
public ActivityPayload Payload { get; protected set; }

[Parameter(Key = "payload", AllowDuplicates = true)]
public string PayloadJson { get; protected set; }

internal string DebuggerDisplay
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class ActivityPayload

internal string DebuggerDisplay
{
get { return Repository.FullName; }
get { return $"Type: {this.GetType().Name} Repo: {Repository?.FullName ?? "null"}"; }
}
}
}
9 changes: 8 additions & 1 deletion Octokit/SimpleJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1474,13 +1474,20 @@ public virtual object DeserializeObject(object value, Type type)
{
if (type == typeof(object))
obj = value;
else if (type == typeof(string))
obj = value.ToString();
else
{
obj = ConstructorCache[type]();
foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type])
{
object jsonValue;
if (jsonObject.TryGetValue(setter.Key, out jsonValue))
var key = setter.Key;
if (key.Contains("_octokit_"))
{
key = key.Substring(0, key.IndexOf("_octokit_"));
}
if (jsonObject.TryGetValue(key, out jsonValue))
{
jsonValue = DeserializeObject(jsonValue, setter.Value.Key);
setter.Value.Value(obj, jsonValue);
Expand Down