Skip to content

Testing migrations

Testing a migration

A NPL migration is made up of one or more changes as described in the migration descriptor file (migration.yml). The testing tool, EngineMigrationTester, provides a way to run all the changes in one step, or each change individually.

To get started,

  1. Specify the directory where the migration artifacts reside. To see what artifacts are necessary, see Migrations - getting started.
  2. Use a classpath resolver to determine the actual system location of the migration artifact directory.
  3. Provide various configuration details using the Configuration object. Use the configuration inner class, DbConfig, to set the connection details for the test database.
  4. Instantiate the tester
val migrationDir = "migration-files/"
val migrationLocation = File(javaClass.getResource(migrationDir)!!.path)
val dbConfig = DBConfig(dbUrl = url, dbSchema = schema, dbUser = user, dbPass = password)

val tester = EngineMigrationTester(migrationLocation, Configuration(dbConfig = dbConfig))

Verify a change

To validate a change, the typical assertions methods will suffice.

The following functions can be used to retrieve and modify protocols and the associated states.

Create a new protocol state

val createdId = tester.createProtocol(
    "/ENGINE_IT-1.0.0?/testpkg/Iou",
    listOf(NumberValue(1000)),
    listOf(issuerParty, payeeParty)
)

Execute a protocol permission

To execute a permission, e.g. on the IOU protocol, would be like this:

selectAction(createdId, "pay", listOf(NumberValue(100)))

or like this if the action returns a value

val amount = selectAction(createdId, "getAmountOwed") as NumberValue

Retrieve protocol state(s)

There are several ways to retrieve protocol states.

To retrieve a specific protocol state by its id, use getProtocolStateById

val protocolState = tester.getProtocolStateById(createdId)

To retrieve all states for a given protocol prototype id, use getCurrentProtocolStates

val allContract100States = tester.getCurrentProtocolStates(CONTRACT_1_0_0).toList()

To retrieve the states for all protocol instances, use getAllProtocolStates

val allProtocolStates = tester.getAllProtocolStates().toList()

Run migration in one step

To run all changes and then verify, use the run function.

val tester = EngineMigrationTester(migrationHome, Configuration(getDbConfig()))

tester.run()

Examples

An example of running all changes in a migration and asserting success

fun `Run full migration and assert`() {
    val migrationDir = "migration-files/"
    val migrationLocation = File(javaClass.getResource(migrationDir)!!.path)
    val tester = EngineMigrationTester(migrationLocation, Configuration(getDbConfig()))

    tester.run()

    val protocolStates = tester.getCurrentProtocolStates("/ENGINE_IT-1.0.2?/testpkg/Foo")

    assertEquals(2, protocolStates.count())
    tester.close()
}

Run a migration step by step

To run multiple changes independently, use the runTo function. The runTo function will apply the changes in order, determined by the descriptor file, up to and including the change with the name specified. All previously migrated steps will be skipped.

val tester = EngineMigrationTester(migrationLocation, Configuration(getDbConfig()))

// run to specific change
tester.runTo("1.0.2")

//  ... assertions verifying specified change was migrated successful ...

tester.runTo("1.0.3")

Examples

An example of running each change separately and asserting success after each

fun `Migrate to each version and assert`() {
        val migrationDir = "migration-files/"
        val migrationLocation = File(javaClass.getResource(migrationDir)!!.path)
        val tester = EngineMigrationTester(migrationLocation, Configuration(getDbConfig()))

        tester.runTo("1.0.0") // Apply only the first change

        // Assert first change completed successfully
        val createdId =
            tester.createProtocol("/ENGINE_IT-1.0.0?/testpkg/Iou", listOf(NumberValue(1000)), listOf(p1, p2))

        tester.apply {
            val originalState = getProtocolStateById(createdId)
            val stateAmount = (originalState?.getFrameValue("forAmount") as NumberValue).value
            val selectAmount = (selectAction(createdId, "getAmountOwed") as NumberValue).value

            assertNotNull(originalState)
            assertEquals(1000, stateAmount.intValueExact())
            assertEquals(1000, selectAmount.intValueExact())

            selectAction(createdId, "pay", listOf(NumberValue(100)))
            selectAction(createdId, "pay", listOf(NumberValue(0)))
            selectAction(createdId, "pay", listOf(NumberValue(50)))

            val afterPayments = getProtocolStateById(createdId)
            val payments = (afterPayments?.getFrameValue("payments") as ListValue).value.map {
                ((it as StructValue).slots.getValue("amount") as NumberValue).value.intValueExact()
            }

            assertContentEquals(listOf(100, 0, 50), payments)
        }

        // Apply and assert second change
        tester.runTo("1.0.1")
        tester.apply {
            val state = getProtocolStateById(createdId)
            val payments = (state?.getFrameValue("payments") as ListValue).value.map {
                ((it as StructValue).slots.getValue("amount") as NumberValue).value.intValueExact()
            }

            assertContentEquals(listOf(100, 50), payments)
        }

        // Apply and assert third change
        tester.runTo("1.0.2")
        tester.apply {
            val state = getProtocolStateById(createdId)
            val lateFee = (state?.getFrameValue("lateFee") as NumberValue).value.intValueExact()
            val paymentDeadline = (state.getFrameValue("paymentDeadline") as DateTimeValue).value
            val expectedPaymentDate = ZonedDateTime.now().plusMonths(3).truncatedTo(ChronoUnit.DAYS)

            assertEquals(300, lateFee)
            assertEquals(expectedPaymentDate, paymentDeadline)
        }

        // Assert logs
        val logs = tester.getLog()
        assertEquals(3, logs.size)
        assertTrue(logs.all { it.executionResult == NPLMigrationResult.SUCCESSFUL })

        logs.filter { it.changeId.startsWith("1.0.0") }.apply {
            assertEquals(1, size)
            assertEquals(1, filter { it.changeId.endsWith("migrate") }.size)
        }

        logs.filter { it.changeId.startsWith("1.0.1") }.apply {
            single { it.changeId.endsWith("migrate") }
        }

        logs.filter { it.changeId.startsWith("1.0.2") }.apply {
            single { it.changeId.endsWith("migrate") }
        }
        tester.close()
}