@@ -5,6 +5,7 @@ import com.google.gson.Gson
55import com.google.gson.JsonParser
66import io.github.sds100.keymapper.actions.sound.SoundFileInfo
77import io.github.sds100.keymapper.actions.sound.SoundsManager
8+ import io.github.sds100.keymapper.backup.BackupContent
89import io.github.sds100.keymapper.backup.BackupManagerImpl
910import io.github.sds100.keymapper.backup.RestoreType
1011import io.github.sds100.keymapper.data.db.AppDatabase
@@ -46,10 +47,12 @@ import org.junit.Rule
4647import org.junit.Test
4748import org.junit.rules.TemporaryFolder
4849import org.junit.runner.RunWith
50+ import org.mockito.ArgumentMatchers
4951import org.mockito.junit.MockitoJUnitRunner
5052import org.mockito.kotlin.any
5153import org.mockito.kotlin.anyVararg
5254import org.mockito.kotlin.doReturn
55+ import org.mockito.kotlin.inOrder
5356import org.mockito.kotlin.mock
5457import org.mockito.kotlin.never
5558import org.mockito.kotlin.times
@@ -79,6 +82,7 @@ class BackupManagerTest {
7982 private lateinit var fakeFileAdapter: FakeFileAdapter
8083 private lateinit var fakePreferenceRepository: PreferenceRepository
8184 private lateinit var mockKeyMapRepository: KeyMapRepository
85+ private lateinit var mockGroupRepository: GroupRepository
8286 private lateinit var mockSoundsManager: SoundsManager
8387 private lateinit var mockUuidGenerator: UuidGenerator
8488
@@ -94,6 +98,10 @@ class BackupManagerTest {
9498 fakePreferenceRepository = FakePreferenceRepository ()
9599
96100 mockKeyMapRepository = mock()
101+ mockGroupRepository = mock<GroupRepository > {
102+ on { getAllGroups() } doReturn MutableStateFlow (emptyList())
103+ on { getGroupsByParent(ArgumentMatchers .any()) }.thenReturn(MutableStateFlow (emptyList()))
104+ }
97105
98106 fakeFileAdapter = FakeFileAdapter (temporaryFolder)
99107
@@ -116,9 +124,7 @@ class BackupManagerTest {
116124 floatingLayoutRepository = mock<FloatingLayoutRepository > {
117125 on { layouts } doReturn MutableStateFlow (State .Data (emptyList()))
118126 },
119- groupRepository = mock<GroupRepository > {
120- on { getAllGroups() } doReturn MutableStateFlow (emptyList())
121- },
127+ groupRepository = mockGroupRepository,
122128 )
123129
124130 parser = JsonParser ()
@@ -130,6 +136,62 @@ class BackupManagerTest {
130136 Dispatchers .resetMain()
131137 }
132138
139+ /* *
140+ * Issue #1655. If the list of groups in the backup has a child before the parent then the
141+ * parent must be restored first. Otherwise the SqliteConstraintException will be thrown.
142+ */
143+ @Test
144+ fun `restore groups breadth first so parents exist before children are restored` () = runTest(testDispatcher) {
145+ val parentGroup1 = GroupEntity (
146+ uid = " parent_group_1_uid" ,
147+ name = " parent_group_1_name" ,
148+ parentUid = null ,
149+ lastOpenedDate = 0L ,
150+ )
151+
152+ val parentGroup2 = GroupEntity (
153+ uid = " parent_group_2_uid" ,
154+ name = " parent_group_2_name" ,
155+ parentUid = null ,
156+ lastOpenedDate = 0L ,
157+ )
158+
159+ val childGroup = GroupEntity (
160+ uid = " child_group_uid" ,
161+ name = " child_group_name" ,
162+ parentUid = parentGroup1.uid,
163+ lastOpenedDate = 0L ,
164+ )
165+
166+ val grandChildGroup = GroupEntity (
167+ uid = " grand_child_group_uid" ,
168+ name = " grand_child_group_name" ,
169+ parentUid = childGroup.uid,
170+ lastOpenedDate = 0L ,
171+ )
172+
173+ val backupContent = BackupContent (
174+ appVersion = Constants .VERSION_CODE ,
175+ dbVersion = AppDatabase .DATABASE_VERSION ,
176+ groups = listOf (parentGroup2, grandChildGroup, childGroup, parentGroup1),
177+ )
178+
179+ inOrder(mockGroupRepository) {
180+ backupManager.restore(
181+ RestoreType .REPLACE ,
182+ backupContent,
183+ emptyList(),
184+ currentTime = 0L ,
185+ )
186+
187+ verify(mockGroupRepository).insert(parentGroup1)
188+ verify(mockGroupRepository).insert(childGroup)
189+ verify(mockGroupRepository).insert(grandChildGroup)
190+ verify(mockGroupRepository).insert(parentGroup2)
191+ verify(mockGroupRepository, never()).update(any())
192+ }
193+ }
194+
133195 @Test
134196 fun `when backing up everything include layouts that are not in the list of key maps` () = runTest(testDispatcher) {
135197 val layoutWithButtons = FloatingLayoutEntityWithButtons (
0 commit comments