/// <inheritdoc /> public ZoneRegistryController([FromServices] IZoneServerRepository zoneRepository, IClaimsPrincipalReader claimsReader, ILogger <AuthorizationReadyController> logger) : base(claimsReader, logger) { ZoneRepository = zoneRepository ?? throw new ArgumentNullException(nameof(zoneRepository)); }
public static async Task Test_Controller_Produces_AlreadyHasActiveSession_When_Session_Has() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0)); //We can't create the claimed session through this interface because it's a stored procedure. //Raw SQL can't execute. So we must interact directly with the DbSet //await sessionRepo.TryClaimUnclaimedSession(1, 1); CharacterDatabaseContext context = serviceProvider.GetService <CharacterDatabaseContext>(); await context.ClaimedSession.AddAsync(new ClaimedSessionsModel(1)); await context.SaveChangesAsync(); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful, $"Characters that already have "); Assert.AreEqual(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession, response.ResultCode); }
public static async Task Test_Controller_Produces_InvalidId_When_Empty() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); //act CharacterSessionEnterResponse response = await controller.EnterSession(5, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful); Assert.AreEqual(CharacterSessionEnterResponseCode.InvalidCharacterIdError, response.ResultCode); }
public async Task Test_ZoneServer_GetEndpoint_Succeeds_On_Known_Id(string endpoint, int port) { //arrange IServiceProvider provider = BuildServiceProvider <ZoneServerController>("Test", 1); ZoneServerController controller = provider.GetService <ZoneServerController>(); IZoneServerRepository repo = provider.GetService <IZoneServerRepository>(); await repo.TryCreateAsync(new ZoneInstanceEntryModel(endpoint, (short)port, 1)); //assert ResolveServiceEndpointResponse result = GetActionResultObject <ResolveServiceEndpointResponse>(await controller.GetServerEndpoint(1)); //assert Assert.True(result.isSuccessful); Assert.AreEqual(ResolveServiceEndpointResponseCode.Success, result.ResultCode); Assert.AreEqual(endpoint, result.Endpoint.EndpointAddress); Assert.AreEqual(port, result.Endpoint.EndpointPort); }
public async Task Test_ZoneServer_GetEndpoint_ReturnsFail_On_NoExistingZoneId(int zoneId) { //arrange IServiceProvider provider = BuildServiceProvider <ZoneServerController>("Test", 1); ZoneServerController controller = provider.GetService <ZoneServerController>(); IZoneServerRepository repo = provider.GetService <IZoneServerRepository>(); await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1)); await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1)); await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1)); //assert ResolveServiceEndpointResponse result = GetActionResultObject <ResolveServiceEndpointResponse>(await controller.GetServerEndpoint(25)); //assert Assert.False(result.isSuccessful); }
public static async Task Test_Controller_Creates_UnclaimedSession_On_OnEnterSession(int accountId) { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing")); //act: We also test that we can do it multiple times CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.True(response.isSuccessful); Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode); }
public async Task <IActionResult> UpdateCharacterLocation( [FromRoute(Name = "id")] int characterId, [FromBody] ZoneServerCharacterLocationSaveRequest saveRequest, [NotNull][FromServices] ICharacterLocationRepository locationRepository, [NotNull][FromServices] IZoneServerRepository zoneRepository) { if (locationRepository == null) { throw new ArgumentNullException(nameof(locationRepository)); } if (zoneRepository == null) { throw new ArgumentNullException(nameof(zoneRepository)); } //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond. if (!await CheckZoneAuthorizedToModifyCharacterData(characterId)) { return(Forbid()); } //TODO: This could fail if we unregistered. More gracefully handle that case. //Get world, we need it for location ZoneInstanceEntryModel zoneEntry = await zoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User)); //If world was deleted then they won't have location. //TODO: Is this the best way to deal with this? if (await locationRepository.ContainsAsync(characterId).ConfigureAwaitFalse()) { await locationRepository.UpdateAsync(characterId, BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId)) .ConfigureAwaitFalseVoid(); } else { await locationRepository.TryCreateAsync(BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId)) .ConfigureAwaitFalse(); } return(Ok()); }
public static async Task Test_Controller_Produces_InvalidId_When_Wrong_AccountId() { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 2); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0)); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.False(response.isSuccessful, $"Characters should not be able to create sessions when the accountid doesn't match."); Assert.AreEqual(response.ResultCode, CharacterSessionEnterResponseCode.InvalidCharacterIdError); }
public async Task <IActionResult> SaveFullCharacterDataAsync([FromRoute(Name = "id")] int characterId, [FromBody][NotNull] FullCharacterDataSaveRequest request, [NotNull][FromServices] ICharacterLocationRepository locationRepository, [NotNull][FromServices] IZoneServerRepository zoneRepository, [FromServices][NotNull] ICharacterDataRepository characterDataRepository) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (characterId <= 0) { throw new ArgumentOutOfRangeException(nameof(characterId)); } //TODO: Is it safe to not do this as a transaction?? //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond. if (!await CheckZoneAuthorizedToModifyCharacterData(characterId)) { return(Forbid()); } //Don't always want to save the position of the user. if (request.isPositionSaved) { await UpdateCharacterLocation(characterId, request.CharacterLocationData, locationRepository, zoneRepository); } //TODO: Probably need to handle this abit better and more data than just experience. //Entity data can now be saved. await UpdatePlayerData(characterId, new CharacterDataInstance(request.PlayerDataSnapshot.GetFieldValue <int>(PlayerObjectField.PLAYER_TOTAL_EXPERIENCE)), characterDataRepository); if (request.ShouldReleaseCharacterSession) { await CharacterSessionRepository.TryDeleteClaimedSession(characterId); } return(Ok()); }
public static async Task Test_Controller_Produces_SessionGranted_With_Zone_Id_If_UnclaimedSession_Exists(int accountId, int zoneid) { //arrange IServiceProvider serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId); CharacterSessionController controller = serviceProvider.GetService <CharacterSessionController>(); ICharacterRepository characterRepo = serviceProvider.GetService <ICharacterRepository>(); ICharacterSessionRepository sessionRepo = serviceProvider.GetService <ICharacterSessionRepository>(); ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>(); IZoneServerRepository zoneRepository = serviceProvider.GetService <IZoneServerRepository>(); await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing")); await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, zoneid)); //act CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository); //assert Assert.True(response.isSuccessful, $"Created sessions should be granted if no active account session or character session is claimed."); Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode); Assert.AreEqual(zoneid, response.ZoneId, $"Provided zone id was not the same as the session."); }
//[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //TODO: Eventually we'll need to auth these zoneservers. public async Task <IActionResult> RegisterZoneServer([FromBody] ZoneServerRegisterationRequest registerationRequest, [FromServices] IZoneInstanceWorkQueue instanceWorkQueue, [FromServices] IZoneServerRepository zoneRepo) { //TODO: JSON if (!ModelState.IsValid) { return(BadRequest()); } //Conceptually, just because there is no work doesn't mean this is an error //Requesting users who were trying to make an instance could have abandoned that request. //Instances may eventually free themselves after inactivity and attempt to reregister instead of shutting down at first //So we just want to say "Nothing to do right now" so they can sleep and maybe manually shutdown after a timeout period. if (instanceWorkQueue.isEmpty) { return(NoWorkForInstanceResponse()); } //The specification says this could complete immediately //with null if no works exists, there is technically a data race condition between checking isEmpty //and trying to dequeue so the result may not be predictible. ZoneInstanceWorkEntry zoneInstanceWorkEntry = await instanceWorkQueue.DequeueAsync() .ConfigureAwait(false); //TODO: If anything here fails after dequeueing we could lose CRITICAL data to keep things running //We need VERY good failure handling, and to reenter this work request into the queue somehow. //Otherwise the request for the instance will be lost and unhandled forever. ProjectVersionStage.AssertAlpha(); if (zoneInstanceWorkEntry == null) { return(NoWorkForInstanceResponse()); } //TODO: Validate endpoint //Since there IS work to do, we can't just tell the zone instance //We must register it into the zone server repo if (!await zoneRepo.TryCreateAsync(new ZoneInstanceEntryModel(registerationRequest.ZoneServerEndpoint.EndpointAddress, (short)registerationRequest.ZoneServerEndpoint.EndpointPort, zoneInstanceWorkEntry.WorldId))) { //As stated above, we need good handling for this else //we will encounter MAJOR issues. return(NoWorkForInstanceResponse()); } //Success return(Ok(new ZoneServerRegisterationResponse(zoneInstanceWorkEntry.WorldId))); }
public async Task<CharacterSessionEnterResponse> EnterSession([FromRoute(Name = "id")] int characterId, [FromServices] ICharacterLocationRepository characterLocationRepository, [FromServices] IZoneServerRepository zoneServerRepository) { if(!await IsCharacterIdValidForUser(characterId, CharacterRepository)) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.InvalidCharacterIdError); int accountId = ClaimsReader.GetAccountIdInt(User); //This checks to see if the account, not just the character, has an active session. //We do this before we check anything to reject quick even though the query behind this //may be abit more expensive //As a note, this checks (or should) CLAIMED SESSIONS. So, it won't prevent multiple session entries for an account //This is good because we actually use the left over session data to re-enter the instances on disconnect. if(await CharacterSessionRepository.AccountHasActiveSession(accountId)) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession); //They may have a session entry already, which is ok. So long as they don't have an active claimed session //which the above query checks for. bool hasSession = await CharacterSessionRepository.ContainsAsync(characterId); //We need to check active or not if (hasSession) { //It's possible that the session no longer matches the character's //persisted location. We should check their location and put them in the correct zone. //If it's active we can just retrieve the data and send them off on their way CharacterSessionModel sessionModel = await CharacterSessionRepository.RetrieveAsync(characterId, true); if (await characterLocationRepository.ContainsAsync(characterId)) { CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId); //They have a location, verify it matches the session if (locationModel.WorldId != sessionModel.ZoneEntry.WorldId) { //The location world and the session's world do not match, so remove the session. await CharacterSessionRepository.TryDeleteAsync(sessionModel.CharacterId); } else return new CharacterSessionEnterResponse(sessionModel.ZoneId); } else //TODO: Handle case when we have an inactive session that can be claimed return new CharacterSessionEnterResponse(sessionModel.ZoneId); } //If we didn't return above then we should be in a state where the below can handle this now. try { int targetSessionZoneId = 0; //TO know what zone we should connect to we need to check potential //character location. if (await characterLocationRepository.ContainsAsync(characterId)) { CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId); //We have no session so we need to find a zone that matches this server. ZoneInstanceEntryModel firstWithWorldId = await zoneServerRepository.FindFirstWithWorldId(locationModel.WorldId); //TODO: Should we request one be created for the user?? //There is NO instance available for this world. if (firstWithWorldId == null) { //Location is basically invalid since there is no running world await characterLocationRepository.TryDeleteAsync(locationModel.CharacterId); } else { targetSessionZoneId = firstWithWorldId.ZoneId; } } //Try to get into any zone if (targetSessionZoneId == 0) { ZoneInstanceEntryModel entryModel = await zoneServerRepository.AnyAsync(); if (entryModel != null) targetSessionZoneId = entryModel.ZoneId; } //Still zero means literally no zone servers are available. if(targetSessionZoneId == 0) return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError); if(await CharacterSessionRepository.TryCreateAsync(new CharacterSessionModel(characterId, targetSessionZoneId))) return new CharacterSessionEnterResponse(targetSessionZoneId); else return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError); } catch (Exception e) { if(Logger.IsEnabled(LogLevel.Error)) Logger.LogError($"Character with ID: {characterId} failed to create session. Potentially no default world assigned or World with session was deleted and orphaned. Reason: {e.Message}"); throw; } }