med-det-samme/app/src/androidTest/java/no/naiv/meddetsamme/data/MigrationTest.kt
Ole-Morten Duesund 9b5817fcef Inventory split: InventoryItem entity, schema v2 with tested migration, Lager screen
Stock, package size and prescription state move to inventory_item;
medication is now a regimen (itemId FK RESTRICT — deleting a stock item
can never silently take adherence history with it). Supply warnings
aggregate consumption across all regimens sharing an item. New Lager
screen with one-tap '+1 pakning' restock; med editor picks an existing
item or creates one inline via the shared ItemFormState (one form, no
drift). Backup format v2 mirrors the split and still restores v1 files
by performing the same conversion the DB migration does.

MIGRATION_1_2 statements are copied from the exported 2.json and proven
by an instrumented MigrationTestHelper test on the emulator (validates
against the schema AND asserts data survival) before it ever touches
the phone's medical record. Heads-up: connectedAndroidTest must target
the emulator (ANDROID_SERIAL) — the phone's release signature rejects
debug test APKs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:13:28 +02:00

83 lines
3.4 KiB
Kotlin

package no.naiv.meddetsamme.data
import androidx.room.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Proves MIGRATION_1_2 against the exported v1 schema: the helper creates a
* REAL v1 database, we fill it like the app would have, migrate, and Room
* validates the result against 2.json. This runs before the migration ever
* touches the phone's medical record.
*/
@RunWith(AndroidJUnit4::class)
class MigrationTest {
@get:Rule
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
MedDatabase::class.java,
)
@Test
fun migrate1To2_splitsItemsAndKeepsHistory() {
helper.createDatabase(TEST_DB, 1).use { db ->
db.execSQL(
"INSERT INTO medication (id, name, strength, unit, form, withFood, notes, " +
"inventoryUnits, packageSize, lowStockLeadDays, rxExpiryEpochDay, " +
"refillsRemaining, atcCode, active) VALUES " +
"(7, 'Levaxin', '50 µg', 'tablett', 'TABLET', 0, 'fastende', " +
"42.5, 100.0, 10, 20800, 2, 'H03AA01', 1)",
)
db.execSQL(
"INSERT INTO dose_time (id, medId, minuteOfDay, amount, daysOfWeekMask, " +
"intervalDays, anchorEpochDay) VALUES (3, 7, 450, 1.5, 127, 0, NULL)",
)
db.execSQL(
"INSERT INTO dose_log (id, medId, doseTimeId, scheduledAtMillis, amount, " +
"status, actionedAtMillis, nagCount) VALUES " +
"(11, 7, 3, 1780000000000, 1.5, 'TAKEN', 1780000123000, 2)",
)
}
// Migrates AND validates the result against the exported 2.json schema.
helper.runMigrationsAndValidate(TEST_DB, 2, true, MedDatabase.MIGRATION_1_2).use { db ->
db.query("SELECT id, name, stockUnits, rxExpiryEpochDay FROM inventory_item").use { c ->
assertTrue(c.moveToFirst())
assertEquals(7, c.getLong(0)) // item id mirrors the old med id
assertEquals("Levaxin", c.getString(1))
assertEquals(42.5, c.getDouble(2), 0.0)
assertEquals(20800, c.getLong(3))
assertEquals(1, c.count)
}
db.query("SELECT id, itemId, withFood, notes, active FROM medication").use { c ->
assertTrue(c.moveToFirst())
assertEquals(7, c.getLong(0))
assertEquals(7, c.getLong(1))
assertEquals("fastende", c.getString(3))
assertEquals(1, c.getInt(4))
}
// The medical record must come through untouched.
db.query("SELECT medId, doseTimeId, status, nagCount FROM dose_log").use { c ->
assertTrue(c.moveToFirst())
assertEquals(7, c.getLong(0))
assertEquals(3, c.getLong(1))
assertEquals("TAKEN", c.getString(2))
assertEquals(2, c.getInt(3))
}
db.query("SELECT COUNT(*) FROM dose_time").use { c ->
c.moveToFirst()
assertEquals(1, c.getInt(0))
}
}
}
private companion object {
const val TEST_DB = "migration-test.db"
}
}