Skip to content

Commit 40ff026

Browse files
committed
test: cover PlotsX UUID migration and rollback
1 parent 301dd22 commit 40ff026

1 file changed

Lines changed: 172 additions & 0 deletions

File tree

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package pl.syntaxdevteam.plotsx.identity
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Assert.assertFalse
5+
import org.junit.Assert.assertTrue
6+
import org.junit.Test
7+
import pl.syntaxdevteam.plotsx.databases.DatabaseMigrations
8+
import pl.syntaxdevteam.plotsx.databases.OperationJournal
9+
import pl.syntaxdevteam.plotsx.geometry.ChunkPosition
10+
import java.math.BigDecimal
11+
import java.sql.Connection
12+
import java.sql.DriverManager
13+
import java.util.UUID
14+
15+
class IdentityMigrationStoreTest {
16+
private val source = UUID.fromString("11111111-1111-3111-8111-111111111111")
17+
private val target = UUID.fromString("22222222-2222-4222-8222-222222222222")
18+
19+
@Test
20+
fun `migration and rollback preserve exact ownership and membership on sqlite and h2`() {
21+
databases { connection ->
22+
val plotId = insertPlot(connection, source)
23+
connection.prepareStatement(
24+
"INSERT INTO plot_members(plot_id, member_uuid, role) VALUES (?, ?, ?)",
25+
).use {
26+
it.setInt(1, plotId)
27+
it.setString(2, source.toString())
28+
it.setString(3, "trusted")
29+
it.executeUpdate()
30+
}
31+
32+
val migrationId = UUID.randomUUID()
33+
val inspection = IdentityMigrationStore.inspect(connection, migrationId, source, target)
34+
assertEquals(IdentityMigrationBridgeStatus.READY, inspection.status)
35+
assertTrue(inspection.legacyEvidence)
36+
37+
val migrated = IdentityMigrationStore.migrate(connection, migrationId, source, target)
38+
assertEquals(IdentityMigrationBridgeStatus.SUCCESS, migrated.status)
39+
assertEquals(target.toString(), scalar(connection, "SELECT owner_uuid FROM plots WHERE plot_id = $plotId"))
40+
assertEquals(target.toString(), scalar(connection, "SELECT member_uuid FROM plot_members WHERE plot_id = $plotId"))
41+
assertEquals(target.toString(), scalar(connection, "SELECT target_uuid FROM plotsx_uuid_aliases WHERE source_uuid = '$source'"))
42+
43+
val repeated = IdentityMigrationStore.migrate(connection, migrationId, source, target)
44+
assertEquals(IdentityMigrationBridgeStatus.SUCCESS, repeated.status)
45+
46+
val rolledBack = IdentityMigrationStore.rollback(connection, migrationId, source, target)
47+
assertEquals(IdentityMigrationBridgeStatus.ROLLED_BACK, rolledBack.status)
48+
assertEquals(source.toString(), scalar(connection, "SELECT owner_uuid FROM plots WHERE plot_id = $plotId"))
49+
assertEquals(source.toString(), scalar(connection, "SELECT member_uuid FROM plot_members WHERE plot_id = $plotId"))
50+
assertEquals("trusted", scalar(connection, "SELECT role FROM plot_members WHERE plot_id = $plotId"))
51+
assertEquals(0, count(connection, "SELECT COUNT(*) FROM plotsx_uuid_aliases WHERE source_uuid = '$source'"))
52+
53+
val repeatedRollback = IdentityMigrationStore.rollback(connection, migrationId, source, target)
54+
assertEquals(IdentityMigrationBridgeStatus.ROLLED_BACK, repeatedRollback.status)
55+
}
56+
}
57+
58+
@Test
59+
fun `member collision blocks migration without changing rows`() {
60+
databases { connection ->
61+
val plotId = insertPlot(connection, UUID.randomUUID())
62+
for (uuid in listOf(source, target)) {
63+
connection.prepareStatement(
64+
"INSERT INTO plot_members(plot_id, member_uuid, role) VALUES (?, ?, ?)",
65+
).use {
66+
it.setInt(1, plotId)
67+
it.setString(2, uuid.toString())
68+
it.setString(3, "member")
69+
it.executeUpdate()
70+
}
71+
}
72+
73+
val result = IdentityMigrationStore.inspect(connection, UUID.randomUUID(), source, target)
74+
75+
assertEquals(IdentityMigrationBridgeStatus.BLOCKED, result.status)
76+
assertEquals("PLOTSX_MEMBER_COLLISION", result.reasonCode)
77+
assertEquals(2, count(connection, "SELECT COUNT(*) FROM plot_members WHERE plot_id = $plotId"))
78+
}
79+
}
80+
81+
@Test
82+
fun `unresolved payment journal blocks identity migration`() {
83+
databases { connection ->
84+
val plotId = insertPlot(connection, source)
85+
val operation = OperationJournal.Operation(
86+
UUID.randomUUID(),
87+
plotId,
88+
source,
89+
source,
90+
"world",
91+
ChunkPosition(0, 0),
92+
ChunkPosition(1, 0),
93+
0,
94+
BigDecimal("10.00"),
95+
"Vault:Test",
96+
"default",
97+
createdAt = 100,
98+
)
99+
assertTrue(OperationJournal.prepare(connection, operation))
100+
101+
val result = IdentityMigrationStore.inspect(connection, UUID.randomUUID(), source, target)
102+
103+
assertEquals(IdentityMigrationBridgeStatus.BLOCKED, result.status)
104+
assertEquals("PLOTSX_PENDING_FINANCIAL_OPERATION", result.reasonCode)
105+
assertTrue(result.legacyEvidence)
106+
}
107+
}
108+
109+
@Test
110+
fun `alias registry resolves chains and removes only matching alias`() {
111+
val registry = IdentityAliasRegistry()
112+
val third = UUID.fromString("33333333-3333-4333-8333-333333333333")
113+
registry.put(source, target)
114+
registry.put(target, third)
115+
assertEquals(third, registry.resolve(source))
116+
registry.remove(source, third)
117+
assertEquals(third, registry.resolve(source))
118+
registry.remove(source, target)
119+
assertEquals(source, registry.resolve(source))
120+
assertFalse(registry.snapshot().containsKey(source))
121+
}
122+
123+
private fun databases(test: (Connection) -> Unit) {
124+
for (type in listOf("sqlite", "h2")) {
125+
val url = if (type == "sqlite") "jdbc:sqlite::memory:" else "jdbc:h2:mem:identity-${UUID.randomUUID()}"
126+
DriverManager.getConnection(url).use { connection ->
127+
if (type == "sqlite") {
128+
connection.createStatement().use { it.execute("PRAGMA foreign_keys=ON") }
129+
}
130+
DatabaseMigrations.migrate(connection, type)
131+
OperationJournal.migrate(connection)
132+
IdentityMigrationStore.migrateSchema(connection)
133+
test(connection)
134+
}
135+
}
136+
}
137+
138+
private fun insertPlot(connection: Connection, owner: UUID): Int {
139+
connection.prepareStatement(
140+
"""INSERT INTO plots
141+
(owner_uuid, x, z, y, radius, world, name, creation_time, geometry_type, geometry_revision)
142+
VALUES (?, ?, ?, 64, 8, 'world', 'home', 1, 'classic', 0)""".trimIndent(),
143+
).use {
144+
it.setString(1, owner.toString())
145+
it.setInt(2, (Math.random() * 100000).toInt())
146+
it.setInt(3, (Math.random() * 100000).toInt())
147+
it.executeUpdate()
148+
}
149+
return connection.createStatement().use {
150+
it.executeQuery("SELECT MAX(plot_id) FROM plots").use { rows ->
151+
assertTrue(rows.next())
152+
rows.getInt(1)
153+
}
154+
}
155+
}
156+
157+
private fun scalar(connection: Connection, sql: String): String =
158+
connection.createStatement().use { statement ->
159+
statement.executeQuery(sql).use { rows ->
160+
assertTrue(rows.next())
161+
rows.getString(1)
162+
}
163+
}
164+
165+
private fun count(connection: Connection, sql: String): Int =
166+
connection.createStatement().use { statement ->
167+
statement.executeQuery(sql).use { rows ->
168+
assertTrue(rows.next())
169+
rows.getInt(1)
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)