using Octokit.Helpers; using Octokit.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Xunit; namespace Octokit.Tests { public class SimpleJsonSerializerTests { public class TheSerializeMethod { [Fact] public void UsesRubyCasing() { var item = new Sample { Id = 42, FirstName = "Phil", IsSomething = true, Private = true }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"id\":42,\"first_name\":\"Phil\",\"is_something\":true,\"private\":true}", json); } [Fact] public void OmitsPropertiesWithNullValue() { var item = new { Object = (object)null, NullableInt = (int?)null, NullableBool = (bool?)null }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{}", json); } [Fact] public void DoesNotOmitsNullablePropertiesWithAValue() { var item = new { Object = new { Id = 42 }, NullableInt = (int?)1066, NullableBool = (bool?)true }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"object\":{\"id\":42},\"nullable_int\":1066,\"nullable_bool\":true}", json); } [Fact] public void HandlesMixingNullAndNotNullData() { var item = new { Int = 42, Bool = true, NullableInt = (int?)null, NullableBool = (bool?)null }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"int\":42,\"bool\":true}", json); } [Fact] public void HandleUnicodeCharacters() { const string backspace = "\b"; const string tab = "\t"; var sb = new StringBuilder(); sb.Append("My name has Unicode characters"); Enumerable.Range(0, 19).Select(e => System.Convert.ToChar(e)) .Aggregate(sb, (a, b) => a.Append(b)); sb.Append(backspace).Append(tab); var data = sb.ToString(); var json = new SimpleJsonSerializer().Serialize(data); var lastTabCharacter = json .Reverse() .Skip(1) .Take(2) .Reverse() .Aggregate(new StringBuilder(), (a, b) => a.Append(b)); var deserializeData = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("\\t", lastTabCharacter.ToString()); Assert.Equal(data, deserializeData); } [Fact] public void HandlesBase64EncodedStrings() { var item = new SomeObject { Name = "Ferris Bueller", Content = "Day off", Description = "stuff" }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}", json); } [Fact] public void HandlesEnum() { var item = new ObjectWithEnumProperty { Name = "Ferris Bueller", SomeEnum = SomeEnum.Unicode, SomeEnumNullable = SomeEnum.Unicode, StringEnum = SomeEnum.SomethingElse, StringEnumNullable = SomeEnum.SomethingElse }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"name\":\"Ferris Bueller\",\"some_enum\":\"unicode\",\"some_enum_nullable\":\"unicode\",\"string_enum\":\"something else\",\"string_enum_nullable\":\"something else\"}", json); } [Fact] public void HandlesEnumDefaults() { var item = new ObjectWithEnumProperty { Name = "Ferris Bueller", SomeEnum = SomeEnum.PlusOne, StringEnum = SomeEnum.PlusOne }; var json = new SimpleJsonSerializer().Serialize(item); Assert.Equal("{\"name\":\"Ferris Bueller\",\"some_enum\":\"+1\",\"string_enum\":\"+1\"}", json); } } public class TheDeserializeMethod { [Fact] public void DeserializesEventInfosWithUnderscoresInName() { const string json = "{\"event\":\"head_ref_deleted\"}"; new SimpleJsonSerializer().Deserialize(json); } public class MessageSingle { public string Message { get; private set; } } [Fact] public void DeserializesStringsWithHyphensAndUnderscoresIntoString() { const string json = @"{""message"":""-my-test-string_with_underscores_""}"; var response = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("-my-test-string_with_underscores_", response.Message); } public class MessageList { public IReadOnlyList Message { get; private set; } } [Fact] public void DeserializesStringsWithHyphensAndUnderscoresIntoStringList() { const string json = @"{""message"":""-my-test-string_with_underscores_""}"; var response = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("-my-test-string_with_underscores_", response.Message[0]); } [Fact] public void UnderstandsRubyCasing() { const string json = "{\"id\":42,\"first_name\":\"Phil\",\"is_something\":true,\"private\":true}"; var sample = new SimpleJsonSerializer().Deserialize(json); Assert.Equal(42, sample.Id); Assert.Equal("Phil", sample.FirstName); Assert.True(sample.IsSomething); Assert.True(sample.Private); } [Fact] public void DeserializesPublicReadonlyAutoProperties() { const string json = "{\"content\":\"hello\"}"; var someObject = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("hello", someObject.Content); } public class ReadOnlyAutoProperties { public string Content { get; private set; } } [Fact] public void DeserializesProtectedProperties() { const string json = "{\"content\":\"hello\"}"; var someObject = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("*hello*", someObject.Content); } public class AnotherObject { [Parameter(Key = "content")] protected string EncodedContent { get; set; } public string Content { get { return "*" + EncodedContent + "*"; } } } [Fact] public void HandlesBase64EncodedStrings() { const string json = "{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}"; var item = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("Ferris Bueller", item.Name); Assert.Equal("Day off", item.Content); Assert.Equal("stuff", item.Description); } [Fact] public void CanDeserializeOrganization() { const string json = "{" + "\"login\": \"mono\"," + "\"id\": 53395," + "\"avatar_url\": \"https://avatars.githubusercontent.com/u/53395?\"," + "\"gravatar_id\": \"f275a99c0b4e6044d3e81daf445f8174\"," + "\"url\": \"https://api.github.com/users/mono\"," + "\"html_url\": \"https://github.com/mono\"," + "\"followers_url\": \"https://api.github.com/users/mono/followers\"," + "\"following_url\": \"https://api.github.com/users/mono/following{/other_user}\"," + "\"gists_url\": \"https://api.github.com/users/mono/gists{/gist_id}\"," + "\"starred_url\": \"https://api.github.com/users/mono/starred{/owner}{/repo}\"," + "\"subscriptions_url\": \"https://api.github.com/users/mono/subscriptions\"," + "\"organizations_url\": \"https://api.github.com/users/mono/orgs\"," + "\"repos_url\": \"https://api.github.com/users/mono/repos\"," + "\"events_url\": \"https://api.github.com/users/mono/events{/privacy}\"," + "\"received_events_url\": \"https://api.github.com/users/mono/received_events\"," + "\"type\": \"Organization\"," + "\"site_admin\": false," + "\"name\": \"Mono Project\"," + "\"company\": null," + "\"blog\": \"http://mono-project.com\"," + "\"location\": \"Boston, MA\"," + "\"email\": \"mono@xamarin.com\"," + "\"hireable\": null," + "\"bio\": null," + "\"public_repos\": 161," + "\"public_gists\": 0," + "\"followers\": 0," + "\"following\": 0," + "\"created_at\": \"2009-02-10T17:53:17Z\"," + "\"updated_at\": 1404691976" + "}"; var result = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("Mono Project", result.Name); Assert.Null(result.Hireable); Assert.Equal(new DateTimeOffset(2009, 02, 10, 17, 53, 17, TimeSpan.Zero), result.CreatedAt); Assert.Equal(new DateTimeOffset(2014, 07, 07, 00, 12, 56, TimeSpan.Zero), result.UpdatedAt); } [Fact] public void DeserializesInheritedProperties() { const string json = "{\"sha\":\"commit-sha\",\"url\":\"commit-url\",\"message\":\"commit-message\"}"; var result = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("commit-sha", result.Sha); Assert.Equal("commit-url", result.Url); Assert.Equal("commit-message", result.Message); } [Fact] public void RespectsParameterKeyName() { const string json = "{\"_links\":\"blah\"}"; var result = new SimpleJsonSerializer().Deserialize(json); Assert.Equal("blah", result.Links); } [Fact] public void DefaultsMissingParameters() { const string json = @"{""private"":true}"; var sample = new SimpleJsonSerializer().Deserialize(json); Assert.Equal(0, sample.Id); Assert.Null(sample.FirstName); Assert.False(sample.IsSomething); Assert.True(sample.Private); } [Fact] public void DeserializesEnums() { const string json1 = @"{""some_enum"":""+1""}"; const string json2 = @"{""some_enum"":""utf-8""}"; const string json3 = @"{""some_enum"":""something else""}"; const string json4 = @"{""some_enum"":""another_example""}"; const string json5 = @"{""some_enum"":""unicode""}"; var sample1 = new SimpleJsonSerializer().Deserialize(json1); var sample2 = new SimpleJsonSerializer().Deserialize(json2); var sample3 = new SimpleJsonSerializer().Deserialize(json3); var sample4 = new SimpleJsonSerializer().Deserialize(json4); var sample5 = new SimpleJsonSerializer().Deserialize(json5); Assert.Equal(SomeEnum.PlusOne, sample1.SomeEnum); Assert.Equal(SomeEnum.Utf8, sample2.SomeEnum); Assert.Equal(SomeEnum.SomethingElse, sample3.SomeEnum); Assert.Equal(SomeEnum.AnotherExample, sample4.SomeEnum); Assert.Equal(SomeEnum.Unicode, sample5.SomeEnum); } [Fact] public void DeserializesNullableEnums() { const string json1 = @"{""some_enum_nullable"":""+1""}"; const string json2 = @"{""some_enum_nullable"":""utf-8""}"; const string json3 = @"{""some_enum_nullable"":""something else""}"; const string json4 = @"{""some_enum_nullable"":""another_example""}"; const string json5 = @"{""some_enum_nullable"":""unicode""}"; const string json6 = @"{""some_enum_nullable"":null}"; var sample1 = new SimpleJsonSerializer().Deserialize(json1); var sample2 = new SimpleJsonSerializer().Deserialize(json2); var sample3 = new SimpleJsonSerializer().Deserialize(json3); var sample4 = new SimpleJsonSerializer().Deserialize(json4); var sample5 = new SimpleJsonSerializer().Deserialize(json5); var sample6 = new SimpleJsonSerializer().Deserialize(json6); Assert.Equal(SomeEnum.PlusOne, sample1.SomeEnumNullable); Assert.Equal(SomeEnum.Utf8, sample2.SomeEnumNullable); Assert.Equal(SomeEnum.SomethingElse, sample3.SomeEnumNullable); Assert.Equal(SomeEnum.AnotherExample, sample4.SomeEnumNullable); Assert.Equal(SomeEnum.Unicode, sample5.SomeEnumNullable); Assert.False(sample6.SomeEnumNullable.HasValue); } [Fact] public void DeserializesStringEnums() { const string json1 = @"{""string_enum"":""+1""}"; const string json2 = @"{""string_enum"":""utf-8""}"; const string json3 = @"{""string_enum"":""something else""}"; const string json4 = @"{""string_enum"":""another_example""}"; const string json5 = @"{""string_enum"":""unicode""}"; var sample1 = new SimpleJsonSerializer().Deserialize(json1); var sample2 = new SimpleJsonSerializer().Deserialize(json2); var sample3 = new SimpleJsonSerializer().Deserialize(json3); var sample4 = new SimpleJsonSerializer().Deserialize(json4); var sample5 = new SimpleJsonSerializer().Deserialize(json5); Assert.Equal(SomeEnum.PlusOne, sample1.StringEnum); Assert.Equal(SomeEnum.Utf8, sample2.StringEnum); Assert.Equal(SomeEnum.SomethingElse, sample3.StringEnum); Assert.Equal(SomeEnum.AnotherExample, sample4.StringEnum); Assert.Equal(SomeEnum.Unicode, sample5.StringEnum); } [Fact] public void DeserializesNullableStringEnums() { const string json1 = @"{""string_enum_nullable"":""+1""}"; const string json2 = @"{""string_enum_nullable"":""utf-8""}"; const string json3 = @"{""string_enum_nullable"":""something else""}"; const string json4 = @"{""string_enum_nullable"":""another_example""}"; const string json5 = @"{""string_enum_nullable"":""unicode""}"; const string json6 = @"{""string_enum_nullable"":null}"; var sample1 = new SimpleJsonSerializer().Deserialize(json1); var sample2 = new SimpleJsonSerializer().Deserialize(json2); var sample3 = new SimpleJsonSerializer().Deserialize(json3); var sample4 = new SimpleJsonSerializer().Deserialize(json4); var sample5 = new SimpleJsonSerializer().Deserialize(json5); var sample6 = new SimpleJsonSerializer().Deserialize(json6); Assert.Equal(SomeEnum.PlusOne, sample1.StringEnumNullable); Assert.Equal(SomeEnum.Utf8, sample2.StringEnumNullable); Assert.Equal(SomeEnum.SomethingElse, sample3.StringEnumNullable); Assert.Equal(SomeEnum.AnotherExample, sample4.StringEnumNullable); Assert.Equal(SomeEnum.Unicode, sample5.StringEnumNullable); Assert.False(sample6.StringEnumNullable.HasValue); } [Fact] public void ShouldDeserializeMultipleEnumValues() { var strings = new[] { "locked", "unlocked", "head_ref_deleted", "head_ref_restored" }; foreach (var value in strings) { var enumValue = SimpleJsonSerializer.DeserializeEnum(value, typeof(EventInfoState)); // Test passes if no exception thrown } } // this test is the root cause of https://github.com/octokit/octokit.net/issues/2052 // as the API is returning a null value where the enum is expecting // something like a string // // this should be removed once we can confirm the GitHub API is no // longer returning a null for the parent Team's permission value, // making the test redundant [Fact] public void ShouldDeserializeParentTeamWithNullPermission() { var teamJson = @"{ ""id"": 1, ""node_id"": ""MDQ6VGVhbTE="", ""url"": ""https://api.github.com/teams/1"", ""html_url"": ""https://api.github.com/teams/justice-league"", ""name"": ""Justice League"", ""slug"": ""justice-league"", ""description"": ""A great team."", ""privacy"": ""closed"", ""permission"": ""admin"", ""members_url"": ""https://api.github.com/teams/1/members{/member}"", ""repositories_url"": ""https://api.github.com/teams/1/repos"", ""parent"": { ""id"": 1, ""node_id"": ""MDQ6LFJSbTE="", ""url"": ""https://api.github.com/teams/2"", ""html_url"": ""https://api.github.com/teams/super-friends"", ""name"": ""Super Friends"", ""slug"": ""super-friends"", ""description"": ""Also a great team."", ""privacy"": ""closed"", ""permission"": null, ""members_url"": ""https://api.github.com/teams/2/members{/member}"", ""repositories_url"": ""https://api.github.com/teams/2/repos"", } } }"; var result = new SimpleJsonSerializer().Deserialize(teamJson); // original value works as expected Assert.Equal("admin", result.Permission); // parent permission is marked as null and cannot be parsed Assert.Null(result.Parent.Permission); } } public class Sample { public int Id { get; set; } public string FirstName { get; set; } public bool IsSomething { get; set; } public bool Private { get; set; } [Parameter(Key = "_links")] public string Links { get; set; } } public class SomeObject { [SerializeAsBase64] public string Name { get; set; } [SerializeAsBase64] public string Content; public string Description { get; set; } } } public class ObjectWithEnumProperty { public string Name { get; set; } public SomeEnum SomeEnum { get; set; } public SomeEnum? SomeEnumNullable { get; set; } public StringEnum StringEnum { get; set; } public StringEnum? StringEnumNullable { get; set; } } public enum SomeEnum { [Parameter(Value = "+1")] PlusOne, [Parameter(Value = "utf-8")] Utf8, [Parameter(Value = "something else")] SomethingElse, [Parameter(Value = "another_example")] AnotherExample, [Parameter(Value = "unicode")] Unicode } }