mirror of
https://github.com/gameyfin/gameyfin.git
synced 2026-01-09 06:33:51 +08:00
Release 2.3.2 (#831)
* Optimize performance of web UI while downloads are active * chore: bump version to v2.3.2-preview * Fix test * Fix GameCover not refreshing until reload * Bump actions/upload-artifact from 4 to 6 (#829) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/download-artifact from 5 to 7 (#830) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix login redirect issue when behind NPM (#832) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
400c4d1c61
commit
386374f39c
4
.github/workflows/docker-fix.yml
vendored
4
.github/workflows/docker-fix.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
- name: Upload build outputs
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-outputs
|
||||
path: |
|
||||
@ -51,7 +51,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download build outputs
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-outputs
|
||||
path: .
|
||||
|
||||
4
.github/workflows/docker-preview.yml
vendored
4
.github/workflows/docker-preview.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
- name: Upload build outputs
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-outputs
|
||||
path: |
|
||||
@ -83,7 +83,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build outputs
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-outputs
|
||||
path: .
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
jq ".version = \"$RELEASE_VERSION\"" app/package.json > app/package.json.tmp && mv app/package.json.tmp app/package.json
|
||||
|
||||
- name: Upload modified files
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: modified-files
|
||||
path: |
|
||||
@ -71,7 +71,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download modified files
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: modified-files
|
||||
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
- name: Upload build outputs
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-outputs
|
||||
path: |
|
||||
@ -114,12 +114,12 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download modified files
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: modified-files
|
||||
|
||||
- name: Download build outputs
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-outputs
|
||||
path: .
|
||||
@ -158,7 +158,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download modified files
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: modified-files
|
||||
|
||||
@ -189,7 +189,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download modified files
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: modified-files
|
||||
|
||||
|
||||
@ -22,6 +22,19 @@ const GameCoverComponent = ({game, size = 300, radius = "sm", interactive = fals
|
||||
const [isImageLoaded, setIsImageLoaded] = useState(isCached);
|
||||
const [blurhashUrl, setBlurhashUrl] = useState<string | undefined>(undefined);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const prevCoverIdRef = useRef<number | undefined>(game.cover?.id);
|
||||
|
||||
// Reset state when cover ID changes
|
||||
useEffect(() => {
|
||||
const currentCoverId = game.cover?.id;
|
||||
if (prevCoverIdRef.current !== currentCoverId) {
|
||||
prevCoverIdRef.current = currentCoverId;
|
||||
const newIsCached = currentCoverId ? loadedImagesCache.has(currentCoverId) : false;
|
||||
setIsImageLoaded(newIsCached);
|
||||
setBlurhashUrl(undefined);
|
||||
setShouldLoad(!lazy);
|
||||
}
|
||||
}, [game.cover?.id, lazy]);
|
||||
|
||||
// Generate blurhash placeholder image
|
||||
useEffect(() => {
|
||||
@ -116,9 +129,10 @@ const GameCoverComponent = ({game, size = 300, radius = "sm", interactive = fals
|
||||
};
|
||||
|
||||
// Memoize the component to prevent unnecessary re-renders
|
||||
// Only re-render if the game ID, size, radius, interactive, or lazy props change
|
||||
// Only re-render if the game ID, cover ID, size, radius, interactive, or lazy props change
|
||||
export const GameCover = memo(GameCoverComponent, (prevProps, nextProps) => {
|
||||
return prevProps.game.id === nextProps.game.id &&
|
||||
prevProps.game.cover?.id === nextProps.game.cover?.id &&
|
||||
prevProps.size === nextProps.size &&
|
||||
prevProps.radius === nextProps.radius &&
|
||||
prevProps.interactive === nextProps.interactive &&
|
||||
|
||||
@ -135,9 +135,6 @@ export default function MainLayout() {
|
||||
radius="full"
|
||||
isIconOnly
|
||||
className="gradient-primary"
|
||||
/* This is hacky but works since "/loginredirect" is not configured and returns 401 for not logged-in users.
|
||||
This triggers Hilla to redirect to the correct login page (integrated or SSO) automatically.
|
||||
Otherwise, SSO login would not be possible if we redirect to "/login" directly */
|
||||
onPress={() => window.location.href = "/loginredirect"}>
|
||||
<SignInIcon fill="text-background/80"/>
|
||||
</Button>
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package org.gameyfin.app.core.config
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.apache.coyote.ProtocolHandler
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
/**
|
||||
* Tomcat configuration to optimize for concurrent connections
|
||||
* and prevent download operations from blocking the server.
|
||||
*/
|
||||
@Configuration
|
||||
class TomcatConfig {
|
||||
|
||||
companion object {
|
||||
private val log = KotlinLogging.logger { }
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun protocolHandlerCustomizer(): TomcatProtocolHandlerCustomizer<*> {
|
||||
return TomcatProtocolHandlerCustomizer { protocolHandler: ProtocolHandler ->
|
||||
if (protocolHandler is AbstractHttp11Protocol<*>) {
|
||||
// Increase max connections to handle more concurrent users
|
||||
protocolHandler.maxConnections = 10000
|
||||
|
||||
// Increase max threads to handle more concurrent requests
|
||||
protocolHandler.maxThreads = 200
|
||||
|
||||
// Set minimum spare threads
|
||||
protocolHandler.minSpareThreads = 10
|
||||
|
||||
// Set connection timeout (20 seconds)
|
||||
protocolHandler.connectionTimeout = 20000
|
||||
|
||||
// Keep alive settings to reuse connections
|
||||
protocolHandler.keepAliveTimeout = 60000
|
||||
protocolHandler.maxKeepAliveRequests = 100
|
||||
|
||||
log.debug {
|
||||
"Configured Tomcat connector: maxConnections=${protocolHandler.maxConnections}, " +
|
||||
"maxThreads=${protocolHandler.maxThreads}, " +
|
||||
"minSpareThreads=${protocolHandler.minSpareThreads}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,10 @@ import org.gameyfin.pluginapi.download.FileDownload
|
||||
import org.gameyfin.pluginapi.download.LinkDownload
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.context.request.async.DeferredResult
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/download")
|
||||
@ -19,40 +22,57 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
|
||||
@AnonymousAllowed
|
||||
class DownloadEndpoint(
|
||||
private val downloadService: DownloadService,
|
||||
private val gameService: GameService
|
||||
private val gameService: GameService,
|
||||
) {
|
||||
|
||||
private val downloadExecutor: Executor = Executors.newVirtualThreadPerTaskExecutor()
|
||||
|
||||
@GetMapping("/{gameId}")
|
||||
fun downloadGame(
|
||||
@PathVariable gameId: Long,
|
||||
@RequestParam provider: String,
|
||||
request: HttpServletRequest
|
||||
): ResponseEntity<StreamingResponseBody> {
|
||||
val game = gameService.getById(gameId)
|
||||
gameService.incrementDownloadCount(game)
|
||||
val sessionId = request.session.id
|
||||
val remoteIp = request.getRemoteIp(LookupPolicy.IPV4_PREFERRED)
|
||||
): DeferredResult<ResponseEntity<StreamingResponseBody>> {
|
||||
val deferredResult = DeferredResult<ResponseEntity<StreamingResponseBody>>()
|
||||
|
||||
return when (val download = downloadService.getDownload(game.metadata.path, provider)) {
|
||||
is FileDownload -> {
|
||||
val responseBuilder = ResponseEntity.ok()
|
||||
.header("Content-Disposition", "attachment; filename=\"${game.title}.${download.fileExtension}\"")
|
||||
downloadExecutor.execute {
|
||||
try {
|
||||
val game = gameService.getById(gameId)
|
||||
gameService.incrementDownloadCount(game)
|
||||
val sessionId = request.session.id
|
||||
val remoteIp = request.getRemoteIp(LookupPolicy.IPV4_PREFERRED)
|
||||
|
||||
responseBuilder.body(StreamingResponseBody { outputStream ->
|
||||
downloadService.processDownload(
|
||||
download.data,
|
||||
outputStream,
|
||||
game,
|
||||
getCurrentAuth()?.name,
|
||||
sessionId,
|
||||
remoteIp
|
||||
)
|
||||
})
|
||||
}
|
||||
val result = when (val download = downloadService.getDownload(game.metadata.path, provider)) {
|
||||
is FileDownload -> {
|
||||
val responseBuilder = ResponseEntity.ok()
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"${game.title}.${download.fileExtension}\""
|
||||
)
|
||||
|
||||
is LinkDownload -> {
|
||||
TODO("Handle download link")
|
||||
responseBuilder.body(StreamingResponseBody { outputStream ->
|
||||
downloadService.processDownload(
|
||||
download.data,
|
||||
outputStream,
|
||||
game,
|
||||
getCurrentAuth()?.name,
|
||||
sessionId,
|
||||
remoteIp
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
is LinkDownload -> {
|
||||
TODO("Handle download link")
|
||||
}
|
||||
}
|
||||
|
||||
deferredResult.setResult(result)
|
||||
} catch (e: Exception) {
|
||||
deferredResult.setErrorResult(e)
|
||||
}
|
||||
}
|
||||
|
||||
return deferredResult
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package org.gameyfin.app.core.security
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.gameyfin.app.config.ConfigProperties
|
||||
import org.gameyfin.app.config.ConfigService
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
/**
|
||||
* Controller to handle login redirects properly for both SSO and direct login.
|
||||
* This replaces the previous hack of using a non-existent endpoint that returns 401.
|
||||
*/
|
||||
@Controller
|
||||
class LoginRedirectController(
|
||||
private val config: ConfigService
|
||||
) {
|
||||
|
||||
@GetMapping("/loginredirect")
|
||||
fun loginRedirect(request: HttpServletRequest, response: HttpServletResponse) {
|
||||
val continueParam = request.getParameter("continue")
|
||||
val directParam = request.getParameter("direct")
|
||||
|
||||
// Check if SSO is enabled
|
||||
val isSsoEnabled = config.get(ConfigProperties.SSO.OIDC.Enabled) == true
|
||||
|
||||
if (isSsoEnabled && directParam != "1") {
|
||||
// Redirect to SSO provider with continue parameter if present
|
||||
val ssoUrl = "/oauth2/authorization/${SecurityConfig.SSO_PROVIDER_KEY}"
|
||||
if (!continueParam.isNullOrBlank()) {
|
||||
response.sendRedirect("$ssoUrl?continue=$continueParam")
|
||||
} else {
|
||||
response.sendRedirect(ssoUrl)
|
||||
}
|
||||
} else {
|
||||
// Redirect to direct login page with continue parameter if present
|
||||
if (!continueParam.isNullOrBlank()) {
|
||||
response.sendRedirect("/login?continue=$continueParam")
|
||||
} else {
|
||||
response.sendRedirect("/login")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ class SecurityConfig(
|
||||
// Gameyfin static resources and public endpoints
|
||||
.requestMatchers(
|
||||
"/login",
|
||||
"/loginredirect",
|
||||
"/setup",
|
||||
"/reset-password",
|
||||
"/accept-invitation",
|
||||
@ -85,7 +86,9 @@ class SecurityConfig(
|
||||
}
|
||||
|
||||
// Use custom success handler to handle user registration
|
||||
http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) }
|
||||
http.oauth2Login { oauth2Login ->
|
||||
oauth2Login.successHandler(ssoAuthenticationSuccessHandler)
|
||||
}
|
||||
// Prevent unnecessary redirects
|
||||
http.logout { logout -> logout.logoutSuccessHandler((HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) }
|
||||
|
||||
|
||||
@ -81,7 +81,15 @@ class SsoAuthenticationSuccessHandler(
|
||||
UsernamePasswordAuthenticationToken(authentication.principal, authentication.credentials, mappedAuthorities)
|
||||
SecurityContextHolder.getContext().authentication = newAuth
|
||||
|
||||
response.sendRedirect("/")
|
||||
// Get the continue parameter from the request to redirect back to the original page
|
||||
val continueUrl = request.getParameter("continue")
|
||||
val redirectUrl = if (!continueUrl.isNullOrBlank() && continueUrl.startsWith("/")) {
|
||||
continueUrl
|
||||
} else {
|
||||
"/"
|
||||
}
|
||||
|
||||
response.sendRedirect(redirectUrl)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,10 @@ server:
|
||||
tracking-modes: cookie
|
||||
timeout: 24h
|
||||
forward-headers-strategy: framework
|
||||
tomcat:
|
||||
remoteip:
|
||||
protocol-header: X-Forwarded-Proto
|
||||
remote-ip-header: X-Forwarded-For
|
||||
|
||||
management:
|
||||
server:
|
||||
|
||||
@ -12,13 +12,14 @@ import org.gameyfin.pluginapi.download.FileDownload
|
||||
import org.gameyfin.pluginapi.download.LinkDownload
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.context.request.async.DeferredResult
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
@ -51,6 +52,35 @@ class DownloadEndpointTest {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to wait for DeferredResult to complete and get the result.
|
||||
* Handles async processing with timeout.
|
||||
*/
|
||||
private fun <T> awaitDeferredResult(deferredResult: DeferredResult<T>, timeoutSeconds: Long = 5): T {
|
||||
val latch = CountDownLatch(1)
|
||||
var result: T? = null
|
||||
var error: Throwable? = null
|
||||
|
||||
deferredResult.setResultHandler { value ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
result = value as T
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
deferredResult.onError { throwable ->
|
||||
error = throwable
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
val completed = latch.await(timeoutSeconds, TimeUnit.SECONDS)
|
||||
if (!completed) {
|
||||
throw AssertionError("DeferredResult did not complete within $timeoutSeconds seconds")
|
||||
}
|
||||
|
||||
error?.let { throw AssertionError("DeferredResult completed with error", it) }
|
||||
return result ?: throw AssertionError("DeferredResult completed but result is null")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `downloadGame should return file download with correct headers`() {
|
||||
val gameId = 1L
|
||||
@ -73,13 +103,13 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
assertNotNull(response.body)
|
||||
assertTrue(response.headers.containsKey("Content-Disposition"))
|
||||
assertTrue(response.headers["Content-Disposition"]!![0].contains("Test Game.zip"))
|
||||
// Content-Length may or may not be present depending on whether the path exists as a file
|
||||
|
||||
verify(exactly = 1) { gameService.getById(gameId) }
|
||||
verify(exactly = 1) { gameService.incrementDownloadCount(game) }
|
||||
@ -108,7 +138,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(dirPath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
assertTrue(response.headers.containsKey("Content-Disposition"))
|
||||
@ -136,7 +167,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
assertFalse(response.headers.containsKey("Content-Length"))
|
||||
@ -162,7 +194,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
assertFalse(response.headers.containsKey("Content-Length"))
|
||||
@ -191,7 +224,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
|
||||
@ -231,7 +265,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
|
||||
@ -270,7 +305,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
awaitDeferredResult(deferredResult)
|
||||
|
||||
verify(exactly = 1) { gameService.incrementDownloadCount(game) }
|
||||
}
|
||||
@ -293,8 +329,10 @@ class DownloadEndpointTest {
|
||||
every { gameService.incrementDownloadCount(game) } just Runs
|
||||
every { downloadService.getDownload(gamePath, provider) } returns linkDownload
|
||||
|
||||
assertThrows(NotImplementedError::class.java) {
|
||||
endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
|
||||
assertThrows(AssertionError::class.java) {
|
||||
awaitDeferredResult(deferredResult)
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,7 +356,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
val contentDisposition = response.headers["Content-Disposition"]!![0]
|
||||
@ -350,7 +389,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
val contentDisposition = response.headers["Content-Disposition"]!![0]
|
||||
assertTrue(
|
||||
@ -360,18 +400,6 @@ class DownloadEndpointTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `downloadGame should propagate service exceptions`() {
|
||||
val gameId = 1L
|
||||
val provider = "TestProvider"
|
||||
|
||||
every { gameService.getById(gameId) } throws RuntimeException("Game not found")
|
||||
|
||||
assertThrows(RuntimeException::class.java) {
|
||||
endpoint.downloadGame(gameId, provider, request)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `downloadGame should handle session without id`() {
|
||||
val gameId = 1L
|
||||
@ -394,7 +422,8 @@ class DownloadEndpointTest {
|
||||
every { downloadService.getDownload(gamePath, provider) } returns fileDownload
|
||||
every { downloadService.processDownload(any(), any(), any(), any(), any(), any()) } just Runs
|
||||
|
||||
val response = endpoint.downloadGame(gameId, provider, request)
|
||||
val deferredResult = endpoint.downloadGame(gameId, provider, request)
|
||||
val response = awaitDeferredResult(deferredResult)
|
||||
|
||||
assertEquals(HttpStatus.OK, response.statusCode)
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
||||
import java.nio.file.Files
|
||||
|
||||
group = "org.gameyfin"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2-preview"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user