Update Calendars.OWNER_ACCOUNT when renaming an account (#1751)

* Fix typo

* Also set OWNER_ACCOUNT when updating calendar because renaming account

* Add test

* Update comment clarifying content values

* Assume calendar provider is present and drop null checks
This commit is contained in:
Sunik Kupfer
2025-11-05 12:40:34 +01:00
committed by GitHub
parent bd13d27e38
commit 0959624dee
4 changed files with 138 additions and 2 deletions

View File

@@ -0,0 +1,118 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.net.Uri
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import androidx.core.content.contentValuesOf
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import javax.inject.Inject
@HiltAndroidTest
class LocalCalendarStoreTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val initCalendarProviderRule: TestRule = InitCalendarProviderRule.initialize()
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localCalendarStore: LocalCalendarStore
private lateinit var provider: ContentProviderClient
private lateinit var account: Account
private lateinit var calendarUri: Uri
@Before
fun setUp() {
hiltRule.inject()
provider = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
account = TestAccount.create(accountName = "InitialAccountName")
calendarUri = createCalendarForAccount(account)
}
@After
fun tearDown() {
provider.delete(calendarUri, null, null)
TestAccount.remove(account)
provider.closeCompat()
}
@Test
fun testUpdateAccount_updatesOwnerAccount() {
// Verify initial state
verifyOwnerAccountIs("InitialAccountName")
// Rename account
val oldAccount = account
account = TestAccount.rename(account, "ChangedAccountName")
// Update account name in local calendar
localCalendarStore.updateAccount(oldAccount, account)
// Verify [Calendar.OWNER_ACCOUNT] of local calendar was updated
verifyOwnerAccountIs("ChangedAccountName")
}
// helpers
private fun createCalendarForAccount(account: Account): Uri {
var uri: Uri? = null
provider.use { providerClient ->
val values = contentValuesOf(
Calendars.ACCOUNT_NAME to account.name,
Calendars.ACCOUNT_TYPE to account.type,
Calendars.OWNER_ACCOUNT to account.name,
Calendars.VISIBLE to 1,
Calendars.SYNC_EVENTS to 1,
Calendars._SYNC_ID to 999,
Calendars.CALENDAR_DISPLAY_NAME to "displayName",
)
uri = providerClient.insert(
Calendars.CONTENT_URI.asSyncAdapter(account),
values
)!!.asSyncAdapter(account)
}
return uri!!
}
private fun verifyOwnerAccountIs(expectedOwnerAccount: String) = provider.use {
it.query(
calendarUri,
arrayOf(Calendars.OWNER_ACCOUNT),
"${Calendars.ACCOUNT_NAME}=?",
arrayOf(account.name),
null
)!!.use { cursor ->
cursor.moveToNext()
val ownerAccount = cursor.getString(0)
assertEquals(expectedOwnerAccount, ownerAccount)
}
}
}

View File

@@ -8,6 +8,8 @@ import android.accounts.AccountManager
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.davdroid.R
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.account.TestAccount.remove
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
object TestAccount {
@@ -30,6 +32,16 @@ object TestAccount {
return account
}
/**
* Renames a test account in a blocking way (usually what you want in tests)
*/
fun rename(account: Account, newName: String): Account {
val am = AccountManager.get(targetContext)
val newAccount = am.renameAccount(account, newName, null, null).result
assertEquals(newName, newAccount.name)
return newAccount
}
/**
* Removes a test account, usually in the `@After` tearDown of a test.
*/

View File

@@ -169,7 +169,7 @@ class AccountRepository @Inject constructor(
/**
* Renames an account.
*
* **Not**: It is highly advised to re-sync the account after renaming in order to restore
* **Note**: It is highly advised to re-sync the account after renaming in order to restore
* a consistent state.
*
* @param oldName current name of the account

View File

@@ -139,7 +139,13 @@ class LocalCalendarStore @Inject constructor(
}
override fun updateAccount(oldAccount: Account, newAccount: Account) {
val values = contentValuesOf(Calendars.ACCOUNT_NAME to newAccount.name)
val values = contentValuesOf(
// Account name to be changed
Calendars.ACCOUNT_NAME to newAccount.name,
// Owner account of this calendar to be changed. Used by the calendar
// provider to determine whether the user is ORGANIZER/ATTENDEE (usually an email address) for a certain event.
Calendars.OWNER_ACCOUNT to newAccount.name
)
val uri = Calendars.CONTENT_URI.asSyncAdapter(oldAccount)
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.use {
it.update(uri, values, "${Calendars.ACCOUNT_NAME}=?", arrayOf(oldAccount.name))