Skip to content

Commit

Permalink
Add maintenance controller (#642)
Browse files Browse the repository at this point in the history
- Add missing test + fixes for zena functionality
  • Loading branch information
SapiensAnatis authored Feb 12, 2024
1 parent aac6339 commit aad9e47
Show file tree
Hide file tree
Showing 22 changed files with 422 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using DragaliaAPI.Models.Options;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DragaliaAPI.Integration.Test.Features.Maintenance;

public class MaintenanceTest : TestFixture
{
private readonly CustomWebApplicationFactory factory;

public MaintenanceTest(CustomWebApplicationFactory factory, ITestOutputHelper testOutputHelper)
: base(factory, testOutputHelper)
{
this.factory = factory;
}

[Fact]
public async Task MaintenanceActive_ReturnsResultCode()
{
this.ConfigureMaintenanceClient(new MaintenanceOptions() { Enabled = true });

DragaliaResponse<ResultCodeData> response = await this.Client.PostMsgpack<ResultCodeData>(
"load/index",
new LoadIndexRequest(),
ensureSuccessHeader: false
);

response.data_headers.result_code.Should().Be(ResultCode.CommonMaintenance);
}

[Fact]
public async Task MaintenanceActive_CoreEndpoint_ReturnsNormalResponse()
{
this.ConfigureMaintenanceClient(new MaintenanceOptions() { Enabled = true });

DragaliaResponse<ToolGetServiceStatusData> response =
await this.Client.PostMsgpack<ToolGetServiceStatusData>(
"tool/get_service_status",
new ToolGetServiceStatusRequest(),
ensureSuccessHeader: false
);

response.data_headers.result_code.Should().Be(ResultCode.Success);
response.data.service_status.Should().Be(1);
}

[Fact]
public async Task MaintenanceActive_GetText_ReturnsText()
{
this.ConfigureMaintenanceClient(
new MaintenanceOptions()
{
Enabled = true,
Title = "Title",
Body = "Body",
End = DateTimeOffset.UnixEpoch
}
);

DragaliaResponse<MaintenanceGetTextData> response =
await this.Client.PostMsgpack<MaintenanceGetTextData>(
"maintenance/get_text",
new MaintenanceGetTextRequest()
);

response
.data.maintenance_text.Should()
.BeEquivalentTo(
$"""
<title>Title</title>
<body>Body</body>
<schedule>Check back at:</schedule>
<date>1970-01-01T09:00:00</date>
""" // Date must be in Japan Standard Time
);
}

private void ConfigureMaintenanceClient(MaintenanceOptions options) =>
this.Client = this.CreateClient(builder =>
builder.ConfigureTestServices(services =>
services.Configure<MaintenanceOptions>(opts =>
{
opts.Enabled = options.Enabled;
opts.Title = options.Title;
opts.Body = options.Body;
opts.End = options.End;
})
)
);
}
86 changes: 86 additions & 0 deletions DragaliaAPI.Integration.Test/Features/Zena/ZenaTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using DragaliaAPI.Features.Zena;

namespace DragaliaAPI.Integration.Test.Features.Zena;

public class ZenaTest : TestFixture
{
public ZenaTest(CustomWebApplicationFactory factory, ITestOutputHelper testOutputHelper)
: base(factory, testOutputHelper)
{
Environment.SetEnvironmentVariable("ZENA_TOKEN", "token");

this.Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
"token"
);
}

[Fact]
public async Task GetTeamData_ValidId_ReturnsTeamData()
{
HttpResponseMessage zenaResponse = await this.Client.GetAsync(
$"zena/get_team_data?id={this.ViewerId}&teamnum=1"
);

zenaResponse.Should().BeSuccessful();

GetTeamDataResponse? deserialized =
await zenaResponse.Content.ReadFromJsonAsync<GetTeamDataResponse>();

deserialized
.Should()
.BeEquivalentTo(
new GetTeamDataResponse()
{
Name = "Euden",
Unit1 = Charas.ThePrince,
Unit2 = Charas.Empty,
Unit3 = Charas.Empty,
Unit4 = Charas.Empty,
}
);
}

[Fact]
public async Task GetTeamData_ValidId_MultiTeam_ReturnsTeamData()
{
HttpResponseMessage zenaResponse = await this.Client.GetAsync(
$"zena/get_team_data?id={this.ViewerId}&teamnum=1&teamnum2=2"
);

zenaResponse.Should().BeSuccessful();

GetTeamDataResponse? deserialized =
await zenaResponse.Content.ReadFromJsonAsync<GetTeamDataResponse>();

deserialized
.Should()
.BeEquivalentTo(
new GetTeamDataResponse()
{
Name = "Euden",
Unit1 = Charas.ThePrince,
Unit2 = Charas.Empty,
Unit3 = Charas.Empty,
Unit4 = Charas.Empty,
Unit5 = Charas.ThePrince,
Unit6 = Charas.Empty,
Unit7 = Charas.Empty,
Unit8 = Charas.Empty,
}
);
}

[Fact]
public async Task GetTeamData_InvalidId_Returns404()
{
HttpResponseMessage zenaResponse = await this.Client.GetAsync(
"zena/get_team_data?id=9999&teamnum=1&teamnum2=2"
);

zenaResponse.Should().HaveStatusCode(HttpStatusCode.NotFound);
}
}
49 changes: 29 additions & 20 deletions DragaliaAPI.Integration.Test/TestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class TestFixture : IClassFixture<CustomWebApplicationFactory>, IAsyncLif
/// <summary>
/// The session ID which is associated with the logged in test user.
/// </summary>
private const string SessionId = "session_id";
protected const string SessionId = "session_id";

