tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit fdf492bdd079c37419a36b07abd37fdaa83ac7d3
parent f40847365ade1cfb875f3798bfaa8edd103bcbd3
Author: Segun Famisa <sfamisa@mozilla.com>
Date:   Mon, 24 Nov 2025 13:17:17 +0000

Bug 1998196 - Add tests for the state management logic for the Credit Card Editor r=android-reviewers,boek

This patch introduces unit tests for the `CreditCardEditorStore`.

The new tests cover:
- Reducer logic for state updates in `CreditCardEditorReducerTest`.
- Middleware logic for handling side effects like saving, deleting, and initialization in `CreditCardEditorStoreTest`.

To support these tests, the following test fakes and helpers have been added:
- `FakeCreditCardsStorage`: A fake implementation of `CreditCardsAddressesStorage`.
- `FakeCalendarDataProvider`: A fake for providing month and year data.
- `CreditCardEditorStateTestHelper`: A helper function to create instances of `CreditCardEditorState` for testing.

Differential Revision: https://phabricator.services.mozilla.com/D271089

Diffstat:
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorReducerTest.kt | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStateTestHelper.kt | 36++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStoreTest.kt | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCalendarDataProvider.kt | 22++++++++++++++++++++++
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCreditCardsStorage.kt | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 730 insertions(+), 0 deletions(-)

diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorReducerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorReducerTest.kt @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings.creditcards.ui + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.mozilla.fenix.settings.creditcards.ui.CreditCardEditorAction.DeleteDialogAction +import org.mozilla.fenix.settings.creditcards.ui.CreditCardEditorAction.FieldChanged + +class CreditCardEditorReducerTest { + + @Test + fun `GIVEN a state, WHEN a CardNumberChanged action is received, then the card number is updated`() { + val state = createState() + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.CardNumberChanged("55554444"), + ) + + assertEquals( + "Expected card number to be updated", + "55554444", + result.cardNumber, + ) + } + + @Test + fun `GIVEN a state, WHEN a MonthSelected action is received, then the month is updated`() { + val state = createState( + expiryMonths = listOf("January", "February", "March"), + selectedExpiryMonthIndex = 1, + ) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.MonthSelected(index = 2), + ) + + assertEquals( + "Expected month index is updated", + 2, + result.selectedExpiryMonthIndex, + ) + } + + @Test + fun `GIVEN a state, WHEN a YearSelected action is received, then the year is updated`() { + val state = createState( + expiryYears = listOf("2025", "2026", "2027"), + selectedExpiryYearIndex = 0, + ) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.YearSelected(index = 1), + ) + + assertEquals( + "Expected year index is updated", + 1, + result.selectedExpiryYearIndex, + ) + } + + @Test + fun `GIVEN a state, WHEN a NameOnCardChanged action is received, then the name on card is updated`() { + val state = createState(nameOnCard = "Jane Doe") + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.NameOnCardChanged("Janey Doe"), + ) + + assertEquals( + "Expected name on card to be updated", + "Janey Doe", + result.nameOnCard, + ) + } + + @Test + fun `GIVEN a state with name error, WHEN the name changes, then an error is cleared on the name field`() { + val state = createState(nameOnCard = "", showNameOnCardError = true) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.NameOnCardChanged("Janey Doe"), + ) + + assertFalse( + "Expected name on card error to be cleared", + result.showNameOnCardError, + ) + } + + @Test + fun `GIVEN a state with card number error, WHEN the card number changes, then an error is cleared on the name field`() { + val state = createState(cardNumber = "5555", showCardNumberError = true) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.CardNumberChanged("55554"), + ) + + assertFalse( + "Expected name on card error to be cleared", + result.showCardNumberError, + ) + } + + @Test + fun `GIVEN a state, WHEN a delete dialog cancel action is received, then the dialog is hidden`() { + val state = createState(showDeleteDialog = true) + + val result = creditCardEditorReducer( + state = state, + action = DeleteDialogAction.Cancel, + ) + + assertFalse( + "Expected delete dialog to be hidden", + result.showDeleteDialog, + ) + } + + @Test + fun `GIVEN a state, WHEN a delete dialog confirm action is received, then the dialog is hidden`() { + val state = createState(showDeleteDialog = true) + + val result = creditCardEditorReducer( + state = state, + action = DeleteDialogAction.Confirm, + ) + + assertFalse( + "Expected delete dialog to be hidden", + result.showDeleteDialog, + ) + } + + @Test + fun `GIVEN a state, WHEN a DeleteClicked action is received, then the dialog is shown`() { + val state = createState(showDeleteDialog = false) + + val result = creditCardEditorReducer( + state = state, + action = CreditCardEditorAction.DeleteClicked, + ) + + assertTrue( + "Expected delete dialog to be shown", + result.showDeleteDialog, + ) + } + + @Test + fun `GIVEN a state with invalid card number, WHEN save action is received, then the error is shown`() { + val state = createState(cardNumber = "3333") + + val result = creditCardEditorReducer( + state = state, + action = CreditCardEditorAction.Save, + ) + + assertTrue( + "Expected card number error to be shown", + result.showCardNumberError, + ) + } + + @Test + fun `GIVEN state with card number error, WHEN card number field is changed, then the error is cleared`() { + val state = createState(showCardNumberError = true) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.CardNumberChanged(cardNumber = "5555"), + ) + + assertFalse( + "Expected card number error to no longer be shown", + result.showCardNumberError, + ) + } + + @Test + fun `GIVEN state with empty name on card, WHEN a save action is received, then the error is shown`() { + val state = createState(nameOnCard = "") + + val result = creditCardEditorReducer( + state = state, + action = CreditCardEditorAction.Save, + ) + + assertTrue( + "Expected name on card error to be shown", + result.showNameOnCardError, + ) + } + + @Test + fun `GIVEN a state with name on card error, WHEN the name field changes, then the error is cleared`() { + val state = createState(showNameOnCardError = true) + + val result = creditCardEditorReducer( + state = state, + action = FieldChanged.NameOnCardChanged(nameOnCard = "John"), + ) + + assertFalse( + "Expected name on card error to no longer be shown", + result.showNameOnCardError, + ) + } + + @Test + fun `GIVEN a state, WHEN initialization is completed, then the state is updated accordingly`() { + val oldState = createState() + + val newState = createState( + nameOnCard = "New Name", + cardNumber = "1234556789900", + ) + + val result = creditCardEditorReducer( + state = oldState, + action = CreditCardEditorAction.Initialization.InitCompleted(state = newState), + ) + + assertEquals( + "Expected the state to be what was received in the action", + newState, + result, + ) + } +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStateTestHelper.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStateTestHelper.kt @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings.creditcards.ui + +/** + * Creates a [CreditCardEditorState] with the given parameters for use in tests. + */ +internal fun createState( + guid: String = "", + cardNumber: String = "5555444433331111", + showCardNumberError: Boolean = false, + nameOnCard: String = "Jane Doe", + showNameOnCardError: Boolean = false, + expiryMonths: List<String> = listOf("January", "February", "March"), + selectedExpiryMonthIndex: Int = 0, + expiryYears: List<String> = listOf("2025", "2026", "2027"), + selectedExpiryYearIndex: Int = 1, + inEditMode: Boolean = false, + showDeleteDialog: Boolean = false, +): CreditCardEditorState { + return CreditCardEditorState( + guid = guid, + cardNumber = cardNumber, + showCardNumberError = showCardNumberError, + nameOnCard = nameOnCard, + showNameOnCardError = showNameOnCardError, + expiryMonths = expiryMonths, + selectedExpiryMonthIndex = selectedExpiryMonthIndex, + expiryYears = expiryYears, + selectedExpiryYearIndex = selectedExpiryYearIndex, + inEditMode = inEditMode, + showDeleteDialog = showDeleteDialog, + ) +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStoreTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/CreditCardEditorStoreTest.kt @@ -0,0 +1,325 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings.creditcards.ui + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.runTest +import mozilla.components.concept.storage.CreditCard +import mozilla.components.concept.storage.CreditCardNumber +import mozilla.components.concept.storage.CreditCardsAddressesStorage +import mozilla.components.concept.storage.NewCreditCardFields +import mozilla.components.concept.storage.UpdatableCreditCardFields +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.utils.CreditCardNetworkType +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CreditCardEditorStoreTest { + + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + private val creditCardsStorage = FakeCreditCardsStorage() + private val calendarDataProvider = FakeCalendarDataProvider( + expectedMonths = listOf("January", "February", "March"), + expectedYears = listOf("2025", "2026", "2027"), + ) + + @Test + fun `WHEN DeleteClicked event is received, delete confirmation dialog is shown`() = runTest { + val store = makeStore() + + store.dispatch(CreditCardEditorAction.DeleteClicked) + + assertTrue( + "Delete confirmation dialog is not shown", + store.state.showDeleteDialog, + ) + } + + @Test + fun `WHEN delete confirmation is cancelled, the dialog is hidden`() = runTest { + val store = makeStore() + + store.dispatch(CreditCardEditorAction.DeleteClicked) + store.dispatch(CreditCardEditorAction.DeleteDialogAction.Cancel) + + assertFalse( + "Expected delete confirmation dialog to be hidden", + store.state.showDeleteDialog, + ) + } + + @Test + fun `WHEN delete confirmation is confirmed, the card is deleted`() = runTest { + val store = makeStore( + state = createState(guid = "card-id"), + ) + + store.dispatch(CreditCardEditorAction.DeleteDialogAction.Confirm) + + assertEquals( + "Expected that the deleted card has guid 'card-id'", + "card-id", + creditCardsStorage.deletedCard, + ) + } + + @Test + fun `GIVEN valid form in 'edit' mode, WHEN save action is received, the card is saved`() { + val validMasterCard = "5555444433331111" + val store = makeStore( + state = createState( + guid = "1234", + inEditMode = true, + nameOnCard = "Jane Doe", + cardNumber = validMasterCard, + expiryYears = listOf("2025", "2026", "2027"), + selectedExpiryYearIndex = 1, + expiryMonths = listOf("January", "February", "March"), + selectedExpiryMonthIndex = 0, + ), + ) + + store.dispatch(CreditCardEditorAction.Save) + + val updatedCardGuid = creditCardsStorage.updatedCard?.first + val updatedCardFields = creditCardsStorage.updatedCard?.second + assertEquals( + "Expected that a card is updated with the guid is 1234", + "1234", + updatedCardGuid, + ) + assertEquals( + "Expected that a card is updated with the right fields", + UpdatableCreditCardFields( + billingName = "Jane Doe", + cardNumber = CreditCardNumber.Plaintext(validMasterCard), + cardNumberLast4 = "1111", + expiryMonth = 1, + expiryYear = 2026L, + cardType = "mastercard", + ), + updatedCardFields, + ) + } + + @Test + fun `GIVEN valid form in 'create' mode, WHEN save action is received, the card is saved`() { + val validMasterCard = "5555444433331111" + val store = makeStore( + state = createState( + nameOnCard = "Jane Doe", + cardNumber = validMasterCard, + expiryYears = listOf("2025", "2026", "2027"), + selectedExpiryYearIndex = 1, + expiryMonths = listOf("January", "February", "March"), + selectedExpiryMonthIndex = 0, + ), + ) + + store.dispatch(CreditCardEditorAction.Save) + + assertEquals( + "Expected that a card is successfully saved with the right values", + NewCreditCardFields( + billingName = "Jane Doe", + plaintextCardNumber = CreditCardNumber.Plaintext(validMasterCard), + cardNumberLast4 = "1111", + expiryMonth = 1, + expiryYear = 2026L, + cardType = "mastercard", + ), + creditCardsStorage.newAddedCard, + ) + } + + @Test + fun `GIVEN invalid card number, WHEN save action is received, an error is shown on the card field`() { + val americanExpressCardInvalid = "371449635398432" + val store = makeStore( + state = createState(cardNumber = americanExpressCardInvalid), + ) + + store.dispatch(CreditCardEditorAction.Save) + + assertTrue( + "Expected that an error is shown on the card number field", + store.state.showCardNumberError, + ) + } + + @Test + fun `GIVEN empty card number, WHEN save action is received, an error is shown on the card field`() { + val store = makeStore( + state = createState(cardNumber = ""), + ) + + store.dispatch(CreditCardEditorAction.Save) + + assertTrue( + "Expected that an error is shown on the card number field", + store.state.showCardNumberError, + ) + } + + @Test + fun `GIVEN an empty name on card, WHEN save action is received, an error is shown on the name field`() { + val store = makeStore( + state = createState(nameOnCard = ""), + ) + + store.dispatch(CreditCardEditorAction.Save) + + assertTrue( + "Expected that an error is shown on the name field", + store.state.showNameOnCardError, + ) + } + + @Test + fun `WHEN cancel action is received, user is navigated back`() { + var navigatedBack = false + val store = makeStore( + environment = CreditCardEditorEnvironment.Default.copy( + navigateBack = { + navigatedBack = true + }, + ), + ) + + store.dispatch(CreditCardEditorAction.Cancel) + + assertTrue( + "Expected that we navigate back", + navigatedBack, + ) + } + + @Test + fun `WHEN 'navigate back' action is received, user is navigated back`() { + var navigatedBack = false + val store = makeStore( + environment = CreditCardEditorEnvironment.Default.copy( + navigateBack = { + navigatedBack = true + }, + ), + ) + + store.dispatch(CreditCardEditorAction.NavigateBack) + + assertTrue( + "Expected that we navigate back", + navigatedBack, + ) + } + + @Test + fun `WHEN Initializing without a credit card, the state is correct`() = runTest { + calendarDataProvider.expectedMonths = listOf("January", "February") + calendarDataProvider.expectedYears = listOf("2025", "2026") + + val initialState = CreditCardEditorState.Default + val store = makeStore(state = initialState) + + store.dispatch(CreditCardEditorAction.Initialization.InitStarted(creditCard = null)) + + assertEquals( + "Expected that the state is loaded with everything empty", + CreditCardEditorState( + guid = "", + cardNumber = "", + showCardNumberError = false, + nameOnCard = "", + showNameOnCardError = false, + expiryMonths = listOf("January", "February"), + selectedExpiryMonthIndex = 0, + expiryYears = listOf("2025", "2026"), + selectedExpiryYearIndex = 0, + inEditMode = false, + showDeleteDialog = false, + ), + store.state, + ) + } + + @Test + fun `WHEN Initializing with a credit card, the state is initialized with the card details`() = + runTest { + calendarDataProvider.expectedMonths = listOf("January", "February", "March") + calendarDataProvider.expectedYears = listOf("2025", "2026") + val expectedEncryptedCardNumber = "encryptedCard" + val expectedPlainCardNumber = "5555444433331111" + + creditCardsStorage.expectedPlainCardNumber = expectedPlainCardNumber + creditCardsStorage.expectedEncryptedCardNumber = expectedEncryptedCardNumber + + val creditCard = CreditCard( + guid = "id", + billingName = "Banana Apple", + encryptedCardNumber = CreditCardNumber.Encrypted(expectedEncryptedCardNumber), + cardNumberLast4 = "1111", + expiryMonth = 2, + expiryYear = 2025, + cardType = CreditCardNetworkType.MASTERCARD.cardName, + timeCreated = 1L, + timeLastUsed = 1L, + timeLastModified = 1L, + timesUsed = 1L, + ) + + val store = makeStore(state = CreditCardEditorState.Default) + + store.dispatch(CreditCardEditorAction.Initialization.InitStarted(creditCard = creditCard)) + + assertEquals( + "Expected that the state is initialized with the card details", + CreditCardEditorState( + guid = "id", + cardNumber = expectedPlainCardNumber, + showCardNumberError = false, + nameOnCard = "Banana Apple", + showNameOnCardError = false, + expiryMonths = listOf("January", "February", "March"), + selectedExpiryMonthIndex = 1, + expiryYears = listOf("2025", "2026"), + selectedExpiryYearIndex = 0, + inEditMode = true, + showDeleteDialog = false, + ), + store.state, + ) + } + + private fun makeStore( + state: CreditCardEditorState = createState(), + monthsProvider: CalendarDataProvider = calendarDataProvider, + storage: CreditCardsAddressesStorage = creditCardsStorage, + scope: CoroutineScope = coroutinesTestRule.scope, + dispatcher: CoroutineDispatcher = coroutinesTestRule.testDispatcher, + environment: CreditCardEditorEnvironment = CreditCardEditorEnvironment.Default, + ): CreditCardEditorStore { + return CreditCardEditorStore( + initialState = state, + middleware = listOf( + CreditCardEditorMiddleware( + environment = environment, + calendarDataProvider = monthsProvider, + storage = storage, + coroutineScope = scope, + ioDispatcher = dispatcher, + mainDispatcher = dispatcher, + ), + ), + ) + } +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCalendarDataProvider.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCalendarDataProvider.kt @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings.creditcards.ui + +class FakeCalendarDataProvider( + var expectedMonths: List<String> = emptyList(), + var expectedYears: List<String> = emptyList(), +) : CalendarDataProvider { + override fun months(): List<String> { + return expectedMonths + } + + override fun years(): List<String> { + return expectedYears + } + + override fun years(startYear: Long): List<String> { + return expectedYears + } +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCreditCardsStorage.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/creditcards/ui/FakeCreditCardsStorage.kt @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings.creditcards.ui + +import mozilla.components.concept.storage.Address +import mozilla.components.concept.storage.CreditCard +import mozilla.components.concept.storage.CreditCardCrypto +import mozilla.components.concept.storage.CreditCardNumber +import mozilla.components.concept.storage.CreditCardsAddressesStorage +import mozilla.components.concept.storage.ManagedKey +import mozilla.components.concept.storage.NewCreditCardFields +import mozilla.components.concept.storage.UpdatableAddressFields +import mozilla.components.concept.storage.UpdatableCreditCardFields + +/** + * Fake implementation of [CreditCardsAddressesStorage] that is used for testing credit cards feature + */ +class FakeCreditCardsStorage( + var deletedCard: String? = null, + var newAddedCard: NewCreditCardFields? = null, + var updatedCard: Pair<String, UpdatableCreditCardFields>? = null, +) : CreditCardsAddressesStorage { + + /** + * Plain card number + */ + var expectedPlainCardNumber: String = "" + + /** + * Encrypted card number + */ + var expectedEncryptedCardNumber: String = "encrypted" + + override suspend fun addCreditCard(creditCardFields: NewCreditCardFields): CreditCard { + newAddedCard = creditCardFields + return CreditCard( + guid = "new-card-id", + billingName = creditCardFields.billingName, + encryptedCardNumber = CreditCardNumber.Encrypted(data = expectedEncryptedCardNumber), + cardNumberLast4 = creditCardFields.cardNumberLast4, + expiryMonth = creditCardFields.expiryMonth, + expiryYear = creditCardFields.expiryYear, + cardType = creditCardFields.cardType, + ) + } + + override suspend fun updateCreditCard( + guid: String, + creditCardFields: UpdatableCreditCardFields, + ) { + updatedCard = Pair(guid, creditCardFields) + } + + override suspend fun getCreditCard(guid: String): CreditCard? = null + + override suspend fun getAllCreditCards(): List<CreditCard> = emptyList() + + override suspend fun deleteCreditCard(guid: String): Boolean { + deletedCard = guid + return true + } + + override suspend fun touchCreditCard(guid: String) = Unit + + override fun getCreditCardCrypto(): CreditCardCrypto { + return object : CreditCardCrypto { + override fun encrypt( + key: ManagedKey, + plaintextCardNumber: CreditCardNumber.Plaintext, + ): CreditCardNumber.Encrypted { + return CreditCardNumber.Encrypted(data = expectedEncryptedCardNumber) + } + + override fun decrypt( + key: ManagedKey, + encryptedCardNumber: CreditCardNumber.Encrypted, + ): CreditCardNumber.Plaintext { + return CreditCardNumber.Plaintext(data = expectedPlainCardNumber) + } + + override suspend fun getOrGenerateKey(): ManagedKey { + return ManagedKey(key = "key") + } + } + } + + override suspend fun scrubEncryptedData() { + error("Not yet implemented") + } + + override suspend fun addAddress(addressFields: UpdatableAddressFields): Address { + throw NotImplementedError("Address features are not used in this test") + } + + override suspend fun getAddress(guid: String): Address? = null + + override suspend fun getAllAddresses(): List<Address> = emptyList() + + override suspend fun updateAddress(guid: String, address: UpdatableAddressFields) = Unit + + override suspend fun deleteAddress(guid: String): Boolean = false + + override suspend fun touchAddress(guid: String) = Unit +}