2022-12-22 17:14:37 +08: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 18:10:33 +08:00
using System.Linq ;
2022-12-22 17:14:37 +08:00
using NUnit.Framework ;
using osu.Framework.Allocation ;
using osu.Framework.Testing ;
using osu.Game.Models ;
2024-02-11 12:38:33 +08:00
using osu.Game.Online ;
2022-12-22 17:14:37 +08:00
using osu.Game.Online.API ;
using osu.Game.Online.API.Requests ;
using osu.Game.Online.API.Requests.Responses ;
using osu.Game.Online.Spectator ;
2022-12-22 18:10:33 +08:00
using osu.Game.Rulesets ;
2022-12-22 17:14:37 +08:00
using osu.Game.Rulesets.Osu ;
using osu.Game.Scoring ;
using osu.Game.Users ;
namespace osu.Game.Tests.Visual.Online
{
[HeadlessTest]
2024-02-29 06:11:01 +08:00
public partial class TestSceneUserStatisticsWatcher : OsuTestScene
2022-12-22 17:14:37 +08:00
{
protected override bool UseOnlineAPI = > false ;
2024-10-25 14:25:32 +08:00
private LocalUserStatisticsProvider statisticsProvider = null ! ;
2024-02-23 02:50:46 +08:00
private UserStatisticsWatcher watcher = null ! ;
2022-12-22 17:14:37 +08:00
[Resolved]
private SpectatorClient spectatorClient { get ; set ; } = null ! ;
private DummyAPIAccess dummyAPI = > ( DummyAPIAccess ) API ;
private Action < GetUsersRequest > ? handleGetUsersRequest ;
private Action < GetUserRequest > ? handleGetUserRequest ;
2022-12-22 18:10:33 +08:00
private readonly Dictionary < ( int userId , string rulesetName ) , UserStatistics > serverSideStatistics = new Dictionary < ( int userId , string rulesetName ) , UserStatistics > ( ) ;
2022-12-22 17:14:37 +08:00
[SetUpSteps]
public void SetUpSteps ( )
{
2022-12-22 18:10:33 +08:00
AddStep ( "clear server-side stats" , ( ) = > serverSideStatistics . Clear ( ) ) ;
2022-12-22 17:14:37 +08:00
AddStep ( "set up request handling" , ( ) = >
{
handleGetUserRequest = null ;
handleGetUsersRequest = null ;
dummyAPI . HandleRequest = request = >
{
switch ( request )
{
case GetUsersRequest getUsersRequest :
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
return true ;
case GetUserRequest getUserRequest :
2022-12-22 18:10:33 +08:00
if ( handleGetUserRequest ! = null )
{
handleGetUserRequest . Invoke ( getUserRequest ) ;
}
else
{
int userId = int . Parse ( getUserRequest . Lookup ) ;
2024-01-25 05:01:59 +08:00
string rulesetName = getUserRequest . Ruleset ! . ShortName ;
2022-12-22 18:10:33 +08:00
var response = new APIUser
{
Id = userId ,
Statistics = tryGetStatistics ( userId , rulesetName )
} ;
getUserRequest . TriggerSuccess ( response ) ;
}
2022-12-22 17:14:37 +08:00
return true ;
default :
return false ;
}
} ;
} ) ;
AddStep ( "create watcher" , ( ) = >
{
2024-02-11 12:38:33 +08:00
Clear ( ) ;
Add ( statisticsProvider = new LocalUserStatisticsProvider ( ) ) ;
2024-10-25 14:25:32 +08:00
Add ( watcher = new UserStatisticsWatcher ( statisticsProvider ) ) ;
2022-12-22 17:14:37 +08:00
} ) ;
}
2022-12-22 18:10:33 +08:00
private UserStatistics tryGetStatistics ( int userId , string rulesetName )
= > serverSideStatistics . TryGetValue ( ( userId , rulesetName ) , out var stats ) ? stats : new UserStatistics ( ) ;
2022-12-22 17:14:37 +08:00
[Test]
public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed ( )
{
2022-12-22 18:10:33 +08:00
int userId = getUserId ( ) ;
long scoreId = getScoreId ( ) ;
setUpUser ( userId ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 17:14:37 +08:00
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-22 18:10:33 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 17:14:37 +08:00
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
}
[Test]
public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded ( )
{
2022-12-22 18:10:33 +08:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-22 18:10:33 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 17:14:37 +08:00
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
}
[Test]
public void TestStatisticsUpdateNotFiredIfUserLoggedOut ( )
{
2022-12-22 18:10:33 +08:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 17:14:37 +08:00
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-22 18:10:33 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 17:14:37 +08:00
AddStep ( "log out user" , ( ) = > dummyAPI . Logout ( ) ) ;
2022-12-22 18:10:33 +08:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 17:14:37 +08:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
2022-12-22 18:10:33 +08:00
2024-01-24 01:04:41 +08:00
AddStep ( "log in user" , ( ) = >
{
dummyAPI . Login ( "user" , "password" ) ;
dummyAPI . AuthenticateSecondFactor ( "abcdefgh" ) ;
} ) ;
2022-12-22 17:14:37 +08:00
}
[Test]
public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn ( )
{
2022-12-22 18:10:33 +08:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 17:14:37 +08:00
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-22 18:10:33 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
AddStep ( "change user" , ( ) = > dummyAPI . LocalUser . Value = new APIUser { Id = getUserId ( ) } ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
2022-12-22 17:14:37 +08:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
}
[Test]
public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch ( )
{
2022-12-22 18:10:33 +08:00
int userId = getUserId ( ) ;
setUpUser ( userId ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
long scoreId = getScoreId ( ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2022-12-22 17:14:37 +08:00
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-22 18:10:33 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08:00
AddStep ( "signal another score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , getScoreId ( ) ) ) ;
2022-12-22 17:14:37 +08:00
AddWaitStep ( "wait a bit" , 5 ) ;
AddAssert ( "update not received" , ( ) = > update = = null ) ;
}
2022-12-23 02:46:41 +08: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 ) ;
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2022-12-23 02:46:41 +08:00
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 ) ) ;
}
2024-01-03 20:15:32 +08:00
[Test]
public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed ( )
{
int userId = getUserId ( ) ;
long scoreId = getScoreId ( ) ;
setUpUser ( userId ) ;
var ruleset = new OsuRuleset ( ) . RulesetInfo ;
2024-11-18 07:13:37 +08:00
ScoreBasedUserStatisticsUpdate ? update = null ;
2024-01-03 20:15:32 +08:00
registerForUpdates ( scoreId , ruleset , receivedUpdate = > update = receivedUpdate ) ;
feignScoreProcessing ( userId , ruleset , 5_000_000 ) ;
AddStep ( "signal score processed" , ( ) = > ( ( ISpectatorClient ) spectatorClient ) . UserScoreProcessed ( userId , scoreId ) ) ;
AddUntilStep ( "update received" , ( ) = > update ! = null ) ;
2024-11-18 08:13:23 +08:00
AddAssert ( "statistics values are correct" , ( ) = > statisticsProvider . GetStatisticsFor ( ruleset ) ! . TotalScore , ( ) = > Is . EqualTo ( 5_000_000 ) ) ;
2024-01-03 20:15:32 +08:00
}
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
{
2022-12-22 18:10:33 +08:00
AddStep ( "fetch initial stats" , ( ) = >
2022-12-22 17:14:37 +08:00
{
2022-12-22 18:10:33 +08: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 } ;
} ) ;
}
2024-11-18 07:13:37 +08:00
private void registerForUpdates ( long scoreId , RulesetInfo rulesetInfo , Action < ScoreBasedUserStatisticsUpdate > onUpdateReady ) = >
2024-02-09 17:41:36 +08:00
AddStep ( "register for updates" , ( ) = >
{
watcher . RegisterForStatisticsUpdateAfter (
new ScoreInfo ( Beatmap . Value . BeatmapInfo , new OsuRuleset ( ) . RulesetInfo , new RealmUser ( ) )
{
Ruleset = rulesetInfo ,
OnlineID = scoreId
} ) ;
watcher . LatestUpdate . BindValueChanged ( update = >
2022-12-22 17:14:37 +08:00
{
2024-02-09 17:41:36 +08:00
if ( update . NewValue ? . Score . OnlineID = = scoreId )
onUpdateReady . Invoke ( update . NewValue ) ;
} ) ;
} ) ;
2022-12-22 17:14:37 +08:00
2022-12-22 18:10:33 +08: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 17:14:37 +08:00
}
}