Release 2.2.1 (#799)

* chore: bump version to v2.2.1-preview

* Fix Platform filter being ignored in matchManually (#798)

* Fix platforms being ignored in manual match

* Update tests
This commit is contained in:
Simon
2025-11-20 16:59:31 +01:00
committed by GitHub
parent bb9e70b578
commit 5a3077d219
6 changed files with 196 additions and 18 deletions
@@ -614,7 +614,10 @@ class GameService(
// Step 3: Merge results into a single Game entity
val mergedGame = mergeResults(validResults, path, library)
// Step 4: If a replaceGameId is provided, set it (overwriting the existing entity)
// Step 4: Filter platforms to only those supported by the library
mergedGame.platforms = library.platforms.intersect(mergedGame.platforms.toSet()).toMutableList()
// Step 5: If a replaceGameId is provided, set it (overwriting the existing entity)
if (replaceGameId != null) {
val existingGame = getById(replaceGameId)
@@ -802,7 +805,7 @@ class GameService(
metadata.platforms?.takeIf { it.isNotEmpty() }?.let { platforms ->
if (!metadataMap.containsKey("platforms")) {
mergedGame.platforms = platforms.toList()
mergedGame.platforms = platforms.toMutableList()
metadataMap["platforms"] =
GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
@@ -30,7 +30,7 @@ class Game(
@ElementCollection(targetClass = Platform::class, fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
var platforms: List<Platform> = emptyList(),
var platforms: MutableList<Platform> = mutableListOf(),
var title: String? = null,
@@ -639,7 +639,7 @@ class GameServiceTest {
val game = createTestGame(1L)
game.metadata.originalIds = mapOf(pluginEntry to "123")
game.title = "Test Game"
game.platforms = listOf(Platform.PC_MICROSOFT_WINDOWS)
game.platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
// Add existing field metadata to simulate a previously matched game
game.metadata.fields["title"] = GameFieldMetadata(source = GameFieldPluginSource(plugin = pluginEntry))
game.metadata.fields["platforms"] = GameFieldMetadata(source = GameFieldPluginSource(plugin = pluginEntry))
@@ -658,6 +658,7 @@ class GameServiceTest {
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
every { library.platforms } returns mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
val result = gameService.update(game)
@@ -704,7 +705,7 @@ class GameServiceTest {
val game = createTestGame(1L)
game.metadata.originalIds = mapOf(pluginEntry to "123")
game.title = "User Modified Title"
game.platforms = listOf(Platform.PC_MICROSOFT_WINDOWS)
game.platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
game.metadata.fields["title"] = GameFieldMetadata(source = GameFieldUserSource(user = mockUser))
game.metadata.fields["platforms"] = GameFieldMetadata(source = GameFieldPluginSource(plugin = pluginEntry))
@@ -722,6 +723,7 @@ class GameServiceTest {
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
every { library.platforms } returns mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
val result = gameService.update(game)
@@ -1195,6 +1197,179 @@ class GameServiceTest {
assertEquals(listOf(Genre.ACTION), result.genres)
}
@Test
fun `matchManually should filter platforms to only those supported by the library`() {
val metadata = org.gameyfin.pluginapi.gamemetadata.GameMetadata(
originalId = "123",
title = "Multi-Platform Game",
platforms = setOf(
Platform.PC_MICROSOFT_WINDOWS,
Platform.PLAYSTATION_5,
Platform.XBOX_SERIES_X_S,
Platform.NINTENDO_SWITCH
)
)
val provider = spyk(TestProvider(metadata))
val pluginEntry = mockk<PluginManagementEntry>(relaxed = true) {
every { pluginId } returns "test-plugin"
every { priority } returns 1
}
val originalIds = mapOf(
provider.javaClass.name to ExternalProviderIdDto("test-plugin", "123")
)
val path = Path.of("/test/game.exe")
// Library only supports PC and PlayStation 5
val restrictedLibrary = mockk<Library>(relaxed = true) {
every { id } returns 1L
every { platforms } returns mutableListOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5)
}
every { pluginManager.getExtensions(GameMetadataProvider::class.java) } returns listOf(provider)
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
val result = gameService.matchManually(originalIds, path, restrictedLibrary, null, persist = false)
assertNotNull(result)
assertEquals("Multi-Platform Game", result.title)
// Platforms should be filtered to only PC and PlayStation 5
assertEquals(2, result.platforms.size)
assertEquals(
setOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5),
result.platforms.toSet()
)
}
@Test
fun `matchManually should return empty platforms when library platforms don't match metadata platforms`() {
val metadata = org.gameyfin.pluginapi.gamemetadata.GameMetadata(
originalId = "123",
title = "Console Exclusive Game",
platforms = setOf(Platform.PLAYSTATION_5, Platform.XBOX_SERIES_X_S)
)
val provider = spyk(TestProvider(metadata))
val pluginEntry = mockk<PluginManagementEntry>(relaxed = true) {
every { pluginId } returns "test-plugin"
every { priority } returns 1
}
val originalIds = mapOf(
provider.javaClass.name to ExternalProviderIdDto("test-plugin", "123")
)
val path = Path.of("/test/game.exe")
// Library only supports PC
val pcOnlyLibrary = mockk<Library>(relaxed = true) {
every { id } returns 1L
every { platforms } returns mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
}
every { pluginManager.getExtensions(GameMetadataProvider::class.java) } returns listOf(provider)
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
val result = gameService.matchManually(originalIds, path, pcOnlyLibrary, null, persist = false)
assertNotNull(result)
assertEquals("Console Exclusive Game", result.title)
// Platforms should be empty since there's no intersection
assertEquals(0, result.platforms.size)
}
@Test
fun `matchManually should preserve all platforms when library platforms list is empty`() {
val metadata = org.gameyfin.pluginapi.gamemetadata.GameMetadata(
originalId = "123",
title = "Multi-Platform Game",
platforms = setOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5)
)
val provider = spyk(TestProvider(metadata))
val pluginEntry = mockk<PluginManagementEntry>(relaxed = true) {
every { pluginId } returns "test-plugin"
every { priority } returns 1
}
val originalIds = mapOf(
provider.javaClass.name to ExternalProviderIdDto("test-plugin", "123")
)
val path = Path.of("/test/game.exe")
// Library with no platform restrictions
val unrestrictedLibrary = mockk<Library>(relaxed = true) {
every { id } returns 1L
every { platforms } returns mutableListOf()
}
every { pluginManager.getExtensions(GameMetadataProvider::class.java) } returns listOf(provider)
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
val result = gameService.matchManually(originalIds, path, unrestrictedLibrary, null, persist = false)
assertNotNull(result)
assertEquals("Multi-Platform Game", result.title)
// When library has no platform restrictions, result should have no platforms (empty intersect empty)
assertEquals(0, result.platforms.size)
}
@Test
fun `matchManually should filter platforms when replacing an existing game`() {
val metadata = org.gameyfin.pluginapi.gamemetadata.GameMetadata(
originalId = "123",
title = "Updated Game",
platforms = setOf(
Platform.PC_MICROSOFT_WINDOWS,
Platform.PLAYSTATION_5,
Platform.XBOX_SERIES_X_S
)
)
val provider = spyk(TestProvider(metadata))
val pluginEntry = mockk<PluginManagementEntry>(relaxed = true) {
every { pluginId } returns "test-plugin"
every { priority } returns 1
}
val originalIds = mapOf(
provider.javaClass.name to ExternalProviderIdDto("test-plugin", "123")
)
val path = Path.of("/test/game.exe")
val replaceGameId = 5L
val existingGame = createTestGame(replaceGameId)
// Library only supports PC and PlayStation 5
val restrictedLibrary = mockk<Library>(relaxed = true) {
every { id } returns 1L
every { platforms } returns mutableListOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5)
}
every { pluginManager.getExtensions(GameMetadataProvider::class.java) } returns listOf(provider)
every { pluginService.getPluginManagementEntry(provider.javaClass) } returns pluginEntry
every { gameRepository.findByIdOrNull(replaceGameId) } returns existingGame
every { imageService.createOrGet(any()) } returns mockk(relaxed = true)
every { filesystemService.calculateFileSize(any()) } returns 1000L
val result = gameService.matchManually(originalIds, path, restrictedLibrary, replaceGameId, persist = false)
assertNotNull(result)
assertEquals(replaceGameId, result.id)
assertEquals("Updated Game", result.title)
// Platforms should be filtered even when replacing
assertEquals(2, result.platforms.size)
assertEquals(
setOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5),
result.platforms.toSet()
)
}
private fun createTestGame(id: Long?, title: String = "Test Game"): Game {
return Game(
id = id,
@@ -1202,7 +1377,7 @@ class GameServiceTest {
updatedAt = Instant.now(),
library = library,
title = title,
platforms = listOf(Platform.PC_MICROSOFT_WINDOWS),
platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS),
coverImage = mockk<Image>(),
headerImage = mockk<Image>(),
comment = "Comment",
@@ -159,7 +159,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = emptyList(),
platforms = mutableListOf(),
metadata = org.gameyfin.app.games.entities.GameMetadata(path = "/test/path")
)
@@ -183,7 +183,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = emptyList(),
platforms = mutableListOf(),
metadata = org.gameyfin.app.games.entities.GameMetadata(path = "/test/path")
)
@@ -208,7 +208,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = emptyList(),
platforms = mutableListOf(),
release = releaseInstant,
metadata = org.gameyfin.app.games.entities.GameMetadata(path = "/test/path")
)
@@ -227,7 +227,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = emptyList(),
platforms = mutableListOf(),
videoUrls = listOf(URI("https://example.com/video1"), URI("https://example.com/video2")),
metadata = org.gameyfin.app.games.entities.GameMetadata(path = "/test/path")
)
@@ -255,7 +255,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = emptyList(),
platforms = mutableListOf(),
images = mutableListOf(image1, image2, image3),
metadata = org.gameyfin.app.games.entities.GameMetadata(path = "/test/path")
)
@@ -332,7 +332,7 @@ class GameExtensionsTest {
updatedAt = Instant.now(),
library = library,
title = "Test Game",
platforms = listOf(Platform.PC_MICROSOFT_WINDOWS),
platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS),
coverImage = coverImage,
headerImage = headerImage,
comment = "Test comment",
@@ -639,7 +639,7 @@ class GameRequestServiceTest {
@Test
fun `onGameCreated should complete matching requests`() {
val game = createTestGame(1L, "Test Game")
game.platforms = listOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5)
game.platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5)
val request1 = createTestGameRequest(1L, "Test Game")
request1.status = GameRequestStatus.PENDING
val request2 = createTestGameRequest(2L, "Test Game")
@@ -691,7 +691,7 @@ class GameRequestServiceTest {
@Test
fun `onGameCreated should handle multiple platforms`() {
val game = createTestGame(1L, "Multi Platform Game")
game.platforms = listOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5, Platform.XBOX_SERIES_X_S)
game.platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS, Platform.PLAYSTATION_5, Platform.XBOX_SERIES_X_S)
val request1 = createTestGameRequest(1L, "Multi Platform Game")
val request2 = createTestGameRequest(2L, "Multi Platform Game")
val request3 = createTestGameRequest(3L, "Multi Platform Game")
@@ -727,7 +727,7 @@ class GameRequestServiceTest {
@Test
fun `onGameUpdated should complete matching requests`() {
val game = createTestGame(1L, "Updated Game")
game.platforms = listOf(Platform.NINTENDO_SWITCH)
game.platforms = mutableListOf(Platform.NINTENDO_SWITCH)
val request = createTestGameRequest(1L, "Updated Game")
request.status = GameRequestStatus.PENDING
@@ -751,7 +751,7 @@ class GameRequestServiceTest {
@Test
fun `onGameCreated should not update already fulfilled requests`() {
val game = createTestGame(1L, "Test Game")
game.platforms = listOf(Platform.PC_MICROSOFT_WINDOWS)
game.platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS)
val request = createTestGameRequest(1L, "Test Game")
request.status = GameRequestStatus.FULFILLED
@@ -810,7 +810,7 @@ class GameRequestServiceTest {
updatedAt = Instant.now(),
library = mockk(relaxed = true),
title = title,
platforms = listOf(Platform.PC_MICROSOFT_WINDOWS),
platforms = mutableListOf(Platform.PC_MICROSOFT_WINDOWS),
coverImage = null,
headerImage = null,
comment = null,
+1 -1
View File
@@ -6,7 +6,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.nio.file.Files
group = "org.gameyfin"
version = "2.2.0"
version = "2.2.1-preview"
allprojects {
repositories {