Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ data class OwnerResponse(
data class QuotaResponse(
val remaining: Long?,
val state: String?,
val total: Long,
val total: Long?,
val used: Long?,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* openCloud Android Library is available under MIT license
* Copyright (C) 2026 OpenCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package eu.opencloud.android.lib.resources.spaces.responses

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test

class SpacesResponseTest {

private lateinit var adapter: JsonAdapter<SpacesResponseWrapper>

@Before
fun setUp() {
adapter = Moshi.Builder().build().adapter(SpacesResponseWrapper::class.java)
}

@Test
fun `empty quota object is parsed`() {
val response = adapter.fromJson(
"""
{
"value": [
{
"driveType": "personal",
"id": "personal-space-id",
"name": "Personal",
"quota": {},
"root": {
"id": "personal-space-id",
"webDavUrl": "https://server.url/dav/spaces/personal-space-id"
}
}
]
}
""".trimIndent()
)

assertEquals(1, response?.value?.size)
assertNull(response?.value?.first()?.quota?.total)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ data class SpaceQuotaEntity(
@ColumnInfo(name = SPACES_QUOTA_STATE)
val state: String?,
@ColumnInfo(name = SPACES_QUOTA_TOTAL)
val total: Long,
val total: Long?,
@ColumnInfo(name = SPACES_QUOTA_USED)
val used: Long?,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import eu.opencloud.android.data.spaces.datasources.RemoteSpacesDataSource
import eu.opencloud.android.data.user.datasources.LocalUserDataSource
import eu.opencloud.android.domain.spaces.SpacesRepository
import eu.opencloud.android.domain.spaces.model.OCSpace
import eu.opencloud.android.domain.user.model.UserQuotaState
import eu.opencloud.android.domain.user.model.UserQuota
import eu.opencloud.android.domain.user.model.UserQuotaState

class OCSpacesRepository(
private val localSpacesDataSource: LocalSpacesDataSource,
Expand All @@ -43,19 +43,36 @@ class OCSpacesRepository(
val personalSpace = listOfSpaces.find { it.isPersonal }
val capabilities = localCapabilitiesDataSource.getCapabilitiesForAccount(accountName)
val isMultiPersonal = capabilities?.spaces?.hasMultiplePersonalSpaces
val userQuota = personalSpace?.let {
if (isMultiPersonal == true) {
UserQuota(accountName, -4, 0, 0, UserQuotaState.NORMAL)
} else if (it.quota?.total!!.toInt() == 0) {
UserQuota(accountName, -3, it.quota?.used!!, it.quota?.total!!, UserQuotaState.fromValue(it.quota?.state!!))
} else {
UserQuota(accountName, it.quota?.remaining!!, it.quota?.used!!, it.quota?.total!!, UserQuotaState.fromValue(it.quota?.state!!))
}
} ?: UserQuota(accountName, -4, 0, 0, UserQuotaState.NORMAL)
val userQuota = getUserQuotaForPersonalSpace(accountName, personalSpace, isMultiPersonal == true)
localUserDataSource.saveQuotaForAccount(accountName, userQuota)
}
}

private fun getUserQuotaForPersonalSpace(
accountName: String,
personalSpace: OCSpace?,
isMultiPersonal: Boolean,
): UserQuota {
if (isMultiPersonal || personalSpace == null) {
return unavailableQuota(accountName)
}

val quota = personalSpace.quota ?: return unavailableQuota(accountName)
val total = quota.total ?: return unavailableQuota(accountName)
val used = quota.used ?: 0
val state = quota.state?.let { UserQuotaState.fromValue(it) } ?: UserQuotaState.NORMAL

return if (total == 0L) {
UserQuota(accountName, -3, used, total, state)
} else {
val remaining = quota.remaining ?: (total - used).coerceAtLeast(0)
UserQuota(accountName, remaining, used, total, state)
}
}

private fun unavailableQuota(accountName: String) =
UserQuota(accountName, -4, 0, 0, UserQuotaState.NORMAL)

override fun getSpacesFromEveryAccountAsStream() =
localSpacesDataSource.getSpacesFromEveryAccountAsStream()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import eu.opencloud.android.testutil.OC_ACCOUNT_NAME
import eu.opencloud.android.testutil.OC_CAPABILITY
import eu.opencloud.android.testutil.OC_CAPABILITY_WITH_MULTIPERSONAL_ENABLED
import eu.opencloud.android.testutil.OC_SPACE_PERSONAL
import eu.opencloud.android.testutil.OC_SPACE_PERSONAL_WITH_EMPTY_QUOTA
import eu.opencloud.android.testutil.OC_SPACE_PERSONAL_WITH_LIMITED_QUOTA
import eu.opencloud.android.testutil.OC_SPACE_PERSONAL_WITH_UNLIMITED_QUOTA
import eu.opencloud.android.testutil.OC_SPACE_PERSONAL_WITHOUT_QUOTA
import eu.opencloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE
import eu.opencloud.android.testutil.OC_USER_QUOTA_LIMITED
import eu.opencloud.android.testutil.OC_USER_QUOTA_UNLIMITED
Expand Down Expand Up @@ -133,6 +135,46 @@ class OCSpacesRepositoryTest {
}
}

@Test
fun `refreshSpacesForAccount refreshes spaces for account correctly when quota is missing`() {
every {
remoteSpacesDataSource.refreshSpacesForAccount(OC_ACCOUNT_NAME)
} returns listOf(OC_SPACE_PERSONAL_WITHOUT_QUOTA)

every {
localCapabilitiesDataSource.getCapabilitiesForAccount(OC_ACCOUNT_NAME)
} returns OC_CAPABILITY

ocSpacesRepository.refreshSpacesForAccount(OC_ACCOUNT_NAME)

verify(exactly = 1) {
remoteSpacesDataSource.refreshSpacesForAccount(OC_ACCOUNT_NAME)
localSpacesDataSource.saveSpacesForAccount(listOf(OC_SPACE_PERSONAL_WITHOUT_QUOTA))
localCapabilitiesDataSource.getCapabilitiesForAccount(OC_ACCOUNT_NAME)
localUserDataSource.saveQuotaForAccount(OC_ACCOUNT_NAME, OC_USER_QUOTA_WITHOUT_PERSONAL)
}
}

@Test
fun `refreshSpacesForAccount refreshes spaces for account correctly when quota is empty`() {
every {
remoteSpacesDataSource.refreshSpacesForAccount(OC_ACCOUNT_NAME)
} returns listOf(OC_SPACE_PERSONAL_WITH_EMPTY_QUOTA)

every {
localCapabilitiesDataSource.getCapabilitiesForAccount(OC_ACCOUNT_NAME)
} returns OC_CAPABILITY

ocSpacesRepository.refreshSpacesForAccount(OC_ACCOUNT_NAME)

verify(exactly = 1) {
remoteSpacesDataSource.refreshSpacesForAccount(OC_ACCOUNT_NAME)
localSpacesDataSource.saveSpacesForAccount(listOf(OC_SPACE_PERSONAL_WITH_EMPTY_QUOTA))
localCapabilitiesDataSource.getCapabilitiesForAccount(OC_ACCOUNT_NAME)
localUserDataSource.saveQuotaForAccount(OC_ACCOUNT_NAME, OC_USER_QUOTA_WITHOUT_PERSONAL)
}
}

@Test
fun `getSpacesFromEveryAccountAsStream returns a Flow with a list of OCSpace`() = runTest {
every {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ data class SpaceOwner(
data class SpaceQuota(
val remaining: Long?,
val state: String?,
val total: Long,
val total: Long?,
val used: Long?,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,19 @@ val OC_SPACE_PERSONAL_WITH_LIMITED_QUOTA = OC_SPACE_PERSONAL.copy(
)
)

val OC_SPACE_PERSONAL_WITHOUT_QUOTA = OC_SPACE_PERSONAL.copy(
quota = null
)

val OC_SPACE_PERSONAL_WITH_EMPTY_QUOTA = OC_SPACE_PERSONAL.copy(
quota = SpaceQuota(
remaining = null,
state = null,
total = null,
used = null
)
)

val OC_SPACE_SHARES = OCSpace(
accountName = OC_ACCOUNT_NAME,
driveAlias = "virtual/shares",
Expand Down
Loading