Added OpenID endpoints tests and documentation
This commit is contained in:
@@ -14,6 +14,18 @@
|
|||||||
<Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" />
|
<Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" />
|
||||||
<Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" />
|
<Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" />
|
||||||
</AssemblyExplorer></s:String>
|
</AssemblyExplorer></s:String>
|
||||||
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=027ac703_002Df1f3_002D42aa_002D9c67_002D7cbaeecdbead/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="Refresh_ShouldReturnConflict_WhenRefreshTokenNotValid" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
|
<TestAncestor>
|
||||||
|
<TestId>xUnit::25DE1510-47E5-46FF-89A4-B9F99542218E::net8.0::HopFrame.Tests.Api.Controllers.OpenIdControllerTests.Refresh_ShouldReturnConflict_WhenRefreshTokenNotValid</TestId>
|
||||||
|
</TestAncestor>
|
||||||
|
</SessionState></s:String>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
120
docs/api/endpoints/auth.md
Normal file
120
docs/api/endpoints/auth.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Auth Endpoints
|
||||||
|
|
||||||
|
## Used Models
|
||||||
|
- [UserLogin](../../models.md#userlogin)
|
||||||
|
- [UserRegister](../../models.md#userregister)
|
||||||
|
- [SingleValueResult](../../models.md#singlevalueresult)
|
||||||
|
- [UserPasswordValidation](../../models.md#userpasswordvalidation)
|
||||||
|
|
||||||
|
## API Endpoint: Login
|
||||||
|
|
||||||
|
**Endpoint:** `PUT /api/v1/auth/login`
|
||||||
|
|
||||||
|
**Description:** Authenticates a user and provides access and refresh tokens.
|
||||||
|
|
||||||
|
**Authorization Required:** No
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- **UserLogin** (required): The login credentials of the user.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** Returns the access token.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **400 Bad Request:** HopFrame authentication scheme is disabled.
|
||||||
|
- **404 Not Found:** The provided email address was not found.
|
||||||
|
- **403 Forbidden:** The provided password is not correct.
|
||||||
|
|
||||||
|
## API Endpoint: Register
|
||||||
|
|
||||||
|
**Endpoint:** `POST /api/v1/auth/register`
|
||||||
|
|
||||||
|
**Description:** Registers a new user and provides access and refresh tokens.
|
||||||
|
|
||||||
|
**Authorization Required:** No
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- **UserRegister** (required): The registration details of the user.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"email": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** Returns the access token.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **400 Bad Request:** HopFrame authentication scheme is disabled or the password is too short.
|
||||||
|
- **409 Conflict:** Username or email is already registered.
|
||||||
|
|
||||||
|
## API Endpoint: Authenticate
|
||||||
|
|
||||||
|
**Endpoint:** `GET /api/v1/auth/authenticate`
|
||||||
|
|
||||||
|
**Description:** Authenticates the user using the refresh token and provides a new access token.
|
||||||
|
|
||||||
|
**Authorization Required:** Yes
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** Returns the access token.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **400 Bad Request:** HopFrame authentication scheme is disabled or refresh token not provided.
|
||||||
|
- **404 Not Found:** The refresh token is not valid.
|
||||||
|
- **403 Forbidden:** The refresh token is expired.
|
||||||
|
- **409 Conflict:** The provided token is not a refresh token.
|
||||||
|
|
||||||
|
## API Endpoint: Logout
|
||||||
|
|
||||||
|
**Endpoint:** `DELETE /api/v1/auth/logout`
|
||||||
|
|
||||||
|
**Description:** Logs out the user and deletes the access and refresh tokens.
|
||||||
|
|
||||||
|
**Authorization Required:** Yes
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** User is logged out successfully.
|
||||||
|
|
||||||
|
## API Endpoint: Delete
|
||||||
|
|
||||||
|
**Endpoint:** `DELETE /api/v1/auth/delete`
|
||||||
|
|
||||||
|
**Description:** Deletes the user account.
|
||||||
|
|
||||||
|
**Authorization Required:** Yes
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- **UserPasswordValidation** (required): The password validation for the user.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** User account is deleted successfully.
|
||||||
|
- **403 Forbidden:** The provided password is not correct.
|
||||||
82
docs/api/endpoints/openId.md
Normal file
82
docs/api/endpoints/openId.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# OpenID Endpoints
|
||||||
|
|
||||||
|
## Used Models
|
||||||
|
- [SingleValueResult](../../models.md#singlevalueresult)
|
||||||
|
|
||||||
|
## API Endpoint: RedirectToProvider
|
||||||
|
|
||||||
|
**Endpoint:** `GET /api/v1/openid/redirect`
|
||||||
|
|
||||||
|
**Description:** Redirects the user to the OpenID provider's authorization endpoint.
|
||||||
|
|
||||||
|
**Authorization Required:** No
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- **redirectAfter** (query, optional): The URL to redirect to after authentication.
|
||||||
|
- **performRedirect** (query, optional): A flag to indicate if the user should be redirected (default is 1).
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **302 Found:** Redirects the user to the OpenID provider's authorization endpoint.
|
||||||
|
- **200 OK:** Returns the constructed authorization URI.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoint: Callback
|
||||||
|
|
||||||
|
**Endpoint:** `GET /api/v1/openid/callback`
|
||||||
|
|
||||||
|
**Description:** Handles the callback from the OpenID provider and exchanges the authorization code for tokens.
|
||||||
|
|
||||||
|
**Authorization Required:** No
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- **code** (query, required): The authorization code received from the OpenID provider.
|
||||||
|
- **state** (query, optional): The state parameter to handle the redirect after authentication.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** Returns the access token.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **400 Bad Request:** Authorization code is missing.
|
||||||
|
- **403 Forbidden:** Authorization code is not valid.
|
||||||
|
|
||||||
|
## API Endpoint: Refresh
|
||||||
|
|
||||||
|
**Endpoint:** `GET /api/v1/openid/refresh`
|
||||||
|
|
||||||
|
**Description:** Refreshes the access token using the refresh token.
|
||||||
|
|
||||||
|
**Authorization Required:** Yes
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** Returns the refreshed access token.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **400 Bad Request:** Refresh token not provided.
|
||||||
|
- **409 Conflict**: Refresh token not valid.
|
||||||
|
|
||||||
|
## API Endpoint: Logout
|
||||||
|
|
||||||
|
**Endpoint:** `DELETE /api/v1/openid/logout`
|
||||||
|
|
||||||
|
**Description:** Logs out the user by deleting the authentication cookies.
|
||||||
|
|
||||||
|
**Authorization Required:** Yes
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- **200 OK:** User is logged out successfully.
|
||||||
@@ -14,3 +14,66 @@ public sealed class UserPasswordValidation {
|
|||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## OpenIdConfiguration
|
||||||
|
```csharp
|
||||||
|
public sealed class OpenIdConfiguration {
|
||||||
|
public string Issuer { get; set; }
|
||||||
|
public string AuthorizationEndpoint { get; set; }
|
||||||
|
public string TokenEndpoint { get; set; }
|
||||||
|
public string UserinfoEndpoint { get; set; }
|
||||||
|
public string EndSessionEndpoint { get; set; }
|
||||||
|
public string IntrospectionEndpoint { get; set; }
|
||||||
|
public string RevocationEndpoint { get; set; }
|
||||||
|
public string DeviceAuthorizationEndpoint { get; set; }
|
||||||
|
public List<string> ResponseTypesSupported { get; set; }
|
||||||
|
public List<string> ResponseModesSupported { get; set; }
|
||||||
|
public string JwksUri { get; set; }
|
||||||
|
public List<string> GrantTypesSupported { get; set; }
|
||||||
|
public List<string> IdTokenSigningAlgValuesSupported { get; set; }
|
||||||
|
public List<string> SubjectTypesSupported { get; set; }
|
||||||
|
public List<string> TokenEndpointAuthMethodsSupported { get; set; }
|
||||||
|
public List<string> AcrValuesSupported { get; set; }
|
||||||
|
public List<string> ScopesSupported { get; set; }
|
||||||
|
public bool RequestParameterSupported { get; set; }
|
||||||
|
public List<string> ClaimsSupported { get; set; }
|
||||||
|
public bool ClaimsParameterSupported { get; set; }
|
||||||
|
public List<string> CodeChallengeMethodsSupported { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## OpenIdIntrospection
|
||||||
|
```csharp
|
||||||
|
public sealed class OpenIdIntrospection {
|
||||||
|
public string Issuer { get; set; }
|
||||||
|
public string Subject { get; set; }
|
||||||
|
public string Audience { get; set; }
|
||||||
|
public long Expiration { get; set; }
|
||||||
|
public long IssuedAt { get; set; }
|
||||||
|
public long AuthTime { get; set; }
|
||||||
|
public string Acr { get; set; }
|
||||||
|
public List<string> AuthenticationMethods { get; set; }
|
||||||
|
public string SessionId { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public bool EmailVerified { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string GivenName { get; set; }
|
||||||
|
public string PreferredUsername { get; set; }
|
||||||
|
public string Nickname { get; set; }
|
||||||
|
public List<string> Groups { get; set; }
|
||||||
|
public bool Active { get; set; }
|
||||||
|
public string Scope { get; set; }
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## OpenIdToken
|
||||||
|
```csharp
|
||||||
|
public sealed class OpenIdToken {
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
public string IdToken { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -81,3 +81,17 @@ public class UserCreator {
|
|||||||
```csharp
|
```csharp
|
||||||
public interface IPermissionOwner;
|
public interface IPermissionOwner;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SingleValueResult
|
||||||
|
```csharp
|
||||||
|
public struct SingleValueResult<TValue>(TValue value) {
|
||||||
|
public TValue Value { get; set; } = value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## UserPasswordValidation
|
||||||
|
```csharp
|
||||||
|
public sealed class UserPasswordValidation {
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class OpenIdController(IOpenIdAccessor accessor) : ControllerBase {
|
|||||||
var token = await accessor.RefreshAccessToken(refreshToken);
|
var token = await accessor.RefreshAccessToken(refreshToken);
|
||||||
|
|
||||||
if (token is null)
|
if (token is null)
|
||||||
return NotFound("Refresh token not valid");
|
return Conflict("Refresh token not valid");
|
||||||
|
|
||||||
accessor.SetAuthenticationCookies(token);
|
accessor.SetAuthenticationCookies(token);
|
||||||
|
|
||||||
|
|||||||
177
tests/HopFrame.Tests.Api/Controllers/OpenIdControllerTests.cs
Normal file
177
tests/HopFrame.Tests.Api/Controllers/OpenIdControllerTests.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using HopFrame.Api.Controller;
|
||||||
|
using HopFrame.Api.Models;
|
||||||
|
using HopFrame.Security.Authentication.OpenID;
|
||||||
|
using HopFrame.Security.Authentication.OpenID.Models;
|
||||||
|
using HopFrame.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace HopFrame.Tests.Api.Controllers;
|
||||||
|
|
||||||
|
public class OpenIdControllerTests {
|
||||||
|
private (Mock<IOpenIdAccessor>, OpenIdController) SetupEnvironment(out HttpContext httpContext) {
|
||||||
|
var mockAccessor = new Mock<IOpenIdAccessor>();
|
||||||
|
var controller = new OpenIdController(mockAccessor.Object);
|
||||||
|
|
||||||
|
httpContext = new DefaultHttpContext();
|
||||||
|
controller.ControllerContext = new ControllerContext {
|
||||||
|
HttpContext = httpContext
|
||||||
|
};
|
||||||
|
return (mockAccessor, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RedirectToProvider_ShouldRedirect_WhenPerformRedirectIsTrue() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out _);
|
||||||
|
var uri = "https://example.com/auth";
|
||||||
|
mockAccessor.Setup(a => a.ConstructAuthUri(It.IsAny<string>())).ReturnsAsync(uri);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.RedirectToProvider("https://redirectafter.com", 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var redirectResult = Assert.IsType<RedirectResult>(result);
|
||||||
|
Assert.Equal(uri, redirectResult.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RedirectToProvider_ShouldReturnOk_WhenPerformRedirectIsFalse() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out _);
|
||||||
|
var uri = "https://example.com/auth";
|
||||||
|
mockAccessor.Setup(a => a.ConstructAuthUri(It.IsAny<string>())).ReturnsAsync(uri);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.RedirectToProvider("https://redirectafter.com", 0);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var singleValueResult = Assert.IsType<SingleValueResult<string>>(okResult.Value);
|
||||||
|
Assert.Equal(uri, singleValueResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Callback_ShouldReturnBadRequest_WhenAuthorizationCodeIsMissing() {
|
||||||
|
// Arrange
|
||||||
|
var (_, controller) = SetupEnvironment(out _);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Callback(string.Empty, "state");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
|
||||||
|
Assert.Equal("Authorization code is missing", badRequestResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Callback_ShouldReturnForbidden_WhenAuthorizationCodeIsNotValid() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out _);
|
||||||
|
mockAccessor.Setup(a => a.RequestToken(It.IsAny<string>())).ReturnsAsync((OpenIdToken)null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Callback("invalid_code", "state");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var forbidResult = Assert.IsType<ForbidResult>(result);
|
||||||
|
Assert.Equal("Authorization code is not valid", forbidResult.AuthenticationSchemes.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Callback_ShouldReturnOk_WhenStateIsNull() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out _);
|
||||||
|
var token = new OpenIdToken { AccessToken = "valid_token" };
|
||||||
|
mockAccessor.Setup(a => a.RequestToken(It.IsAny<string>())).ReturnsAsync(token);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Callback("valid_code", null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var singleValueResult = Assert.IsType<SingleValueResult<string>>(okResult.Value);
|
||||||
|
Assert.Equal("valid_token", singleValueResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Callback_ShouldRedirect_WhenStateIsProvided() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out _);
|
||||||
|
var token = new OpenIdToken { AccessToken = "valid_token" };
|
||||||
|
mockAccessor.Setup(a => a.RequestToken(It.IsAny<string>())).ReturnsAsync(token);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Callback("valid_code", "https://redirect.com/{token}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var redirectResult = Assert.IsType<RedirectResult>(result);
|
||||||
|
Assert.Equal("https://redirect.com/valid_token", redirectResult.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Refresh_ShouldReturnBadRequest_WhenRefreshTokenNotProvided() {
|
||||||
|
// Arrange
|
||||||
|
var (_, controller) = SetupEnvironment(out _);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Refresh();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
|
||||||
|
Assert.Equal("Refresh token not provided", badRequestResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Refresh_ShouldReturnConflict_WhenRefreshTokenNotValid() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out var httpContext);
|
||||||
|
var cookies = new Mock<IRequestCookieCollection>();
|
||||||
|
cookies
|
||||||
|
.SetupGet(c => c[ITokenContext.RefreshTokenType])
|
||||||
|
.Returns("invalid_token");
|
||||||
|
httpContext.Request.Cookies = cookies.Object;
|
||||||
|
mockAccessor.Setup(a => a.RefreshAccessToken(It.IsAny<string>())).ReturnsAsync((OpenIdToken)null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Refresh();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var conflictResult = Assert.IsType<ConflictObjectResult>(result);
|
||||||
|
Assert.Equal("Refresh token not valid", conflictResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Refresh_ShouldReturnOk_WhenRefreshTokenIsValid() {
|
||||||
|
// Arrange
|
||||||
|
var (mockAccessor, controller) = SetupEnvironment(out var httpContext);
|
||||||
|
var cookies = new Mock<IRequestCookieCollection>();
|
||||||
|
cookies
|
||||||
|
.SetupGet(c => c[ITokenContext.RefreshTokenType])
|
||||||
|
.Returns("valid_token");
|
||||||
|
httpContext.Request.Cookies = cookies.Object;
|
||||||
|
var token = new OpenIdToken { AccessToken = "new_access_token", RefreshToken = "new_refresh_token", ExpiresIn = 3600 };
|
||||||
|
mockAccessor.Setup(a => a.RefreshAccessToken(It.IsAny<string>())).ReturnsAsync(token);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await controller.Refresh();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var singleValueResult = Assert.IsType<SingleValueResult<string>>(okResult.Value);
|
||||||
|
Assert.Equal("new_access_token", singleValueResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Logout_ShouldReturnOk() {
|
||||||
|
// Arrange
|
||||||
|
var (_, controller) = SetupEnvironment(out _);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = controller.Logout();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<OkResult>(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user