private readonly CustomWebApplicationFactory factory;

Expand All @@ -39,24 +39,7 @@ protected TestFixture(CustomWebApplicationFactory factory, ITestOutputHelper tes
this.factory = factory;
this.TestOutputHelper = testOutputHelper;

this.Client = factory
.WithWebHostBuilder(builder =>
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddXUnit(this.TestOutputHelper);
})
)
.CreateClient(
new WebApplicationFactoryClientOptions()
{
BaseAddress = new Uri("http://localhost/api/", UriKind.Absolute),
}
);

this.Client.DefaultRequestHeaders.Add("SID", SessionId);
this.Client.DefaultRequestHeaders.Add("Platform", "2");
this.Client.DefaultRequestHeaders.Add("Res-Ver", "y2XM6giU6zz56wCm");
this.Client = this.CreateClient();

this.MockBaasApi.Setup(x => x.GetKeys()).ReturnsAsync(TokenHelper.SecurityKeys);
this.MockDateTimeProvider.SetupGet(x => x.UtcNow).Returns(() => DateTimeOffset.UtcNow);
Expand Down Expand Up @@ -91,7 +74,7 @@ protected TestFixture(CustomWebApplicationFactory factory, ITestOutputHelper tes
/// </remarks>
protected long ViewerId { get; private set; }

protected HttpClient Client { get; }
protected HttpClient Client { get; set; }

protected IMapper Mapper { get; }

Expand Down Expand Up @@ -189,6 +172,32 @@ protected long GetDragonKeyId(Dragons dragon)
.First();
}

protected HttpClient CreateClient(Action<IWebHostBuilder>? extraBuilderConfig = null)
{
HttpClient client = factory
.WithWebHostBuilder(builder =>
{
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddXUnit(this.TestOutputHelper);
});
extraBuilderConfig?.Invoke(builder);
})
.CreateClient(
new WebApplicationFactoryClientOptions()
{
BaseAddress = new Uri("http://localhost/api/", UriKind.Absolute),
}
);

client.DefaultRequestHeaders.Add("SID", SessionId);
client.DefaultRequestHeaders.Add("Platform", "2");
client.DefaultRequestHeaders.Add("Res-Ver", "y2XM6giU6zz56wCm");

return client;
}

