Files
octokit.net/Octokit.Tests.Integration/Helpers/Audit/Section.cs
2016-07-11 17:01:22 -07:00

191 lines
7.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace Octokit.Tests.Integration
{
public class Section
{
public string Route { get; private set; }
public string Url { get; private set; }
readonly IEnumerable<Endpoint> endpoints;
public IEnumerable<Endpoint> Endpoints { get { return endpoints; } }
public Section(string route, string url)
{
this.Route = route;
this.Url = url;
endpoints = WebsiteScraper.FindEndpointsAtUrl(url);
}
// naming things is hard, so here's a quick set of rules
// we're using to map the route values to our conventions
//
// most of this is around de-pluralization to ensure we're
// naming things consistently across the codebase
static readonly Dictionary<string, string> pluralToSingleMap = new Dictionary<string, string>() {
// dropping the pluralization
{ "gists", "gist" },
{ "comments", "comment" },
{ "issues", "issue" },
{ "assignees", "assignee" },
{ "milestones", "milestone" },
{ "users", "user" },
{ "commits", "commit" },
{ "blobs", "blob" },
{ "trees", "tree" },
{ "tags", "tag" },
{ "teams", "team" },
{ "members", "member" },
{ "deployments", "deployment" },
{ "emails", "email" },
{ "contents", "content" },
// replace abbreviation with something more user-friendly
{ "pulls", "pullrequest" },
{ "repos", "repository" },
{ "orgs", "organization" },
{ "refs", "reference" },
{ "oauth_authorizations", "authorization" }
};
// these routes are handled by specific clients
// which you won't find by traversing from the root
// i've called out the specific clients here - and why
// they're located in a different spot, so we can
// parse them properly in the future
static readonly Dictionary<string, Type> manualMappings = new Dictionary<string, Type>() {
// these should be implemented on the same client
// unless there's some significant granularity
{ "/rate_limit/", typeof(IMiscellaneousClient) },
{ "/emojis/", typeof(IMiscellaneousClient) },
{ "/gitignore/", typeof(IMiscellaneousClient) },
{ "/meta/", typeof(IMiscellaneousClient) },
{ "/markdown/", typeof(IMiscellaneousClient) },
{ "/licenses/", typeof(IMiscellaneousClient) },
// due to the cascading rename, these are currently broken
{ "/git/blobs/", typeof(IBlobsClient) },
{ "/git/commits/", typeof(ICommitsClient) },
{ "/git/refs/", typeof(IReferencesClient) },
{ "/git/tags/", typeof(ITagsClient) },
{ "/git/trees/", typeof(ITreesClient) },
// this property is called DeployKeys - i kinda like that name
{ "/repos/keys/", typeof(IRepositoryDeployKeysClient) }
};
// these routes either don't exist or are incorrectly named
// so I've opened issues to capture these distinctions
// and ignore them from subsequent tests
static readonly Dictionary<string, string> reportedIssues = new Dictionary<string, string>() {
// these routes are not implemented
{ "/orgs/migrations/", "#1029" },
{ "/orgs/hooks/", "#1028" },
{ "/enterprise/search_indexing/", "#1026" },
{ "/enterprise/orgs/", "#1025" },
{ "/enterprise/management_console/", "#1024" },
{ "/enterprise/license/", "#1023" },
{ "/enterprise/ldap/", "#1022" },
{ "/enterprise/admin_stats/", "#1021" },
{ "/users/administration/", "#1030" },
{ "/repos/pages/", "#1033" },
// these methods need to be renamed
{ "/repos/comments/", "#1031" },
{ "/repos/releases/", "#1032" },
{ "/repos/statuses/", "#1034" },
{ "/repos/commits/", "#1035" },
{ "/repos/collaborators/", "#1036" },
{ "/git/", "#1037" }
};
public IEnumerable<string> Validate()
{
if (reportedIssues.ContainsKey(Route))
{
// don't validate routes with known issues
return Enumerable.Empty<string>();
}
if (Endpoints.All(a => a.IsDeprecated))
{
// we assume all endpoints of a section
// are obsolete, so this is kind of a dumb check
return Enumerable.Empty<string>();
}
Type clientType;
if (manualMappings.ContainsKey(Route))
{
clientType = manualMappings[Route];
}
else
{
var tuple = ResolveFromRoot(Route);
if (tuple.Item1.Any())
{
return tuple.Item1;
}
clientType = tuple.Item2;
}
// TODO: validate methods implement each
// of the documented endpoints
return Enumerable.Empty<string>();
}
static Tuple<List<string>, Type> ResolveFromRoot(string route)
{
var properties = route.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
List<string> errors = new List<string>();
// starting at the `IGitHubClient` interface
// we'll look for properties which match with
// the route for this page
//
// for example:
//
// /activity/events/
//
// 1. find a property named Activity on IGitHubClient
// 2. find a property named Events on IActivitiesClient
//
// this code will report back as soon as it isn't able
// to resolve a property, and it will use the original
// route value, not a translated value
//
var currentType = typeof(IGitHubClient);
foreach (var property in properties)
{
string lookup = pluralToSingleMap.ContainsKey(property)
? pluralToSingleMap[property]
: property;
var found = currentType.GetProperties()
.FirstOrDefault(p => p.Name.Equals(lookup, StringComparison.OrdinalIgnoreCase));
if (found == null)
{
errors.Add(string.Format("For route '{0}' a property named '{1}' is expected on type '{2}' but it wasn't found",
route,
property,
currentType.Name));
break;
}
currentType = found.PropertyType;
}
return Tuple.Create(errors, currentType);
}
}
}