Repository: ImFlog/schema-registry-plugin
Pull request #128 (2023-04-18 17:24:10)

Fetch latest version for references, if not specified
**Story:**

As a developer using Confluent's schema registry, I want to always have a remote reference pointing at the latest remote version. There are cases where the version is unknown, or where it drifts between environments. By simply using the latest version, a large burden of managing versions across environments in our build file is lifted.

As a developer using this plugin, I don't want to necessarily specify the remote schema version when specifying my references. I need a method of communicating this in my definition(s).

**Implementation**

A new method has been added to the Subject class, addReference(name: String, subject:String). When a reference is not specified with a verison, the compatibility and register tasks will fetch the latest version from the configured schema registry.

I updated tests to use the new `@TempDir` in JUnit5, which I also think is an improvement over using the shadowed JUnit 4 class. I added some updates to the README, but please let me know if I could add more.

ndwhelan: This is a feature that exists in the Maven plugin Confluent manages. Not having this feature has caused my team immense pain and churn.

For what it's worth, in talking with Confluent folks, they don't seem too interested in making/owning a Gradle plugin, as well. I suppose that could change, though.
ImFlog: Hello @ndwhelan and thank you for using this plugin.

I wish you had created an issue first because It could have saved you some time doing this PR 😞
Fetching the latest version for a reference is already supported, If you take a look at the [examples](examples/avro/build.gradle), you will find some references using `-1`. When done, it will fetch the last version of the reference.

