using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Timeline.Models.Http; using Timeline.Services; using Timeline.Tests.Helpers; using Xunit; namespace Timeline.Tests.IntegratedTests { public class TokenTest : IntegratedTestBase { private const string CreateTokenUrl = "token/create"; private const string VerifyTokenUrl = "token/verify"; public TokenTest(WebApplicationFactory factory) : base(factory) { } private static async Task CreateUserTokenAsync(HttpClient client, string username, string password, int? expireOffset = null) { var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, Expire = expireOffset }); return response.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; } public static IEnumerable CreateToken_InvalidModel_Data() { yield return new[] { null, "p", null }; yield return new[] { "u", null, null }; yield return new object[] { "u", "p", 2000 }; yield return new object[] { "u", "p", -1 }; } [Theory] [MemberData(nameof(CreateToken_InvalidModel_Data))] public async Task CreateToken_InvalidModel(string username, string password, int expire) { using var client = await CreateClientWithNoAuth(); (await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password, Expire = expire })).Should().BeInvalidModel(); } public static IEnumerable CreateToken_UserCredential_Data() { yield return new[] { "usernotexist", "p" }; yield return new[] { MockUser.User.Username, "???" }; } [Theory] [MemberData(nameof(CreateToken_UserCredential_Data))] public async void CreateToken_UserCredential(string username, string password) { using var client = await CreateClientWithNoAuth(); var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password }); response.Should().HaveStatusCode(400) .And.HaveCommonBody() .Which.Code.Should().Be(ErrorCodes.TokenController.Create_BadCredential); } [Fact] public async Task CreateToken_Success() { using var client = await CreateClientWithNoAuth(); var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password }); var body = response.Should().HaveStatusCode(200) .And.HaveJsonBody().Which; body.Token.Should().NotBeNullOrWhiteSpace(); body.User.Should().BeEquivalentTo(MockUser.User.Info); } [Fact] public async Task VerifyToken_InvalidModel() { using var client = await CreateClientWithNoAuth(); (await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = null })).Should().BeInvalidModel(); } [Fact] public async Task VerifyToken_BadFormat() { using var client = await CreateClientWithNoAuth(); var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = "bad token hahaha" }); response.Should().HaveStatusCode(400) .And.HaveCommonBody() .Which.Code.Should().Be(ErrorCodes.TokenController.Verify_BadFormat); } [Fact] public async Task VerifyToken_OldVersion() { using var client = await CreateClientWithNoAuth(); var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token; using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped. { // create a user for test var userService = scope.ServiceProvider.GetRequiredService(); await userService.PatchUser(MockUser.User.Username, null, null); } (await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token })) .Should().HaveStatusCode(400) .And.HaveCommonBody() .Which.Code.Should().Be(ErrorCodes.TokenController.Verify_OldVersion); } [Fact] public async Task VerifyToken_UserNotExist() { using var client = await CreateClientWithNoAuth(); var token = (await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password)).Token; using (var scope = Factory.Server.Host.Services.CreateScope()) // UserService is scoped. { var userService = scope.ServiceProvider.GetRequiredService(); await userService.DeleteUser(MockUser.User.Username); } (await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = token })) .Should().HaveStatusCode(400) .And.HaveCommonBody() .Which.Code.Should().Be(ErrorCodes.TokenController.Verify_UserNotExist); } //[Fact] //public async Task VerifyToken_Expired() //{ // using (var client = await CreateClientWithNoAuth()) // { // // I can only control the token expired time but not current time // // because verify logic is encapsuled in other library. // var mockClock = _factory.GetTestClock(); // mockClock.MockCurrentTime = DateTime.Now - TimeSpan.FromDays(2); // var token = (await client.CreateUserTokenAsync(MockUsers.UserUsername, MockUsers.UserPassword, 1)).Token; // var response = await client.PostAsJsonAsync(VerifyTokenUrl, // new VerifyTokenRequest { Token = token }); // response.Should().HaveStatusCodeBadRequest() // .And.Should().HaveBodyAsCommonResponseWithCode(TokenController.ErrorCodes.Verify_Expired); // mockClock.MockCurrentTime = null; // } //} [Fact] public async Task VerifyToken_Success() { using var client = await CreateClientWithNoAuth(); var createTokenResult = await CreateUserTokenAsync(client, MockUser.User.Username, MockUser.User.Password); var response = await client.PostAsJsonAsync(VerifyTokenUrl, new VerifyTokenRequest { Token = createTokenResult.Token }); response.Should().HaveStatusCode(200) .And.HaveJsonBody() .Which.User.Should().BeEquivalentTo(MockUser.User.Info); } } }