2022-12-22 09:14:37 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System ;
using System.Collections.Generic ;
2022-12-22 10:10:33 +00:00
using System.Linq ;
2022-12-22 09:14:37 +00:00
using NUnit.Framework ;
using osu.Framework.Allocation ;
using osu.Framework.Testing ;
using osu.Game.Models ;
using osu.Game.Online.API ;
using osu.Game.Online.API.Requests ;
using osu.Game.Online.API.Requests.Responses ;
using osu.Game.Online.Solo ;
using osu.Game.Online.Spectator ;
2022-12-22 10:10:33 +00:00
using osu.Game.Rulesets ;
2022-12-22 09:14:37 +00:00
using osu.Game.Rulesets.Osu ;
using osu.Game.Scoring ;
using osu.Game.Users ;
namespace osu.Game.Tests.Visual.Online
{
[HeadlessTest]
public partial class TestSceneSoloStatisticsWatcher : OsuTestScene
{
protected override bool UseOnlineAPI = > false ;
private SoloStatisticsWatcher watcher = null ! ;
[Resolved]
private SpectatorClient spectatorClient { get ; set ; } = null ! ;
private DummyAPIAccess dummyAPI = > ( DummyAPIAccess ) API ;
private Action < GetUsersRequest > ? handleGetUsersRequest ;
private Action < GetUserRequest > ? handleGetUserRequest ;
2022-12-28 06:50:10 +00:00
private IDisposable ? subscription ;
2022-12-22 10:10:33 +00:00
private readonly Dictionary < ( int userId , string rulesetName ) , UserStatistics > serverSideStatistics = new Dictionary < ( int userId , string rulesetName ) , UserStatistics > ( ) ;
2022-12-22 09:14:37 +00:00
[SetUpSteps]
public void SetUpSteps ( )
{
2022-12-22 10:10:33 +00:00
AddStep ( "clear server-side stats" , ( ) = > serverSideStatistics . Clear ( ) ) ;
2022-12-22 09:14:37 +00:00
AddStep ( "set up request handling" , ( ) = >
{
handleGetUserRequest = null ;
handleGetUsersRequest = null ;
dummyAPI . HandleRequest = request = >
{
switch ( request )
{
case GetUsersRequest getUsersRequest :
2022-12-22 10:10:33 +00:00
if ( handleGetUsersRequest ! = null )
{
handleGetUsersRequest ? . Invoke ( getUsersRequest ) ;
}
else
{
int userId = getUsersRequest . UserIds . Single ( ) ;
var response = new GetUsersResponse
{
Users = new List < APIUser >
{
new APIUser
{
Id = userId ,
RulesetsStatistics = new Dictionary < string , UserStatistics >
{
["osu"] = tryGetStatistics ( userId , "osu" ) ,
["taiko"] = tryGetStatistics ( userId , "taiko" ) ,
["fruits"] = tryGetStatistics ( userId , "fruits" ) ,
["mania"] = tryGetStatistics ( userId , "mania" ) ,
}
}
}
} ;
getUsersRequest . TriggerSuccess ( response ) ;
}
2022-12-22 09:14:37 +00:00
return true ;
case GetUserRequest getUserRequest :
2022-12-22 10:10:33 +00:00
if ( handleGetUserRequest ! = null )
{
handleGetUserRequest . Invoke ( getUserRequest ) ;
}
else
{
int userId = int . Parse ( getUserRequest . Lookup ) ;
string rulesetName = getUserRequest . Ruleset . ShortName ;
var response = new APIUser
{
Id = userId ,
Statistics = tryGetStatistics ( userId , rulesetName )
} ;
getUserRequest . TriggerSuccess ( response ) ;
}
2022-12-22 09:14:37 +00:00
return true ;
default :
return false ;
}
} ;
} ) ;
AddStep ( "create watcher" , ( ) = >
{
Child = watcher = new SoloStatisticsWatcher ( ) ;
} ) ;
}
2022-12-22 10:10:33 +00:00
private UserStatistics tryGetStatistics ( int userId , string rulesetName )
= > serverSideStatistics . TryGetValue ( ( userId , rulesetName ) , out var stats ) ? stats : new UserStatistics ( ) ;
2022-12-22 09:14:37 +00:00
[Test]
public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed ( )
{
2022-12-22 10:10:33 +00:00
int userId = getUserId ( ) ;
long scoreId = getScoreId ( ) ;
setUpUser ( userId ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 09:14:37 +00:00
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
2022-12-22 10:10:33 +00:00
AddAssert ( "values before are correct" , ( ) = > update ! . Before . TotalScore , ( ) = > Is . EqualTo ( 4_000_000 ) ) ;
AddAssert ( "values after are correct" , ( ) = > update ! . After . TotalScore , ( ) = > Is . EqualTo ( 5_000_000 ) ) ;
2022-12-22 09:14:37 +00:00
}
[Test]
public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded ( )
{
2022-12-22 10:10:33 +00:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
// note ordering - in this test processing completes *before* the registration is added.
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 09:14:37 +00:00
SoloStatisticsUpdate ? update = null ;
2022-12-22 10:10:33 +00:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 09:14:37 +00:00
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
2022-12-22 10:10:33 +00:00
AddAssert ( "values before are correct" , ( ) = > update ! . Before . TotalScore , ( ) = > Is . EqualTo ( 4_000_000 ) ) ;
AddAssert ( "values after are correct" , ( ) = > update ! . After . TotalScore , ( ) = > Is . EqualTo ( 5_000_000 ) ) ;
2022-12-22 09:14:37 +00:00
}
[Test]
public void TestStatisticsUpdateNotFiredIfUserLoggedOut ( )
{
2022-12-22 10:10:33 +00:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 09:14:37 +00:00
AddStep ( "log out user" , ( ) = > dummyAPI . Logout ( ) ) ;
2022-12-22 10:10:33 +00:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 09:14:37 +00:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
2022-12-22 10:10:33 +00:00
AddStep ( "log in user" , ( ) = > dummyAPI . Login ( "user" , "password" ) ) ;
2022-12-22 09:14:37 +00:00
}
[Test]
public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn ( )
{
2022-12-22 10:10:33 +00:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
AddStep ( "change user" , ( ) = > dummyAPI . LocalUser . Value = new APIUser { Id = getUserId ( ) } ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 09:14:37 +00:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
}
[Test]
public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch ( )
{
2022-12-22 10:10:33 +00:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
AddStep ( "signal another score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , getScoreId ( ) ) ) ;
2022-12-22 09:14:37 +00:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
}
2022-12-22 18:46:41 +00:00
// the behaviour exercised in this test may not be final, it is mostly assumed for simplicity.
// in the long run we may want each score's update to be entirely isolated from others, rather than have prior unobserved updates merge into the latest.
[Test]
public void TestIgnoredScoreUpdateIsMergedIntoNextOne ( )
{
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
long firstScoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , firstScoreId ) ) ;
long secondScoreId = getScoreId ( ) ;
feignScoreProcessing ( userId , ruleset , 6_000_000 ) ;
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( secondScoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , secondScoreId ) ) ;
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
AddAssert ( "values before are correct" , ( ) = > update ! . Before . TotalScore , ( ) = > Is . EqualTo ( 4_000_000 ) ) ;
AddAssert ( "values after are correct" , ( ) = > update ! . After . TotalScore , ( ) = > Is . EqualTo ( 6_000_000 ) ) ;
}
2022-12-28 06:50:10 +00:00
[Test]
public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal ( )
{
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
SoloStatisticsUpdate ? update = null ;
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
AddStep ( "unsubscribe" , ( ) = > subscription ! . Dispose ( ) ) ;
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
}
2022-12-22 10:10:33 +00:00
private int nextUserId = 2000 ;
private long nextScoreId = 50000 ;
private int getUserId ( ) = > + + nextUserId ;
private long getScoreId ( ) = > + + nextScoreId ;
private void setUpUser ( int userId )
2022-12-22 09:14:37 +00:00
{
2022-12-22 10:10:33 +00:00
AddStep ( "fetch initial stats" , ( ) = >
2022-12-22 09:14:37 +00:00
{
2022-12-22 10:10:33 +00:00
serverSideStatistics [ ( userId , "osu" ) ] = new UserStatistics { TotalScore = 4_000_000 } ;
serverSideStatistics [ ( userId , "taiko" ) ] = new UserStatistics { TotalScore = 3_000_000 } ;
serverSideStatistics [ ( userId , "fruits" ) ] = new UserStatistics { TotalScore = 2_000_000 } ;
serverSideStatistics [ ( userId , "mania" ) ] = new UserStatistics { TotalScore = 1_000_000 } ;
dummyAPI . LocalUser . Value = new APIUser { Id = userId } ;
} ) ;
}
private void registerForUpdates ( long scoreId , RulesetInfo rulesetInfo , Action < SoloStatisticsUpdate > onUpdateReady ) = >
2022-12-28 06:50:10 +00:00
AddStep ( "register for updates" , ( ) = > subscription = watcher . RegisterForStatisticsUpdateAfter (
2022-12-22 10:10:33 +00:00
new ScoreInfo ( Beatmap . Value . BeatmapInfo , new OsuRuleset ( ) . RulesetInfo , new RealmUser ( ) )
2022-12-22 09:14:37 +00:00
{
2022-12-22 10:10:33 +00:00
Ruleset = rulesetInfo ,
OnlineID = scoreId
} ,
onUpdateReady ) ) ;
2022-12-22 09:14:37 +00:00
2022-12-22 10:10:33 +00:00
private void feignScoreProcessing ( int userId , RulesetInfo rulesetInfo , long newTotalScore )
= > AddStep ( "feign score processing" , ( ) = > serverSideStatistics [ ( userId , rulesetInfo . ShortName ) ] = new UserStatistics { TotalScore = newTotalScore } ) ;
2022-12-22 09:14:37 +00:00
}
}