1+ using Refresh . Database . Models . Assets ;
2+ using Refresh . Database . Models . Photos ;
3+ using Refresh . Database . Models . Users ;
4+ using Refresh . Database . Models . Workers ;
5+ using Refresh . Interfaces . Workers . Migrations ;
6+ using Refresh . Workers . State ;
7+
8+ namespace RefreshTests . GameServer . Tests . Workers ;
9+
10+ public class PhotoMigrationTests : GameServerTest
11+ {
12+ private const string TEST_ASSET_HASH = "0ec63b140374ba704a58fa0c743cb357683313dd" ;
13+
14+ [ Test ]
15+ public void MigratesPhotoSubjectsButNotMultipleTimes ( )
16+ {
17+ using TestContext context = this . GetServer ( ) ;
18+ GameUser user = context . CreateUser ( ) ;
19+ context . Database . AddAssetToDatabase ( new ( )
20+ {
21+ AssetHash = TEST_ASSET_HASH ,
22+ AssetType = GameAssetType . Png ,
23+ AssetFormat = GameAssetFormat . Unknown ,
24+ OriginalUploader = user ,
25+ UploadDate = new ( new DateTime ( 2026 , 3 , 11 ) , TimeSpan . Zero ) ,
26+ } ) ;
27+
28+ for ( int i = 0 ; i < 10 ; i ++ )
29+ {
30+ context . Database . Add ( new GamePhoto ( )
31+ {
32+ TakenAt = new ( new DateTime ( 2026 , 3 , 18 ) , TimeSpan . Zero ) ,
33+ PublishedAt = new ( new DateTime ( 2026 , 3 , 18 ) , TimeSpan . Zero ) ,
34+ PublisherId = user . UserId ,
35+ SmallAssetHash = TEST_ASSET_HASH ,
36+ MediumAssetHash = TEST_ASSET_HASH ,
37+ LargeAssetHash = TEST_ASSET_HASH ,
38+ PlanHash = "plan" ,
39+ LevelType = "pod" ,
40+ OriginalLevelId = 0 ,
41+ OriginalLevelName = "" ,
42+
43+ #pragma warning disable CS0618 // obsoletion
44+ Subject1DisplayName = "p" ,
45+ Subject1Bounds = [ 3 , 1 , 4 , 1 ] ,
46+ Subject2DisplayName = "i" ,
47+ Subject2Bounds = [ 5 , 9 , 2 , 7 ] ,
48+ #pragma warning restore CS0618
49+ } ) ;
50+ }
51+
52+ context . Database . SaveChanges ( ) ;
53+ Assert . That ( context . Database . GetTotalPhotoCount ( ) , Is . EqualTo ( 10 ) ) ;
54+
55+ MoveSubjectsOutOfGamePhotosMigration job = new ( ) ;
56+ context . Database . UpdateOrCreateJobState ( typeof ( MoveSubjectsOutOfGamePhotosMigration ) . Name , new MigrationJobState ( )
57+ {
58+ Total = 10
59+ } , WorkerClass . Refresh ) ;
60+ context . Database . Refresh ( ) ;
61+
62+ object ? stateObject = context . Database . GetJobState ( typeof ( MoveSubjectsOutOfGamePhotosMigration ) . Name , typeof ( MigrationJobState ) , WorkerClass . Refresh ) ;
63+ Assert . That ( stateObject , Is . Not . Null ) ;
64+ job . JobState = stateObject ! ;
65+
66+ // Migrate - First cycle
67+ job . ExecuteJob ( context . GetWorkContext ( ) ) ;
68+ context . Database . Refresh ( ) ;
69+
70+ Assert . That ( context . Database . GetTotalPhotoCount ( ) , Is . EqualTo ( 10 ) ) ;
71+ foreach ( GamePhoto photo in context . Database . GetRecentPhotos ( 10 , 0 ) . Items . ToArray ( ) )
72+ {
73+ Assert . That ( photo . Subjects . Count , Is . EqualTo ( 2 ) ) ;
74+ Assert . That ( context . Database . GetTotalSubjectsInPhoto ( photo ) , Is . EqualTo ( 2 ) ) ;
75+
76+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "p" ) , Is . EqualTo ( 1 ) ) ;
77+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "i" ) , Is . EqualTo ( 1 ) ) ;
78+
79+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 1 ) , Is . EqualTo ( 1 ) ) ;
80+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 2 ) , Is . EqualTo ( 1 ) ) ;
81+ }
82+
83+ stateObject = context . Database . GetJobState ( typeof ( MoveSubjectsOutOfGamePhotosMigration ) . Name , typeof ( MigrationJobState ) , WorkerClass . Refresh ) ;
84+ Assert . That ( stateObject , Is . Not . Null ) ;
85+
86+ MigrationJobState jobState = ( MigrationJobState ) stateObject ! ;
87+ Assert . That ( jobState . Processed , Is . EqualTo ( 10 ) ) ;
88+ Assert . That ( jobState . Total , Is . EqualTo ( 10 ) ) ;
89+ Assert . That ( jobState . Complete , Is . True ) ;
90+
91+ // Add new, unmigrated photos
92+ for ( int i = 0 ; i < 5 ; i ++ )
93+ {
94+ context . Database . Add ( new GamePhoto ( )
95+ {
96+ TakenAt = new ( new DateTime ( 2026 , 3 , 27 ) , TimeSpan . Zero ) ,
97+ PublishedAt = new ( new DateTime ( 2026 , 3 , 27 ) , TimeSpan . Zero ) ,
98+ PublisherId = user . UserId ,
99+ SmallAssetHash = TEST_ASSET_HASH ,
100+ MediumAssetHash = TEST_ASSET_HASH ,
101+ LargeAssetHash = TEST_ASSET_HASH ,
102+ PlanHash = "plan" ,
103+ LevelType = "pod" ,
104+ OriginalLevelId = 0 ,
105+ OriginalLevelName = "" ,
106+
107+ #pragma warning disable CS0618 // obsoletion
108+ Subject1DisplayName = "d" ,
109+ Subject1Bounds = [ 1 , 2 , 3 , 4 ] ,
110+ Subject2DisplayName = "a" ,
111+ Subject2Bounds = [ 1 , 2 , 3 , 4 ] ,
112+ Subject3DisplayName = "n" ,
113+ Subject3Bounds = [ 1 , 2 , 3 , 4 ] ,
114+ Subject4DisplayName = "k" ,
115+ Subject4Bounds = [ 1 , 2 , 3 , 4 ] ,
116+ #pragma warning restore CS0618
117+ } ) ;
118+ }
119+ context . Database . SaveChanges ( ) ;
120+ Assert . That ( context . Database . GetTotalPhotoCount ( ) , Is . EqualTo ( 15 ) ) ;
121+ context . Database . Refresh ( ) ;
122+
123+ // Reset state, to test both the migrated photos not having their subjects re-migrated (should be skipped entirely due to SortAndFilter()),
124+ // and to also test the new photos being migrated
125+ jobState . Total = 5 ;
126+ jobState . Processed = 0 ;
127+ job . JobState = jobState ;
128+ context . Database . UpdateOrCreateJobState ( typeof ( MoveSubjectsOutOfGamePhotosMigration ) . Name , jobState , WorkerClass . Refresh ) ;
129+ context . Database . Refresh ( ) ;
130+
131+ // Migrate - Second cycle
132+ job . ExecuteJob ( context . GetWorkContext ( ) ) ;
133+ context . Database . Refresh ( ) ;
134+
135+ Assert . That ( context . Database . GetTotalPhotoCount ( ) , Is . EqualTo ( 15 ) ) ;
136+ foreach ( GamePhoto photo in context . Database . GetRecentPhotos ( 10 , 5 ) . Items . ToArray ( ) )
137+ {
138+ // The photos from the first cycle still only have 2 subjects each
139+ Assert . That ( photo . Subjects . Count , Is . EqualTo ( 2 ) ) ;
140+ Assert . That ( context . Database . GetTotalSubjectsInPhoto ( photo ) , Is . EqualTo ( 2 ) ) ;
141+
142+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "p" ) , Is . EqualTo ( 1 ) ) ;
143+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "i" ) , Is . EqualTo ( 1 ) ) ;
144+
145+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 1 ) , Is . EqualTo ( 1 ) ) ;
146+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 2 ) , Is . EqualTo ( 1 ) ) ;
147+ }
148+
149+ foreach ( GamePhoto photo in context . Database . GetRecentPhotos ( 5 , 0 ) . Items . ToArray ( ) )
150+ {
151+ Assert . That ( photo . Subjects . Count , Is . EqualTo ( 4 ) ) ;
152+ Assert . That ( context . Database . GetTotalSubjectsInPhoto ( photo ) , Is . EqualTo ( 4 ) ) ;
153+
154+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "d" ) , Is . EqualTo ( 1 ) ) ;
155+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "a" ) , Is . EqualTo ( 1 ) ) ;
156+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "n" ) , Is . EqualTo ( 1 ) ) ;
157+ Assert . That ( photo . Subjects . Count ( s => s . DisplayName == "k" ) , Is . EqualTo ( 1 ) ) ;
158+
159+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 1 ) , Is . EqualTo ( 1 ) ) ;
160+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 2 ) , Is . EqualTo ( 1 ) ) ;
161+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 3 ) , Is . EqualTo ( 1 ) ) ;
162+ Assert . That ( photo . Subjects . Count ( s => s . PlayerId == 4 ) , Is . EqualTo ( 1 ) ) ;
163+ }
164+
165+ stateObject = context . Database . GetJobState ( typeof ( MoveSubjectsOutOfGamePhotosMigration ) . Name , typeof ( MigrationJobState ) , WorkerClass . Refresh ) ;
166+ Assert . That ( stateObject , Is . Not . Null ) ;
167+
168+ jobState = ( MigrationJobState ) stateObject ! ;
169+ Assert . That ( jobState . Processed , Is . EqualTo ( 5 ) ) ;
170+ Assert . That ( jobState . Total , Is . EqualTo ( 5 ) ) ;
171+ Assert . That ( jobState . Complete , Is . True ) ;
172+ }
173+ }
0 commit comments