Skip to content

Commit

Permalink
implement db, repo tests, e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Ombrelin committed Jul 11, 2023
1 parent e2d1aeb commit d2b07b3
Show file tree
Hide file tree
Showing 23 changed files with 578 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
# Ignore Gradle build output directory
build
.idea
**/videos
4 changes: 4 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ repositories {
}

dependencies {
constraints {
// Define dependency versions as constraints
implementation("org.apache.commons:commons-text:1.10.0")
}

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2")
// Use JUnit Jupiter for testing.
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")

testRuntimeOnly("org.junit.platform:junit-platform-launcher")

}

tasks.named<Test>("test") {
Expand Down
23 changes: 23 additions & 0 deletions ci/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:
postgres:
container_name: postgres
image: postgres
ports:
- 5432:5432
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=fsabank
fsabank:
container_name: fsabank
build:
dockerfile: web/Dockerfile
context: ..
depends_on:
- postgres
environment:
- DB_HOST=postgres
- DB_NAME=fsabank
- DB_PASSWORD=password
- DB_USERNAME=postgres
ports:
- 5000:8080
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import java.math.BigDecimal

class BankApplication(private val accountRepository: AccountRepository) {

fun createAccount(accountHolderName: String): Account{
throw NotImplementedError()
suspend fun createAccount(accountHolderName: String): Account {
val account = Account(accountHolderName)
accountRepository.insert(account)

return account
}

fun listAccounts(): Set<Account>{
throw NotImplementedError()
suspend fun listAccounts(): Collection<Account> {
return accountRepository.getAll()
}

fun paySalaries() {
suspend fun paySalaries() {
throw NotImplementedError()
}

fun addDebit(operationName: String, amountBigDecimal: BigDecimal): Operation {
suspend fun addDebit(operationName: String, amountBigDecimal: BigDecimal): Operation {
throw NotImplementedError()
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,50 @@
package fr.arsenelapostolet.fsa_banking_system.bank

import fr.arsenelapostolet.fsa_banking_system.bank.entities.Account
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class BankApplicationTests {

val target: BankApplication = BankApplication(FakeAccountRepository())
private val repository = FakeAccountRepository()
private val target: BankApplication = BankApplication(repository)

@Test
fun createAccount_insertsRecord(){
fun createAccount_insertsRecord() = runBlocking {
// Given
val accountName = "test account";

// When
target.createAccount(accountName)
val result = target.createAccount(accountName)

// Then

assertEquals(accountName, result.name)
assertEquals(accountName, repository.data[accountName]!!.name)
}

@Test
fun listAccounts_returnsAccountsInDb() = runBlocking {
// Given
val accounts = listOf(
Account("Test account 1"),
Account("Test account 2"),
Account("Test account 3")
)
for(account in accounts){
repository.data[account.name] = account
}

// When
val result = target.listAccounts()

// Then
assertEquals(3, result.size)
for(account in accounts){
assertTrue(result.contains(account))
}

}

}
10 changes: 10 additions & 0 deletions web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM gradle:8.2.0-jdk17 AS build
WORKDIR /app
COPY . .
RUN gradle buildFatJar --no-daemon

FROM openjdk:17 AS run
EXPOSE 8080
RUN mkdir /app
COPY --from=build /app/web/build/libs/web-all.jar /app/web-all.jar
ENTRYPOINT ["java","-jar","/app/web-all.jar"]
35 changes: 35 additions & 0 deletions web/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,46 @@ application {
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

ktor {
docker {
jreVersion.set(io.ktor.plugin.features.JreVersion.JRE_17)
localImageName.set("fsa-bank")
imageTag.set("0.0.1")
portMappings.set(
listOf(
io.ktor.plugin.features.DockerPortMapping(
80,
8080,
io.ktor.plugin.features.DockerPortMappingProtocol.TCP
)
)
)
externalRegistry.set(
io.ktor.plugin.features.DockerImageRegistry.dockerHub(
appName = provider { "ktor-app" },
username = providers.environmentVariable("DOCKER_HUB_USERNAME"),
password = providers.environmentVariable("DOCKER_HUB_PASSWORD")
)
)
}
}

dependencies {

implementation("io.ktor:ktor-server-html-builder:2.3.2")
implementation("org.kodein.di:kodein-di:7.19.0")
implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:7.19.0")
implementation("org.ufoss.kotysa:kotysa-r2dbc:3.1.0")
implementation("io.ktor:ktor-server-core-jvm:2.3.2")
implementation("io.ktor:ktor-server-netty-jvm:2.3.2")
implementation("ch.qos.logback:logback-classic:1.2.11")
runtimeOnly("org.postgresql:r2dbc-postgresql:1.0.1.RELEASE")
testImplementation("io.ktor:ktor-server-tests-jvm:2.3.2")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.9.0")
testImplementation("com.microsoft.playwright:playwright:1.35.0")
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
package fr.arsenelapostolet.fsa_banking_system.app

import io.ktor.server.application.*
import fr.arsenelapostolet.fsa_banking_system.app.database.DatabaseAccounts
import fr.arsenelapostolet.fsa_banking_system.app.database.PostgresAccountRepository
import fr.arsenelapostolet.fsa_banking_system.bank.BankApplication
import fr.arsenelapostolet.fsa_banking_system.bank.persistance.AccountRepository
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.r2dbc.spi.ConnectionFactories
import io.r2dbc.spi.ConnectionFactoryOptions
import kotlinx.coroutines.runBlocking
import org.kodein.di.*
import org.kodein.di.ktor.*
import org.ufoss.kotysa.PostgresqlR2dbcSqlClient
import org.ufoss.kotysa.PostgresqlTables
import org.ufoss.kotysa.R2dbcSqlClient
import org.ufoss.kotysa.r2dbc.coSqlClient
import org.ufoss.kotysa.tables

fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}

fun Application.module() {
configureRouting()
}
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
di {
bind<PostgresqlR2dbcSqlClient>() with singleton {
ConnectionFactories
.get(
ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, "postgresql")
.option(ConnectionFactoryOptions.HOST, System.getenv("DB_HOST"))
.option(ConnectionFactoryOptions.USER, System.getenv("DB_USERNAME"))
.option(ConnectionFactoryOptions.PASSWORD, System.getenv("DB_PASSWORD"))
.option(ConnectionFactoryOptions.DATABASE, System.getenv("DB_NAME"))
.build()
)
.coSqlClient(tables().postgresql(DatabaseAccounts))
}
bind<AccountRepository> {
singleton {
PostgresAccountRepository(instance())
}
}
bind<BankApplication> {
singleton {
BankApplication(instance())
}
}
}
runBlocking {
val client by closestDI().instance<R2dbcSqlClient>()
client createTable DatabaseAccounts
}
configureRouting()
}.start(wait = true)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,115 @@
package fr.arsenelapostolet.fsa_banking_system.app

import fr.arsenelapostolet.fsa_banking_system.app.templates.PageTemplate
import fr.arsenelapostolet.fsa_banking_system.bank.BankApplication
import io.ktor.http.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.http.content.*
import io.ktor.server.request.*
import kotlinx.html.*
import org.kodein.di.instance
import org.kodein.di.ktor.closestDI

fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
staticResources("/static", ".")
get("/accounts") {
val bankingApp by closestDI().instance<BankApplication>()
val accounts = bankingApp.listAccounts()

call.respondHtmlTemplate(PageTemplate(), HttpStatusCode.OK) {
content {
div(classes = "center") {
div(classes = "box") {
h1(classes = "title") {
+"Accounts"
}
table(classes = "table is-striped") {
thead {
tr {
th {
+"Account Name"
}
th {
+"Actions"
}
}
}
tbody {
accounts.map {
tr {
td { +it.name }
td("is-flex") {
button(classes = "button") {
+"View"
}
button(classes = "button is-danger") {
+"Delete"
}
}
}
}
}
}
button (classes = "button"){
+ "Pay salaries"
}
a(href = "/accounts/create") {
button(classes = "button is-primary") {
+"Create new account"
}
}
}
}
}
}
}
get("/accounts/create") {
call.respondHtmlTemplate(PageTemplate(), HttpStatusCode.OK) {
content {
div(classes = "center") {

form(action = "/accounts/create", method = FormMethod.post, classes = "box") {
h1(classes = "title") {
+"Create new Account"
}
div(classes = "field") {
label(classes = "label") {
+"Holder Account Name :"
}
div(classes = "control") {
textInput(name = "holderName", classes = "input")
}

}
div(classes = "control") {
submitInput(classes = "button is-primary") { value = "Create Account" }
}
}
}
}
}
}
post("/accounts/create") {
val formParameters = call.receiveParameters()
val bankingApp by closestDI().instance<BankApplication>()

val holderName = formParameters["holderName"]
if (holderName == null) {
call.respondHtml(HttpStatusCode.BadRequest) {
body {
p {
"holderName is required"
}
}
}
}

bankingApp.createAccount(holderName!!)
call.respondRedirect("/accounts", permanent = true)
}
}
}
Loading

0 comments on commit d2b07b3

Please sign in to comment.