aboutsummaryrefslogtreecommitdiff
path: root/Timeline.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline.Tests')
-rw-r--r--Timeline.Tests/Controllers/UserControllerTest.cs1
-rw-r--r--Timeline.Tests/Controllers/UserDetailControllerTest.cs98
-rw-r--r--Timeline.Tests/DatabaseTest.cs17
-rw-r--r--Timeline.Tests/ErrorCodeTest.cs52
-rw-r--r--Timeline.Tests/Helpers/HttpClientExtensions.cs12
-rw-r--r--Timeline.Tests/Helpers/ParameterInfoAssertions.cs63
-rw-r--r--Timeline.Tests/Helpers/ReflectionHelper.cs13
-rw-r--r--Timeline.Tests/Helpers/ResponseAssertions.cs (renamed from Timeline.Tests/Helpers/AssertionResponseExtensions.cs)22
-rw-r--r--Timeline.Tests/IntegratedTests/TokenTest.cs12
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTest.cs35
-rw-r--r--Timeline.Tests/IntegratedTests/UserDetailTest.cs164
-rw-r--r--Timeline.Tests/IntegratedTests/UserTest.cs16
-rw-r--r--Timeline.Tests/PasswordGenerator.cs24
-rw-r--r--Timeline.Tests/Properties/launchSettings.json52
-rw-r--r--Timeline.Tests/Services/UserAvatarServiceTest.cs (renamed from Timeline.Tests/UserAvatarServiceTest.cs)5
-rw-r--r--Timeline.Tests/Services/UserDetailServiceTest.cs108
-rw-r--r--Timeline.Tests/Timeline.Tests.csproj4
17 files changed, 624 insertions, 74 deletions
diff --git a/Timeline.Tests/Controllers/UserControllerTest.cs b/Timeline.Tests/Controllers/UserControllerTest.cs
index a9cce970..83b8cdcf 100644
--- a/Timeline.Tests/Controllers/UserControllerTest.cs
+++ b/Timeline.Tests/Controllers/UserControllerTest.cs
@@ -13,7 +13,6 @@ using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Mock.Data;
-using Timeline.Tests.Mock.Services;
using Xunit;
using static Timeline.ErrorCodes.Http.User;
diff --git a/Timeline.Tests/Controllers/UserDetailControllerTest.cs b/Timeline.Tests/Controllers/UserDetailControllerTest.cs
new file mode 100644
index 00000000..ffd88790
--- /dev/null
+++ b/Timeline.Tests/Controllers/UserDetailControllerTest.cs
@@ -0,0 +1,98 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Timeline.Controllers;
+using Timeline.Filters;
+using Timeline.Models.Validation;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+using Xunit;
+
+namespace Timeline.Tests.Controllers
+{
+ public class UserDetailControllerTest : IDisposable
+ {
+ private readonly Mock<IUserDetailService> _mockUserDetailService;
+ private readonly UserDetailController _controller;
+
+ public UserDetailControllerTest()
+ {
+ _mockUserDetailService = new Mock<IUserDetailService>();
+ _controller = new UserDetailController(_mockUserDetailService.Object);
+ }
+
+ public void Dispose()
+ {
+ _controller.Dispose();
+ }
+
+ [Fact]
+ public void AttributeTest()
+ {
+ typeof(UserDetailController).Should().BeDecoratedWith<ApiControllerAttribute>();
+
+ var getNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.GetNickname));
+ getNickname.Should().BeDecoratedWith<HttpGetAttribute>()
+ .And.BeDecoratedWith<CatchUserNotExistExceptionAttribute>();
+ getNickname.GetParameter("username").Should().BeDecoratedWith<UsernameAttribute>()
+ .And.BeDecoratedWith<FromRouteAttribute>();
+
+ var putNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.PutNickname));
+ putNickname.Should().BeDecoratedWith<HttpPutAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<CatchUserNotExistExceptionAttribute>();
+ putNickname.GetParameter("username").Should().BeDecoratedWith<UsernameAttribute>()
+ .And.BeDecoratedWith<FromRouteAttribute>();
+ var stringLengthAttributeOnPutBody = putNickname.GetParameter("body").Should().BeDecoratedWith<FromBodyAttribute>()
+ .And.BeDecoratedWith<StringLengthAttribute>()
+ .Which;
+ stringLengthAttributeOnPutBody.MinimumLength.Should().Be(1);
+ stringLengthAttributeOnPutBody.MaximumLength.Should().Be(10);
+
+ var deleteNickname = typeof(UserDetailController).GetMethod(nameof(UserDetailController.DeleteNickname));
+ deleteNickname.Should().BeDecoratedWith<HttpDeleteAttribute>()
+ .And.BeDecoratedWith<AuthorizeAttribute>()
+ .And.BeDecoratedWith<SelfOrAdminAttribute>()
+ .And.BeDecoratedWith<CatchUserNotExistExceptionAttribute>();
+ deleteNickname.GetParameter("username").Should().BeDecoratedWith<UsernameAttribute>()
+ .And.BeDecoratedWith<FromRouteAttribute>();
+ }
+
+ [Fact]
+ public async Task GetNickname_ShouldWork()
+ {
+ const string username = "uuu";
+ const string nickname = "nnn";
+ _mockUserDetailService.Setup(s => s.GetNickname(username)).ReturnsAsync(nickname);
+ var actionResult = await _controller.GetNickname(username);
+ actionResult.Result.Should().BeAssignableTo<OkObjectResult>(nickname);
+ _mockUserDetailService.VerifyAll();
+ }
+
+ [Fact]
+ public async Task PutNickname_ShouldWork()
+ {
+ const string username = "uuu";
+ const string nickname = "nnn";
+ _mockUserDetailService.Setup(s => s.SetNickname(username, nickname)).Returns(Task.CompletedTask);
+ var actionResult = await _controller.PutNickname(username, nickname);
+ actionResult.Should().BeAssignableTo<OkResult>();
+ _mockUserDetailService.VerifyAll();
+ }
+
+ [Fact]
+ public async Task DeleteNickname_ShouldWork()
+ {
+ const string username = "uuu";
+ _mockUserDetailService.Setup(s => s.SetNickname(username, null)).Returns(Task.CompletedTask);
+ var actionResult = await _controller.DeleteNickname(username);
+ actionResult.Should().BeAssignableTo<OkResult>();
+ _mockUserDetailService.VerifyAll();
+ }
+ }
+}
diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs
index b5681491..fc153c24 100644
--- a/Timeline.Tests/DatabaseTest.cs
+++ b/Timeline.Tests/DatabaseTest.cs
@@ -42,5 +42,22 @@ namespace Timeline.Tests
_context.SaveChanges();
_context.UserAvatars.Count().Should().Be(0);
}
+
+ [Fact]
+ public void DeleteUserShouldAlsoDeleteDetail()
+ {
+ var user = _context.Users.First();
+ _context.UserDetails.Count().Should().Be(0);
+ _context.UserDetails.Add(new UserDetail
+ {
+ Nickname = null,
+ UserId = user.Id
+ });
+ _context.SaveChanges();
+ _context.UserDetails.Count().Should().Be(1);
+ _context.Users.Remove(user);
+ _context.SaveChanges();
+ _context.UserDetails.Count().Should().Be(0);
+ }
}
}
diff --git a/Timeline.Tests/ErrorCodeTest.cs b/Timeline.Tests/ErrorCodeTest.cs
new file mode 100644
index 00000000..78a58131
--- /dev/null
+++ b/Timeline.Tests/ErrorCodeTest.cs
@@ -0,0 +1,52 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Timeline.Tests
+{
+ public class ErrorCodeTest
+ {
+ private readonly ITestOutputHelper _output;
+
+ public ErrorCodeTest(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public void ShouldWork()
+ {
+ var errorCodes = new Dictionary<int, string>();
+
+ void RecursiveCheckErrorCode(Type type)
+ {
+ foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
+ .Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(int)))
+ {
+ var name = type.FullName + "." + field.Name;
+ var value = (int)field.GetRawConstantValue();
+ _output.WriteLine($"Find error code {name} , value is {value}.");
+
+ value.Should().BeInRange(1000_0000, 9999_9999, "Error code should have exactly 8 digits.");
+
+ errorCodes.Should().NotContainKey(value,
+ "identical error codes are found and conflict paths are {0} and {1}",
+ name, errorCodes.GetValueOrDefault(value));
+
+ errorCodes.Add(value, name);
+ }
+
+ foreach (var nestedType in type.GetNestedTypes())
+ {
+ RecursiveCheckErrorCode(nestedType);
+ }
+ }
+
+ RecursiveCheckErrorCode(typeof(ErrorCodes));
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/HttpClientExtensions.cs b/Timeline.Tests/Helpers/HttpClientExtensions.cs
index 38641f90..6513bbe7 100644
--- a/Timeline.Tests/Helpers/HttpClientExtensions.cs
+++ b/Timeline.Tests/Helpers/HttpClientExtensions.cs
@@ -35,5 +35,17 @@ namespace Timeline.Tests.Helpers
content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
return client.PutAsync(url, content);
}
+
+ public static Task<HttpResponseMessage> PutStringAsync(this HttpClient client, string url, string body, string mimeType = null)
+ {
+ return client.PutStringAsync(new Uri(url, UriKind.RelativeOrAbsolute), body, mimeType);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
+ public static Task<HttpResponseMessage> PutStringAsync(this HttpClient client, Uri url, string body, string mimeType = null)
+ {
+ var content = new StringContent(body, Encoding.UTF8, mimeType ?? MediaTypeNames.Text.Plain);
+ return client.PutAsync(url, content);
+ }
}
}
diff --git a/Timeline.Tests/Helpers/ParameterInfoAssertions.cs b/Timeline.Tests/Helpers/ParameterInfoAssertions.cs
new file mode 100644
index 00000000..e3becee1
--- /dev/null
+++ b/Timeline.Tests/Helpers/ParameterInfoAssertions.cs
@@ -0,0 +1,63 @@
+using FluentAssertions;
+using FluentAssertions.Execution;
+using FluentAssertions.Formatting;
+using FluentAssertions.Primitives;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Timeline.Tests.Helpers
+{
+ public class ParameterInfoValueFormatter : IValueFormatter
+ {
+ public bool CanHandle(object value)
+ {
+ return value is ParameterInfo;
+ }
+
+ public string Format(object value, FormattingContext context, FormatChild formatChild)
+ {
+ var param = (ParameterInfo)value;
+ return $"{param.Member.DeclaringType.FullName}.{param.Member.Name}#{param.Name}";
+ }
+ }
+
+ public class ParameterInfoAssertions : ReferenceTypeAssertions<ParameterInfo, ParameterInfoAssertions>
+ {
+ static ParameterInfoAssertions()
+ {
+ Formatter.AddFormatter(new ParameterInfoValueFormatter());
+ }
+
+ public ParameterInfoAssertions(ParameterInfo parameterInfo)
+ {
+ Subject = parameterInfo;
+ }
+
+ protected override string Identifier => "parameter";
+
+ public AndWhichConstraint<ParameterInfoAssertions, TAttribute> BeDecoratedWith<TAttribute>(string because = "", params object[] becauseArgs)
+ where TAttribute : Attribute
+ {
+ var attribute = Subject.GetCustomAttribute<TAttribute>(false);
+
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .ForCondition(attribute != null)
+ .FailWith("Expected {0} {1} to be decorated with {2}{reason}, but that attribute was not found.",
+ Identifier, Subject, typeof(TAttribute).FullName);
+
+ return new AndWhichConstraint<ParameterInfoAssertions, TAttribute>(this, attribute);
+ }
+ }
+
+ public static class ParameterInfoAssertionExtensions
+ {
+ public static ParameterInfoAssertions Should(this ParameterInfo parameterInfo)
+ {
+ return new ParameterInfoAssertions(parameterInfo);
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/ReflectionHelper.cs b/Timeline.Tests/Helpers/ReflectionHelper.cs
new file mode 100644
index 00000000..3f6036e3
--- /dev/null
+++ b/Timeline.Tests/Helpers/ReflectionHelper.cs
@@ -0,0 +1,13 @@
+using System.Linq;
+using System.Reflection;
+
+namespace Timeline.Tests.Helpers
+{
+ public static class ReflectionHelper
+ {
+ public static ParameterInfo GetParameter(this MethodInfo methodInfo, string name)
+ {
+ return methodInfo.GetParameters().Where(p => p.Name == name).Single();
+ }
+ }
+}
diff --git a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs b/Timeline.Tests/Helpers/ResponseAssertions.cs
index 08f10b2b..0e6f215b 100644
--- a/Timeline.Tests/Helpers/AssertionResponseExtensions.cs
+++ b/Timeline.Tests/Helpers/ResponseAssertions.cs
@@ -61,20 +61,20 @@ namespace Timeline.Tests.Helpers
protected override string Identifier => "HttpResponseMessage";
- public AndConstraint<HttpResponseMessage> HaveStatusCode(int expected, string because = "", params object[] becauseArgs)
+ public AndConstraint<HttpResponseMessageAssertions> HaveStatusCode(int expected, string because = "", params object[] becauseArgs)
{
return HaveStatusCode((HttpStatusCode)expected, because, becauseArgs);
}
- public AndConstraint<HttpResponseMessage> HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs)
+ public AndConstraint<HttpResponseMessageAssertions> HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs)
{
Execute.Assertion.BecauseOf(because, becauseArgs)
.ForCondition(Subject.StatusCode == expected)
.FailWith("Expected status code of {context:HttpResponseMessage} to be {0}{reason}, but found {1}.", expected, Subject.StatusCode);
- return new AndConstraint<HttpResponseMessage>(Subject);
+ return new AndConstraint<HttpResponseMessageAssertions>(this);
}
- public AndWhichConstraint<HttpResponseMessage, T> HaveJsonBody<T>(string because = "", params object[] becauseArgs)
+ public AndWhichConstraint<HttpResponseMessageAssertions, T> HaveJsonBody<T>(string because = "", params object[] becauseArgs)
{
var a = Execute.Assertion.BecauseOf(because, becauseArgs);
string body;
@@ -85,11 +85,11 @@ namespace Timeline.Tests.Helpers
catch (AggregateException e)
{
a.FailWith("Expected response body of {context:HttpResponseMessage} to be json string{reason}, but failed to read it or it was not a string. Exception is {0}.", e.InnerExceptions);
- return new AndWhichConstraint<HttpResponseMessage, T>(Subject, null);
+ return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, null);
}
var result = JsonConvert.DeserializeObject<T>(body);
- return new AndWhichConstraint<HttpResponseMessage, T>(Subject, result);
+ return new AndWhichConstraint<HttpResponseMessageAssertions, T>(this, result);
}
}
@@ -100,12 +100,12 @@ namespace Timeline.Tests.Helpers
return new HttpResponseMessageAssertions(instance);
}
- public static AndWhichConstraint<HttpResponseMessage, CommonResponse> HaveCommonBody(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static AndWhichConstraint<HttpResponseMessageAssertions, CommonResponse> HaveCommonBody(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
return assertions.HaveJsonBody<CommonResponse>(because, becauseArgs);
}
- public static AndWhichConstraint<HttpResponseMessage, CommonDataResponse<TData>> HaveCommonDataBody<TData>(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
+ public static AndWhichConstraint<HttpResponseMessageAssertions, CommonDataResponse<TData>> HaveCommonDataBody<TData>(this HttpResponseMessageAssertions assertions, string because = "", params object[] becauseArgs)
{
return assertions.HaveJsonBody<CommonDataResponse<TData>>(because, becauseArgs);
}
@@ -113,7 +113,7 @@ namespace Timeline.Tests.Helpers
public static void BePut(this HttpResponseMessageAssertions assertions, bool create, string because = "", params object[] becauseArgs)
{
var body = assertions.HaveStatusCode(create ? 201 : 200, because, becauseArgs)
- .And.Should().HaveJsonBody<CommonPutResponse>(because, becauseArgs)
+ .And.HaveJsonBody<CommonPutResponse>(because, becauseArgs)
.Which;
body.Code.Should().Be(0);
body.Data.Create.Should().Be(create);
@@ -122,7 +122,7 @@ namespace Timeline.Tests.Helpers
public static void BeDelete(this HttpResponseMessageAssertions assertions, bool delete, string because = "", params object[] becauseArgs)
{
var body = assertions.HaveStatusCode(200, because, becauseArgs)
- .And.Should().HaveJsonBody<CommonDeleteResponse>(because, becauseArgs)
+ .And.HaveJsonBody<CommonDeleteResponse>(because, becauseArgs)
.Which;
body.Code.Should().Be(0);
body.Data.Delete.Should().Be(delete);
@@ -132,7 +132,7 @@ namespace Timeline.Tests.Helpers
{
message = string.IsNullOrEmpty(message) ? "" : ", " + message;
assertions.HaveStatusCode(400, "Invalid Model Error must have 400 status code{0}", message)
- .And.Should().HaveCommonBody("Invalid Model Error must have CommonResponse body{0}", message)
+ .And.HaveCommonBody("Invalid Model Error must have CommonResponse body{0}", message)
.Which.Code.Should().Be(ErrorCodes.Http.Common.InvalidModel,
"Invalid Model Error must have code {0} in body{1}",
ErrorCodes.Http.Common.InvalidModel, message);
diff --git a/Timeline.Tests/IntegratedTests/TokenTest.cs b/Timeline.Tests/IntegratedTests/TokenTest.cs
index e9b6e1e9..111e8d8e 100644
--- a/Timeline.Tests/IntegratedTests/TokenTest.cs
+++ b/Timeline.Tests/IntegratedTests/TokenTest.cs
@@ -69,7 +69,7 @@ namespace Timeline.Tests.IntegratedTests
var response = await client.PostAsJsonAsync(CreateTokenUrl,
new CreateTokenRequest { Username = username, Password = password });
response.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Create.BadCredential);
}
@@ -80,7 +80,7 @@ namespace Timeline.Tests.IntegratedTests
var response = await client.PostAsJsonAsync(CreateTokenUrl,
new CreateTokenRequest { Username = MockUser.User.Username, Password = MockUser.User.Password });
var body = response.Should().HaveStatusCode(200)
- .And.Should().HaveJsonBody<CreateTokenResponse>().Which;
+ .And.HaveJsonBody<CreateTokenResponse>().Which;
body.Token.Should().NotBeNullOrWhiteSpace();
body.User.Should().BeEquivalentTo(MockUser.User.Info);
}
@@ -100,7 +100,7 @@ namespace Timeline.Tests.IntegratedTests
var response = await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = "bad token hahaha" });
response.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Verify.BadFormat);
}
@@ -120,7 +120,7 @@ namespace Timeline.Tests.IntegratedTests
(await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = token }))
.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Verify.OldVersion);
}
@@ -139,7 +139,7 @@ namespace Timeline.Tests.IntegratedTests
(await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = token }))
.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Verify.UserNotExist);
}
@@ -169,7 +169,7 @@ namespace Timeline.Tests.IntegratedTests
var response = await client.PostAsJsonAsync(VerifyTokenUrl,
new VerifyTokenRequest { Token = createTokenResult.Token });
response.Should().HaveStatusCode(200)
- .And.Should().HaveJsonBody<VerifyTokenResponse>()
+ .And.HaveJsonBody<VerifyTokenResponse>()
.Which.User.Should().BeEquivalentTo(MockUser.User.Info);
}
}
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
index ce389046..2310fc66 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTest.cs
@@ -13,7 +13,6 @@ using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
-using Timeline.Controllers;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Helpers.Authentication;
@@ -23,12 +22,12 @@ using static Timeline.ErrorCodes.Http.UserAvatar;
namespace Timeline.Tests.IntegratedTests
{
- public class UserAvatarUnitTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ public class UserAvatarTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
{
private readonly TestApplication _testApp;
private readonly WebApplicationFactory<Startup> _factory;
- public UserAvatarUnitTest(WebApplicationFactory<Startup> factory)
+ public UserAvatarTest(WebApplicationFactory<Startup> factory)
{
_testApp = new TestApplication(factory);
_factory = _testApp.Factory;
@@ -54,7 +53,7 @@ namespace Timeline.Tests.IntegratedTests
{
var res = await client.GetAsync("users/usernotexist/avatar");
res.Should().HaveStatusCode(404)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Get.UserNotExist);
}
@@ -95,7 +94,7 @@ namespace Timeline.Tests.IntegratedTests
request.Headers.TryAddWithoutValidation("If-None-Match", "\"dsdfd");
var res = await client.SendAsync(request);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.BadFormat_IfNonMatch);
+ .And.HaveCommonBody().Which.Code.Should().Be(Header.IfNonMatch.BadFormat);
}
{
@@ -125,7 +124,7 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Missing_ContentLength);
+ .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Filter.Header.ContentLength.Missing); ;
}
{
@@ -133,7 +132,7 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentLength = 1;
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Missing_ContentType);
+ .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Filter.Header.ContentType.Missing);
}
{
@@ -142,7 +141,7 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Common.Header.Zero_ContentLength);
+ .And.HaveCommonBody().Which.Code.Should().Be(ErrorCodes.Http.Filter.Header.ContentLength.Zero);
}
{
@@ -156,7 +155,7 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.TooBig);
+ .And.HaveCommonBody().Which.Code.Should().Be(Content.TooBig);
}
{
@@ -165,7 +164,7 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Smaller);
+ .And.HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Smaller);
}
{
@@ -174,25 +173,25 @@ namespace Timeline.Tests.IntegratedTests
content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
var res = await client.PutAsync("users/user/avatar", content);
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Bigger);
+ .And.HaveCommonBody().Which.Code.Should().Be(Content.UnmatchedLength_Bigger);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_CantDecode);
+ .And.HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_CantDecode);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", mockAvatar.Data, "image/jpeg");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_UnmatchedFormat);
+ .And.HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_UnmatchedFormat);
}
{
var res = await client.PutByteArrayAsync("users/user/avatar", ImageHelper.CreatePngWithSize(100, 200), "image/png");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_BadSize);
+ .And.HaveCommonBody().Which.Code.Should().Be(Put.BadFormat_BadSize);
}
{
@@ -222,13 +221,13 @@ namespace Timeline.Tests.IntegratedTests
{
var res = await client.PutByteArrayAsync("users/admin/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(HttpStatusCode.Forbidden)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Put.Forbid);
+ .And.HaveCommonBody().Which.Code.Should().Be(Put.Forbid);
}
{
var res = await client.DeleteAsync("users/admin/avatar");
res.Should().HaveStatusCode(HttpStatusCode.Forbidden)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Delete.Forbid);
+ .And.HaveCommonBody().Which.Code.Should().Be(Delete.Forbid);
}
for (int i = 0; i < 2; i++) // double delete should work.
@@ -255,14 +254,14 @@ namespace Timeline.Tests.IntegratedTests
{
var res = await client.PutByteArrayAsync("users/usernotexist/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Put.UserNotExist);
}
{
var res = await client.DeleteAsync("users/usernotexist/avatar");
res.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody().Which.Code.Should().Be(Delete.UserNotExist);
+ .And.HaveCommonBody().Which.Code.Should().Be(Delete.UserNotExist);
}
}
diff --git a/Timeline.Tests/IntegratedTests/UserDetailTest.cs b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
new file mode 100644
index 00000000..8f2b6925
--- /dev/null
+++ b/Timeline.Tests/IntegratedTests/UserDetailTest.cs
@@ -0,0 +1,164 @@
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using System;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Threading.Tasks;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Helpers.Authentication;
+using Timeline.Tests.Mock.Data;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class UserDetailTest : IClassFixture<WebApplicationFactory<Startup>>, IDisposable
+ {
+ private readonly TestApplication _testApp;
+ private readonly WebApplicationFactory<Startup> _factory;
+
+ public UserDetailTest(WebApplicationFactory<Startup> factory)
+ {
+ _testApp = new TestApplication(factory);
+ _factory = _testApp.Factory;
+ }
+
+ public void Dispose()
+ {
+ _testApp.Dispose();
+ }
+
+ [Fact]
+ public async Task PermissionTest()
+ {
+ { // unauthorize
+ using var client = _factory.CreateDefaultClient();
+ { // GET
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ }
+ { // DELETE
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.Unauthorized);
+ }
+ }
+ { // user
+ using var client = await _factory.CreateClientAsUser();
+ { // GET
+ var res = await client.GetAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT self
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // PUT other
+ var res = await client.PutStringAsync($"users/{MockUser.Admin.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
+ }
+ { // DELETE self
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // DELETE other
+ var res = await client.DeleteAsync($"users/{MockUser.Admin.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.Forbidden);
+ }
+ }
+ { // user
+ using var client = await _factory.CreateClientAsAdmin();
+ { // PUT other
+ var res = await client.PutStringAsync($"users/{MockUser.User.Username}/nickname", "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ { // DELETE other
+ var res = await client.DeleteAsync($"users/{MockUser.User.Username}/nickname");
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task FunctionTest()
+ {
+ var url = $"users/{MockUser.User.Username}/nickname";
+ var userNotExistUrl = "users/usernotexist/nickname";
+ {
+ using var client = await _factory.CreateClientAsUser();
+ {
+ var res = await client.GetAsync(userNotExistUrl);
+ res.Should().HaveStatusCode(HttpStatusCode.NotFound)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Filter.User.NotExist);
+
+ }
+ {
+ var res = await client.GetAsync(url);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ res.Content.Headers.ContentType.Should().Be(new MediaTypeHeaderValue(MediaTypeNames.Text.Plain) { CharSet = "utf-8" });
+ var body = await res.Content.ReadAsStringAsync();
+ body.Should().Be(MockUser.User.Username);
+ }
+ {
+ var res = await client.PutStringAsync(url, "");
+ res.Should().BeInvalidModel();
+ }
+ {
+ var res = await client.PutStringAsync(url, new string('a', 11));
+ res.Should().BeInvalidModel();
+ }
+ var nickname1 = "nnn";
+ var nickname2 = "nn2";
+ {
+ var res = await client.PutStringAsync(url, nickname1);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ (await client.GetStringAsync(url)).Should().Be(nickname1);
+ }
+ {
+ var res = await client.PutStringAsync(url, nickname2);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ (await client.GetStringAsync(url)).Should().Be(nickname2);
+ }
+ {
+ var res = await client.DeleteAsync(url);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ (await client.GetStringAsync(url)).Should().Be(MockUser.User.Username);
+ }
+ {
+ var res = await client.DeleteAsync(url);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ }
+ }
+ {
+ using var client = await _factory.CreateClientAsAdmin();
+ {
+ var res = await client.PutStringAsync(userNotExistUrl, "aaa");
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Filter.User.NotExist);
+ }
+ {
+ var res = await client.DeleteAsync(userNotExistUrl);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.HaveCommonBody()
+ .Which.Code.Should().Be(ErrorCodes.Http.Filter.User.NotExist);
+ }
+ var nickname = "nnn";
+ {
+ var res = await client.PutStringAsync(url, nickname);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ (await client.GetStringAsync(url)).Should().Be(nickname);
+ }
+ {
+ var res = await client.DeleteAsync(url);
+ res.Should().HaveStatusCode(HttpStatusCode.OK);
+ (await client.GetStringAsync(url)).Should().Be(MockUser.User.Username);
+ }
+ }
+ }
+ }
+}
diff --git a/Timeline.Tests/IntegratedTests/UserTest.cs b/Timeline.Tests/IntegratedTests/UserTest.cs
index ec70b7e8..7e99ddba 100644
--- a/Timeline.Tests/IntegratedTests/UserTest.cs
+++ b/Timeline.Tests/IntegratedTests/UserTest.cs
@@ -36,7 +36,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await _factory.CreateClientAsAdmin();
var res = await client.GetAsync("users");
res.Should().HaveStatusCode(200)
- .And.Should().HaveJsonBody<UserInfo[]>()
+ .And.HaveJsonBody<UserInfo[]>()
.Which.Should().BeEquivalentTo(MockUser.UserInfoList);
}
@@ -46,7 +46,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await _factory.CreateClientAsAdmin();
var res = await client.GetAsync("users/" + MockUser.User.Username);
res.Should().HaveStatusCode(200)
- .And.Should().HaveJsonBody<UserInfo>()
+ .And.HaveJsonBody<UserInfo>()
.Which.Should().BeEquivalentTo(MockUser.User.Info);
}
@@ -64,7 +64,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await _factory.CreateClientAsAdmin();
var res = await client.GetAsync("users/usernotexist");
res.Should().HaveStatusCode(404)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Get.NotExist);
}
@@ -89,7 +89,7 @@ namespace Timeline.Tests.IntegratedTests
{
var res = await client.GetAsync("users/" + username);
res.Should().HaveStatusCode(200)
- .And.Should().HaveJsonBody<UserInfo>()
+ .And.HaveJsonBody<UserInfo>()
.Which.Administrator.Should().Be(administrator);
}
@@ -128,7 +128,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await _factory.CreateClientAsAdmin();
var res = await client.PatchAsJsonAsync("users/usernotexist", new UserPatchRequest { });
res.Should().HaveStatusCode(404)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Patch.NotExist);
}
@@ -208,7 +208,7 @@ namespace Timeline.Tests.IntegratedTests
var res = await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = "usernotexist", NewUsername = "newUsername" });
res.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Op.ChangeUsername.NotExist);
}
@@ -219,7 +219,7 @@ namespace Timeline.Tests.IntegratedTests
var res = await client.PostAsJsonAsync(changeUsernameUrl,
new ChangeUsernameRequest { OldUsername = MockUser.User.Username, NewUsername = MockUser.Admin.Username });
res.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Op.ChangeUsername.AlreadyExist);
}
@@ -258,7 +258,7 @@ namespace Timeline.Tests.IntegratedTests
using var client = await _factory.CreateClientAsUser();
var res = await client.PostAsJsonAsync(changePasswordUrl, new ChangePasswordRequest { OldPassword = "???", NewPassword = "???" });
res.Should().HaveStatusCode(400)
- .And.Should().HaveCommonBody()
+ .And.HaveCommonBody()
.Which.Code.Should().Be(Op.ChangePassword.BadOldPassword);
}
diff --git a/Timeline.Tests/PasswordGenerator.cs b/Timeline.Tests/PasswordGenerator.cs
new file mode 100644
index 00000000..6c07836b
--- /dev/null
+++ b/Timeline.Tests/PasswordGenerator.cs
@@ -0,0 +1,24 @@
+using System;
+using Timeline.Services;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Timeline.Tests
+{
+ public class PasswordGenerator
+ {
+ private readonly ITestOutputHelper _output;
+
+ public PasswordGenerator(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public void Generate()
+ {
+ var service = new PasswordService();
+ _output.WriteLine(service.HashPassword("crupest"));
+ }
+ }
+}
diff --git a/Timeline.Tests/Properties/launchSettings.json b/Timeline.Tests/Properties/launchSettings.json
index 0c1cae5d..7a94d57a 100644
--- a/Timeline.Tests/Properties/launchSettings.json
+++ b/Timeline.Tests/Properties/launchSettings.json
@@ -1,27 +1,27 @@
-{
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:11197/",
- "sslPort": 0
- }
- },
- "profiles": {
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "Timeline.Tests": {
- "commandName": "Project",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "applicationUrl": "https://localhost:11199/;http://localhost:11198/"
- }
- }
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:52040/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Timeline.Tests": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:52041/"
+ }
+ }
} \ No newline at end of file
diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/Services/UserAvatarServiceTest.cs
index 1f71f6f6..cf3d2a0a 100644
--- a/Timeline.Tests/UserAvatarServiceTest.cs
+++ b/Timeline.Tests/Services/UserAvatarServiceTest.cs
@@ -1,6 +1,5 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using SixLabors.ImageSharp.Formats.Png;
@@ -13,11 +12,9 @@ using Timeline.Entities;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Mock.Data;
-using Timeline.Tests.Mock.Services;
using Xunit;
-using Xunit.Abstractions;
-namespace Timeline.Tests
+namespace Timeline.Tests.Services
{
public class UserAvatarValidatorTest : IClassFixture<UserAvatarValidator>
{
diff --git a/Timeline.Tests/Services/UserDetailServiceTest.cs b/Timeline.Tests/Services/UserDetailServiceTest.cs
new file mode 100644
index 00000000..c7037c6e
--- /dev/null
+++ b/Timeline.Tests/Services/UserDetailServiceTest.cs
@@ -0,0 +1,108 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Entities;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+using Timeline.Tests.Mock.Data;
+using Xunit;
+
+namespace Timeline.Tests.Services
+{
+ public class UserDetailServiceTest : IDisposable
+ {
+ private readonly TestDatabase _testDatabase;
+
+ private readonly UserDetailService _service;
+
+ public UserDetailServiceTest()
+ {
+ _testDatabase = new TestDatabase();
+ _service = new UserDetailService(_testDatabase.DatabaseContext, NullLogger<UserDetailService>.Instance);
+ }
+
+ public void Dispose()
+ {
+ _testDatabase.Dispose();
+ }
+
+ [Theory]
+ [InlineData(null, typeof(ArgumentNullException))]
+ [InlineData("", typeof(UsernameBadFormatException))]
+ [InlineData("a!a", typeof(UsernameBadFormatException))]
+ [InlineData("usernotexist", typeof(UserNotExistException))]
+ public async Task GetNickname_ShouldThrow(string username, Type exceptionType)
+ {
+ await _service.Awaiting(s => s.GetNickname(username)).Should().ThrowAsync(exceptionType);
+ }
+
+ [Fact]
+ public async Task GetNickname_ShouldReturnUsername()
+ {
+ var result = await _service.GetNickname(MockUser.User.Username);
+ result.Should().Be(MockUser.User.Username);
+ }
+
+ [Fact]
+ public async Task GetNickname_ShouldReturnData()
+ {
+ const string nickname = "aaaaaa";
+ {
+ var context = _testDatabase.DatabaseContext;
+ var userId = (await context.Users.Where(u => u.Name == MockUser.User.Username).Select(u => new { u.Id }).SingleAsync()).Id;
+ context.UserDetails.Add(new UserDetail
+ {
+ Nickname = nickname,
+ UserId = userId
+ });
+ await context.SaveChangesAsync();
+ }
+ var result = await _service.GetNickname(MockUser.User.Username);
+ result.Should().Be(nickname);
+ }
+
+ [Theory]
+ [InlineData(null, typeof(ArgumentNullException))]
+ [InlineData("", typeof(UsernameBadFormatException))]
+ [InlineData("a!a", typeof(UsernameBadFormatException))]
+ [InlineData("usernotexist", typeof(UserNotExistException))]
+ public async Task SetNickname_ShouldThrow(string username, Type exceptionType)
+ {
+ await _service.Awaiting(s => s.SetNickname(username, null)).Should().ThrowAsync(exceptionType);
+ }
+
+ [Fact]
+ public async Task SetNickname_ShouldThrow_ArgumentException()
+ {
+ await _service.Awaiting(s => s.SetNickname("uuu", new string('a', 50))).Should().ThrowAsync<ArgumentException>();
+ }
+
+ [Fact]
+ public async Task SetNickname_ShouldWork()
+ {
+ var username = MockUser.User.Username;
+ var user = await _testDatabase.DatabaseContext.Users.Where(u => u.Name == username).Include(u => u.Detail).SingleAsync();
+
+ var nickname1 = "nickname1";
+ var nickname2 = "nickname2";
+
+ await _service.SetNickname(username, null);
+ user.Detail.Should().BeNull();
+
+ await _service.SetNickname(username, nickname1);
+ user.Detail.Should().NotBeNull();
+ user.Detail.Nickname.Should().Be(nickname1);
+
+ await _service.SetNickname(username, nickname2);
+ user.Detail.Should().NotBeNull();
+ user.Detail.Nickname.Should().Be(nickname2);
+
+ await _service.SetNickname(username, null);
+ user.Detail.Should().NotBeNull();
+ user.Detail.Nickname.Should().BeNull();
+ }
+ }
+}
diff --git a/Timeline.Tests/Timeline.Tests.csproj b/Timeline.Tests/Timeline.Tests.csproj
index 497a00b7..21e887eb 100644
--- a/Timeline.Tests/Timeline.Tests.csproj
+++ b/Timeline.Tests/Timeline.Tests.csproj
@@ -31,4 +31,8 @@
<ItemGroup>
<ProjectReference Include="..\Timeline\Timeline.csproj" />
</ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="Properties\" />
+ </ItemGroup>
</Project>