diff --git a/AllReadyApp/AllReady.IntegrationTests/AllReady.IntegrationTests.csproj b/AllReadyApp/AllReady.IntegrationTests/AllReady.IntegrationTests.csproj new file mode 100644 index 000000000..c5311150f --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/AllReady.IntegrationTests.csproj @@ -0,0 +1,24 @@ + + + + + + netcoreapp2.0 + false + + + + + + + + + + + + + + + + + diff --git a/AllReadyApp/AllReady.IntegrationTests/IntegrationTestStartup.cs b/AllReadyApp/AllReady.IntegrationTests/IntegrationTestStartup.cs new file mode 100644 index 000000000..902ec9ade --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/IntegrationTestStartup.cs @@ -0,0 +1,70 @@ +using System; +using AllReady.DataAccess; +using AllReady.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace AllReady.IntegrationTests +{ + public class IntegrationTestStartup : Startup + { + public IntegrationTestStartup(IConfiguration configuration) : base(configuration) + { + } + + protected override void AddDatabaseServices(IServiceCollection services) + { + services + .AddEntityFrameworkInMemoryDatabase() + .AddDbContext(options => + { + options.UseInMemoryDatabase("InMemory"); + }); + } + + protected override void LoadSeedData(bool purgeRefreshSampleData, SampleDataGenerator sampleDataGenerator) + { + // tests will load their own data? Possibly have some default data instead + } + + protected override void MigrateDatabase(bool purgeRefreshSampleData, IHostingEnvironment hostingEnvironment, AllReadyContext context) + { + // for now we load up a default set of data used by all tests - this works for now, but needs review + + var campaign = new Campaign + { + EndDateTime = DateTimeOffset.UtcNow.AddDays(10), + Featured = true, + Published = true, + Locked = false, + Name = "Featured Campaign Name", + Description = "This is a featured campaign", + Headline = "This is a featured headline", + ManagingOrganization = new Organization + { + Name = "Test Organisation" + } + }; + + context.Campaigns.Add(campaign); + + context.Events.Add(new Event { Campaign = campaign, Name = "Event Name 1", EndDateTime = DateTimeOffset.UtcNow.AddDays(2) }); + context.Events.Add(new Event { Campaign = campaign, Name = "Event Name 2", EndDateTime = DateTimeOffset.UtcNow.AddDays(2) }); + + context.SaveChanges(); + } + + protected override void AddHangFire(IServiceCollection services) + { + // do nothing for now - may need to be added later in some form + } + + protected override void RegisterHangFire(IApplicationBuilder app) + { + // do nothing for now - may need to be added later in some form + } + } +} diff --git a/AllReadyApp/AllReady.IntegrationTests/Pages/AboutTests.cs b/AllReadyApp/AllReady.IntegrationTests/Pages/AboutTests.cs new file mode 100644 index 000000000..0720c56de --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/Pages/AboutTests.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace AllReady.IntegrationTests.Pages +{ + public class AboutTests : IClassFixture> + { + private readonly HttpClient _client; + + public AboutTests(TestFixture fixture) + { + _client = fixture.Client; + } + + [Fact] + public async Task CanGetAboutPage() + { + // Act + var response = await _client.GetAsync("/about"); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + } +} diff --git a/AllReadyApp/AllReady.IntegrationTests/Pages/IndexTests.cs b/AllReadyApp/AllReady.IntegrationTests/Pages/IndexTests.cs new file mode 100644 index 000000000..015cc5548 --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/Pages/IndexTests.cs @@ -0,0 +1,54 @@ +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace AllReady.IntegrationTests.Pages +{ + public class IndexTests : IClassFixture> + { + private readonly HttpClient _client; + + private const string FeaturedCampaignName = "Featured Campaign Name"; + + public IndexTests(TestFixture fixture) + { + _client = fixture.Client; + } + + [Fact] + public async Task CanGetHomePage() + { + // Act + var response = await _client.GetAsync("/"); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task IncludesFeaturedCampaign_WhenSet() + { + // Act + var response = await _client.GetAsync("/"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + content.ShouldContain(FeaturedCampaignName); + } + + [Fact] + public async Task IncludesExpectedNumberOfEvents() + { + // Act + var response = await _client.GetAsync("/"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + var newLineFreeContent = Regex.Replace(content, @"\t|\n|\r|\s+", string.Empty); + Regex.Matches(newLineFreeContent, "

Campaign:").Count.ShouldBe(2); + } + } +} diff --git a/AllReadyApp/AllReady.IntegrationTests/Pages/PrivacyPolicyTests.cs b/AllReadyApp/AllReady.IntegrationTests/Pages/PrivacyPolicyTests.cs new file mode 100644 index 000000000..28fe1c53d --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/Pages/PrivacyPolicyTests.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace AllReady.IntegrationTests.Pages +{ + public class PrivacyPolicyTests : IClassFixture> + { + private readonly HttpClient _client; + + public PrivacyPolicyTests(TestFixture fixture) + { + _client = fixture.Client; + } + + [Fact] + public async Task CanGetAboutPage() + { + // Act + var response = await _client.GetAsync("/privacypolicy"); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + } +} diff --git a/AllReadyApp/AllReady.IntegrationTests/TestFixture.cs b/AllReadyApp/AllReady.IntegrationTests/TestFixture.cs new file mode 100644 index 000000000..c44c70ad5 --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/TestFixture.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Reflection; +using AllReady.Models; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; + +namespace AllReady.IntegrationTests +{ + public class TestFixture : IDisposable + { + private readonly TestServer _server; + + public TestFixture() + { + var startupAssembly = typeof(TStartup).GetTypeInfo().BaseType.Assembly; + var contentRoot = GetProjectPath(startupAssembly); + + var builder = new WebHostBuilder() + .UseContentRoot(contentRoot) + .ConfigureServices(InitializeServices) + .UseEnvironment("Development") + .UseStartup(typeof(TStartup)); + + _server = new TestServer(builder); + + DbContext = _server.Host.Services.GetService(typeof(AllReadyContext)) as AllReadyContext; + + Client = _server.CreateClient(); + Client.BaseAddress = new Uri("http://localhost"); + } + + public HttpClient Client { get; } + + public AllReadyContext DbContext { get; } + + public void Dispose() + { + Client.Dispose(); + _server.Dispose(); + } + + protected virtual void InitializeServices(IServiceCollection services) + { + var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly; + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new AssemblyPart(startupAssembly)); + manager.FeatureProviders.Add(new ControllerFeatureProvider()); + manager.FeatureProviders.Add(new ViewComponentFeatureProvider()); + services.AddSingleton(manager); + } + + // Get the full path to the target project for testing + private static string GetProjectPath(Assembly startupAssembly) + { + var projectName = startupAssembly.GetName().Name; + var applicationBasePath = System.AppContext.BaseDirectory; + var directoryInfo = new DirectoryInfo(applicationBasePath); + do + { + directoryInfo = directoryInfo.Parent.Parent.Parent.Parent; + + var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "Web-App")); + if (projectDirectoryInfo.Exists) + { + var projectFileInfo = new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj")); + if (projectFileInfo.Exists) + { + return Path.Combine(projectDirectoryInfo.FullName, projectName); + } + } + } + while (directoryInfo.Parent != null); + + throw new Exception($"Project root could not be located using the application root {applicationBasePath}."); + } + } +} diff --git a/AllReadyApp/AllReady.IntegrationTests/build/testing.targets b/AllReadyApp/AllReady.IntegrationTests/build/testing.targets new file mode 100644 index 000000000..3982282d3 --- /dev/null +++ b/AllReadyApp/AllReady.IntegrationTests/build/testing.targets @@ -0,0 +1,25 @@ + + + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/AllReadyApp/AllReadyWebOnly.sln b/AllReadyApp/AllReadyWebOnly.sln index 11f806661..d005445f1 100644 --- a/AllReadyApp/AllReadyWebOnly.sln +++ b/AllReadyApp/AllReadyWebOnly.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +VisualStudioVersion = 15.0.27130.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{2C1DD2C4-73FF-4D1D-9E88-6B413317FC29}" EndProject @@ -32,6 +32,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Builds", "Builds", "{79E5CF EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AllReady.ScenarioTest", "AllReady.IntegrationTest\AllReady.ScenarioTest.fsproj", "{44055C15-5757-4822-908B-C66B60251C54}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllReady.IntegrationTests", "AllReady.IntegrationTests\AllReady.IntegrationTests.csproj", "{B45509F1-9C64-4A90-8143-C5D7CDB57904}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{AECA1833-B3D9-4E87-B64C-042FDDBEFF7B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +62,10 @@ Global {44055C15-5757-4822-908B-C66B60251C54}.Debug|Any CPU.Build.0 = Debug|Any CPU {44055C15-5757-4822-908B-C66B60251C54}.Release|Any CPU.ActiveCfg = Release|Any CPU {44055C15-5757-4822-908B-C66B60251C54}.Release|Any CPU.Build.0 = Release|Any CPU + {B45509F1-9C64-4A90-8143-C5D7CDB57904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B45509F1-9C64-4A90-8143-C5D7CDB57904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B45509F1-9C64-4A90-8143-C5D7CDB57904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B45509F1-9C64-4A90-8143-C5D7CDB57904}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -69,9 +77,8 @@ Global {BFF4752B-274B-47E5-82FD-9B351792FF2D} = {CB31A2DF-9F47-41D5-AD0F-491C98A8D4CB} {79E5CFB4-6FF7-4F89-B560-9342FFE91898} = {364DE571-98D2-4F69-8D93-F25F832E7D7A} {44055C15-5757-4822-908B-C66B60251C54} = {1475C1FD-CE7D-4E6B-83B1-F7F82EA24FF8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {645EFF42-0422-4249-B544-6038D44E3CDE} + {B45509F1-9C64-4A90-8143-C5D7CDB57904} = {1475C1FD-CE7D-4E6B-83B1-F7F82EA24FF8} + {AECA1833-B3D9-4E87-B64C-042FDDBEFF7B} = {1475C1FD-CE7D-4E6B-83B1-F7F82EA24FF8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FCAA194F-AD24-45C2-AA51-78C71433DCC0} diff --git a/AllReadyApp/Web-App/AllReady.UnitTest/Features/Campaigns/FeaturedCampaignQueryHandlerShould.cs b/AllReadyApp/Web-App/AllReady.UnitTest/Features/Campaigns/FeaturedCampaignQueryHandlerShould.cs index 55b3f5ddc..bd8c2f96a 100644 --- a/AllReadyApp/Web-App/AllReady.UnitTest/Features/Campaigns/FeaturedCampaignQueryHandlerShould.cs +++ b/AllReadyApp/Web-App/AllReady.UnitTest/Features/Campaigns/FeaturedCampaignQueryHandlerShould.cs @@ -1,4 +1,4 @@ -using System; +using System; using AllReady.Features.Campaigns; using System.Linq; using System.Threading.Tasks; @@ -15,7 +15,7 @@ public async Task ReturnASingleCampaignThatIsFeaturedAndHasNotEnded() // Arrange var handler = new FeaturedCampaignQueryHandler(Context) { - DateTimeOffsetUtcNow = () => new DateTime(2017, 01, 07) + DateTimeOffsetUtcNow = () => DateTime.Now }; // Act @@ -105,7 +105,7 @@ protected override void LoadTestData() Featured = true, ManagingOrganization = org, Published = true, - EndDateTime = new DateTime(2012, 1, 1) + EndDateTime = DateTime.Now.AddDays(-10) }); Context.Campaigns.Add(new Campaign @@ -115,7 +115,7 @@ protected override void LoadTestData() Featured = true, ManagingOrganization = org, Published = true, - EndDateTime = new DateTime(2018, 1, 1) + EndDateTime = DateTime.Now.AddDays(90) // future date }); Context.Campaigns.Add(new Campaign @@ -125,7 +125,7 @@ protected override void LoadTestData() Featured = false, ManagingOrganization = org, Published = true, - EndDateTime = new DateTime(2018, 1, 1) + EndDateTime = DateTime.Now.AddDays(90) // future date }); Context.Campaigns.Add(new Campaign @@ -135,10 +135,10 @@ protected override void LoadTestData() Featured = true, ManagingOrganization = org, Published = true, - EndDateTime = new DateTime(2018, 1, 1) + EndDateTime = DateTime.Now.AddDays(90) // future date }); Context.SaveChanges(); } } -} \ No newline at end of file +} diff --git a/AllReadyApp/Web-App/AllReady/Startup.cs b/AllReadyApp/Web-App/AllReady/Startup.cs index c4b8766de..833bc0890 100644 --- a/AllReadyApp/Web-App/AllReady/Startup.cs +++ b/AllReadyApp/Web-App/AllReady/Startup.cs @@ -39,7 +39,7 @@ public Startup(IConfiguration configuration) Configuration = configuration; Configuration["version"] = new ApplicationEnvironment().ApplicationVersion; } - + public IConfiguration Configuration { get; } public IServiceProvider ConfigureServices(IServiceCollection services) @@ -51,8 +51,7 @@ public IServiceProvider ConfigureServices(IServiceCollection services) options.AddPolicy("allReady", AllReadyCorsPolicyFactory.BuildAllReadyOpenCorsPolicy()); }); - // Add Entity Framework services to the services container. - services.AddDbContext(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + AddDatabaseServices(services); Options.LoadConfigurationOptions(services, Configuration); @@ -77,7 +76,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services) if (Configuration["Authentication:Facebook:AppId"] != null) { services.AddAuthentication() - .AddFacebook(options => { + .AddFacebook(options => + { options.AppId = Configuration["authentication:facebook:appid"]; options.AppSecret = Configuration["authentication:facebook:appsecret"]; options.BackchannelHttpHandler = new FacebookBackChannelHandler(); @@ -92,7 +92,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services) if (Configuration["Authentication:MicrosoftAccount:ClientId"] != null) { services.AddAuthentication() - .AddMicrosoftAccount(options => { + .AddMicrosoftAccount(options => + { options.ClientId = Configuration["Authentication:MicrosoftAccount:ClientId"]; options.ClientSecret = Configuration["Authentication:MicrosoftAccount:ClientSecret"]; options.Events = new OAuthEvents() @@ -105,7 +106,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services) if (Configuration["Authentication:Twitter:ConsumerKey"] != null) { services.AddAuthentication() - .AddTwitter(options => { + .AddTwitter(options => + { options.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"]; options.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"]; options.RetrieveUserDetails = true; @@ -119,7 +121,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services) if (Configuration["Authentication:Google:ClientId"] != null) { services.AddAuthentication() - .AddGoogle(options => { + .AddGoogle(options => + { options.ClientId = Configuration["Authentication:Google:ClientId"]; options.ClientSecret = Configuration["Authentication:Google:ClientSecret"]; options.Events = new OAuthEvents() @@ -167,8 +170,7 @@ public IServiceProvider ConfigureServices(IServiceCollection services) options.IdleTimeout = TimeSpan.FromMinutes(20); }); - //Hangfire - services.AddHangfire(configuration => configuration.UseSqlServerStorage(Configuration["Data:HangfireConnection:ConnectionString"])); + AddHangFire(services); services.AddScoped(); services.AddScoped(); @@ -179,6 +181,26 @@ public IServiceProvider ConfigureServices(IServiceCollection services) return container.Resolve(); } + + protected virtual void GetDbContext(IServiceCollection services) + { + // Add Entity Framework services to the services container. + services.AddDbContext(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + } + + + protected virtual void AddDatabaseServices(IServiceCollection services) + { + // Add Entity Framework services to the services container. + services.AddDbContext(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + } + + protected virtual void AddHangFire(IServiceCollection services) + { + //Hangfire + services.AddHangfire(configuration => configuration.UseSqlServerStorage(Configuration["Data:HangfireConnection:ConnectionString"])); + } + // Configure is called after ConfigureServices is called. public void Configure(IApplicationBuilder app, IHostingEnvironment env, AllReadyContext context, SampleDataGenerator sampleData) { @@ -224,15 +246,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, AllReady context.Database.EnsureDeleted(); } - //call Migrate here to force the creation of the AllReady database so Hangfire can create its schema under it - if (purgeRefreshSampleData || !env.IsProduction()) - { - context.Database.Migrate(); - } + MigrateDatabase(purgeRefreshSampleData, env, context); - ////Hangfire - app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new HangfireDashboardAuthorizationFilter() } }); - app.UseHangfireServer(); + RegisterHangFire(app); // Add MVC to the request pipeline. app.UseMvc(routes => @@ -241,16 +257,36 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, AllReady routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); + LoadSeedData(purgeRefreshSampleData, sampleData); + } + + protected virtual void RegisterHangFire(IApplicationBuilder app) + { + app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new HangfireDashboardAuthorizationFilter() } }); + app.UseHangfireServer(); + } + + protected virtual void LoadSeedData(bool purgeRefreshSampleData, SampleDataGenerator sampleDataGenerator) + { // Add sample data and test admin accounts if specified in Config.Json. // for production applications, this should either be set to false or deleted. if (purgeRefreshSampleData || Configuration["SampleData:InsertSampleData"] == "true") { - sampleData.InsertTestData(); + sampleDataGenerator.InsertTestData(); } if (Configuration["SampleData:InsertTestUsers"] == "true") { - sampleData.CreateAdminUser().GetAwaiter().GetResult(); + sampleDataGenerator.CreateAdminUser().GetAwaiter().GetResult(); + } + } + + protected virtual void MigrateDatabase(bool purgeRefreshSampleData, IHostingEnvironment hostingEnvironment, AllReadyContext context) + { + //call Migrate here to force the creation of the AllReady database so Hangfire can create its schema under it + if (purgeRefreshSampleData || !hostingEnvironment.IsProduction()) + { + context.Database.Migrate(); } } }