Thus there is improvment to do:
* Update the DSL to fetch the latest version (like what you done with the `addReference` without the version arg but that should default to -1
* Update the documentation

As for the interest in a Gradle plugin from Confluent I think they are pretty happy to have this plugin updated without costing them a cent 👀

If you want I can update this PR to reflect the 2 actions I listed above or I can let you do the changes yourself ?

Once again sorry for the time spent and the pain in your team 🙏

ndwhelan: Ah jeeze. Yeah, I'll update things.
ndwhelan: I updated the README! Thanks for the feedback!

Patch:
diff --git a/README.md b/README.md
index 09ca90c..d496d2a 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,8 @@ schemaRegistry {
                 .addLocalReference("localAvroSubject", "/a/local/path.avsc")
         subject('avroWithRemoteReferences', '/absolutPath/dependent/path.avsc', "AVRO")
                 .addReference('avroSubject', 'avroSubjectType', 1)
+                .addReference('avroSubjectLatestVersion', 'avroSubjectLatestVersionType')
+                .addReference('avroSubjectLatestVersionExplicit', 'avroSubjectLatestVersionExplicitType', -1)
         subject('protoWithReferences', 'dependent/path.proto', "PROTOBUF").addReference('protoSubject', 'protoSubjectType', 1)
         subject('jsonWithReferences', 'dependent/path.json', "JSON").addReference('jsonSubject', 'jsonSubjectType', 1)
     }
@@ -124,7 +126,11 @@ schemaRegistry {
 You have to list all the (subject, avsc file path) pairs that you want to test. 
 
 If you have references with other schemas stored in the registry that are required before the compatibility check,
-you can call the `addReference("name", "subject", version)`, this will add a reference to fetch dynamically from the registry.
+you can call the `addReference("name", "subject", version)`,
+this will add a reference to use from the registry.
+A convenience method, `addReference("name", "subject")`,
+uses the latest version of the schema in the registry.
+You can also specify `-1` explicitly to use the latest version.
 The addReference calls can be chained.
 
 If you have local references to add before calling the compatibility in the registry,
@@ -175,13 +181,19 @@ schemaRegistry {
                 .addLocalReference("localAvroSubject", "/a/local/path.avsc")
         subject('avroWithRemoteReferences', '/absolutPath/dependent/path.avsc', "AVRO")
                 .addReference('avroSubject', 'avroSubjectType', 1)
+                .addReference('avroSubjectLatestVersion', 'avroSubjectLatestVersionType')
+                .addReference('avroSubjectLatestVersionExplicit', 'avroSubjectLatestVersionExplicitType', -1)
         subject('protoWithReferences', 'dependent/path.proto', "PROTOBUF").addReference('protoSubject', 'protoSubjectType', 1)
         subject('jsonWithReferences', 'dependent/path.json', "JSON").addReference('jsonSubject', 'jsonSubjectType', 1)
     }
 }
 ```
 If you have references to other schemas required before the register,
-you can call the `addReference("name", "subject", version)`, this will add a reference to use from the registry.
+you can call the `addReference("name", "subject", version)`,
+this will add a reference to use from the registry.
+A convenience method, `addReference("name", "subject")`,
+uses the latest version of the schema in the registry.
+You can also specify `-1` explicitly to use the latest version.
 The addReference calls can be chained.
 
 If you have local references to add before calling the register,
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/Subject.kt b/src/main/kotlin/com/github/imflog/schema/registry/Subject.kt
index f6038a4..04d3c6f 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/Subject.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/Subject.kt
@@ -15,6 +15,11 @@ data class Subject(
         return this
     }
 
+    fun addReference(name: String, subject:String): Subject {
+        references.add(SchemaReference(name, subject, -1))
+        return this
+    }
+
     fun addLocalReference(name: String, path: String): Subject {
         localReferences.add(LocalReference(name, path))
         return this
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskAction.kt b/src/main/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskAction.kt
index 48bdd49..c0583d9 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskAction.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskAction.kt
@@ -27,7 +27,7 @@ class RegisterTaskAction(
     fun run(): Int {
         var errorCount = 0
         writeOutputFileHeader()
-        subjects.forEach { (subject, path, type, references, localReferences) ->
+        subjects.forEach { (subject, path, type, references: List<SchemaReference>, localReferences) ->
             try {
                 val schemaId = registerSchema(subject, path, type.toSchemaType(), references, localReferences)
                 writeRegisteredSchemaOutput(subject, path, schemaId)


Test patch:
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
index ce2dc35..e05f3ef 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
@@ -3,32 +3,29 @@ package com.github.imflog.schema.registry
 import com.github.imflog.schema.registry.tasks.download.DownloadTask
 import org.assertj.core.api.Assertions
 import org.gradle.api.Project
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.gradle.testfixtures.ProjectBuilder
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.UnexpectedBuildFailure
-import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
 import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
 
 class SchemaRegistryPluginTest {
     lateinit var project: Project
-    lateinit var folderRule: TemporaryFolder
+    @TempDir
+    lateinit var folderRule: Path
     lateinit var buildFile: File
 
     private val subject = "test-subject"
 
     @BeforeEach
     fun init() {
-        folderRule = TemporaryFolder()
         project = ProjectBuilder.builder().build()
         project.pluginManager.apply(SchemaRegistryPlugin::class.java)
-    }
-
-    @AfterEach
-    fun tearDown() {
-        folderRule.delete()
+        Files.createFile(folderRule.resolve("build.gradle"))
     }
 
     @Test
@@ -41,8 +38,7 @@ class SchemaRegistryPluginTest {
 
     @Test
     fun `plugin should fail with wrong url extension configuration`() {
-        folderRule.create()
-        buildFile = folderRule.newFile("build.gradle")
+        buildFile = File(folderRule.toFile(), "build.gradle")
         buildFile.writeText(
             """
             plugins {
@@ -61,7 +57,7 @@ class SchemaRegistryPluginTest {
         try {
             GradleRunner.create()
                 .withGradleVersion("6.7.1")
-                .withProjectDir(folderRule.root)
+                .withProjectDir(folderRule.toFile())
                 .withArguments(DownloadTask.TASK_NAME)
                 .withPluginClasspath()
                 .withDebug(true)
@@ -74,8 +70,7 @@ class SchemaRegistryPluginTest {
 
     @Test
     fun `plugin should fail with wrong credentials extension configuration`() {
-        folderRule.create()
-        buildFile = folderRule.newFile("build.gradle")
+        buildFile = File(folderRule.toFile(), "build.gradle")
         buildFile.writeText(
             """
             plugins {
@@ -99,7 +94,7 @@ class SchemaRegistryPluginTest {
         try {
             GradleRunner.create()
                 .withGradleVersion("6.7.1")
-                .withProjectDir(folderRule.root)
+                .withProjectDir(folderRule.toFile())
                 .withArguments(DownloadTask.TASK_NAME)
                 .withPluginClasspath()
                 .withDebug(true)
@@ -112,8 +107,7 @@ class SchemaRegistryPluginTest {
 
     @Test
     fun `plugin should only parse nested extensions`() {
-        folderRule.create()
-        buildFile = folderRule.newFile("build.gradle")
+        buildFile = File(folderRule.toFile(), "build.gradle")
         buildFile.writeText(
             """
             plugins {
@@ -136,7 +130,7 @@ class SchemaRegistryPluginTest {
         try {
             GradleRunner.create()
                 .withGradleVersion("6.7.1")
-                .withProjectDir(folderRule.root)
+                .withProjectDir(folderRule.toFile())
                 .withArguments(DownloadTask.TASK_NAME)
                 .withPluginClasspath()
                 .withDebug(true)
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/parser/AvroSchemaParserTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/parser/AvroSchemaParserTest.kt
index ba075c7..664abb4 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/parser/AvroSchemaParserTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/parser/AvroSchemaParserTest.kt
@@ -3,21 +3,18 @@ package com.github.imflog.schema.registry.parser
 import com.github.imflog.schema.registry.LocalReference
 import io.confluent.kafka.schemaregistry.client.MockSchemaRegistryClient
 import org.assertj.core.api.Assertions
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.json.JSONObject
-import org.junit.jupiter.api.AfterAll
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
 
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class AvroSchemaParserTest {
 
     private val schemaRegistryClient = MockSchemaRegistryClient()
-    private val folderRule: TemporaryFolder = TemporaryFolder().apply { create() }
-    private val parser = AvroSchemaParser(
-        schemaRegistryClient,
-        folderRule.root
-    )
+    @TempDir
+    lateinit var folderRule: Path
 
     companion object {
         private const val ADDRESS_REFERENCE_NAME = "Address"
@@ -39,14 +36,10 @@ class AvroSchemaParserTest {
         }"""
     }
 
-    @AfterAll
-    fun tearDown() {
-        folderRule.delete()
-    }
-
     @Test
     fun `Should format local references correctly`() {
         // Given
+        val parser = AvroSchemaParser(schemaRegistryClient, folderRule.toFile())
         val aLocalReference = givenALocalReference()
 
         // When
@@ -61,7 +54,7 @@ class AvroSchemaParserTest {
     }
 
     private fun givenALocalReference(): LocalReference {
-        val addressLocalFile = folderRule.root.resolve("Address.avsc")
+        val addressLocalFile = folderRule.resolve("Address.avsc").toFile()
         addressLocalFile.writeText(ADDRESS_SCHEMA)
         return LocalReference(ADDRESS_REFERENCE_NAME, addressLocalFile.path)
     }
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/parser/JsonSchemaParserTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/parser/JsonSchemaParserTest.kt
index 045efc9..819e52f 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/parser/JsonSchemaParserTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/parser/JsonSchemaParserTest.kt
@@ -3,21 +3,18 @@ package com.github.imflog.schema.registry.parser
 import com.github.imflog.schema.registry.LocalReference
 import io.confluent.kafka.schemaregistry.client.MockSchemaRegistryClient
 import org.assertj.core.api.Assertions
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.json.JSONObject
-import org.junit.jupiter.api.AfterAll
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
 
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class JsonSchemaParserTest {
 
     private val schemaRegistryClient = MockSchemaRegistryClient()
-    private val folderRule: TemporaryFolder = TemporaryFolder().apply { create() }
-    private val parser = JsonSchemaParser(
-        schemaRegistryClient,
-        folderRule.root
-    )
+    @TempDir
+    lateinit var folderRule: Path
 
     companion object {
         private const val ADDRESS_REFERENCE_NAME = "Address"
@@ -43,14 +40,10 @@ class JsonSchemaParserTest {
         }"""
     }
 
-    @AfterAll
-    fun tearDown() {
-        folderRule.delete()
-    }
-
     @Test
     fun `Should format local references correctly`() {
         // Given
+        val parser = JsonSchemaParser(schemaRegistryClient, folderRule.toFile())
         val aLocalReference = givenALocalReference()
 
         // When
@@ -65,7 +58,7 @@ class JsonSchemaParserTest {
     }
 
     private fun givenALocalReference(): LocalReference {
-        val addressLocalFile = folderRule.root.resolve("Address.json")
+        val addressLocalFile = folderRule.resolve("Address.json").toFile()
         addressLocalFile.writeText(ADDRESS_SCHEMA)
         return LocalReference(ADDRESS_REFERENCE_NAME, addressLocalFile.path)
     }
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/tasks/compatibility/CompatibilityTaskActionTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/tasks/compatibility/CompatibilityTaskActionTest.kt
index fd4aefd..ac3a1e0 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/tasks/compatibility/CompatibilityTaskActionTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/tasks/compatibility/CompatibilityTaskActionTest.kt
@@ -10,25 +10,22 @@ import io.confluent.kafka.schemaregistry.rest.exceptions.Errors
 import io.mockk.every
 import io.mockk.spyk
 import org.assertj.core.api.Assertions
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
-import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
 import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
 
 class CompatibilityTaskActionTest {
 
-    lateinit var folderRule: TemporaryFolder
+    @TempDir
+    lateinit var folderRule: Path
 
     @BeforeEach
-    fun setUp() {
-        folderRule = TemporaryFolder()
-        folderRule.create()
-    }
-
-    @AfterEach
-    fun tearDown() {
-        folderRule.delete()
+    fun init() {
+        Files.createDirectories(folderRule.resolve("src/main/avro/external/"))
+        Files.createFile(folderRule.resolve("src/main/avro/external/test.avsc"))
     }
 
     @Test
@@ -49,11 +46,10 @@ class CompatibilityTaskActionTest {
                 listOf()
             ).get()
         )
-        folderRule.newFolder("src", "main", "avro", "external")
 
         val subjects =
             arrayListOf(Subject("test", "src/main/avro/external/test.avsc", "AVRO"))
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -68,7 +64,7 @@ class CompatibilityTaskActionTest {
         // when
         val errorCount = CompatibilityTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects
         ).run()
 
@@ -127,8 +123,7 @@ class CompatibilityTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -153,7 +148,91 @@ class CompatibilityTaskActionTest {
         // when
         val errorCount = CompatibilityTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
+            subjects
+        ).run()
+
+        // then
+        Assertions.assertThat(errorCount).isEqualTo(0)
+    }
+
+    @Test
+    fun `Should verify compatibility when fetching latest version of references`() {
+        // given
+        val registryClient = MockSchemaRegistryClient()
+        registryClient.register(
+            "test",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{
+                    "type": "record",
+                    "name": "test",
+                    "fields": [
+                        { "name": "name", "type": "string" }
+                    ]
+                }""",
+                listOf()
+            ).get()
+        )
+
+        // Register dependency
+        registryClient.register(
+            "Street",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{
+                    "type": "record",
+                    "name": "Street",
+                    "fields": [
+                        {"name": "street", "type": "string" }
+                    ]
+                }""",
+                listOf()
+            ).get()
+        )
+
+        registryClient.register(
+            "Address",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{
+                    "type": "record",
+                    "name": "Address",
+                    "fields": [
+                        {"name": "city", "type": "string" },
+                        {"name": "street", "type": "Street" }
+                    ]
+                }""",
+                listOf(SchemaReference("Street", "Street", 1))
+            ).get()
+        )
+
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
+            """
+            {"type": "record",
+             "name": "test",
+             "fields": [
+                {"name": "name", "type": "string" },
+                {"name": "address", "type": [ "null", "Address" ], "default": null}
+             ]
+            }
+        """
+        )
+
+        val subjects = listOf(
+            Subject(
+                "test",
+                "src/main/avro/external/test.avsc",
+                "AVRO"
+            )
+                .addReference("Address", "Address")
+                .addReference("Street", "Street", -1)
+        )
+
+        // when
+        val errorCount = CompatibilityTaskAction(
+            registryClient,
+            folderRule.toFile(),
             subjects
         ).run()
 
@@ -197,8 +276,11 @@ class CompatibilityTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "local")
-        File(folderRule.root, "src/main/avro/local/country.avsc").writeText(
+        Files.createDirectories(folderRule.resolve("src/main/avro/local"))
+        Files.createFile(folderRule.resolve("src/main/avro/local/country.avsc"))
+        Files.createFile(folderRule.resolve("src/main/avro/local/address.avsc"))
+
+        File(folderRule.toFile(), "src/main/avro/local/country.avsc").writeText(
             """{
                     "type": "enum",
                     "name": "Country",
@@ -214,7 +296,7 @@ class CompatibilityTaskActionTest {
                   "default": "UNKNOWN"
                 }"""
         )
-        File(folderRule.root, "src/main/avro/local/address.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/local/address.avsc").writeText(
             """{
                     "type": "record",
                     "name": "Address",
@@ -226,7 +308,7 @@ class CompatibilityTaskActionTest {
                 }"""
         )
 
-        File(folderRule.root, "src/main/avro/local/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/local/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -252,7 +334,7 @@ class CompatibilityTaskActionTest {
         // when
         val errorCount = CompatibilityTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects
         ).run()
 
@@ -278,11 +360,10 @@ class CompatibilityTaskActionTest {
                 listOf()
             ).get()
         )
-        folderRule.newFolder("src", "main", "avro", "external")
 
         val subjects =
             arrayListOf(Subject("test", "src/main/avro/external/test.avsc", "AVRO"))
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -296,7 +377,7 @@ class CompatibilityTaskActionTest {
         // when
         val errorCount = CompatibilityTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects,
         ).run()
 
@@ -313,8 +394,6 @@ class CompatibilityTaskActionTest {
             spySchemaRegistry.testCompatibility(any(), any<ParsedSchema>())
         } throws RestClientException("Subject not found", 404, Errors.SUBJECT_NOT_FOUND_ERROR_CODE)
 
-        folderRule.newFolder("src", "main", "avro", "external")
-
         val subjects = listOf(
             Subject(
                 "test",
@@ -322,7 +401,7 @@ class CompatibilityTaskActionTest {
                 "AVRO"
             )
         )
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -336,7 +415,7 @@ class CompatibilityTaskActionTest {
         // when
         val errorCount = CompatibilityTaskAction(
             spySchemaRegistry,
-            folderRule.root,
+            folderRule.toFile(),
             subjects,
         ).run()
 
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/tasks/download/DownloadTaskActionTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/tasks/download/DownloadTaskActionTest.kt
index 7993cdc..8a73e05 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/tasks/download/DownloadTaskActionTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/tasks/download/DownloadTaskActionTest.kt
@@ -6,25 +6,14 @@ import io.confluent.kafka.schemaregistry.client.MockSchemaRegistryClient
 import io.confluent.kafka.schemaregistry.json.JsonSchemaProvider
 import io.confluent.kafka.schemaregistry.protobuf.ProtobufSchemaProvider
 import org.assertj.core.api.Assertions
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
 import java.io.File
+import java.nio.file.Path
 
 class DownloadTaskActionTest {
-    lateinit var folderRule: TemporaryFolder
-
-    @BeforeEach
-    fun setUp() {
-        folderRule = TemporaryFolder()
-        folderRule.create()
-    }
-
-    @AfterEach
-    fun tearDown() {
-        folderRule.delete()
-    }
+    @TempDir
+    lateinit var folderRule: Path
 
     @Test
     fun `Should download schemas`() {
@@ -63,12 +52,13 @@ class DownloadTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
+        folderRule.resolve("src/main/avro/external").toFile().mkdir()
+        val folderRoot = folderRule.toFile()
 
         // when
         val errorCount = DownloadTaskAction(
             registryClient,
-            folderRule.root,
+            folderRoot,
             arrayListOf(
                 DownloadSubject(testSubject, outputDir),
                 DownloadSubject(fooSubject, outputDir)
@@ -77,11 +67,11 @@ class DownloadTaskActionTest {
 
         // then
         Assertions.assertThat(errorCount).isEqualTo(0)
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc")).isNotNull
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc")).isNotNull
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc").readText())
             .containsIgnoringCase("test")
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/foo.avsc")).isNotNull
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/foo.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/foo.avsc")).isNotNull
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/foo.avsc").readText())
             .containsIgnoringCase("foo")
     }
 
@@ -135,12 +125,13 @@ class DownloadTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
+        folderRule.resolve("src/main/avro/external").toFile().mkdir()
+        val folderRoot = folderRule.toFile()
 
         // when
         val errorCount = DownloadTaskAction(
             registryClient,
-            folderRule.root,
+            folderRoot,
             arrayListOf(
                 DownloadSubject("te.*", outputDir, null, true)
             ),
@@ -148,13 +139,13 @@ class DownloadTaskActionTest {
 
         // then
         Assertions.assertThat(errorCount).isEqualTo(0)
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc")).exists()
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc")).exists()
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc").readText())
             .containsIgnoringCase("test")
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/tea.avsc")).exists()
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/tea.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/tea.avsc")).exists()
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/tea.avsc").readText())
             .containsIgnoringCase("tea")
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/foo.avsc")).doesNotExist()
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/foo.avsc")).doesNotExist()
     }
 
     @Test
@@ -183,12 +174,13 @@ class DownloadTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
+        folderRule.resolve("src/main/avro/external").toFile().mkdir()
+        val folderRoot = folderRule.toFile()
 
         // when
         val errorCount = DownloadTaskAction(
             registryClient,
-            folderRule.root,
+            folderRoot,
             arrayListOf(DownloadSubject(subject, outputDir)),
         ).run()
 
@@ -214,12 +206,13 @@ class DownloadTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
+        folderRule.resolve("src/main/avro/external").toFile().mkdir()
+        val folderRoot = folderRule.toFile()
 
         // when
         val errorCount = DownloadTaskAction(
             registryClient,
-            folderRule.root,
+            folderRoot,
             arrayListOf(
                 DownloadSubject(invalidSubjectPattern, outputDir, null, true),
                 DownloadSubject("test", outputDir)
@@ -228,8 +221,8 @@ class DownloadTaskActionTest {
 
         // then
         Assertions.assertThat(errorCount).isEqualTo(0)
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc")).exists()
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc")).exists()
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc").readText())
             .containsIgnoringCase("test")
     }
 
@@ -267,12 +260,13 @@ class DownloadTaskActionTest {
         )
         Assertions.assertThat(v1Id).isNotEqualTo(v2Id)
 
-        folderRule.newFolder("src", "main", "avro", "external")
+        folderRule.resolve("src/main/avro/external").toFile().mkdir()
+        val folderRoot = folderRule.toFile()
 
         // When
         val errorCount = DownloadTaskAction(
             registryClient,
-            folderRule.root,
+            folderRoot,
             arrayListOf(
                 DownloadSubject("test", outputDir, v1Id)
             ),
@@ -280,10 +274,10 @@ class DownloadTaskActionTest {
 
         // Then
         Assertions.assertThat(errorCount).isEqualTo(0)
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc")).isNotNull
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc")).isNotNull
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc").readText())
             .containsIgnoringCase("test")
-        Assertions.assertThat(File(folderRule.root, "src/main/avro/external/test.avsc").readText())
+        Assertions.assertThat(File(folderRoot, "src/main/avro/external/test.avsc").readText())
             .doesNotContain("desc")
     }
 }
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskActionTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskActionTest.kt
index 0ae6884..762e631 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskActionTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/tasks/register/RegisterTaskActionTest.kt
@@ -8,24 +8,21 @@ import io.confluent.kafka.schemaregistry.client.rest.entities.SchemaReference
 import io.confluent.kafka.schemaregistry.json.JsonSchemaProvider
 import io.confluent.kafka.schemaregistry.protobuf.ProtobufSchemaProvider
 import org.assertj.core.api.Assertions
-import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
-import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
 import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
 
 class RegisterTaskActionTest {
-    lateinit var folderRule: TemporaryFolder
+    @TempDir
+    lateinit var folderRule: Path
 
     @BeforeEach
-    fun setUp() {
-        folderRule = TemporaryFolder()
-        folderRule.create()
-    }
-
-    @AfterEach
-    fun tearDown() {
-        folderRule.delete()
+    fun init() {
+        Files.createDirectories(folderRule.resolve("src/main/avro/external/"))
+        Files.createFile(folderRule.resolve("src/main/avro/external/test.avsc"))
     }
 
     @Test
@@ -33,8 +30,7 @@ class RegisterTaskActionTest {
         // given
         val registryClient =
             MockSchemaRegistryClient(listOf(AvroSchemaProvider(), JsonSchemaProvider(), ProtobufSchemaProvider()))
-        folderRule.newFolder("src", "main", "avro", "external")
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -53,7 +49,7 @@ class RegisterTaskActionTest {
         // when
         val errorCount = RegisterTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects,
             null
         ).run()
@@ -77,8 +73,7 @@ class RegisterTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -101,7 +96,7 @@ class RegisterTaskActionTest {
         // when
         val errorCount = RegisterTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects,
             null
         ).run()
@@ -126,8 +121,6 @@ class RegisterTaskActionTest {
             ).get()
         )
 
-        folderRule.newFolder("src", "main", "avro", "external")
-
         // Register dependency
         registryClient.register(
             "Street",
@@ -160,7 +153,7 @@ class RegisterTaskActionTest {
             ).get()
         )
 
-        File(folderRule.root, "src/main/avro/external/test.avsc").writeText(
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
             """
             {"type": "record",
              "name": "test",
@@ -186,7 +179,89 @@ class RegisterTaskActionTest {
         // when
         val errorCount = RegisterTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
+            subjects,
+            null
+        ).run()
+
+        // then
+        Assertions.assertThat(errorCount).isEqualTo(0)
+        Assertions.assertThat(registryClient.getLatestSchemaMetadata("test")).isNotNull
+    }
+
+    @Test
+    fun `Should register schema with latest remote version of references`() {
+        // given
+        val registryClient =
+            MockSchemaRegistryClient(listOf(AvroSchemaProvider(), JsonSchemaProvider(), ProtobufSchemaProvider()))
+        registryClient.register(
+            "test",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{"type": "record", "name": "test", "fields": [{ "name": "name", "type": "string" }]}""",
+                listOf()
+            ).get()
+        )
+
+        // Register dependency
+        registryClient.register(
+            "Street",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{
+                    "type": "record",
+                    "name": "Street",
+                    "fields": [
+                        {"name": "street", "type": "string" }
+                    ]
+                }""",
+                listOf()
+            ).get()
+        )
+
+        registryClient.register(
+            "Address",
+            registryClient.parseSchema(
+                AvroSchema.TYPE,
+                """{
+                    "type": "record",
+                    "name": "Address",
+                    "fields": [
+                        {"name": "city", "type": "string" },
+                        {"name": "street", "type": "Street" }
+                    ]
+                }""",
+                listOf(SchemaReference("Street", "Street", 1))
+            ).get()
+        )
+
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc").writeText(
+            """
+            {"type": "record",
+             "name": "test",
+             "fields": [
+                {"name": "name", "type": "string" },
+                {"name": "address", "type": "Address"}
+             ]
+            }
+        """.trimIndent()
+        )
+
+
+        val subjects = listOf(
+            Subject(
+                "test",
+                "src/main/avro/external/test.avsc",
+                "AVRO"
+            )
+                .addReference("Address", "Address")
+                .addReference("Street", "Street", -1)
+        )
+
+        // when
+        val errorCount = RegisterTaskAction(
+            registryClient,
+            folderRule.toFile(),
             subjects,
             null
         ).run()
@@ -201,9 +276,8 @@ class RegisterTaskActionTest {
         // given
         val registryClient =
             MockSchemaRegistryClient(listOf(AvroSchemaProvider(), JsonSchemaProvider(), ProtobufSchemaProvider()))
-        folderRule.newFolder("src", "main", "avro", "external")
-        val resultFolder = folderRule.newFolder("results", "avro")
-        File(folderRule.root, "src/main/avro/external/test.avsc")
+        val resultFolder = Files.createDirectories(folderRule.resolve("results/avro")).toFile()
+        File(folderRule.toFile(), "src/main/avro/external/test.avsc")
             .writeText(
                 """
                 {"type": "record",
@@ -215,7 +289,7 @@ class RegisterTaskActionTest {
                 }
             """
             )
-        File(folderRule.root, "src/main/avro/external/test_2.avsc")
+        File(folderRule.toFile(), "src/main/avro/external/test_2.avsc")
             .writeText(
                 """
                 {"type": "record",
@@ -236,7 +310,7 @@ class RegisterTaskActionTest {
         // when
         RegisterTaskAction(
             registryClient,
-            folderRule.root,
+            folderRule.toFile(),
             subjects,
             resultFolder.path
         ).run()