mirror of
https://github.com/wismna/DoNext.git
synced 2025-12-06 00:02:40 -05:00
Test database v6 -> v7 migration (finally!)
This commit is contained in:
26
.idea/androidTestResultsUserPreferences.xml
generated
26
.idea/androidTestResultsUserPreferences.xml
generated
@@ -3,6 +3,32 @@
|
||||
<component name="AndroidTestResultsUserPreferences">
|
||||
<option name="androidTestResultsTableState">
|
||||
<map>
|
||||
<entry key="-2002158262">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Medium_Phone_API_36.0" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="-1209641426">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Medium_Phone_API_36.0" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1337588336">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
|
||||
7
.idea/deploymentTargetSelector.xml
generated
7
.idea/deploymentTargetSelector.xml
generated
@@ -5,10 +5,13 @@
|
||||
<SelectionState runConfigName="donextv2">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="overdueCount_correctlyCalculated()">
|
||||
<SelectionState runConfigName="donext">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="donext">
|
||||
<SelectionState runConfigName="DatabaseMigrationTest">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="migrate_v6_to_v7_preserves_data_and_transforms_columns()">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
|
||||
@@ -14,7 +14,6 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21'
|
||||
classpath 'org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.0.21'
|
||||
|
||||
@@ -38,5 +37,3 @@ allprojects {
|
||||
tasks.register('clean', Delete) {
|
||||
delete rootProject.layout.buildDirectory
|
||||
}
|
||||
|
||||
apply plugin: 'org.sonarqube'
|
||||
@@ -22,15 +22,6 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
sonarqube {
|
||||
properties {
|
||||
property 'sonar.host.url', '#{sonar.host.url}'
|
||||
property 'sonar.login', '#{sonar.login}'
|
||||
property 'sonar.organization', '#{sonar.organization}'
|
||||
property 'sonar.projectKey', '#{sonar.projectkey}'
|
||||
property 'sonar.branch', 'master'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
@@ -6,6 +6,10 @@ plugins {
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.wismna.geoffroy.donext"
|
||||
compileSdk = 36
|
||||
@@ -20,6 +24,10 @@ android {
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("debug").assets.srcDirs(files("$projectDir/schemas"))
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// Enables code-related app optimization.
|
||||
@@ -46,6 +54,17 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.1.1"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency {
|
||||
when (requested.module.toString()) {
|
||||
// Required for forcing the serialization lib version used by MigrationTestHelper
|
||||
"org.jetbrains.kotlinx:kotlinx-serialization-core-jvm" -> useVersion("1.8.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -60,15 +79,17 @@ dependencies {
|
||||
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||
implementation("androidx.navigation:navigation-compose:2.9.5")
|
||||
implementation("androidx.hilt:hilt-navigation-compose:1.3.0")
|
||||
implementation("androidx.test.ext:junit-ktx:1.3.0")
|
||||
implementation("sh.calvin.reorderable:reorderable:3.0.0")
|
||||
androidTestImplementation("androidx.test.ext:junit-ktx:1.3.0")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.10.01"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
androidTestImplementation("com.google.truth:truth:1.4.4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
||||
val roomVersion = "2.8.3"
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
androidTestImplementation("androidx.room:room-testing:$roomVersion")
|
||||
ksp("androidx.room:room-compiler:$roomVersion")
|
||||
|
||||
val hiltVersion = "2.57.2"
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "a911cf75d24949c0b24bd212cacc860c",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tasks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `description` TEXT, `cycle` INTEGER NOT NULL, `priority` INTEGER NOT NULL, `done` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `displayorder` INTEGER NOT NULL, `todayorder` INTEGER NOT NULL, `list` INTEGER NOT NULL, `duedate` TEXT, `todaydate` TEXT, FOREIGN KEY(`list`) REFERENCES `tasklist`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "_id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "cycle",
|
||||
"columnName": "cycle",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "priority",
|
||||
"columnName": "priority",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "done",
|
||||
"columnName": "done",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "order",
|
||||
"columnName": "displayorder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "todayOrder",
|
||||
"columnName": "todayorder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "taskList",
|
||||
"columnName": "list",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dueDate",
|
||||
"columnName": "duedate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "todayDate",
|
||||
"columnName": "todaydate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_tasks_list",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"list"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_list` ON `${TABLE_NAME}` (`list`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "tasklist",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"list"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"_id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "tasklist",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `visible` INTEGER NOT NULL, `displayorder` INTEGER NOT NULL, `taskCount` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "_id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "visible",
|
||||
"columnName": "visible",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "order",
|
||||
"columnName": "displayorder",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "taskCount",
|
||||
"columnName": "taskCount",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"viewName": "TodayTasksView",
|
||||
"createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM tasks WHERE todaydate = date('now','localtime')"
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a911cf75d24949c0b24bd212cacc860c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "adb2abaced32bebf52bab45ae8069f40",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tasks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `priority` INTEGER NOT NULL, `done` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `task_list_id` INTEGER NOT NULL, `due_date` INTEGER)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "priority",
|
||||
"columnName": "priority",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDone",
|
||||
"columnName": "done",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDeleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "taskListId",
|
||||
"columnName": "task_list_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dueDate",
|
||||
"columnName": "due_date",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "task_lists",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `display_order` INTEGER NOT NULL, `deleted` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "order",
|
||||
"columnName": "display_order",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDeleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'adb2abaced32bebf52bab45ae8069f40')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.wismna.geoffroy.donext.data.local
|
||||
|
||||
import android.content.ContentValues
|
||||
import androidx.room.Room
|
||||
import androidx.room.testing.MigrationTestHelper
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DatabaseMigrationTest {
|
||||
|
||||
private val TEST_DB = "migration-test.db"
|
||||
|
||||
@get:Rule
|
||||
val helper = MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
AppDatabase::class.java,
|
||||
listOf(),
|
||||
FrameworkSQLiteOpenHelperFactory()
|
||||
)
|
||||
|
||||
/**
|
||||
* This test recreates the old SQLite schema (v6 from DatabaseHelper),
|
||||
* inserts sample legacy rows, runs AppDatabase.MIGRATION_6_7 and then
|
||||
* validates the migrated data by calling the real DAOs you provided.
|
||||
*/
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate_v6_to_v7_preserves_data_and_transforms_columns() {
|
||||
// Arrange
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
val db: SupportSQLiteDatabase = helper.createDatabase(TEST_DB, 6)
|
||||
|
||||
val listValues = ContentValues().apply {
|
||||
put("_id", 1)
|
||||
put("name", "Legacy List")
|
||||
put("displayorder", 10)
|
||||
put("visible", 1)
|
||||
put("taskCount", 0)
|
||||
}
|
||||
db.insert("tasklist", 0, listValues)
|
||||
|
||||
val taskValues = ContentValues().apply {
|
||||
put("_id", 1) // explicit id
|
||||
put("name", "Legacy Task")
|
||||
put("description", "Old task description")
|
||||
put("priority", 2)
|
||||
put("cycle", 0)
|
||||
put("done", 0)
|
||||
put("deleted", 0)
|
||||
put("displayorder", 5)
|
||||
put("todayorder", 0)
|
||||
put("list", 1) // references tasklist _id = 1
|
||||
put("duedate", "2025-09-15") // legacy text date format that migration converts
|
||||
put("todaydate", null as String?)
|
||||
}
|
||||
db.insert("tasks", 0, taskValues)
|
||||
db.close()
|
||||
|
||||
// Act
|
||||
helper.runMigrationsAndValidate(TEST_DB, 7, true, AppDatabase.MIGRATION_6_7)
|
||||
|
||||
val migratedRoom = Room.databaseBuilder(context, AppDatabase::class.java, TEST_DB)
|
||||
.addMigrations(AppDatabase.MIGRATION_6_7)
|
||||
.build()
|
||||
|
||||
// Assert
|
||||
try {
|
||||
val listDao = migratedRoom.taskListDao()
|
||||
val taskDao = migratedRoom.taskDao()
|
||||
|
||||
runBlocking {
|
||||
val migratedList = listDao.getTaskListById(1L)
|
||||
assertThat(migratedList).isNotNull()
|
||||
assertThat(migratedList!!.id).isEqualTo(1L)
|
||||
assertThat(migratedList.name).isEqualTo("Legacy List")
|
||||
assertThat(migratedList.isDeleted).isEqualTo(false)
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
val migratedTask = taskDao.getTaskById(1L)
|
||||
assertThat(migratedTask).isNotNull()
|
||||
assertThat(migratedTask!!.id).isEqualTo(1L)
|
||||
assertThat(migratedTask.name).isEqualTo("Legacy Task")
|
||||
assertThat(migratedTask.description).isEqualTo("Old task description")
|
||||
assertThat(migratedTask.priority).isEqualTo(Priority.HIGH)
|
||||
assertThat(migratedTask.isDone).isEqualTo(false)
|
||||
assertThat(migratedTask.isDeleted).isEqualTo(false)
|
||||
assertThat(migratedTask.dueDate).isNotNull()
|
||||
assertThat(migratedTask.dueDate!!).isGreaterThan(0L)
|
||||
assertThat(migratedTask.taskListId).isEqualTo(1L)
|
||||
}
|
||||
} finally {
|
||||
migratedRoom.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user