This commit is contained in:
Julien Lengrand-Lambert
2023-10-20 17:40:50 +02:00
parent 585ff758f9
commit d03e935c4d
8 changed files with 177 additions and 40 deletions

4
.gitignore vendored
View File

@@ -39,4 +39,6 @@ bin/
.vscode/ .vscode/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
src/test/resources/supabase

1
.idea/gradle.xml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

7
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/test/kotlin/MainKtTest.kt" dialect="GenericSQL" />
<file url="PROJECT" dialect="PostgreSQL" />
</component>
</project>

70
README.md Normal file
View File

@@ -0,0 +1,70 @@
# Supabase Test Container for Kotlin
This repo demonstrates the use of [Test Containers](https://testcontainers.com/) against a full local [Supabase](https://supabase.com/) instance
Supabase allows for self-hosting (see [here](https://supabase.com/docs/guides/self-hosting/docker)). We use that to run a local instance of Supabase for testing.
## Manual steps (for now) - do it once
* You need to have the Supabase repo available locally to play around
```bash
$ git clone --depth 1 https://github.com/supabase/supabase src/test/resources/supabase
$ cp src/test/resources/supabase/docker/.env.example src/test/resources/supabase/docker/.env
```
_Note : I've added `src/test/resources/supabase` to the `.gitignore` file, so that it does not get pushed to the repo._
### Container names fun stuff
Interestingly, Test Containers don't support the `container_name` property in the `docker-compose.yml` file, [and don't seem to intend to either](https://github.com/testcontainers/testcontainers-java/pull/2741).
But it also doesn't get ignored meaning that if you run the tests now, you'll get the following error:
```
java.lang.IllegalStateException: Compose file supabase-testcontainers-kotlin/src/test/resources/supabase/docker/docker-compose.yml has 'container_name' property set for service 'studio' but this property is not supported by Testcontainers, consider removing it
at org.testcontainers.containers.ParsedDockerComposeFile.validateNoContainerNameSpecified(ParsedDockerComposeFile.java:113)
```
To avoid that, we'll remove the `container_name` properties from the `docker-compose.yml` file. Do note that it might have an impact if you want to run the containers manually though.
```bash
$ grep -v "container_name" docker/docker-compose.yml > tmpfile && mv tmpfile docker/docker-compose.yml
```
## Updating the Supabase repo to the latest version
Cloning the repo is great, but we also need to stay in think with sync with the latest version of Supabase.
Once in a while, you'll need to update the repo.
```bash
$ cd src/test/resources/supabase
$ git pull
```
_Note : Remember, we modified `docker-compose.yml` to remove the `container_name` property. This might lead to conflict at times if the file gets modified_
## Running the tests
For now, we'll avoid the creation of database and more, and simply run against existing tables of the public database and see what's up.
Run the test in `MainTest.kt`.
Note that the rests are quite long to run (obviously, I'd say), and that the Supabase compose file is setup so that volumes are saved in between runs.
```
$ ./gradlew test  ✔ 17:38:27 
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.2/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 53s
4 actionable tasks: 3 executed, 1 up-to-date
```
## Author
* Julien Lengrand-Lambert (@jlengrand)

View File

@@ -33,6 +33,8 @@ dependencies {
testImplementation("io.mockk:mockk:1.13.7") testImplementation("io.mockk:mockk:1.13.7")
testImplementation("io.ktor:ktor-client-mock:2.3.5") testImplementation("io.ktor:ktor-client-mock:2.3.5")
testImplementation("io.github.cdimascio:dotenv-kotlin:6.4.1")
} }
tasks.test { tasks.test {

View File

@@ -25,18 +25,7 @@ data class ResultPerson (
} }
fun main() { fun main() {
println("Hello World!") // Your application logic
// Application goes here
val supabaseClient = createSupabaseClient(
supabaseUrl = "",
supabaseKey = ""
) {
install(Postgrest)
}
runBlocking {
savePerson(listOf(Person("Jan", 30), Person("Jane", 42)), supabaseClient)
}
} }
suspend fun getPerson(client: SupabaseClient): List<ResultPerson> { suspend fun getPerson(client: SupabaseClient): List<ResultPerson> {

View File

@@ -1,42 +1,22 @@
import io.github.cdimascio.dotenv.dotenv
import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.createSupabaseClient import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.postgrest
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.testcontainers.containers.ComposeContainer import org.testcontainers.containers.ComposeContainer
import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File import java.io.File
import java.sql.DriverManager
@Testcontainers @Testcontainers
class MainKtTestTestContainers { class MainKtTest {
private val jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
private lateinit var supabaseClient: SupabaseClient
@Container
var environment: ComposeContainer =
ComposeContainer(File("src/test/resources/supabase/docker/docker-compose.yml"))
.withExposedService("kong", 8000)
.withExposedService("analytics", 4000)
.withExposedService("db", 5432)
@BeforeEach
fun setUp() {
val fakeSupabaseUrl = environment.getServiceHost("kong", 8000) +
":" + environment.getServicePort("kong", 8000)
supabaseClient = createSupabaseClient(
supabaseUrl = "http://$fakeSupabaseUrl",
supabaseKey = jwtToken
) {
install(Postgrest)
}
}
@Test @Test
fun testEmptyPersonTable(){ fun testEmptyPersonTable(){
@@ -60,4 +40,83 @@ class MainKtTestTestContainers {
assertEquals(randomPersons, fetchResult.map { it.toPerson() }) assertEquals(randomPersons, fetchResult.map { it.toPerson() })
} }
} }
companion object {
private const val DOCKER_COMPOSE_FILE = "src/test/resources/supabase/docker/docker-compose.yml"
private const val ENV_LOCATION = "src/test/resources/supabase/docker/.env" // We grab the JWT token from here
val dotenv = dotenv{
directory = File(ENV_LOCATION).toString()
}
private val jwtToken = dotenv["SERVICE_ROLE_KEY"]
private val dbPassword = dotenv["POSTGRES_PASSWORD"]
private val db = dotenv["POSTGRES_DB"]
private lateinit var supabaseClient: SupabaseClient
@Container
var container: ComposeContainer = ComposeContainer(File(DOCKER_COMPOSE_FILE))
.withExposedService("kong", 8000)
.withExposedService("db", 5432) // Handy but not required
@JvmStatic
@AfterAll
fun tearDown() {
val dbUrl = container.getServiceHost("db", 5432) + ":" + container.getServicePort("db", 5432)
val jdbcUrl = "jdbc:postgresql://$dbUrl/$db"
val connection = DriverManager.getConnection(jdbcUrl, "postgres", dbPassword)
try {
val query = connection.prepareStatement(
"""
drop table public.person;
"""
)
query.executeQuery()
} catch (ex: Exception) {
println(ex)
}
}
@JvmStatic
@BeforeAll
fun setUp() {
val supabaseUrl = container.getServiceHost("kong", 8000) + ":" + container.getServicePort("kong", 8000)
val dbUrl = container.getServiceHost("db", 5432) + ":" + container.getServicePort("db", 5432)
supabaseClient = createSupabaseClient(
supabaseUrl = "http://$supabaseUrl",
supabaseKey = jwtToken
) {
install(Postgrest)
}
val jdbcUrl = "jdbc:postgresql://$dbUrl/$db"
val connection = DriverManager.getConnection(jdbcUrl, "postgres", dbPassword)
try {
val query = connection.prepareStatement(
"""
create table
public.person (
id bigint generated by default as identity not null,
timestamp timestamp with time zone null default now(),
name character varying null,
age bigint null
) tablespace pg_default;
"""
)
query.executeQuery()
} catch (ex: Exception) {
println("Error is fine here. This should actually run only once")
println(ex) // Might be fine, this should actually run only once
}
}
}
} }

View File

@@ -0,0 +1,7 @@
create table
public.person (
id bigint generated by default as identity not null,
timestamp timestamp with time zone null default now(),
name character varying null,
age bigint null
) tablespace pg_default;