protected long GetTalismanKeyId(Talismans talisman)
{
return this.ApiContext.PlayerTalismans.Where(x => x.TalismanId == talisman)
Expand Down

This file was deleted.

3 changes: 1 addition & 2 deletions DragaliaAPI/Controllers/Dragalia/DeployController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ namespace DragaliaAPI.Controllers.Dragalia;

[Route("deploy")]
[AllowAnonymous]
[BypassResourceVersionCheck]
public class DeployController : DragaliaControllerBase
public class DeployController : DragaliaControllerBaseCore
{
private const string DeployHash = "13bb2827ce9e6a66015ac2808112e3442740e862";

Expand Down
3 changes: 1 addition & 2 deletions DragaliaAPI/Controllers/Dragalia/EulaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ namespace DragaliaAPI.Controllers.Dragalia;

[Route("eula")]
[AllowAnonymous]
[BypassResourceVersionCheck]
public class EulaController : DragaliaControllerBase
public class EulaController : DragaliaControllerBaseCore
{
private static readonly List<AtgenVersionHash> AllEulaVersions =
new()
Expand Down
27 changes: 8 additions & 19 deletions DragaliaAPI/Controllers/Dragalia/ToolController.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,24 @@
using DragaliaAPI.Models.Generated;
using DragaliaAPI.Models.Options;
using DragaliaAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace DragaliaAPI.Controllers.Dragalia;

/// <summary>
/// This is presumably used to create a savefile on Dragalia's servers,
/// but we do that after creating a DeviceAccount in the Nintendo endpoint,
/// because we aren't limited by having two separate servers/DBs.
///
/// As a result, this controller just retrieves the existing savefile and
/// responds with its viewer_id.
/// </summary>
[Route("tool")]
[AllowAnonymous]
[BypassResourceVersionCheck]
public class ToolController : DragaliaControllerBase
public class ToolController(IAuthService authService) : DragaliaControllerBaseCore
{
private readonly IAuthService authService;

public ToolController(IAuthService authService)
{
this.authService = authService;
}
private const int OkServiceStatus = 1;
private const int MaintenanceServiceStatus = 2;

[HttpPost]
[Route("signup")]
public async Task<DragaliaResult> Signup([FromHeader(Name = "ID-TOKEN")] string idToken)
{
(long viewerId, _) = await this.authService.DoAuth(idToken);
(long viewerId, _) = await authService.DoAuth(idToken);

return this.Ok(
new ToolSignupData()
Expand All @@ -54,7 +43,7 @@ public async Task<DragaliaResult> Auth([FromHeader(Name = "ID-TOKEN")] string id
// For some reason, the id_token in the ToolAuthRequest does not update with refreshes,
// but the one in the header does.

(long viewerId, string sessionId) = await this.authService.DoAuth(idToken);
(long viewerId, string sessionId) = await authService.DoAuth(idToken);

return this.Ok(
new ToolAuthData()
Expand All @@ -69,7 +58,7 @@ public async Task<DragaliaResult> Auth([FromHeader(Name = "ID-TOKEN")] string id
[HttpPost("reauth")]
public async Task<DragaliaResult> Reauth([FromHeader(Name = "ID-TOKEN")] string idToken)
{
(long viewerId, string sessionId) = await this.authService.DoAuth(idToken);
(long viewerId, string sessionId) = await authService.DoAuth(idToken);

return this.Ok(
new ToolReauthData()
Expand Down
22 changes: 20 additions & 2 deletions DragaliaAPI/Controllers/DragaliaControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@

namespace DragaliaAPI.Controllers;

/// <summary>
/// Defines a controller for all Dawnshard API endpoints that implements the required metadata,
/// and which provides helpers to serialize Dragalia responses.
/// </summary>
/// <remarks>
/// For most controllers that are not involved in the title screen, <see cref="DragaliaControllerBase"/>
/// should be used.
/// </remarks>
[ApiController]
[SerializeException]
[ServiceFilter(typeof(ResourceVersionActionFilter))]
[Authorize(AuthenticationSchemes = SchemeName.Session)]
[Consumes("application/octet-stream")]
[Produces("application/x-msgpack")]
public abstract class DragaliaControllerBase : ControllerBase
public abstract class DragaliaControllerBaseCore : ControllerBase
{
protected string DeviceAccountId =>
this.User.FindFirstValue(CustomClaimType.AccountId)
Expand Down Expand Up @@ -50,3 +57,14 @@ public OkObjectResult Code(ResultCode code)
return this.Code(code, string.Empty);
}
}

/// <summary>
/// Extends <see cref="DragaliaControllerBase"/> with extra action filters that can return the player to the title
/// screen under certain circumstances.
/// </summary>
/// <remarks>
/// Not to be used for endpoints that make up the title screen (/tool/*, /version/*, etc.) to prevent infinite loops.
/// </remarks>
[ServiceFilter<ResourceVersionActionFilter>]
[ServiceFilter<MaintenanceActionFilter>]
public abstract class DragaliaControllerBase : DragaliaControllerBaseCore { }
Loading

0 comments on commit aad9e47

Please sign in to comment.