using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using MongoDB.Entities; using PrivaPub.ClientModels; using PrivaPub.Infrastructure; using PrivaPub.Infrastructure.Cli; using PrivaPub.Models; using PrivaPub.Tests.Support; using PrivaPub.Tests.Support.Host; using System.Net; using System.Net.Http.Headers; namespace PrivaPub.Tests.Http { // Setup is server-wide, so each test opens it again by removing the seal, and seals it once done. [Xunit.Collection(nameof(Exclusive))] [Trait("Category", "Integration")] public sealed class ServerSetupTests : IAsyncLifetime { PrivaPubHost _host; WebApplicationFactory _closed; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _host = await PrivaPubHost.Shared(); // production's mode: sign-up closed, invitations only _closed = _host.WithWebHostBuilder(builder => builder.UseSetting("Registrations:Mode", nameof(RegistrationMode.Invitations))); await DB.Default.DeleteAsync(ServerSetup.Name); } public async ValueTask DisposeAsync() { if (_closed == default) return; await ServerSetup.Seal("tests", default); await _closed.DisposeAsync(); } HttpClient Client(string jwt = default) { var client = _closed.CreateClient(new WebApplicationFactoryClientOptions { BaseAddress = new Uri(PrivaPubHost.Base + "/"), AllowAutoRedirect = false }); client.DefaultRequestHeaders.Remove(PrivaPubHost.ClientHeader); client.DefaultRequestHeaders.Add(PrivaPubHost.ClientHeader, $"10.{Random.Shared.Next(256)}.{Random.Shared.Next(256)}.{Random.Shared.Next(1, 255)}"); if (jwt != default) client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt); return client; } static string Name(string prefix) => $"{prefix}{Guid.NewGuid():N}"[..20]; static object Form(string userName, string avatarUserName, string avatarName = default) => new { userName, password = Accounts.Password, avatarUserName, avatarName }; async Task Pending() { using var client = Client(); return (await client.Get("/clientapi/user/setup")).Ok().Body.Flag("isPending"); } static async Task Message(HttpResponseMessage response) { var body = await response.JsonBody(); return body["errorMessage"]?.GetValue() ?? body["title"]?.GetValue(); } [Fact] public async Task Setup_makes_the_first_login_the_administrator_with_its_persona_and_closes() { var token = TestContext.Current.CancellationToken; var userName = Name("first"); var avatarUserName = Name("founder"); Assert.True(await Pending()); using var client = Client(); var response = await client.PostJson("/clientapi/user/setup", Form(userName, avatarUserName, "The Founder")); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var jwt = await response.JsonBody(); var policies = jwt["policies"]!.AsArray().Select(p => p!.GetValue()).Order(); Assert.Equal(new[] { Policies.IsAdmin, Policies.IsModerator, Policies.IsUser }.Order(), policies); using var admin = Client(jwt["token"]!.GetValue()); Assert.Equal(HttpStatusCode.OK, (await admin.GetAsync("/clientapi/admin/domainblocks/list", token)).StatusCode); var persona = Assert.Single(await (await admin.GetAsync("/clientapi/avatar/private/list", token)).JsonItems())!; Assert.Equal(avatarUserName, persona["userName"]!.GetValue()); Assert.Equal("The Founder", persona["name"]!.GetValue()); var seal = await DB.Default.Find().OneAsync(ServerSetup.Name, token); Assert.Equal(jwt["userId"]!.GetValue(), seal.AdminId); Assert.False(await Pending()); var again = await client.PostJson("/clientapi/user/setup", Form(Name("second"), Name("latecomer"))); Assert.Equal(HttpStatusCode.Forbidden, again.StatusCode); Assert.Contains("already set up", await Message(again)); } [Fact] public async Task Two_setups_at_once_make_one_administrator() { using var first = Client(); using var second = Client(); var answers = await Task.WhenAll( first.PostJson("/clientapi/user/setup", Form(Name("racea"), Name("racera"))), second.PostJson("/clientapi/user/setup", Form(Name("raceb"), Name("racerb")))); Assert.Equal(new[] { HttpStatusCode.OK, HttpStatusCode.Forbidden }, answers.Select(a => a.StatusCode).Order()); Assert.False(await Pending()); } [Fact] public async Task A_setup_that_fails_leaves_the_server_to_set_up() { var taken = await _host.SignUp("taken"); var userName = Name("same"); using var client = Client(); var samePersona = await client.PostJson("/clientapi/user/setup", Form(userName, userName)); var noPersona = await client.PostJson("/clientapi/user/setup", new { userName = Name("bare"), password = Accounts.Password }); var takenLogin = await client.PostJson("/clientapi/user/setup", Form(taken.UserName, Name("takenp"))); Assert.Equal(HttpStatusCode.BadRequest, samePersona.StatusCode); Assert.Contains("must differ", await Message(samePersona)); Assert.Equal(HttpStatusCode.BadRequest, noPersona.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, takenLogin.StatusCode); Assert.Contains("already taken", await Message(takenLogin)); Assert.True(await Pending()); } [Fact] public async Task While_setup_waits_sign_up_stays_closed() { using var client = Client(); var signUp = await client.PostJson("/clientapi/user/signup", new { userName = Name("outsider"), password = Accounts.Password }); Assert.Equal(HttpStatusCode.Forbidden, signUp.StatusCode); Assert.True(await Pending()); Assert.False((await client.Get("/nodeinfo/2.1")).Ok().Body.Flag("openRegistrations")); } [Fact] public async Task Promoting_an_administrator_with_the_cli_closes_setup() { var root = await _host.SignUp("promoted"); Assert.True(await Pending()); Assert.Equal(0, await AdminCommands.Run(new[] { "promote", root.UserName }, _host.Services)); Assert.False(await Pending()); Assert.Equal(root.Id, (await DB.Default.Find().OneAsync(ServerSetup.Name, TestContext.Current.CancellationToken)).AdminId); } } }