mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add more robot tests
This commit is contained in:
parent
14d23af0fd
commit
d257901c64
@ -45,6 +45,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user password (hashed, it can't be read like that). The hashing format is implementation defined.
|
/// The user password (hashed, it can't be read like that). The hashing format is implementation defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SerializeIgnore]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,6 +56,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Arbitrary extra data that can be used by specific authentication implementations.
|
/// Arbitrary extra data that can be used by specific authentication implementations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SerializeIgnore]
|
||||||
public Dictionary<string, string> ExtraData { get; set; }
|
public Dictionary<string, string> ExtraData { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -96,7 +96,10 @@ namespace Kyoo.Authentication
|
|||||||
/// <param name="kind">The kind of permission needed.</param>
|
/// <param name="kind">The kind of permission needed.</param>
|
||||||
/// <param name="group">The group of the permission.</param>
|
/// <param name="group">The group of the permission.</param>
|
||||||
/// <param name="options">The option containing default values.</param>
|
/// <param name="options">The option containing default values.</param>
|
||||||
public PermissionValidatorFilter(string permission, Kind kind, Group group,
|
public PermissionValidatorFilter(
|
||||||
|
string permission,
|
||||||
|
Kind kind,
|
||||||
|
Group group,
|
||||||
IOptionsMonitor<PermissionOption> options)
|
IOptionsMonitor<PermissionOption> options)
|
||||||
{
|
{
|
||||||
_permission = permission;
|
_permission = permission;
|
||||||
|
@ -62,9 +62,6 @@ namespace Kyoo.Authentication
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string CreateAccessToken(User user, out TimeSpan expireIn)
|
public string CreateAccessToken(User user, out TimeSpan expireIn)
|
||||||
{
|
{
|
||||||
if (user == null)
|
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
|
|
||||||
expireIn = new TimeSpan(1, 0, 0);
|
expireIn = new TimeSpan(1, 0, 0);
|
||||||
|
|
||||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
||||||
@ -94,9 +91,6 @@ namespace Kyoo.Authentication
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<string> CreateRefreshToken(User user)
|
public Task<string> CreateRefreshToken(User user)
|
||||||
{
|
{
|
||||||
if (user == null)
|
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
|
|
||||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
||||||
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
||||||
JwtSecurityToken token = new(
|
JwtSecurityToken token = new(
|
||||||
|
@ -31,7 +31,7 @@ namespace Kyoo.Authentication.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default jwt secret.
|
/// The default jwt secret.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DefaultSecret = "jwt-secret";
|
public const string DefaultSecret = "4c@mraGB!KRfF@kpS8739y9FcHemKxBsqqxLbdR?";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The secret used to encrypt the jwt.
|
/// The secret used to encrypt the jwt.
|
||||||
|
@ -175,15 +175,76 @@ namespace Kyoo.Authentication.Views
|
|||||||
/// <returns>The currently authenticated user.</returns>
|
/// <returns>The currently authenticated user.</returns>
|
||||||
/// <response code="403">The given access token is invalid.</response>
|
/// <response code="403">The given access token is invalid.</response>
|
||||||
[HttpGet("me")]
|
[HttpGet("me")]
|
||||||
[Authorize]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult<User>> GetMe()
|
public async Task<ActionResult<User>> GetMe()
|
||||||
{
|
{
|
||||||
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
||||||
return Forbid();
|
return Forbid();
|
||||||
return await _users.Get(userID);
|
try
|
||||||
|
{
|
||||||
|
return await _users.Get(userID);
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add a put to edit the current user.
|
/// <summary>
|
||||||
|
/// Edit self
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Edit information about the currently authenticated user.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="user">The new data for the current user.</param>
|
||||||
|
/// <param name="resetOld">
|
||||||
|
/// Should old properties of the resource be discarded or should null values considered as not changed?
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The currently authenticated user after modifications.</returns>
|
||||||
|
/// <response code="403">The given access token is invalid.</response>
|
||||||
|
[HttpPut("me")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<ActionResult<User>> EditMe(User user, [FromQuery] bool resetOld = true)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
||||||
|
return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
user.ID = userID;
|
||||||
|
return await _users.Edit(user, resetOld);
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete account
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Delete the current account.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The currently authenticated user after modifications.</returns>
|
||||||
|
/// <response code="403">The given access token is invalid.</response>
|
||||||
|
[HttpDelete("me")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<ActionResult<User>> DeleteMe()
|
||||||
|
{
|
||||||
|
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
||||||
|
return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _users.Delete(userID);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ namespace Kyoo.Core.Api
|
|||||||
/// <response code="404">No item could be found with the given id or slug.</response>
|
/// <response code="404">No item could be found with the given id or slug.</response>
|
||||||
[HttpDelete("{identifier:id}")]
|
[HttpDelete("{identifier:id}")]
|
||||||
[PartialPermission(Kind.Delete)]
|
[PartialPermission(Kind.Delete)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> Delete(Identifier identifier)
|
public async Task<IActionResult> Delete(Identifier identifier)
|
||||||
{
|
{
|
||||||
@ -236,7 +236,7 @@ namespace Kyoo.Core.Api
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -250,7 +250,7 @@ namespace Kyoo.Core.Api
|
|||||||
/// <response code="400">One or multiple filters are invalid.</response>
|
/// <response code="400">One or multiple filters are invalid.</response>
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
[PartialPermission(Kind.Delete)]
|
[PartialPermission(Kind.Delete)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
public async Task<IActionResult> Delete([FromQuery] Dictionary<string, string> where)
|
public async Task<IActionResult> Delete([FromQuery] Dictionary<string, string> where)
|
||||||
{
|
{
|
||||||
@ -263,7 +263,7 @@ namespace Kyoo.Core.Api
|
|||||||
return BadRequest(new RequestError(ex.Message));
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
"newUser": ["overall.read", "overall.write"]
|
"newUser": ["overall.read", "overall.write"]
|
||||||
},
|
},
|
||||||
"profilePicturePath": "users/",
|
"profilePicturePath": "users/",
|
||||||
"secret": "jwt-secret"
|
"secret": "4c@mraGB!KRfF@kpS8740y9FcHemKxBsqqxLbdR?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"tvdb": {
|
"tvdb": {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
*** Settings ***
|
|
||||||
Documentation Tests of the /auth route.
|
|
||||||
... Ensures that the user can authenticate on kyoo.
|
|
||||||
Resource ../rest.resource
|
|
||||||
|
|
||||||
|
|
||||||
*** Test Cases ***
|
|
||||||
BadAccount
|
|
||||||
[Documentation] Login fails if user does not exist
|
|
||||||
POST /auth/login {"username": "toto", "password": "tata"}
|
|
||||||
Output
|
|
||||||
Integer response status 403
|
|
||||||
|
|
||||||
Register
|
|
||||||
[Documentation] Create a new user and login in it
|
|
||||||
POST /auth/register {"username": "toto", "password": "tata", "email": "mail@kyoo.moe"}
|
|
||||||
Output
|
|
||||||
Integer response status 403
|
|
83
tests/robot/auth/auth.robot
Normal file
83
tests/robot/auth/auth.robot
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
*** Settings ***
|
||||||
|
Documentation Tests of the /auth route.
|
||||||
|
... Ensures that the user can authenticate on kyoo.
|
||||||
|
|
||||||
|
Resource ../rest.resource
|
||||||
|
|
||||||
|
|
||||||
|
*** Keywords ***
|
||||||
|
Login
|
||||||
|
[Documentation] Shortcut to login with the given username for future requests
|
||||||
|
[Arguments] ${username}
|
||||||
|
&{res}= POST /auth/login {"username": "${username}", "password": "password-${username}"}
|
||||||
|
Output
|
||||||
|
Integer response status 200
|
||||||
|
String response body access_token
|
||||||
|
Set Headers {"Authorization": "Bearer ${res.body.access_token}"}
|
||||||
|
|
||||||
|
Register
|
||||||
|
[Documentation] Shortcut to register with the given username for future requests
|
||||||
|
[Arguments] ${username}
|
||||||
|
&{res}= POST
|
||||||
|
... /auth/register
|
||||||
|
... {"username": "${username}", "password": "password-${username}", "email": "${username}@kyoo.moe"}
|
||||||
|
Output
|
||||||
|
Integer response status 200
|
||||||
|
String response body access_token
|
||||||
|
Set Headers {"Authorization": "Bearer ${res.body.access_token}"}
|
||||||
|
|
||||||
|
Logout
|
||||||
|
[Documentation] Logout the current user, only the local client is affected.
|
||||||
|
Set Headers {"Authorization": ""}
|
||||||
|
|
||||||
|
|
||||||
|
*** Test Cases ***
|
||||||
|
Me cant be accessed without an account
|
||||||
|
Get /auth/me
|
||||||
|
Output
|
||||||
|
Integer response status 403
|
||||||
|
|
||||||
|
Bad Account
|
||||||
|
[Documentation] Login fails if user does not exist
|
||||||
|
POST /auth/login {"username": "i-don-t-exist", "password": "pass"}
|
||||||
|
Output
|
||||||
|
Integer response status 403
|
||||||
|
|
||||||
|
Register
|
||||||
|
[Documentation] Create a new user and login in it
|
||||||
|
Register user-1
|
||||||
|
[Teardown] DELETE /auth/me
|
||||||
|
|
||||||
|
Register Duplicates
|
||||||
|
[Documentation] If two users tries to register with the same username, it fails
|
||||||
|
Register user-duplicate
|
||||||
|
# We can't use the `Register` keyword because it assert for success
|
||||||
|
POST /auth/register {"username": "user-duplicate", "password": "pass", "email": "mail@kyoo.moe"}
|
||||||
|
Output
|
||||||
|
Integer response status 409
|
||||||
|
[Teardown] DELETE /auth/me
|
||||||
|
|
||||||
|
Delete Account
|
||||||
|
[Documentation] Check if a user can delete it's account
|
||||||
|
Register I-should-be-deleted
|
||||||
|
DELETE /auth/me
|
||||||
|
Output
|
||||||
|
Integer response status 204
|
||||||
|
|
||||||
|
Login
|
||||||
|
[Documentation] Create a new user and login in it
|
||||||
|
Register login-user
|
||||||
|
${res}= GET /auth/me
|
||||||
|
Output
|
||||||
|
Integer response status 200
|
||||||
|
String response body username login-user
|
||||||
|
|
||||||
|
Logout
|
||||||
|
Login login-user
|
||||||
|
${me}= Get /auth/me
|
||||||
|
Output
|
||||||
|
Output ${me}
|
||||||
|
Should Be Equal As Strings ${res["body"]} ${me["body"]}
|
||||||
|
|
||||||
|
[Teardown] DELETE /auth/me
|
||||||
|
|
4
tests/robot/pyproject.toml
Normal file
4
tests/robot/pyproject.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[tool.robotidy]
|
||||||
|
configure = [
|
||||||
|
"MergeAndOrderSections:order=comments,settings,keywords,variables,testcases"
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user