Skip to content

Commit

Permalink
Merge pull request #5670 from grzesiek2010/COLLECT-5669
Browse files Browse the repository at this point in the history
Allow editing finalized forms saved before October 1st 2023 UTC
  • Loading branch information
grzesiek2010 committed Jul 13, 2023
2 parents 2edf117 + 970d32a commit c9f5ace
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.odk.collect.android.instancemanagement.InstanceExtKt.OCTOBER_1st_2023_UTC;
import static org.odk.collect.android.support.FileUtils.copyFileFromAssets;

import android.Manifest;
Expand Down Expand Up @@ -40,6 +41,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.function.Supplier;

@RunWith(AndroidJUnit4.class)
public class BackgroundAudioRecordingTest {
Expand All @@ -48,6 +50,8 @@ public class BackgroundAudioRecordingTest {

private final RevokeableRecordAudioPermissionsChecker permissionsChecker = new RevokeableRecordAudioPermissionsChecker(ApplicationProvider.getApplicationContext());
private final ControllableRecordAudioPermissionsProvider permissionsProvider = new ControllableRecordAudioPermissionsProvider(permissionsChecker);
private Long currentTimeMillis = System.currentTimeMillis();

private final TestDependencies testDependencies = new TestDependencies() {

@Override
Expand Down Expand Up @@ -76,6 +80,11 @@ public PermissionsChecker providesPermissionsChecker(Context context) {
public PermissionsProvider providesPermissionsProvider(PermissionsChecker permissionsChecker) {
return permissionsProvider;
}

@Override
public Supplier<Long> providesClock() {
return () -> currentTimeMillis;
}
};

public final CollectTestRule rule = new CollectTestRule();
Expand Down Expand Up @@ -176,6 +185,8 @@ public void whenRecordAudioPermissionNotGranted_openingForm_andDenyingPermission

@Test
public void viewForm_doesNotRecordAudio() {
currentTimeMillis = OCTOBER_1st_2023_UTC + 1;

rule.startAtMainMenu()
.copyForm("one-question-background-audio.xml")
.startBlankForm("One Question")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.odk.collect.android.R
import org.odk.collect.android.instancemanagement.OCTOBER_1st_2023_UTC
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.MainMenuPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
import java.util.function.Supplier

class FormSavedSnackbarTest {
private val rule = CollectTestRule()

private var currentTimeMillis: Long = System.currentTimeMillis()

private val testDependencies = object : TestDependencies() {
override fun providesClock(): Supplier<Long> {
return Supplier { currentTimeMillis }
}
}

@get:Rule
val copyFormChain: RuleChain = TestRuleChain.chain().around(rule)
val copyFormChain: RuleChain = TestRuleChain.chain(testDependencies).around(rule)

@Test
fun whenBlankFormSavedAsDraft_displaySnackbarWithEditAction() {
Expand All @@ -30,7 +41,30 @@ class FormSavedSnackbarTest {
}

@Test
fun whenDraftFinalized_displaySnackbarWithViewAction() {
fun beforeOCTOBER_1st_2023_UTC_whenDraftFinalized_displaySnackbarWithViewActionThatOpensFormForEdit() {
currentTimeMillis = OCTOBER_1st_2023_UTC - 1

rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
.answerQuestion(0, "25")
.swipeToEndScreen()
.clickSaveAsDraft()
.clickEditSavedForm()
.clickOnForm("One Question")
.clickGoToEnd()
.clickFinalize()
.assertText(R.string.form_saved)
.clickOnString(R.string.view_form)
.assertText("25")
.assertText(R.string.jump_to_beginning)
.assertText(R.string.jump_to_end)
}

@Test
fun afterOCTOBER_1st_2023_UTC_whenDraftFinalized_displaySnackbarWithViewActionThatOpensFormForViewOnly() {
currentTimeMillis = OCTOBER_1st_2023_UTC + 1

rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.odk.collect.android.R
import org.odk.collect.android.instancemanagement.OCTOBER_1st_2023_UTC
import org.odk.collect.android.support.CollectHelpers.addGDProject
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.FormEntryPage.QuestionAndAnswer
Expand All @@ -17,11 +18,19 @@ import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain.chain
import org.odk.collect.androidtest.RecordedIntentsRule
import org.odk.collect.projects.Project.New
import java.util.function.Supplier

@RunWith(AndroidJUnit4::class)
class SendFinalizedFormTest {

private val testDependencies = TestDependencies()
private var currentTimeMillis: Long = System.currentTimeMillis()

private val testDependencies = object : TestDependencies() {
override fun providesClock(): Supplier<Long> {
return Supplier { currentTimeMillis }
}
}

private val rule = CollectTestRule(useDemoProject = false)

@get:Rule
Expand All @@ -30,7 +39,22 @@ class SendFinalizedFormTest {
.around(rule)

@Test
fun canViewFormsBeforeSending() {
fun beforeOCTOBER_1st_2023_UTC_canEditFormsBeforeSending() {
currentTimeMillis = OCTOBER_1st_2023_UTC - 1

rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", projectName = testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "52"))
.clickSendFinalizedForm(1)
.clickOnFormToEdit("One Question")
.assertText("52")
}

@Test
fun afterOCTOBER_1st_2023_UTC_canViewFormsBeforeSending() {
currentTimeMillis = OCTOBER_1st_2023_UTC + 1

rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", projectName = testDependencies.server.hostName)
.startBlankForm("One Question")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public ViewFormPage clickOnForm(String formLabel) {
return new ViewFormPage(formLabel).assertOnPage();
}

public FormHierarchyPage clickOnFormToEdit(String formLabel) {
clickOnText(formLabel);
return new FormHierarchyPage(formLabel).assertOnPage();
}

public OkDialog clickSendSelected() {
clickOnText(getTranslatedString(R.string.send_selected_data));
return new OkDialog();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.odk.collect.android.activities.FormFillingActivity
import org.odk.collect.android.analytics.AnalyticsEvents
import org.odk.collect.android.injection.DaggerUtils
import org.odk.collect.android.instancemanagement.InstanceDeleter
import org.odk.collect.android.instancemanagement.canBeEdited
import org.odk.collect.android.instancemanagement.canBeEditedWithGracePeriod
import org.odk.collect.android.projects.CurrentProjectProvider
import org.odk.collect.android.utilities.ApplicationConstants
import org.odk.collect.android.utilities.ContentUriHelper
Expand Down Expand Up @@ -206,7 +206,7 @@ class FormUriActivity : ComponentActivity() {

val formEditingEnabled = if (uriMimeType == InstancesContract.CONTENT_ITEM_TYPE) {
val instance = instanceRepositoryProvider.get().get(ContentUriHelper.getIdFromUri(uri))
instance!!.canBeEdited(settingsProvider)
instance!!.canBeEditedWithGracePeriod(settingsProvider)
} else {
true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
import org.odk.collect.utilities.UserAgentProvider;

import java.io.File;
import java.util.function.Supplier;

import javax.inject.Named;
import javax.inject.Singleton;
Expand Down Expand Up @@ -195,8 +196,8 @@ WebCredentialsUtils provideWebCredentials(SettingsProvider settingsProvider) {
}

@Provides
public FormDownloader providesFormDownloader(FormSourceProvider formSourceProvider, FormsRepositoryProvider formsRepositoryProvider, StoragePathProvider storagePathProvider) {
return new ServerFormDownloader(formSourceProvider.get(), formsRepositoryProvider.get(), new File(storagePathProvider.getOdkDirPath(StorageSubdirectory.CACHE)), storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS), new FormMetadataParser(), System::currentTimeMillis);
public FormDownloader providesFormDownloader(FormSourceProvider formSourceProvider, FormsRepositoryProvider formsRepositoryProvider, StoragePathProvider storagePathProvider, Supplier<Long> clock) {
return new ServerFormDownloader(formSourceProvider.get(), formsRepositoryProvider.get(), new File(storagePathProvider.getOdkDirPath(StorageSubdirectory.CACHE)), storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS), new FormMetadataParser(), clock);
}

@Provides
Expand Down Expand Up @@ -483,8 +484,8 @@ public FormsRepositoryProvider providesFormsRepositoryProvider(Application appli
}

@Provides
public InstancesRepositoryProvider providesInstancesRepositoryProvider(Context context, StoragePathProvider storagePathProvider) {
return new InstancesRepositoryProvider(context, storagePathProvider);
public InstancesRepositoryProvider providesInstancesRepositoryProvider(Context context, StoragePathProvider storagePathProvider, Supplier<Long> clock) {
return new InstancesRepositoryProvider(context, storagePathProvider, clock);
}

@Provides
Expand Down Expand Up @@ -513,8 +514,8 @@ public FormSourceProvider providesFormSourceProvider(SettingsProvider settingsPr
}

@Provides
public FormsUpdater providesFormsUpdater(Context context, Notifier notifier, SyncStatusAppState syncStatusAppState, ProjectDependencyProviderFactory projectDependencyProviderFactory) {
return new FormsUpdater(context, notifier, syncStatusAppState, projectDependencyProviderFactory, System::currentTimeMillis);
public FormsUpdater providesFormsUpdater(Context context, Notifier notifier, SyncStatusAppState syncStatusAppState, ProjectDependencyProviderFactory projectDependencyProviderFactory, Supplier<Long> clock) {
return new FormsUpdater(context, notifier, syncStatusAppState, projectDependencyProviderFactory, clock);
}

@Provides
Expand Down Expand Up @@ -648,4 +649,9 @@ public FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory(Sett
public SavedInstanceStateProvider providesSavedInstanceStateProvider() {
return savedInstanceState -> savedInstanceState;
}

@Provides
public Supplier<Long> providesClock() {
return System::currentTimeMillis;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import org.odk.collect.forms.instances.Instance
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.keys.ProtectedProjectKeys

const val OCTOBER_1st_2023_UTC = 1696118400000

fun Instance.canBeEdited(settingsProvider: SettingsProvider): Boolean {
return this.status == Instance.STATUS_INCOMPLETE &&
settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_EDIT_SAVED)
}

fun Instance.canBeEditedWithGracePeriod(settingsProvider: SettingsProvider): Boolean {
return (this.status == Instance.STATUS_INCOMPLETE || (this.status == Instance.STATUS_COMPLETE && this.lastStatusChangeDate < OCTOBER_1st_2023_UTC)) &&
settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_EDIT_SAVED)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import org.odk.collect.android.database.instances.DatabaseInstancesRepository
import org.odk.collect.android.storage.StoragePathProvider
import org.odk.collect.android.storage.StorageSubdirectory
import org.odk.collect.forms.instances.InstancesRepository
import java.util.function.Supplier

class InstancesRepositoryProvider @JvmOverloads constructor(
private val context: Context,
private val storagePathProvider: StoragePathProvider = StoragePathProvider()
private val storagePathProvider: StoragePathProvider = StoragePathProvider(),
private val clock: Supplier<Long> = Supplier { System.currentTimeMillis() }
) {

@JvmOverloads
Expand All @@ -17,7 +19,7 @@ class InstancesRepositoryProvider @JvmOverloads constructor(
context,
storagePathProvider.getOdkDirPath(StorageSubdirectory.METADATA, projectId),
storagePathProvider.getOdkDirPath(StorageSubdirectory.INSTANCES, projectId),
System::currentTimeMillis
clock
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.odk.collect.android.injection.config.AppDependencyModule
import org.odk.collect.android.notifications.Notifier
import org.odk.collect.android.projects.ProjectDependencyProviderFactory
import org.odk.collect.android.support.CollectHelpers
import java.util.function.Supplier

@RunWith(AndroidJUnit4::class)
class AutoUpdateTaskSpecTest {
Expand All @@ -29,10 +30,11 @@ class AutoUpdateTaskSpecTest {
fun setup() {
CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() {
override fun providesFormsUpdater(
context: Context,
notifier: Notifier,
syncStatusAppState: SyncStatusAppState,
projectDependencyProviderFactory: ProjectDependencyProviderFactory
context: Context?,
notifier: Notifier?,
syncStatusAppState: SyncStatusAppState?,
projectDependencyProviderFactory: ProjectDependencyProviderFactory?,
clock: Supplier<Long>?
): FormsUpdater {
return formUpdateChecker
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.odk.collect.android.injection.config.AppDependencyModule
import org.odk.collect.android.notifications.Notifier
import org.odk.collect.android.projects.ProjectDependencyProviderFactory
import org.odk.collect.android.support.CollectHelpers
import java.util.function.Supplier

@RunWith(AndroidJUnit4::class)
class SyncFormsTaskSpecTest {
Expand All @@ -26,10 +27,11 @@ class SyncFormsTaskSpecTest {
fun setup() {
CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() {
override fun providesFormsUpdater(
context: Context,
notifier: Notifier,
syncStatusAppState: SyncStatusAppState,
projectDependencyProviderFactory: ProjectDependencyProviderFactory
context: Context?,
notifier: Notifier?,
syncStatusAppState: SyncStatusAppState?,
projectDependencyProviderFactory: ProjectDependencyProviderFactory?,
clock: Supplier<Long>?
): FormsUpdater {
return formsUpdater
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.mockito.kotlin.whenever
import org.odk.collect.android.R
import org.odk.collect.android.activities.FormFillingActivity
import org.odk.collect.android.injection.config.AppDependencyModule
import org.odk.collect.android.instancemanagement.OCTOBER_1st_2023_UTC
import org.odk.collect.android.projects.CurrentProjectProvider
import org.odk.collect.android.storage.StoragePathProvider
import org.odk.collect.android.support.CollectHelpers
Expand All @@ -55,6 +56,7 @@ import org.odk.collect.settings.keys.ProtectedProjectKeys
import org.odk.collect.shared.TempFiles
import org.odk.collect.shared.strings.UUIDGenerator
import java.io.File
import java.util.function.Supplier

@RunWith(AndroidJUnit4::class)
class FormUriActivityTest {
Expand Down Expand Up @@ -98,8 +100,9 @@ class FormUriActivityTest {
}

override fun providesInstancesRepositoryProvider(
context: Context,
storagePathProvider: StoragePathProvider
context: Context?,
storagePathProvider: StoragePathProvider?,
clock: Supplier<Long>?
): InstancesRepositoryProvider {
return mock<InstancesRepositoryProvider>().apply {
whenever(get()).thenReturn(instancesRepository)
Expand Down Expand Up @@ -372,7 +375,7 @@ class FormUriActivityTest {
}

@Test
fun `When attempting to edit a finalized form then start form for view only`() {
fun `When attempting to edit a finalized form saved before October 1st 2023 UTC then start form filling`() {
val project = Project.Saved("123", "First project", "A", "#cccccc")
projectsRepository.save(project)
whenever(currentProjectProvider.getCurrentProject()).thenReturn(project)
Expand All @@ -383,6 +386,32 @@ class FormUriActivityTest {
Instance.Builder()
.formId("1")
.formVersion("1")
.lastStatusChangeDate(OCTOBER_1st_2023_UTC - 1)
.instanceFilePath(TempFiles.createTempFile(TempFiles.createTempDir()).absolutePath)
.status(Instance.STATUS_COMPLETE)
.build()
)

launcherRule.launchForResult<FormUriActivity>(getSavedIntent(project.uuid, instance.dbId))

assertStartSavedFormIntent(project.uuid, instance.dbId, true)
}

@Test
fun `When attempting to edit a finalized form saved after October 1st 2023 UTC then start form for view only`() {
val project = Project.Saved("123", "First project", "A", "#cccccc")
projectsRepository.save(project)
whenever(currentProjectProvider.getCurrentProject()).thenReturn(project)

formsRepository.save(
FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()
)

val instance = instancesRepository.save(
Instance.Builder()
.formId("1")
.formVersion("1")
.lastStatusChangeDate(OCTOBER_1st_2023_UTC + 1)
.instanceFilePath(TempFiles.createTempFile(TempFiles.createTempDir()).absolutePath)
.status(Instance.STATUS_COMPLETE)
.build()
Expand Down

0 comments on commit c9f5ace

Please sign in to comment.