/// <summary> /// Allows callers to add a service via HTTP requests by providing a /// specially formatted service description. /// </summary> public static async Task AddService(HttpContext context, Func <Task> next) { RequiresArgument.NotNull(context, "context"); RequiresArgument.NotNull(next, "next"); if (!context.Request.Matches("POST", "/__vs/services")) { await next.Invoke(); return; } var serviceDesc = await ParseServiceDescription(context.Request); RequestRequires.NotNullOrWhiteSpace(serviceDesc.Method, "An HTTP method, i.e. GET, POST, PUT, DELETE, etc., must be provided"); RequestRequires.NotNullOrWhiteSpace(serviceDesc.Path, "A path must be provided. For example /api/myResources/53"); var service = new Service(serviceDesc); if (services.TryAdd(service)) { await context.Response.Ok(service.Id.ToString()); } else { throw new BadRequestException("Unable to add service. A service with the same ID or the same request filter already exists."); } }
/// <summary> /// Allows callers to query a service via HTTP to obtain information such as /// the number of times the service has been invoked and the body of the /// last request message that was used to invoke the service. /// </summary> public static async Task QueryService(HttpContext context, Func <Task> next) { RequiresArgument.NotNull(context, "context"); RequiresArgument.NotNull(next, "next"); if (context.Request.Method.ToUpperInvariant() != "GET" || !IsAServicePath(context.Request.Path, out Guid serviceId)) { await next.Invoke(); return; } if (services.TryGetById(serviceId, out Service service)) { var stats = service.Stats; var responseBody = new StringBuilder() .AppendLine($"CallCount: {stats.CallCount}") .AppendLine() .AppendLine("LastRequestBody:") .Append(stats.LastRequestBody) .ToString(); await context.Response.Ok(responseBody); } else { context.Response.NotFound(); } }
public void RequiresArgument_LengthEquals_ThrowsOnWrongSizeArray() { // Arrange var arr = new[] { 1, 2 }; // Act RequiresArgument.LengthEquals(arr, 3, "Array should contain 3 elements."); }
/// <summary> /// Writes the services predefined response to the specified /// HttpResponse. /// </summary> public async Task WriteResponseTo(HttpResponse response) { RequiresArgument.NotNull(response, "response"); response.StatusCode = this.response.StatusCode; response.ContentType = this.response.ContentType; await response.WriteAsync(this.response.Body); }
/// <summary> /// Returns true if the specified service will respond to the same /// requests as this one. /// </summary> public bool HandlesSameRequests(Service other) { RequiresArgument.NotNull(other, "other"); return(method == other.method && path == other.path && bodyFilter == other.bodyFilter); }
public void RequiresArgument_LengthEquals_DoesNothingOnRightSizeArray() { // Arrange var arr = new[] { 1, 2, 3 }; // Act RequiresArgument.LengthEquals(arr, 3, "Array should contain 3 elements."); }
/// <summary> /// Returns true if the collection contains the specified service. /// </summary> public bool Contains(Service service) { RequiresArgument.NotNull(service, "service"); lock (thisLock) { return(internalCollection.Contains(service)); } }
/// <summary> /// Updates the service's Stats to reflect the fact that the service /// has been invoked. /// </summary> public void RecordCall(string requestBody) { RequiresArgument.NotNull(requestBody, "requestBody"); lock (thisLock) { callCount++; lastRequestBody = requestBody; } }
/// <summary> /// Gets the first service in the collection that matched the /// specified predicate. Returns true if a service is found; /// otherwise false. /// </summary> public bool TryGetWhere(Func <Service, bool> predicate, out Service service) { RequiresArgument.NotNull(predicate, "predicate"); lock (thisLock) { service = internalCollection.FirstOrDefault(predicate); return(service != null); } }
/// <summary> /// Returns true when the service's method, path, and body filter /// match the specified request and body. /// </summary> public bool MatchesRequest(HttpRequest request, string body) { // This method is called in a loop. While is it possible to read // the body from the request itself, it would be very inefficient. // For this reason, the body is required as a separate argument // to the method. RequiresArgument.NotNull(request, "request"); RequiresArgument.NotNull(body, "body"); return(method == request.Method && path == WebUtility.UrlDecode(request.Path + request.QueryString).ToUpperInvariant() && MatchesBodyFilter(body)); }
/// <summary> /// Allow callers to delete all services via HTTP. This is useful for /// cleaning up after a set of tests have been run. /// </summary> public static Task DeleteAllServices(HttpContext context, Func <Task> next) { RequiresArgument.NotNull(context, "context"); RequiresArgument.NotNull(next, "next"); if (!context.Request.Matches("DELETE", "/__vs/services")) { return(next.Invoke()); } services.Clear(); context.Response.Ok(); return(Task.CompletedTask); }
/// <summary> /// Allows callers to delete a service via HTTP using the service's ID. /// </summary> public static Task DeleteService(HttpContext context, Func <Task> next) { RequiresArgument.NotNull(context, "context"); RequiresArgument.NotNull(next, "next"); if (context.Request.Method.ToUpperInvariant() != "DELETE" || !IsAServicePath(context.Request.Path, out Guid serviceId)) { return(next.Invoke()); } services.TryRemove(serviceId); context.Response.Ok(); return(Task.CompletedTask); }
/// <summary> /// Allows callers to invoke services that have been added. /// </summary> public static async Task InvokeService(HttpContext context, Func <Task> next) { RequiresArgument.NotNull(context, "context"); RequiresArgument.NotNull(next, "next"); string body = context.Request.ReadBody(); if (services.TryGetWhere(x => x.MatchesRequest(context.Request, body), out Service service)) { service.RecordCall(body); await service.WriteResponseTo(context.Response); } else { context.Response.NotFound(); } }
/// <summary> /// Attempts to add a service to the collection. Returns true if the /// service is added. Returns false when another service either has /// the same ID or handles the same requests. /// </summary> public bool TryAdd(Service service) { RequiresArgument.NotNull(service, "service"); lock (thisLock) { if (internalCollection.Any(x => x.Id == service.Id || x.HandlesSameRequests(service) )) { return(false); } else { internalCollection.Add(service); return(true); } } }
/// <summary> /// Creates a new service using the specified service description. See /// the ServiceDesc class for more information. /// </summary> public Service(ServiceDesc desc) { RequiresArgument.NotNullOrWhiteSpace(desc.Method, "Method"); RequiresArgument.NotNullOrWhiteSpace(desc.Path, "Path"); RequiresArgument.NotNullOrWhiteSpace(desc.ContentType, "ContentType"); RequiresArgument.NotNullOrWhiteSpace(desc.StatusCode, "StatusCode"); RequiresArgument.NotNull(desc.Body, "Body"); thisLock = new object(); Id = ParseId(desc.Id); // The method and path values are use for string matching, so //convert them to upper invariant. method = desc.Method.ToUpperInvariant(); path = desc.Path.ToUpperInvariant(); bodyFilter = desc.BodyContains; response = new ServiceResponse() { Body = desc.Body, ContentType = desc.ContentType, StatusCode = ParseStatusCode(desc.StatusCode) }; callCount = 0; lastRequestBody = string.Empty; }
public void RequiresArgument_NotNull_ThrowsOnNullValue() { // Act RequiresArgument.NotNull(null, "Value cannot be null."); }
public void RequiresArgument_NotNull_DoesNothingOnNonNullValue() { // Act RequiresArgument.NotNull(new object(), "Value cannot be null."); }
public void RequiresArgument_NotNullOrWhiteSpace_ThrowsOnNullOrWhiteSpaceValue() { // Act RequiresArgument.NotNullOrWhiteSpace(" \t ", "Value cannot be null or white space."); }
public void RequiresArgument_NotNullOrWhiteSpace_DoesNothingOnNonNullValue() { // Act RequiresArgument.NotNullOrWhiteSpace("lorem ipsum", "Value cannot be null."); }