diff --git a/src/GraphiQl/GraphiQlExtensions.cs b/src/GraphiQl/GraphiQlExtensions.cs index 3f1242c..66b4d3d 100644 --- a/src/GraphiQl/GraphiQlExtensions.cs +++ b/src/GraphiQl/GraphiQlExtensions.cs @@ -3,80 +3,75 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; namespace GraphiQl { public static class GraphiQlExtensions { - private const string DefaultPath = "/graphql"; + private const string DefaultGraphQlPath = "/graphql"; - public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app) - => UseGraphiQl(app, DefaultPath); - - public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app, string path) - => UseGraphiQl(app, path, null); + public static IServiceCollection AddGraphiQl(this IServiceCollection services) + => services.AddGraphiQl(null); - /// - /// In some scenarios it makes sense to specify the API path and file server path independently - /// Examples: hosting in IIS in a virtual application (myapp.com/1.0/...) or hosting API and documentation separately - public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app, string path, string apiPath) + public static IServiceCollection AddGraphiQl(this IServiceCollection services, Action configure) { - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException(nameof(path)); + if (configure != null) + { + services.Configure(configure); + } + + services.TryAdd(ServiceDescriptor.Transient, GraphiQlOptionsSetup>()); + + return services; + } - if (path.EndsWith("/")) - throw new ArgumentException("GraphiQL path must not end in a slash", nameof(path)); + public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app) + { + var options = app.ApplicationServices.GetService>().Value; - var filePath = $"{path}/graphql-path.js"; - var uri = !string.IsNullOrWhiteSpace(apiPath) ? apiPath : path; - app.Map(filePath, x => WritePathJavaScript(x, uri)); + var filePath = $"{options.GraphiQlPath.TrimEnd('/')}/graphql-path.js"; + var graphQlPath = !string.IsNullOrWhiteSpace(options.GraphQlApiPath) ? options.GraphQlApiPath : DefaultGraphQlPath; + app.Map(filePath, x => WritePathJavaScript(x, graphQlPath)); - return UseGraphiQlImp(app, x => x.SetPath(path)); + return UseGraphiQlImp(app, options); } - private static IApplicationBuilder UseGraphiQlImp(this IApplicationBuilder app, Action setConfig) + private static IApplicationBuilder UseGraphiQlImp(this IApplicationBuilder app, GraphiQlOptions options) { if (app == null) throw new ArgumentNullException(nameof(app)); - if (setConfig == null) - throw new ArgumentNullException(nameof(setConfig)); - - var config = new GraphiQlConfig(); - setConfig(config); var fileServerOptions = new FileServerOptions { - RequestPath = config.Path, - FileProvider = BuildFileProvider(), + RequestPath = options.GraphiQlPath, + FileProvider = new EmbeddedFileProvider(typeof(GraphiQlExtensions).GetTypeInfo().Assembly, "GraphiQl.assets"), EnableDefaultFiles = true, StaticFileOptions = {ContentTypeProvider = new FileExtensionContentTypeProvider()} }; + app.UseMiddleware(); app.UseFileServer(fileServerOptions); return app; } - private static IFileProvider BuildFileProvider() - { - var assembly = typeof(GraphiQlExtensions).GetTypeInfo().Assembly; - var embeddedFileProvider = new EmbeddedFileProvider(assembly, "GraphiQl.assets"); - - var fileProvider = new CompositeFileProvider( - embeddedFileProvider - ); - - return fileProvider; - } - - private static void WritePathJavaScript(IApplicationBuilder app, string path) - { + private static void WritePathJavaScript(IApplicationBuilder app, string path) => app.Run(h => { h.Response.ContentType = "application/javascript"; return h.Response.WriteAsync($"var graphqlPath='{path}';"); }); - } + + [Obsolete("This overload has been marked as obsolete, please configure via IServiceCollection.AddGraphiQl(..) instead or consult the documentation", true)] + public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app, string path) + => throw new NotImplementedException(); + + [Obsolete("This overload has been marked as obsolete, please configure via IServiceCollection.AddGraphiQl(..) instead or consult the documentation", true)] + public static IApplicationBuilder UseGraphiQl(this IApplicationBuilder app, string path, string apiPath) + => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/GraphiQl/GraphiQlMiddleware.cs b/src/GraphiQl/GraphiQlMiddleware.cs new file mode 100644 index 0000000..d177f48 --- /dev/null +++ b/src/GraphiQl/GraphiQlMiddleware.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace GraphiQl +{ + public class GraphiQlMiddleware + { + private readonly RequestDelegate _next; + private readonly GraphiQlOptions _options; + + public GraphiQlMiddleware(RequestDelegate next, IOptions options) + { + _next = next; + _options = options.Value; + } + + public async Task Invoke(HttpContext context) + { + if (context.Request.Path.Equals(_options.GraphiQlPath, StringComparison.OrdinalIgnoreCase) + && _options.IsAuthenticated != null + && !await _options.IsAuthenticated.Invoke(context)) + { + return; + } + + await _next(context); + } + } +} \ No newline at end of file diff --git a/src/GraphiQl/GraphiQlOptions.cs b/src/GraphiQl/GraphiQlOptions.cs new file mode 100644 index 0000000..decb0c3 --- /dev/null +++ b/src/GraphiQl/GraphiQlOptions.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace GraphiQl +{ + public class GraphiQlOptions + { + public string GraphiQlPath { get; set; } + + public string GraphQlApiPath { get; set; } + + public Func> IsAuthenticated { get; set; } + + public GraphiQlOptions() + { + GraphiQlPath = "/graphql"; + GraphQlApiPath = "/graphql"; + } + } + + public class GraphiQlOptionsSetup : IConfigureOptions + { + public void Configure(GraphiQlOptions options) + { + if (options.GraphiQlPath == null) + { + options.GraphiQlPath = "/graphql"; + } + + if (options.GraphQlApiPath == null) + { + options.GraphQlApiPath = "/graphql"; + } + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Demo/Controllers/GraphQlController.cs b/tests/GraphiQl.Demo/Controllers/GraphQlController.cs index a7ea569..12255e7 100644 --- a/tests/GraphiQl.Demo/Controllers/GraphQlController.cs +++ b/tests/GraphiQl.Demo/Controllers/GraphQlController.cs @@ -8,6 +8,7 @@ namespace GraphiQl.Demo.Controllers { [Route(Startup.GraphQlPath)] + [Route(Startup.CustomGraphQlPath)] public class GraphQlController : Controller { [HttpPost] diff --git a/tests/GraphiQl.Demo/GraphiQl.Demo.csproj b/tests/GraphiQl.Demo/GraphiQl.Demo.csproj index c5434be..57ce1f0 100644 --- a/tests/GraphiQl.Demo/GraphiQl.Demo.csproj +++ b/tests/GraphiQl.Demo/GraphiQl.Demo.csproj @@ -2,9 +2,6 @@ netcoreapp3.1 - - - diff --git a/tests/GraphiQl.Demo/Startup.cs b/tests/GraphiQl.Demo/Startup.cs index b9b83b6..e95d259 100644 --- a/tests/GraphiQl.Demo/Startup.cs +++ b/tests/GraphiQl.Demo/Startup.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,6 +10,7 @@ namespace GraphiQl.Demo public class Startup { public const string GraphQlPath = "/graphql"; + public const string CustomGraphQlPath = "/custom-path"; public Startup(IConfiguration configuration) { @@ -19,6 +19,9 @@ public Startup(IConfiguration configuration) public IConfiguration Configuration { get; } + public virtual void ConfigureGraphQl(IServiceCollection services) + => services.AddGraphiQl(); + public void ConfigureServices(IServiceCollection services) { services @@ -26,19 +29,13 @@ public void ConfigureServices(IServiceCollection services) .AddNewtonsoftJson( options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore ); + + ConfigureGraphQl(services); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { - - /* - app.Run(async context => - { - await context.Response.WriteAsync("Hello, World!"); - }); - */ - - app.UseGraphiQl(GraphQlPath); + app.UseGraphiQl(); app.UseRouting().UseEndpoints( routing => routing.MapControllers() ); diff --git a/tests/GraphiQl.Tests/AuthenticationTest/ConfigureOptionsSetup.cs b/tests/GraphiQl.Tests/AuthenticationTest/ConfigureOptionsSetup.cs new file mode 100644 index 0000000..3d02176 --- /dev/null +++ b/tests/GraphiQl.Tests/AuthenticationTest/ConfigureOptionsSetup.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.AuthenticationTest +{ + public class ConfigureOptionsSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public ConfigureOptionsSetup() + { + _host = WebHost.CreateDefaultBuilder() + .ConfigureServices(x => { x.AddTransient,GraphiQlTestOptionsSetup>(); }) + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + [Fact] + public void RequiresAuthentication() + { + // Arrange + Act + var result = string.Empty; + RunTest(driver => + { + driver.Navigate().GoToUrl("http://localhost:5001/graphql"); + + driver.Manage() + .Timeouts() + .ImplicitWait = TimeSpan.FromSeconds(2); + + result = driver.PageSource; + }); + + // Assert + result.ShouldContain("This page requires authentication"); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + public Task DisposeAsync() + { + _host.Dispose(); + return Task.CompletedTask; + } + + internal class GraphiQlTestOptionsSetup : IConfigureOptions + { + public void Configure(GraphiQlOptions options) + { + options.IsAuthenticated = context => + { + context.Response.Clear(); + context.Response.StatusCode = 400; + context.Response.WriteAsync("This page requires authentication"); + + return Task.FromResult(false); + }; + } + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/AuthenticationTest/DelegateSetup.cs b/tests/GraphiQl.Tests/AuthenticationTest/DelegateSetup.cs new file mode 100644 index 0000000..c291f28 --- /dev/null +++ b/tests/GraphiQl.Tests/AuthenticationTest/DelegateSetup.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.AuthenticationTest +{ + public class CustomStartup : Startup + { + public CustomStartup(IConfiguration configuration) : base(configuration) {} + + public override void ConfigureGraphQl(IServiceCollection services) + => services.AddGraphiQl(x => x.IsAuthenticated = context => + { + context.Response.Clear(); + context.Response.StatusCode = 400; + context.Response.WriteAsync("This page requires authentication"); + + return Task.FromResult(false); + }); + } + + public class DelegateSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public DelegateSetup() + { + _host = WebHost.CreateDefaultBuilder() + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + [Fact] + public void RequiresAuthentication() + { + // Arrange + Act + var result = string.Empty; + RunTest(driver => + { + driver.Navigate().GoToUrl("http://localhost:5001/graphql"); + + driver.Manage() + .Timeouts() + .ImplicitWait = TimeSpan.FromSeconds(2); + + result = driver.PageSource; + }); + + // Assert + result.ShouldContain("This page requires authentication"); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + public Task DisposeAsync() + { + _host.Dispose(); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/GraphQlTests.cs b/tests/GraphiQl.Tests/BasicTest.cs similarity index 62% rename from tests/GraphiQl.Tests/GraphQlTests.cs rename to tests/GraphiQl.Tests/BasicTest.cs index ef15eb8..d7671cc 100644 --- a/tests/GraphiQl.Tests/GraphQlTests.cs +++ b/tests/GraphiQl.Tests/BasicTest.cs @@ -1,23 +1,23 @@ using System; using System.Text.Json; -using System.Threading.Tasks; +using System.Threading; using GraphiQl.Tests.Fixtures; using Shouldly; using Xunit; namespace GraphiQl.Tests { - public class GraphQlTests : BaseTest, IClassFixture + public class BasicTest : SeleniumTest, IClassFixture { private readonly HostFixture _fixture; - public GraphQlTests(HostFixture fixture) : base(runHeadless: true) + public BasicTest(HostFixture fixture) { _fixture = fixture; } [Fact] - public async Task CanQueryGraphQl() + public void CanQueryGraphQl() { // TODO: Use PageModel @@ -26,17 +26,17 @@ public async Task CanQueryGraphQl() var query = @"{hero{id,name}}"; // Act - await RunTest(async driver => + RunTest( driver => { - Driver.Navigate().GoToUrl(_fixture.GraphiQlUri + Uri.EscapeDataString(query)); - var button = Driver.FindElementByClassName("execute-button"); + driver.Navigate().GoToUrl(_fixture.GraphiQlUri + Uri.EscapeDataString(query)); + var button = driver.FindElementByClassName("execute-button"); button?.Click(); - - await Task.Delay(2000); + + //TODO: https://www.selenium.dev/documentation/en/webdriver/waits/ + Thread.Sleep(2000); // UGH! - result = Driver - .FindElementByClassName("result-window").Text + result = driver.FindElementByClassName("result-window").Text .Replace("\n", "") .Replace(" ", ""); }); diff --git a/tests/GraphiQl.Tests/Fixtures/GraphQlFixture.cs b/tests/GraphiQl.Tests/Fixtures/GraphQlFixture.cs index 49ff1b1..91960be 100644 --- a/tests/GraphiQl.Tests/Fixtures/GraphQlFixture.cs +++ b/tests/GraphiQl.Tests/Fixtures/GraphQlFixture.cs @@ -45,7 +45,7 @@ public IWebHost CreateWebHostOld() }) .Configure(app => { - app.UseGraphiQl("/graphql"); + app.UseGraphiQl(); app.UseRouting().UseEndpoints(routing => routing.MapControllers()); }); diff --git a/tests/GraphiQl.Tests/Fixtures/HostFixture.cs b/tests/GraphiQl.Tests/Fixtures/HostFixture.cs index 1e13942..a716bfb 100644 --- a/tests/GraphiQl.Tests/Fixtures/HostFixture.cs +++ b/tests/GraphiQl.Tests/Fixtures/HostFixture.cs @@ -21,39 +21,13 @@ public HostFixture() .Build(); } - /* - public IWebHost Build() - { - var config = new ConfigurationBuilder().Build(); - var host = new WebHostBuilder() - .UseConfiguration(config) - .UseKestrel() - .UseUrls("http://*:5001") - .ConfigureServices(s => - { - s.AddMvc() - .AddNewtonsoftJson(o => - o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); - s.AddControllers() - .PartManager.ApplicationParts.Add(new AssemblyPart(typeof(GraphQlController).Assembly)); - }) - .Configure(app => - { - app.UseGraphiQl("/graphql"); - app.UseRouting().UseEndpoints(routing => routing.MapControllers()); - }); - - return host.Build(); - } - */ - public async Task InitializeAsync() => await _host.StartAsync().ConfigureAwait(false); - public Task DisposeAsync() + public async Task DisposeAsync() { + await _host.StopAsync().ConfigureAwait(false); _host.Dispose(); - return Task.CompletedTask; } } } \ No newline at end of file diff --git a/tests/GraphiQl.Tests/GraphiQl.Tests.csproj b/tests/GraphiQl.Tests/GraphiQl.Tests.csproj index 925f409..bcc4f04 100644 --- a/tests/GraphiQl.Tests/GraphiQl.Tests.csproj +++ b/tests/GraphiQl.Tests/GraphiQl.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/tests/GraphiQl.Tests/OverrideGraphQlPathTests/ConfigureOptionsSetup.cs b/tests/GraphiQl.Tests/OverrideGraphQlPathTests/ConfigureOptionsSetup.cs new file mode 100644 index 0000000..9c6adae --- /dev/null +++ b/tests/GraphiQl.Tests/OverrideGraphQlPathTests/ConfigureOptionsSetup.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.OverrideGraphQlPathTests +{ + public class ConfigureOptionsSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public ConfigureOptionsSetup() + { + _host = WebHost.CreateDefaultBuilder() + .ConfigureServices(serviceCollection => + { + serviceCollection.AddTransient, GraphiQlTestOptionsSetup>(); + }) + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + [Fact] + public void CanOverrideGraphQlPath() + { + // TODO: Use PageModel + + // Arrange + var result = string.Empty; + var query = @"{hero{id,name}}"; + + // Act + RunTest( driver => + { + driver.Navigate().GoToUrl($"http://localhost:5001/graphql?query=" + Uri.EscapeDataString(query)); + var button = driver.FindElementByClassName("execute-button"); + button?.Click(); + + Thread.Sleep(2000); + + /* + var x = Driver.FindElement(B) + var foo = new WebDriverWait(driver, TimeSpan.FromSeconds(3)) + .Until(drv => drv.FindElement(By.Name("q"))); + */ + + // UGH! + result = driver + .FindElementByClassName("result-window").Text + .Replace("\n", "") + .Replace(" ", ""); + }); + + // Assert + using var channelResponse = JsonDocument.Parse(result); + var data = channelResponse.RootElement.GetProperty("data"); + + data.GetProperty("hero").GetProperty("name").GetString().ShouldBe("R2-D2"); + } + + public async Task DisposeAsync() + { + await _host.StopAsync(); + _host.Dispose(); + } + + internal class GraphiQlTestOptionsSetup : IConfigureOptions + { + public void Configure(GraphiQlOptions options) + { + options.GraphQlApiPath = Startup.CustomGraphQlPath; + } + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/OverrideGraphQlPathTests/DelegateSetup.cs b/tests/GraphiQl.Tests/OverrideGraphQlPathTests/DelegateSetup.cs new file mode 100644 index 0000000..9e14d37 --- /dev/null +++ b/tests/GraphiQl.Tests/OverrideGraphQlPathTests/DelegateSetup.cs @@ -0,0 +1,78 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.OverrideGraphQlPathTests +{ + public class CustomStartup : Startup + { + public CustomStartup(IConfiguration configuration) : base(configuration) {} + + public override void ConfigureGraphQl(IServiceCollection services) + => services.AddGraphiQl(x => x.GraphQlApiPath = CustomGraphQlPath); + } + + public class DelegateSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public DelegateSetup() + { + _host = WebHost.CreateDefaultBuilder() + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + [Fact] + public void CanOverrideGraphQlPath() + { + // TODO: Use PageModel + + // Arrange + var result = string.Empty; + var query = @"{hero{id,name}}"; + + // Act + RunTest( driver => + { + driver.Navigate().GoToUrl("http://localhost:5001/graphql?query=" + Uri.EscapeDataString(query)); + var button = driver.FindElementByClassName("execute-button"); + button?.Click(); + + driver.Manage() + .Timeouts() + .ImplicitWait = TimeSpan.FromSeconds(2); + + // UGH! + result = driver + .FindElementByClassName("result-window").Text + .Replace("\n", "") + .Replace(" ", ""); + }); + + // Assert + using var channelResponse = JsonDocument.Parse(result); + var data = channelResponse.RootElement.GetProperty("data"); + + data.GetProperty("hero").GetProperty("name").GetString().ShouldBe("R2-D2"); + } + + public async Task DisposeAsync() + { + await _host.StopAsync(); + _host.Dispose(); + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/ConfigureOptionsSetup.cs b/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/ConfigureOptionsSetup.cs new file mode 100644 index 0000000..5d9ed1d --- /dev/null +++ b/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/ConfigureOptionsSetup.cs @@ -0,0 +1,81 @@ +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.OverrideGraphiQlPathTests +{ + public class ConfigureOptionsSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public ConfigureOptionsSetup() + { + _host = WebHost.CreateDefaultBuilder() + .ConfigureServices(serviceCollection => + { + serviceCollection.AddTransient, GraphiQlTestOptionsSetup>(); + }) + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + [Fact] + public void CanOverrideGraphiQlPath() + { + // TODO: Use PageModel + + // Arrange + var result = string.Empty; + var query = @"{hero{id,name}}"; + + // Act + RunTest( driver => + { + driver.Navigate().GoToUrl($"http://localhost:5001{Startup.CustomGraphQlPath}?query=" + Uri.EscapeDataString(query)); + var button = driver.FindElementByClassName("execute-button"); + button?.Click(); + + Thread.Sleep(2000); + + // UGH! + result = driver + .FindElementByClassName("result-window").Text + .Replace("\n", "") + .Replace(" ", ""); + }); + + // Assert + using var channelResponse = JsonDocument.Parse(result); + var data = channelResponse.RootElement.GetProperty("data"); + + data.GetProperty("hero").GetProperty("name").GetString().ShouldBe("R2-D2"); + } + + public async Task DisposeAsync() + { + await _host.StopAsync(); + _host.Dispose(); + } + + internal class GraphiQlTestOptionsSetup : IConfigureOptions + { + public void Configure(GraphiQlOptions options) + { + options.GraphiQlPath = Startup.CustomGraphQlPath; + } + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/DelegateSetup.cs b/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/DelegateSetup.cs new file mode 100644 index 0000000..78b2260 --- /dev/null +++ b/tests/GraphiQl.Tests/OverrideGraphiQlPathTests/DelegateSetup.cs @@ -0,0 +1,88 @@ +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using GraphiQl.Demo; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace GraphiQl.Tests.OverrideGraphiQlPathTests +{ + public class CustomStartup : Startup + { + public CustomStartup(IConfiguration configuration) : base(configuration) {} + + public override void ConfigureGraphQl(IServiceCollection services) + => services.AddGraphiQl(x => x.GraphiQlPath = CustomGraphQlPath); + } + + public class DelegateSetup : SeleniumTest, IAsyncLifetime + { + private readonly IWebHost _host; + + public DelegateSetup() + { + _host = WebHost.CreateDefaultBuilder() + .UseStartup() + .UseKestrel() + .UseUrls("http://*:5001") + .Build(); + } + + public async Task InitializeAsync() + => await _host.StartAsync().ConfigureAwait(false); + + [Fact] + public void CanOverrideGraphiQlPath() + { + // TODO: Use PageModel + + // Arrange + var result = string.Empty; + var query = @"{hero{id,name}}"; + + // Act + RunTest( driver => + { + driver.Navigate().GoToUrl($"http://localhost:5001{Startup.CustomGraphQlPath}?query=" + Uri.EscapeDataString(query)); + var button = driver.FindElementByClassName("execute-button"); + button?.Click(); + + driver.Manage() + .Timeouts() + .ImplicitWait = TimeSpan.FromSeconds(2); + + // UGH! + result = driver + .FindElementByClassName("result-window").Text + .Replace("\n", "") + .Replace(" ", ""); + }); + + // Assert + using var channelResponse = JsonDocument.Parse(result); + var data = channelResponse.RootElement.GetProperty("data"); + + data.GetProperty("hero").GetProperty("name").GetString().ShouldBe("R2-D2"); + } + + public async Task DisposeAsync() + { + await _host.StopAsync(); + _host.Dispose(); + } + + internal class GraphiQlTestOptionsSetup : IConfigureOptions + { + public void Configure(GraphiQlOptions options) + { + options.GraphiQlPath = Startup.CustomGraphQlPath; + } + } + } +} \ No newline at end of file diff --git a/tests/GraphiQl.Tests/BaseTest.cs b/tests/GraphiQl.Tests/SeleniumTest.cs similarity index 53% rename from tests/GraphiQl.Tests/BaseTest.cs rename to tests/GraphiQl.Tests/SeleniumTest.cs index dcd1de0..b1f04dd 100644 --- a/tests/GraphiQl.Tests/BaseTest.cs +++ b/tests/GraphiQl.Tests/SeleniumTest.cs @@ -1,39 +1,39 @@ using System; -using System.Threading.Tasks; +using OpenQA.Selenium; using OpenQA.Selenium.Chrome; namespace GraphiQl.Tests { - public abstract class BaseTest + public abstract class SeleniumTest { - protected ChromeDriver Driver { get; } + private ChromeDriver Driver { get; } + protected bool RunHeadless { get; set; } = true; - protected BaseTest(bool runHeadless) + protected SeleniumTest() { var options = new ChromeOptions(); - options.AddArgument("--remote-debugging-port=9222"); options.AddArgument("--disable-dev-shm-usage"); options.AddArgument("--no-sandbox"); - if (runHeadless) + if (RunHeadless) options.AddArgument("--headless"); Driver = new ChromeDriver(options); } - protected async Task RunTest(Func execute) + protected void RunTest(Action execute) { try { - await execute(Driver); + execute(Driver); } - catch (Exception) + catch (Exception ex) { - throw; + throw ex; } finally { - Driver.Close(); + Driver.Quit(); } } } diff --git a/tests/GraphiQl.Tests/xunit.runner.json b/tests/GraphiQl.Tests/xunit.runner.json new file mode 100644 index 0000000..369786b --- /dev/null +++ b/tests/GraphiQl.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false +} \ No newline at end of file