mirror of
https://github.com/jlengrand/supabase-testcontainers-kotlin.git
synced 2026-03-10 08:41:22 +00:00
Working
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,4 +39,6 @@ bin/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
src/test/resources/supabase
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -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
7
.idea/sqldialects.xml
generated
Normal 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
70
README.md
Normal 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)
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
7
src/test/resources/initdb/person.sql
Normal file
7
src/test/resources/initdb/person.sql
Normal 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;
|
||||||
Reference in New Issue
Block a user