diff --git a/.github/workflows/decompose-frontend-test.yml b/.github/workflows/decompose-frontend-test.yml
index c89a105a..3b4f21dc 100644
--- a/.github/workflows/decompose-frontend-test.yml
+++ b/.github/workflows/decompose-frontend-test.yml
@@ -2,6 +2,10 @@ name: Frontend Decompose Module Tests
on:
push:
+ branches:
+ - main
+ - master
+ - 'renovate/**'
paths:
- 'conduit-frontend/frontend-decompose-logic/**'
- 'build-src/**'
diff --git a/.github/workflows/http4k-backend-test.yml b/.github/workflows/http4k-backend-test.yml
index ead46077..94cdd791 100644
--- a/.github/workflows/http4k-backend-test.yml
+++ b/.github/workflows/http4k-backend-test.yml
@@ -2,6 +2,10 @@ name: Http4k Backend Tests
on:
push:
+ branches:
+ - main
+ - master
+ - 'renovate/**'
paths:
- 'conduit-backend/**'
- 'build-src/**'
@@ -10,6 +14,31 @@ jobs:
test:
runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:alpine
+ env:
+ POSTGRES_DB: test-db
+ POSTGRES_USER: test-user
+ POSTGRES_PASSWORD: test-password
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 3s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 5432:5432
+
+ redis:
+ image: redis:alpine
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 3s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 6379:6379
+
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -20,13 +49,26 @@ jobs:
java-version: '21'
distribution: 'temurin'
+ - name: Run Database Migrations
+ uses: red-gate/FlywayGitHubAction@main
+ with:
+ url: jdbc:postgresql://localhost:5432/test-db
+ user: test-user
+ password: test-password
+ locations: filesystem:./conduit-backend/db-migration/main,filesystem:./conduit-backend/db-migration/test
+ extraArgs: -cleanDisabled=false -clean=true
+
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run Backend Tests
working-directory: conduit-backend
+ env:
+ DB__URL: jdbc:postgresql://localhost:5432/test-db
+ DB__USER: test-user
+ DB__PASSWORD: test-password
run: |
- ./gradlew check
+ ./gradlew check
- name: Upload build reports
uses: actions/upload-artifact@v4
diff --git a/build-src/libs.versions.toml b/build-src/libs.versions.toml
index 434ae52c..cde4185d 100644
--- a/build-src/libs.versions.toml
+++ b/build-src/libs.versions.toml
@@ -74,7 +74,7 @@ dev-backend-kotestBom = "io.kotest:kotest-bom:5.9.1"
dev-backend-kotestKoin = "io.kotest.extensions:kotest-extensions-koin:1.3.0"
dev-backend-mockk = "io.mockk:mockk:1.13.13"
dev-backend-hoplite-yaml = "com.sksamuel.hoplite:hoplite-yaml:2.9.0"
-dev-backend-sqlite = "org.xerial:sqlite-jdbc:3.47.1.0"
+dev-backend-postgresql = "org.postgresql:postgresql:42.7.4"
dev-backend-hikari = "com.zaxxer:HikariCP:6.2.1"
dev-backend-flyway = "org.flywaydb:flyway-core:11.0.1"
dev-backend-logback = "ch.qos.logback:logback-classic:1.5.12"
@@ -104,4 +104,4 @@ kotlinCompose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kot
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }
# a plugin used by gradle files for precompiled script plugin
-buildConfig = { id = "com.github.gmazzo.buildconfig", version = "5.5.1" }
\ No newline at end of file
+buildConfig = { id = "com.github.gmazzo.buildconfig", version = "5.5.1" }
diff --git a/conduit-backend/.run/Docker for Dev.run.xml b/conduit-backend/.run/Docker for Dev.run.xml
new file mode 100644
index 00000000..564d52cd
--- /dev/null
+++ b/conduit-backend/.run/Docker for Dev.run.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/conduit-backend/build.gradle.kts b/conduit-backend/build.gradle.kts
index 923f4fec..7b7d884e 100644
--- a/conduit-backend/build.gradle.kts
+++ b/conduit-backend/build.gradle.kts
@@ -37,9 +37,8 @@ dependencies {
implementation("org.jetbrains.exposed:exposed-core")
implementation("org.jetbrains.exposed:exposed-dao")
implementation("org.jetbrains.exposed:exposed-jdbc")
- implementation(libs.dev.backend.sqlite)
+ implementation(libs.dev.backend.postgresql)
implementation(libs.dev.backend.hikari)
- implementation(libs.dev.backend.flyway)
implementation(libs.dev.backend.inlineLogging)
implementation(libs.dev.backend.slf4j)
runtimeOnly(libs.dev.backend.logback)
diff --git a/conduit-backend/src/main/resources/db/migration/V1__create_tables.sql b/conduit-backend/db-migration/main/V1__create_tables.sql
similarity index 82%
rename from conduit-backend/src/main/resources/db/migration/V1__create_tables.sql
rename to conduit-backend/db-migration/main/V1__create_tables.sql
index 82dc3253..1de93e4a 100644
--- a/conduit-backend/src/main/resources/db/migration/V1__create_tables.sql
+++ b/conduit-backend/db-migration/main/V1__create_tables.sql
@@ -1,7 +1,7 @@
-- V1__CreateUsersTable.sql
create table users
(
- id integer primary key autoincrement,
+ id serial primary key,
email varchar(255) not null unique,
username varchar(255) not null unique,
password varchar(255) not null,
diff --git a/conduit-backend/db-migration/test/V999__insert_test_data.sql b/conduit-backend/db-migration/test/V999__insert_test_data.sql
new file mode 100644
index 00000000..b0dbca70
--- /dev/null
+++ b/conduit-backend/db-migration/test/V999__insert_test_data.sql
@@ -0,0 +1,8 @@
+-- V999__insert_test_data.sql
+INSERT INTO users (email, username, password, bio, image)
+VALUES
+ ('john@example.com', 'john', '$2a$10$8BxPYyqz2.X5TKk2R6gGIeZ.Tkk2KmcC6yN1BxLLGCgMgMNEZpgjK', 'Hi, I''m John!', 'https://api.realworld.io/images/john.jpg'),
+ ('jane@example.com', 'jane', '$2a$10$8BxPYyqz2.X5TKk2R6gGIeZ.Tkk2KmcC6yN1BxLLGCgMgMNEZpgjK', 'Hi, I''m Jane!', 'https://api.realworld.io/images/jane.jpg'),
+ ('bob@example.com', 'bob', '$2a$10$8BxPYyqz2.X5TKk2R6gGIeZ.Tkk2KmcC6yN1BxLLGCgMgMNEZpgjK', NULL, NULL),
+ ('alice@example.com', 'alice', '$2a$10$8BxPYyqz2.X5TKk2R6gGIeZ.Tkk2KmcC6yN1BxLLGCgMgMNEZpgjK', 'Software developer', 'https://api.realworld.io/images/alice.jpg'),
+ ('test@test.com', 'test', '$2a$10$8BxPYyqz2.X5TKk2R6gGIeZ.Tkk2KmcC6yN1BxLLGCgMgMNEZpgjK', NULL, NULL);
diff --git a/conduit-backend/docker/.env b/conduit-backend/docker/.env
new file mode 100644
index 00000000..e9127a04
--- /dev/null
+++ b/conduit-backend/docker/.env
@@ -0,0 +1,5 @@
+# PostgreSQL
+POSTGRES_DB=conduit-db
+POSTGRES_USER=conduit-user
+POSTGRES_PASSWORD=conduit-password
+
diff --git a/conduit-backend/docker/compose.base.yml b/conduit-backend/docker/compose.base.yml
new file mode 100644
index 00000000..9cc7f1f9
--- /dev/null
+++ b/conduit-backend/docker/compose.base.yml
@@ -0,0 +1,52 @@
+services:
+ postgres:
+ image: postgres:alpine
+ container_name: conduit-postgres
+ environment:
+ POSTGRES_DB: ${POSTGRES_DB}
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
+ interval: 3s
+ timeout: 5s
+ retries: 5
+
+ redis:
+ image: redis:alpine
+ container_name: conduit-redis
+ volumes:
+ - redis_data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 3s
+ timeout: 5s
+ retries: 5
+
+ flyway:
+ image: flyway/flyway:latest-alpine
+ container_name: conduit-flyway
+ profiles: ["migration"]
+ environment:
+ FLYWAY_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
+ FLYWAY_USER: ${POSTGRES_USER}
+ FLYWAY_PASSWORD: ${POSTGRES_PASSWORD}
+ FLYWAY_CONNECT_RETRIES: 10
+ volumes: # consider having different migration files for different environments, see https://documentation.red-gate.com/fd/configuration-files-224003079.html
+ - ../db/migration:/flyway/sql
+ depends_on:
+ postgres:
+ condition: service_healthy
+ command: migrate
+
+volumes:
+ postgres_data:
+ name: conduit-postgres-data
+ redis_data:
+ name: conduit-redis-data
+
+networks:
+ default:
+ name: conduit-network
diff --git a/conduit-backend/docker/compose.dev.yml b/conduit-backend/docker/compose.dev.yml
new file mode 100644
index 00000000..f77e5c1e
--- /dev/null
+++ b/conduit-backend/docker/compose.dev.yml
@@ -0,0 +1,21 @@
+# This file contains development-specific overrides
+# Use with compose.base.yml like this:
+# docker compose -f compose.base.yml -f compose.dev.yml up
+
+services:
+ postgres:
+ ports:
+ - "5432:5432"
+
+ redis:
+ ports:
+ - "6379:6379"
+
+ flyway:
+ volumes:
+ - ../db-migration/main:/flyway/sql # Regular migrations
+ - ../db-migration/test:/flyway/sql-test # Test migrations in separate directory
+ environment:
+ FLYWAY_LOCATIONS: filesystem:/flyway/sql,filesystem:/flyway/sql-test
+ FLYWAY_CLEAN_DISABLED: false
+ command: clean migrate
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/Bootstrap.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/Bootstrap.kt
index 60e61e62..5ae014b6 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/Bootstrap.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/Bootstrap.kt
@@ -2,36 +2,15 @@ package mikufan.cx.conduit.backend
import mikufan.cx.conduit.backend.controller.ConduitServer
import mikufan.cx.inlinelogging.KInlineLogging
-import org.flywaydb.core.Flyway
class Bootstrap(
- private val flyway: Flyway,
private val server: ConduitServer
) : Runnable {
+
override fun run() {
- dbMigration()
startServer()
}
- private fun dbMigration() {
- log.info { "Performing DB migrations if any" }
- val migrateResult = flyway.migrate()
- if (migrateResult.migrationsExecuted > 0) {
- if (migrateResult.success) {
- log.info { "Database migration successful. Migrations applied: ${migrateResult.migrationsExecuted}" }
- } else {
- val failureString = migrateResult.failedMigrations.joinToString(separator = "\n ", prefix = "[\n", postfix = "\n") {
- "${it.type} ${it.version} @ ${it.filepath} - ${it.description}"
- }
- val errorMessage = "Database migration failed: \n$failureString"
- log.error { errorMessage }
- throw IllegalStateException(errorMessage)
- }
- } else { // no migration applied
- log.info { "No migration applied" }
- }
- }
-
private fun startServer() {
log.info { "Starting server" }
server.start()
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/config/loadConfig.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/config/loadConfig.kt
index 950e0706..56fadf15 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/config/loadConfig.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/config/loadConfig.kt
@@ -1,11 +1,13 @@
package mikufan.cx.conduit.backend.config
import com.sksamuel.hoplite.ConfigLoaderBuilder
+import com.sksamuel.hoplite.addEnvironmentSource
import com.sksamuel.hoplite.addResourceOrFileSource
import com.sksamuel.hoplite.sources.CommandLinePropertySource
fun loadConfig(args: Array): Config = ConfigLoaderBuilder.default()
.addPropertySource(CommandLinePropertySource(args, prefix = "--", delimiter = "=")) // can handle empty array
+ .addEnvironmentSource()
.addResourceOrFileSource("/application-prod.yml", optional = true, allowEmpty = true)
.addResourceOrFileSource("/application.yml", allowEmpty = true)
.build()
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/handler/RegisterUserHandler.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/handler/RegisterUserHandler.kt
index 15e71dad..61e071eb 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/handler/RegisterUserHandler.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/handler/RegisterUserHandler.kt
@@ -15,7 +15,7 @@ class RegisterUserHandler(
override fun invoke(request: Request): Response {
val userDto = userRegistrationLen(request).user
- val UserRegisterDto = userService.registerUser(userDto)
- return userRspLens(UserRsp(UserRegisterDto), Response(Status.CREATED))
+ val userRegisterDto = userService.registerUser(userDto)
+ return userRspLens(UserRsp(userRegisterDto), Response(Status.CREATED))
}
}
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/routes.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/routes.kt
index d5a48752..0a7a604a 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/routes.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/controller/routes.kt
@@ -12,6 +12,7 @@ import org.http4k.server.Http4kServer
import org.http4k.server.JettyLoom
import org.http4k.server.asServer
+//// route setup ////
fun conduitRoute(apiRoute: RoutingHttpHandler): RoutingHttpHandler = routes(
"/healthcheck" bind GET to { Response(Status.OK).body("OK") },
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/TransactionManager.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/TransactionManager.kt
index 17c44fa1..3d2dd505 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/TransactionManager.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/TransactionManager.kt
@@ -5,15 +5,13 @@ import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.transactions.transaction
interface TransactionManager {
- val db: Database
-
- fun tx(block: Transaction.() -> T): T {
- return transaction(db) {
- block()
- }
- }
+ fun tx(block: Transaction.() -> T): T
}
class TransactionManagerImpl(
- override val db: Database
-) : TransactionManager
\ No newline at end of file
+ val db: Database
+) : TransactionManager {
+ override fun tx(block: Transaction.() -> T): T = transaction(db) {
+ block()
+ }
+}
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/di.module.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/di.module.kt
index 4336472d..f23ab764 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/di.module.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/di.module.kt
@@ -11,9 +11,8 @@ import org.koin.dsl.module
*/
val dbModule = module {
single { creatDataSource(get().db) }
- singleOf(::createFlyway)
singleOf(::createExposedDb)
- singleOf(::createTransactionManager)
+ singleOf(::createConduitTransactionManager)
singleOf(::UserRepo)
}
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/models.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/models.kt
index 4fc472df..c6147556 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/models.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/models.kt
@@ -1,5 +1,8 @@
package mikufan.cx.conduit.backend.db
+import org.jetbrains.exposed.dao.IntEntity
+import org.jetbrains.exposed.dao.IntEntityClass
+import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
object Users : IntIdTable() {
@@ -9,3 +12,13 @@ object Users : IntIdTable() {
val bio = varchar("bio", 255).nullable()
val image = varchar("image", 255).nullable()
}
+
+class User(id: EntityID) : IntEntity(id) {
+ companion object : IntEntityClass(Users)
+
+ var email by Users.email
+ var username by Users.username
+ var password by Users.password
+ var bio by Users.bio
+ var image by Users.image
+}
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepo.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepo.kt
index 4232591c..72daf42a 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepo.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepo.kt
@@ -1,44 +1,27 @@
package mikufan.cx.conduit.backend.db.repo
+import mikufan.cx.conduit.backend.db.User
import mikufan.cx.conduit.backend.db.Users
-import mikufan.cx.conduit.common.UserDto
import mikufan.cx.conduit.common.UserRegisterDto
-import org.jetbrains.exposed.sql.ResultRow
-import org.jetbrains.exposed.sql.insertReturning
-import org.jetbrains.exposed.sql.selectAll
+import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
+import org.jetbrains.exposed.sql.deleteWhere
class UserRepo {
- fun getById(id: Int): UserDto? = Users
- .selectAll().where { Users.id eq id }
- .firstOrNull()
- ?.toUserDto()
-
- fun getByEmail(email: String): UserDto? = Users
- .selectAll().where { Users.email eq email }
- .firstOrNull()
- ?.toUserDto()
-
- fun getByUsername(username: String): UserDto? = Users
- .selectAll().where { Users.username eq username }
- .firstOrNull()
- ?.toUserDto()
-
- fun insert(UserRegisterDto: UserRegisterDto): UserDto? = Users.insertReturning {
- it[email] = UserRegisterDto.email
- it[username] = UserRegisterDto.username
- it[password] = UserRegisterDto.password
- it[bio] = ""
- it[image] = null
+ fun getById(id: Int): User? = User.findById(id)
+
+ fun getByEmail(email: String): User? = User.find { Users.email eq email }.firstOrNull()
+
+ fun getByUsername(username: String): User? = User.find { Users.username eq username }.firstOrNull()
+
+ fun insert(UserRegisterDto: UserRegisterDto): User = User.new {
+ email = UserRegisterDto.email
+ username = UserRegisterDto.username
+ password = UserRegisterDto.password
+ bio = ""
+ image = null
}
- .firstOrNull()
- ?.toUserDto()
-
- private fun ResultRow.toUserDto() = UserDto(
- email = this[Users.email],
- username = this[Users.username],
- bio = this[Users.bio],
- image = this[Users.image],
- token = null
- )
+
+ fun delete(id: Int): Boolean = Users.deleteWhere { Users.id eq id } > 0
+
}
\ No newline at end of file
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/setup.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/setup.kt
index 9a1f6268..f65019b1 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/setup.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/db/setup.kt
@@ -3,7 +3,6 @@ package mikufan.cx.conduit.backend.db
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import mikufan.cx.conduit.backend.config.DbConfig
-import org.flywaydb.core.Flyway
import org.jetbrains.exposed.sql.Database
import javax.sql.DataSource
@@ -21,11 +20,6 @@ fun creatDataSource(dbConfig: DbConfig): DataSource {
return hikariDataSource
}
-fun createFlyway(dataSource: DataSource): Flyway = Flyway
- .configure()
- .dataSource(dataSource)
- .load()
-
fun createExposedDb(dataSource: DataSource) : Database = Database.connect(dataSource)
-fun createTransactionManager(db: Database) : TransactionManager = TransactionManagerImpl(db)
\ No newline at end of file
+fun createConduitTransactionManager(db: Database) : TransactionManager = TransactionManagerImpl(db)
diff --git a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/service/UserService.kt b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/service/UserService.kt
index dfad5404..5d7d6da9 100644
--- a/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/service/UserService.kt
+++ b/conduit-backend/src/main/kotlin/mikufan/cx/conduit/backend/service/UserService.kt
@@ -12,17 +12,26 @@ class UserService(
private val userRepo: UserRepo,
) {
- fun registerUser(user: UserRegisterDto): UserDto =
- txManager.tx {
+ fun registerUser(user: UserRegisterDto): UserDto {
+ val newUser = txManager.tx {
val userDto = userRepo.getByUsername(user.username) ?: userRepo.getByEmail(user.email)
if (userDto != null) {
throw ConduitException("User already exists, username or email must be unique")
} else {
- val newUser = userRepo.insert(user) ?: throw ConduitException("Failed to create new user ${user.username}")
- log.info { "Successfully created new user ${user.username}" }
- newUser
+ userRepo.insert(user)
}
}
+ log.info { "Successfully created new user ${user.username}" }
+
+ // TODO: set token
+ return UserDto(
+ email = newUser.email,
+ username = newUser.username,
+ bio = newUser.bio,
+ image = newUser.image,
+ token = null
+ )
+ }
}
private val log = KInlineLogging.logger()
\ No newline at end of file
diff --git a/conduit-backend/src/main/resources/application.yml b/conduit-backend/src/main/resources/application.yml
index 157b6ba5..b29ec731 100644
--- a/conduit-backend/src/main/resources/application.yml
+++ b/conduit-backend/src/main/resources/application.yml
@@ -1,9 +1,9 @@
port: 8080 # Example value
db:
- url: "jdbc:sqlite::memory:" # Example value
-# user: # Optional
-# password: # Optional
- driver: "org.sqlite.JDBC" # Example value
+ url: "jdbc:postgresql://localhost:5432/conduit-db" # Example value
+ user: conduit-user
+ password: conduit-password
+ driver: "org.postgresql.Driver"
cors:
# allowedOrigins:
# allowedMethods:
diff --git a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepoTest.kt b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepoTest.kt
index c6fe0210..9b197d76 100644
--- a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepoTest.kt
+++ b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/UserRepoTest.kt
@@ -8,34 +8,31 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import mikufan.cx.conduit.backend.db.TransactionManager
import mikufan.cx.conduit.common.UserRegisterDto
-import org.flywaydb.core.Flyway
import org.koin.test.KoinTest
import org.koin.test.inject
class UserRepoTest : ShouldSpec(), KoinTest {
override fun extensions(): List = listOf(KoinExtension(testDbConfig, mode = KoinLifecycleMode.Root))
- private val migration: Flyway by inject()
private val txManager: TransactionManager by inject()
private val userRepo: UserRepo by inject()
init {
context("user repo") {
- migration.migrate()
should("insert new user") {
val UserRegisterDto = UserRegisterDto("email@email.com", "new user", "password")
- val newUser = txManager.tx {
- userRepo.insert(UserRegisterDto)
- }
+ txManager.tx {
+ val newUser = userRepo.insert(UserRegisterDto)
+ newUser shouldNotBe null
+ newUser.apply {
+ email shouldBe UserRegisterDto.email
+ username shouldBe UserRegisterDto.username
+ }
- newUser shouldNotBe null
- newUser?.apply {
- email shouldBe UserRegisterDto.email
- username shouldBe UserRegisterDto.username
+ rollback()
}
}
-
}
}
}
diff --git a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/di.module.kt b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/di.module.kt
index ba25b0de..b8f26f38 100644
--- a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/di.module.kt
+++ b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/db/repo/di.module.kt
@@ -1,18 +1,20 @@
package mikufan.cx.conduit.backend.db.repo
-import io.mockk.mockk
+import com.sksamuel.hoplite.ConfigLoaderBuilder
+import com.sksamuel.hoplite.addEnvironmentSource
+import com.sksamuel.hoplite.addResourceOrFileSource
import mikufan.cx.conduit.backend.config.Config
-import mikufan.cx.conduit.backend.config.DbConfig
import mikufan.cx.conduit.backend.db.dbModule
import org.koin.dsl.module
val testDbConfig = module {
single {
- Config(
- port = 0,
- cors = mockk(),
- db = DbConfig(url = "jdbc:sqlite::memory:", driver = "org.sqlite.JDBC")
- )
+ ConfigLoaderBuilder.default()
+ .addEnvironmentSource()
+ .addResourceOrFileSource("/application-test.yml", optional = true, allowEmpty = true)
+ .addResourceOrFileSource("/application.yml", allowEmpty = true)
+ .build()
+ .loadConfigOrThrow()
}
includes(dbModule)
}
\ No newline at end of file
diff --git a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/service/UserServiceTest.kt b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/service/UserServiceTest.kt
index f528e799..f4758cb3 100644
--- a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/service/UserServiceTest.kt
+++ b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/service/UserServiceTest.kt
@@ -6,44 +6,40 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.mockk.every
import io.mockk.mockk
-import mikufan.cx.conduit.backend.db.TransactionManager
+import mikufan.cx.conduit.backend.db.User
import mikufan.cx.conduit.backend.db.repo.UserRepo
import mikufan.cx.conduit.backend.util.ConduitException
+import mikufan.cx.conduit.backend.util.NoOpsTxManager
import mikufan.cx.conduit.common.UserDto
import mikufan.cx.conduit.common.UserRegisterDto
-import org.jetbrains.exposed.sql.Transaction
class UserServiceTest : ShouldSpec({
- // Create a mock of TransactionManager
- val txManager = mockk() {
- // Set up the behavior for the tx method
- every {
- tx(any Any?>())
- } answers {
- // Call the lambda directly with a mocked Transaction
- firstArg Any?>().invoke(mockk())
- }
- }
-
-
+ val txManager = NoOpsTxManager
context("user registration") {
- val UserRegisterDto = UserRegisterDto("new user", "email@email.com", "password")
+ val userRegisterDto = UserRegisterDto("new user", "email@email.com", "password")
should("register user successfully") {
val userRepo = mockk() {
every { getByEmail(any()) } returns null
every { getByUsername(any()) } returns null
every { insert(any()) } answers {
val dto = firstArg()
- UserDto(dto.email, dto.username, "", "", null)
+ mockk {
+ every { email } returns dto.email
+ every { username } returns dto.username
+ every { password } returns dto.password
+ every { bio } returns ""
+ every { image } returns ""
+ }
}
}
val userService = UserService(txManager, userRepo)
- val registerUser = userService.registerUser(UserRegisterDto)
+ val registerUser = userService.registerUser(userRegisterDto)
registerUser shouldBe UserDto("new user", "email@email.com", "", "", null)
}
+
should("throw on duplicate user") {
val userRepo = mockk() {
every { getByEmail(any()) } returns mockk()
@@ -53,7 +49,7 @@ class UserServiceTest : ShouldSpec({
val userService = UserService(txManager, userRepo)
val conduitException = shouldThrow {
- userService.registerUser(UserRegisterDto)
+ userService.registerUser(userRegisterDto)
}
conduitException.message shouldContain "User already exists"
diff --git a/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/util/NoOpsTxManager.kt b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/util/NoOpsTxManager.kt
new file mode 100644
index 00000000..28325844
--- /dev/null
+++ b/conduit-backend/src/test/kotlin/mikufan/cx/conduit/backend/util/NoOpsTxManager.kt
@@ -0,0 +1,15 @@
+package mikufan.cx.conduit.backend.util
+
+import io.mockk.mockk
+import mikufan.cx.conduit.backend.db.TransactionManager
+import org.jetbrains.exposed.sql.Transaction
+
+/**
+ * A transaction manager that does not actually perform any transactions.
+ * Useful for testing purposes.
+ *
+ * However, make sure the test code does not call any functions from [Transaction] or directly.
+ */
+object NoOpsTxManager : TransactionManager {
+ override fun tx(block: Transaction.() -> T): T = mockk().block()
+}
\ No newline at end of file
diff --git a/conduit-backend/src/test/resources/application-test.yml b/conduit-backend/src/test/resources/application-test.yml
new file mode 100644
index 00000000..e69de29b