diff --git a/CopyScriptJsMain.ps1 b/CopyScriptJsMain.ps1 index fd8d0534..0f12a448 100644 --- a/CopyScriptJsMain.ps1 +++ b/CopyScriptJsMain.ps1 @@ -1 +1 @@ -Copy-Item -Force -Path ".\budget-binder-multiplatform-app\build\distributions\*" -Destination ".\budget-binder-server\files\" \ No newline at end of file +Copy-Item -Force -Path ".\budget-binder-multiplatform-app\build\distributions\*" -Destination ".\budget-binder-server\public\" diff --git a/CopyScriptJsMain.sh b/CopyScriptJsMain.sh new file mode 100644 index 00000000..f2ba9055 --- /dev/null +++ b/CopyScriptJsMain.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cp -r ./budget-binder-multiplatform-app/build/distributions/* ./budget-binder-server/public/ diff --git a/Dockerfile b/Dockerfile index 183c920b..1cd757c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,12 @@ WORKDIR /home/gradle/src RUN gradle :budget-binder-multiplatform-app:jsBrowserDistribution :budget-binder-server:shadowJar --no-daemon FROM openjdk:17 -RUN mkdir -p /app/data && mkdir -p /app/files +RUN mkdir -p /app/data && mkdir -p /app/public WORKDIR /app VOLUME /app/data -COPY --from=build /home/gradle/src/budget-binder-server/files /app/files/ -COPY --from=build /home/gradle/src/budget-binder-multiplatform-app/build/distributions/ /app/files/ +COPY --from=build /home/gradle/src/budget-binder-server/files /app/public/ +COPY --from=build /home/gradle/src/budget-binder-multiplatform-app/build/distributions/ /app/public/ COPY --from=build /home/gradle/src/budget-binder-server/build/libs/*.jar /app/ktor-docker-server.jar ENV \ diff --git a/Dockerfile_dev b/Dockerfile_dev index a3759418..b5b8f2f6 100644 --- a/Dockerfile_dev +++ b/Dockerfile_dev @@ -4,12 +4,12 @@ WORKDIR /home/gradle/src RUN gradle :budget-binder-multiplatform-app:jsBrowserDistribution :budget-binder-server:shadowJar --no-daemon FROM openjdk:17 -RUN mkdir -p /app/data && mkdir -p /app/files +RUN mkdir -p /app/data && mkdir -p /app/public WORKDIR /app VOLUME /app/data -COPY --from=build /home/gradle/src/budget-binder-server/files /app/files/ -COPY --from=build /home/gradle/src/budget-binder-multiplatform-app/build/distributions/ /app/files/ +COPY --from=build /home/gradle/src/budget-binder-server/files /app/public/ +COPY --from=build /home/gradle/src/budget-binder-multiplatform-app/build/distributions/ /app/public/ COPY --from=build /home/gradle/src/budget-binder-server/build/libs/*.jar /app/ktor-docker-server.jar ENV \ diff --git a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/APIResponse.kt b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/APIResponse.kt index 7ce6f150..02fecd0b 100644 --- a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/APIResponse.kt +++ b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/APIResponse.kt @@ -7,4 +7,4 @@ data class APIResponse( val error: ErrorModel? = null, val data: T? = null, val success: Boolean = false -) \ No newline at end of file +) diff --git a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/AuthToken.kt b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/AuthToken.kt index 15873dae..06c15495 100644 --- a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/AuthToken.kt +++ b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/AuthToken.kt @@ -3,4 +3,4 @@ package de.hsfl.budgetBinder.common import kotlinx.serialization.Serializable @Serializable -data class AuthToken(val token: String) \ No newline at end of file +data class AuthToken(val token: String) diff --git a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/Category.kt b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/Category.kt index 9874a2ae..f7a0852d 100644 --- a/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/Category.kt +++ b/budget-binder-common/src/commonMain/kotlin/de/hsfl/budgetBinder/common/Category.kt @@ -80,4 +80,4 @@ data class Category( val image: Image, val budget: Float ) -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt index c10157a9..507ea145 100644 --- a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt +++ b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt @@ -4,5 +4,5 @@ import de.hsfl.budgetBinder.android.BudgetBinderApplication import java.io.File actual fun getCookieFileStorage(): File { - return File(BudgetBinderApplication.instance.filesDir, "cookies.txt") + return File(BudgetBinderApplication.instance.filesDir, "cookies.bin") } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/AuthPlugin.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/AuthPlugin.kt index 2162f597..bcf400ef 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/AuthPlugin.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/AuthPlugin.kt @@ -9,7 +9,6 @@ import io.ktor.client.request.* import io.ktor.http.* import io.ktor.util.* - class AuthPlugin private constructor( val loginPath: String, val logoutPath: String, diff --git a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt index 5c75a373..828e8bdc 100644 --- a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt +++ b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/getCookieFileStorage.kt @@ -11,7 +11,7 @@ actual fun getCookieFileStorage(): File { path += "/.bb-client" File(path).toPath().createDirectories() - path += "/cookies.txt" + path += "/cookies.bin" val file = File(path) file.createNewFile() return file diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/FileCookieStorage.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/FileCookieStorage.kt index 8e3dbbd9..56e6e98e 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/FileCookieStorage.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/data/client/plugins/FileCookieStorage.kt @@ -138,6 +138,5 @@ class FileCookieStorage : CookiesStorage { writeCookiesToFile(container) } - override fun close() { - } + override fun close() {} } diff --git a/budget-binder-server/build.gradle.kts b/budget-binder-server/build.gradle.kts index beb767ce..c072bc0a 100644 --- a/budget-binder-server/build.gradle.kts +++ b/budget-binder-server/build.gradle.kts @@ -1,5 +1,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +val ktorVersion: String by project +val exposedVersion: String by project + plugins { application kotlin("jvm") @@ -25,7 +28,6 @@ dependencies { implementation(project(":budget-binder-common")) implementation(kotlin("stdlib")) - val ktorVersion = "2.0.2" implementation("io.ktor:ktor-network-tls-certificates-jvm:$ktorVersion") implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") implementation("io.ktor:ktor-server-auth-jvm:$ktorVersion") @@ -45,10 +47,8 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2") - implementation("ch.qos.logback:logback-classic:1.2.11") - val exposedVersion = "0.38.2" implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") diff --git a/budget-binder-server/data/config_sample.yaml b/budget-binder-server/data/config_sample.yaml index e736fd7c..f11e1fc8 100644 --- a/budget-binder-server/data/config_sample.yaml +++ b/budget-binder-server/data/config_sample.yaml @@ -8,7 +8,7 @@ server: keyStorePassword: null keyStorePath: null noForwardedHeaderSupport: false -dataBase: +database: dbType: SQLITE sqlitePath: null serverAddress: null diff --git a/budget-binder-server/gradle.properties b/budget-binder-server/gradle.properties new file mode 100644 index 00000000..956362bc --- /dev/null +++ b/budget-binder-server/gradle.properties @@ -0,0 +1,2 @@ +ktorVersion=2.0.2 +exposedVersion=0.38.2 diff --git a/budget-binder-server/files/favicon.ico b/budget-binder-server/public/favicon.ico similarity index 100% rename from budget-binder-server/files/favicon.ico rename to budget-binder-server/public/favicon.ico diff --git a/budget-binder-server/files/openapi.json b/budget-binder-server/public/openapi.json similarity index 100% rename from budget-binder-server/files/openapi.json rename to budget-binder-server/public/openapi.json diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/Config.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/Config.kt index 9b9b0c24..cd26a19c 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/Config.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/Config.kt @@ -1,7 +1,8 @@ package de.hsfl.budgetBinder.server.config +import java.io.File -data class Config(val server: Server, val dataBase: DataBase, val jwt: JWT) { +data class Config(val server: Server, val database: Database, val jwt: JWT) { data class Server( val dev: Boolean, val ssl: Boolean, @@ -20,7 +21,7 @@ data class Config(val server: Server, val dataBase: DataBase, val jwt: JWT) { POSTGRES, } - data class DataBase( + data class Database( val dbType: DBType, val sqlitePath: String, val serverAddress: String, @@ -39,4 +40,98 @@ data class Config(val server: Server, val dataBase: DataBase, val jwt: JWT) { val issuer: String, val audience: String ) + + companion object { + fun create(configFile: File? = null): Config { + return configFile?.let { + createFromIntermediate(ConfigIntermediate.createFromFile(it)) + } ?: createFromIntermediate(ConfigIntermediate.createFromEnv()) + } + + fun createFromIntermediate(intermediate: ConfigIntermediate): Config { + val dbType = intermediate.database.dbType + + val sqlitePath: String + val dbServerAddress: String + val dbServerPort: String + val dbName: String + val dbUser: String + val dbPassword: String + if (dbType == DBType.SQLITE) { + sqlitePath = intermediate.database.sqlitePath ?: (System.getProperty("user.dir") + "/data/data.db") + dbServerAddress = "" + dbServerPort = "" + dbName = "" + dbUser = "" + dbPassword = "" + } else { + sqlitePath = "" + dbServerAddress = intermediate.database.serverAddress ?: error("No dbServerAddress specified") + dbServerPort = intermediate.database.serverPort ?: error("No dbServerPort specified") + dbName = intermediate.database.name ?: error("No dbDatabaseName specified") + dbUser = intermediate.database.user ?: error("No dbUser specified") + dbPassword = intermediate.database.password ?: error("No dbPassword specified") + } + + val dev = intermediate.server?.dev ?: false + val ssl = intermediate.server?.ssl ?: false + + val host = intermediate.server?.host ?: "0.0.0.0" + val port = intermediate.server?.port ?: 8080 + val sslHost = intermediate.server?.sslHost ?: "0.0.0.0" + val sslPort = intermediate.server?.sslPort ?: 8443 + + val keyStorePassword: String + val keyStorePath: String + if (ssl) { + keyStorePassword = intermediate.server?.keyStorePassword + ?: if (dev) "budget-binder-server" else error("No KeystorePassword provided") + keyStorePath = intermediate.server?.keyStorePath + ?: if (dev) "data/dev_keystore.jks" else error("No KeystorePath provided") + } else { + keyStorePassword = "" + keyStorePath = "" + } + + val forwardedHeaderSupport = !(intermediate.server?.noForwardedHeaderSupport ?: false) + + val jwtAccessSecret = intermediate.jwt.accessSecret + val jwtRefreshSecret = intermediate.jwt.refreshSecret + val jwtAccessMinutes = intermediate.jwt.accessMinutes ?: 15 + val jwtRefreshDays = intermediate.jwt.refreshDays ?: 7 + val jwtRealm = intermediate.jwt.realm ?: "budget-binder-server" + val jwtIssuer = intermediate.jwt.issuer ?: "http://0.0.0.0:8080/" + val jwtAudience = intermediate.jwt.audience ?: "http://0.0.0.0:8080/" + + return Config( + database = Database( + dbType, + sqlitePath, + dbServerAddress, + dbServerPort, + dbName, + dbUser, + dbPassword + ), server = Server( + dev, + ssl, + host, + port, + sslHost, + sslPort, + keyStorePassword, + keyStorePath, + forwardedHeaderSupport + ), jwt = JWT( + jwtAccessSecret, + jwtRefreshSecret, + jwtAccessMinutes, + jwtRefreshDays, + jwtRealm, + jwtIssuer, + jwtAudience + ) + ) + } + } } diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/ConfigIntermediate.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/ConfigIntermediate.kt index 941313b0..d8f5fe13 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/ConfigIntermediate.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/config/ConfigIntermediate.kt @@ -2,195 +2,94 @@ package de.hsfl.budgetBinder.server.config import com.sksamuel.hoplite.ConfigLoaderBuilder import com.sksamuel.hoplite.addFileSource -import com.sksamuel.hoplite.yaml.YamlPropertySource import java.io.File -data class ConfigIntermediate(val server: Server?, val dataBase: DataBase, val jwt: JWT) { +data class ConfigIntermediate(val server: Server? = null, val database: Database, val jwt: JWT) { data class Server( - val dev: Boolean?, - val ssl: Boolean?, - val host: String?, - val port: Int?, - val sslHost: String?, - val sslPort: Int?, - val keyStorePassword: String?, - val keyStorePath: String?, - val noForwardedHeaderSupport: Boolean? + val dev: Boolean? = null, + val ssl: Boolean? = null, + val host: String? = null, + val port: Int? = null, + val sslHost: String? = null, + val sslPort: Int? = null, + val keyStorePassword: String? = null, + val keyStorePath: String? = null, + val noForwardedHeaderSupport: Boolean? = null ) - data class DataBase( + data class Database( val dbType: Config.DBType, - val sqlitePath: String?, - val serverAddress: String?, - val serverPort: String?, - val name: String?, - val user: String?, - val password: String? + val sqlitePath: String? = null, + val serverAddress: String? = null, + val serverPort: String? = null, + val name: String? = null, + val user: String? = null, + val password: String? = null ) data class JWT( val accessSecret: String, val refreshSecret: String, - val accessMinutes: Int?, - val refreshDays: Int?, - val realm: String?, - val issuer: String?, - val audience: String? + val accessMinutes: Int? = null, + val refreshDays: Int? = null, + val realm: String? = null, + val issuer: String? = null, + val audience: String? = null ) - fun toConfig(): Config { - val dbType = dataBase.dbType - - val sqlitePath: String - val dbServerAddress: String - val dbServerPort: String - val dbName: String - val dbUser: String - val dbPassword: String - if (dbType == Config.DBType.SQLITE) { - sqlitePath = dataBase.sqlitePath ?: (System.getProperty("user.dir") + "/data/data.db") - dbServerAddress = "" - dbServerPort = "" - dbName = "" - dbUser = "" - dbPassword = "" - } else { - sqlitePath = "" - dbServerAddress = dataBase.serverAddress ?: error("No dbServerAddress specified") - dbServerPort = dataBase.serverPort ?: error("No dbServerPort specified") - dbName = dataBase.name ?: error("No dbDatabaseName specified") - dbUser = dataBase.user ?: error("No dbUser specified") - dbPassword = dataBase.password ?: error("No dbPassword specified") - } - - val dev = server?.dev ?: false - val ssl = server?.ssl ?: false - - val host = server?.host ?: "0.0.0.0" - val port = server?.port ?: 8080 - val sslHost = server?.sslHost ?: "0.0.0.0" - val sslPort = server?.sslPort ?: 8443 - - val keyStorePassword: String - val keyStorePath: String - if (ssl) { - keyStorePassword = server?.keyStorePassword - ?: if (dev) "budget-binder-server" else error("No KeystorePassword provided") - keyStorePath = server?.keyStorePath - ?: if (dev) "data/dev_keystore.jks" else error("No KeystorePath provided") - } else { - keyStorePassword = "" - keyStorePath = "" - } - - val forwardedHeaderSupport = !(server?.noForwardedHeaderSupport ?: false) + companion object { + fun createFromEnv(): ConfigIntermediate { + val server = Server( + System.getenv("DEV") != null, + System.getenv("SSL") != null, + System.getenv("HOST"), + System.getenv("PORT")?.toIntOrNull(), + System.getenv("SSL_HOST"), + System.getenv("SSL_PORT")?.toIntOrNull(), + System.getenv("KEYSTORE_PASSWORD"), + System.getenv("KEYSTORE_PATH"), + System.getenv("NO_FORWARDED_HEADER") != null + ) - val jwtAccessSecret = jwt.accessSecret - val jwtRefreshSecret = jwt.refreshSecret - val jwtAccessMinutes = jwt.accessMinutes ?: 15 - val jwtRefreshDays = jwt.refreshDays ?: 7 - val jwtRealm = jwt.realm ?: "budget-binder-server" - val jwtIssuer = jwt.issuer ?: "http://0.0.0.0:8080/" - val jwtAudience = jwt.audience ?: "http://0.0.0.0:8080/" + val dbType = when (System.getenv("DB_TYPE")) { + "SQLITE" -> Config.DBType.SQLITE + "MYSQL" -> Config.DBType.MYSQL + "POSTGRES" -> Config.DBType.POSTGRES + else -> error("No Database Type given") + } - return Config( - dataBase = Config.DataBase( + val dataBase = Database( dbType, - sqlitePath, - dbServerAddress, - dbServerPort, - dbName, - dbUser, - dbPassword - ), server = Config.Server( - dev, - ssl, - host, - port, - sslHost, - sslPort, - keyStorePassword, - keyStorePath, - forwardedHeaderSupport - ), jwt = Config.JWT( - jwtAccessSecret, - jwtRefreshSecret, - jwtAccessMinutes, - jwtRefreshDays, - jwtRealm, - jwtIssuer, - jwtAudience + System.getenv("SQLITE_PATH"), + System.getenv("DB_SERVER"), + System.getenv("DB_PORT"), + System.getenv("DB_DATABASE_NAME"), + System.getenv("DB_USER"), + System.getenv("DB_PASSWORD") ) - ) - } -} - -private fun getConfigIntermediateFromEnv(): ConfigIntermediate { - val server = ConfigIntermediate.Server( - System.getenv("DEV") != null, - System.getenv("SSL") != null, - System.getenv("HOST"), - System.getenv("PORT")?.toIntOrNull(), - System.getenv("SSL_HOST"), - System.getenv("SSL_PORT")?.toIntOrNull(), - System.getenv("KEYSTORE_PASSWORD"), - System.getenv("KEYSTORE_PATH"), - System.getenv("NO_FORWARDED_HEADER") != null - ) - - val dbType = when (System.getenv("DB_TYPE")) { - "SQLITE" -> Config.DBType.SQLITE - "MYSQL" -> Config.DBType.MYSQL - "POSTGRES" -> Config.DBType.POSTGRES - else -> error("No Database Type given") - } - val dataBase = ConfigIntermediate.DataBase( - dbType, - System.getenv("SQLITE_PATH"), - System.getenv("DB_SERVER"), - System.getenv("DB_PORT"), - System.getenv("DB_DATABASE_NAME"), - System.getenv("DB_USER"), - System.getenv("DB_PASSWORD") - ) - - val accessSecret = System.getenv("JWT_ACCESS_SECRET") ?: error("No AccessTokenSecret given") - val refreshSecret = System.getenv("JWT_REFRESH_SECRET") ?: error("No RefreshTokenSecret given") - - val jwt = ConfigIntermediate.JWT( - accessSecret, - refreshSecret, - System.getenv("JWT_ACCESS_MINUTES")?.toIntOrNull(), - System.getenv("JWT_REFRESH_DAYS")?.toIntOrNull(), - System.getenv("JWT_REALM"), - System.getenv("JWT_ISSUER"), - System.getenv("JWT_AUDIENCE") - ) - - return ConfigIntermediate(server, dataBase, jwt) -} - -private fun getConfigIntermediateFromFile(configFile: File): ConfigIntermediate { - return ConfigLoaderBuilder - .default() - .addFileSource(configFile) - .build() - .loadConfigOrThrow() -} + val accessSecret = System.getenv("JWT_ACCESS_SECRET") ?: error("No AccessTokenSecret given") + val refreshSecret = System.getenv("JWT_REFRESH_SECRET") ?: error("No RefreshTokenSecret given") + + val jwt = JWT( + accessSecret, + refreshSecret, + System.getenv("JWT_ACCESS_MINUTES")?.toIntOrNull(), + System.getenv("JWT_REFRESH_DAYS")?.toIntOrNull(), + System.getenv("JWT_REALM"), + System.getenv("JWT_ISSUER"), + System.getenv("JWT_AUDIENCE") + ) -private fun getConfigIntermediateFromString(configString: String): ConfigIntermediate { - return ConfigLoaderBuilder - .default() - .addSource(YamlPropertySource(configString)) - .build() - .loadConfigOrThrow() -} + return ConfigIntermediate(server, dataBase, jwt) + } -fun getServerConfig(configFile: File? = null, configString: String? = null): Config { - return configString?.let { - getConfigIntermediateFromString(it).toConfig() - } ?: configFile?.let { - getConfigIntermediateFromFile(it).toConfig() - } ?: getConfigIntermediateFromEnv().toConfig() + fun createFromFile(configFile: File): ConfigIntermediate { + return ConfigLoaderBuilder + .default() + .addFileSource(configFile) + .build() + .loadConfigOrThrow() + } + } } diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/main.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/main.kt index a00bf74d..7473e610 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/main.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/main.kt @@ -3,7 +3,7 @@ package de.hsfl.budgetBinder.server import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file -import de.hsfl.budgetBinder.server.config.getServerConfig +import de.hsfl.budgetBinder.server.config.Config import io.ktor.network.tls.certificates.* import io.ktor.server.engine.* import io.ktor.server.netty.* @@ -22,7 +22,7 @@ class ServerMain : CliktCommand() { ) override fun run(): Unit = runBlocking { - val config = getServerConfig(configFile = configFile) + val config = Config.create(configFile) val keyStore = when { config.server.dev && config.server.ssl -> { diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/mainModule.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/mainModule.kt index ae2aa2da..08aff2f8 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/mainModule.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/mainModule.kt @@ -1,164 +1,22 @@ package de.hsfl.budgetBinder.server -import de.hsfl.budgetBinder.common.APIResponse -import de.hsfl.budgetBinder.common.ErrorModel import de.hsfl.budgetBinder.server.config.Config -import de.hsfl.budgetBinder.server.models.Categories -import de.hsfl.budgetBinder.server.models.Entries -import de.hsfl.budgetBinder.server.models.Users -import de.hsfl.budgetBinder.server.utils.UnauthorizedException +import de.hsfl.budgetBinder.server.plugins.* import de.hsfl.budgetBinder.server.routes.* -import de.hsfl.budgetBinder.server.services.* -import de.hsfl.budgetBinder.server.services.implementations.CategoryServiceImpl -import de.hsfl.budgetBinder.server.services.implementations.EntryServiceImpl -import de.hsfl.budgetBinder.server.services.implementations.UserServiceImpl -import de.hsfl.budgetBinder.server.services.interfaces.CategoryService -import de.hsfl.budgetBinder.server.services.interfaces.EntryService -import de.hsfl.budgetBinder.server.services.interfaces.UserService import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.auth.jwt.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.plugins.callloging.CallLogging -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.plugins.forwardedheaders.* -import io.ktor.server.plugins.statuspages.* -import kotlinx.serialization.json.Json -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.kodein.di.bindEagerSingleton -import org.kodein.di.bindSingleton -import org.kodein.di.instance -import org.kodein.di.ktor.closestDI -import org.kodein.di.ktor.di -import org.slf4j.event.Level -import java.sql.DriverManager fun Application.mainModule(config: Config) { - val url: String = when (config.dataBase.dbType) { - Config.DBType.SQLITE -> { - val url = "jdbc:sqlite:${config.dataBase.sqlitePath}" - /* - * The url is used in the tests to not create or alter the normal database. - * the connection must be held because exposed closes the connection to the db - * after every transaction and if no connection is alive the memory database will be deleted - * */ - if (url == "jdbc:sqlite:file:test?mode=memory&cache=shared") { - DriverManager.getConnection(url) - } - url - } - Config.DBType.MYSQL -> "jdbc:mysql://${config.dataBase.serverAddress}:${config.dataBase.serverPort}/${config.dataBase.name}" - Config.DBType.POSTGRES -> "jdbc:postgresql://${config.dataBase.serverAddress}:${config.dataBase.serverPort}/${config.dataBase.name}" - } - Database.connect(url, user = config.dataBase.user, password = config.dataBase.password) + // install all Plugin Modules + configureDI(config) + configureDatabase() + configureLogging() + configureForwardHeaders() + configureAuth() + configureContentNegotiation() + configureStatusPages() - transaction { - SchemaUtils.create(Users, Categories, Entries) - } - - di { - bindEagerSingleton { config } - bindSingleton { UserServiceImpl() } - bindSingleton { EntryServiceImpl() } - bindSingleton { CategoryServiceImpl() } - bindSingleton { JWTService(instance()) } - } - - install(CallLogging) { - level = Level.INFO - disableDefaultColors() - } - - if (config.server.forwardedHeaderSupport) - install(XForwardedHeaders) - - install(Authentication) { - form("auth-form") { - userParamName = "username" - passwordParamName = "password" - validate { - val userService: UserService by closestDI().instance() - userService.findUserByEmailAndPassword(it.name, it.password) - } - } - - jwt("auth-jwt") { - val jwtService: JWTService by this@mainModule.closestDI().instance() - realm = jwtService.getRealm() - verifier(jwtService.getAccessTokenVerifier()) - - validate { - val id = it.payload.getClaim("userid").asInt() - val tokenVersion = it.payload.getClaim("token_version").asInt() - val userService: UserService by closestDI().instance() - userService.getUserPrincipalByIDAndTokenVersion(id, tokenVersion) - } - } - } - - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - }) - } - - install(StatusPages) { - exception { call, cause -> - when (cause) { - is UnauthorizedException -> { - call.respond( - HttpStatusCode.Unauthorized, - APIResponse(ErrorModel(cause.message, HttpStatusCode.Unauthorized.value)) - ) - } - else -> { - call.respond( - HttpStatusCode.InternalServerError, - APIResponse( - ErrorModel( - "An Internal-Server-Error occurred. Please contact your Administrator or see the Server-Logs.", - HttpStatusCode.InternalServerError.value - ) - ) - ) - throw cause - } - } - } - status(HttpStatusCode.Unauthorized) { call, status -> - when (call.request.uri) { - "/login" -> call.respond( - status, - APIResponse(ErrorModel("Your username and/or password do not match.", status.value)) - ) - else -> { - val jwtService: JWTService by this@mainModule.closestDI().instance() - call.response.headers.append( - HttpHeaders.WWWAuthenticate, - "Bearer realm=\"${jwtService.getRealm()}\"" - ) - call.respond( - status, - APIResponse(ErrorModel("Your accessToken is absent or does not match.", status.value)) - ) - } - } - } - status(HttpStatusCode.MethodNotAllowed) { call, status -> - call.respond( - status, - APIResponse(ErrorModel("The used HTTP-Method is not allowed on this Endpoint.", status.value)) - ) - } - } - - // install all Modules + // install all Routing Modules baseRoutes() userRoutes() authRoutes() diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/models/UserPrincipal.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/models/UserPrincipal.kt index 71a69368..950cb6eb 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/models/UserPrincipal.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/models/UserPrincipal.kt @@ -2,7 +2,6 @@ package de.hsfl.budgetBinder.server.models import io.ktor.server.auth.* - interface UserPrincipal : Principal { fun getUserID(): Int fun getUserTokenVersion(): Int diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Auth.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Auth.kt new file mode 100644 index 00000000..40941061 --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Auth.kt @@ -0,0 +1,64 @@ +package de.hsfl.budgetBinder.server.plugins + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.ErrorModel +import de.hsfl.budgetBinder.server.services.JWTService +import de.hsfl.budgetBinder.server.services.interfaces.UserService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.response.* +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI + +fun Application.configureAuth() { + install(Authentication) { + form("auth-form") { + userParamName = "username" + passwordParamName = "password" + validate { + val userService: UserService by closestDI().instance() + userService.findUserByEmailAndPassword(it.name, it.password) + } + + challenge { + call.respond( + HttpStatusCode.Unauthorized, + APIResponse( + ErrorModel( + "Your username and/or password do not match.", + HttpStatusCode.Unauthorized.value + ) + ) + ) + } + } + + jwt("auth-jwt") { + val jwtService: JWTService by this@configureAuth.closestDI().instance() + realm = jwtService.getRealm() + verifier(jwtService.accessTokenVerifier) + + validate { + val id = it.payload.getClaim("userid").asInt() + val tokenVersion = it.payload.getClaim("token_version").asInt() + val userService: UserService by closestDI().instance() + userService.getUserPrincipalByIDAndTokenVersion(id, tokenVersion) + } + + challenge { defaultScheme, realm -> + call.response.headers.append(HttpHeaders.WWWAuthenticate, "$defaultScheme realm=$realm") + call.respond( + HttpStatusCode.Unauthorized, + APIResponse( + ErrorModel( + "Your accessToken is absent or does not match.", + HttpStatusCode.Unauthorized.value + ) + ) + ) + } + } + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ContentNegotiation.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ContentNegotiation.kt new file mode 100644 index 00000000..6fa9c11f --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ContentNegotiation.kt @@ -0,0 +1,16 @@ +package de.hsfl.budgetBinder.server.plugins + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import kotlinx.serialization.json.Json + +fun Application.configureContentNegotiation() { + install(ContentNegotiation) { + json(Json { + // ignoreUnknownKeys = true + encodeDefaults = true + prettyPrint = true + }) + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/DI.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/DI.kt new file mode 100644 index 00000000..e7b556ab --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/DI.kt @@ -0,0 +1,25 @@ +package de.hsfl.budgetBinder.server.plugins + +import de.hsfl.budgetBinder.server.config.Config +import de.hsfl.budgetBinder.server.services.JWTService +import de.hsfl.budgetBinder.server.services.implementations.CategoryServiceImpl +import de.hsfl.budgetBinder.server.services.implementations.EntryServiceImpl +import de.hsfl.budgetBinder.server.services.implementations.UserServiceImpl +import de.hsfl.budgetBinder.server.services.interfaces.CategoryService +import de.hsfl.budgetBinder.server.services.interfaces.EntryService +import de.hsfl.budgetBinder.server.services.interfaces.UserService +import io.ktor.server.application.* +import org.kodein.di.bindEagerSingleton +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import org.kodein.di.ktor.di + +fun Application.configureDI(config: Config) { + di { + bindEagerSingleton { config } + bindSingleton { UserServiceImpl() } + bindSingleton { EntryServiceImpl() } + bindSingleton { CategoryServiceImpl() } + bindSingleton { JWTService(instance()) } + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Database.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Database.kt new file mode 100644 index 00000000..c39d1696 --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Database.kt @@ -0,0 +1,41 @@ +package de.hsfl.budgetBinder.server.plugins + +import de.hsfl.budgetBinder.server.config.Config +import de.hsfl.budgetBinder.server.models.Categories +import de.hsfl.budgetBinder.server.models.Entries +import de.hsfl.budgetBinder.server.models.Users +import io.ktor.server.application.* +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI +import java.sql.DriverManager + +fun Application.configureDatabase() { + + val config: Config by closestDI().instance() + + val url: String = when (config.database.dbType) { + Config.DBType.SQLITE -> { + val url = "jdbc:sqlite:${config.database.sqlitePath}" + /* + * The url is used in the tests to not create or alter the normal database. + * the connection must be held because exposed closes the connection to the db + * after every transaction and if no connection is alive the memory database will be deleted + * */ + if (url == "jdbc:sqlite:file:test?mode=memory&cache=shared") { + DriverManager.getConnection(url) + } + url + } + Config.DBType.MYSQL -> "jdbc:mysql://${config.database.serverAddress}:${config.database.serverPort}/${config.database.name}" + Config.DBType.POSTGRES -> "jdbc:postgresql://${config.database.serverAddress}:${config.database.serverPort}/${config.database.name}" + } + + Database.connect(url, user = config.database.user, password = config.database.password) + + transaction { + SchemaUtils.create(Users, Categories, Entries) + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ForwardHeaders.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ForwardHeaders.kt new file mode 100644 index 00000000..9d833f43 --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/ForwardHeaders.kt @@ -0,0 +1,14 @@ +package de.hsfl.budgetBinder.server.plugins + +import de.hsfl.budgetBinder.server.config.Config +import io.ktor.server.application.* +import io.ktor.server.plugins.forwardedheaders.* +import org.kodein.di.instance +import org.kodein.di.ktor.closestDI + +fun Application.configureForwardHeaders() { + val config: Config by closestDI().instance() + + if (config.server.forwardedHeaderSupport) + install(XForwardedHeaders) +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Logging.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Logging.kt new file mode 100644 index 00000000..be8c3615 --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/Logging.kt @@ -0,0 +1,12 @@ +package de.hsfl.budgetBinder.server.plugins + +import io.ktor.server.application.* +import io.ktor.server.plugins.callloging.* +import org.slf4j.event.Level + +fun Application.configureLogging() { + install(CallLogging) { + level = Level.INFO + disableDefaultColors() + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/StatusPages.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/StatusPages.kt new file mode 100644 index 00000000..04f60ef2 --- /dev/null +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/plugins/StatusPages.kt @@ -0,0 +1,42 @@ +package de.hsfl.budgetBinder.server.plugins + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.ErrorModel +import de.hsfl.budgetBinder.server.utils.UnauthorizedException +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.response.* + +fun Application.configureStatusPages() { + install(StatusPages) { + exception { call, cause -> + when (cause) { + is UnauthorizedException -> { + call.respond( + HttpStatusCode.Unauthorized, + APIResponse(ErrorModel(cause.message, HttpStatusCode.Unauthorized.value)) + ) + } + else -> { + call.respond( + HttpStatusCode.InternalServerError, + APIResponse( + ErrorModel( + "An Internal-Server-Error occurred. Please contact your Administrator or see the Server-Logs.", + HttpStatusCode.InternalServerError.value + ) + ) + ) + throw cause + } + } + } + status(HttpStatusCode.MethodNotAllowed) { call, status -> + call.respond( + status, + APIResponse(ErrorModel("The used HTTP-Method is not allowed on this Endpoint.", status.value)) + ) + } + } +} diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/AuthRoutes.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/AuthRoutes.kt index 1bab721a..71d507c2 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/AuthRoutes.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/AuthRoutes.kt @@ -17,7 +17,6 @@ import io.ktor.server.routing.* import org.kodein.di.instance import org.kodein.di.ktor.closestDI - fun Route.login() { authenticate("auth-form") { post("/login") { @@ -60,7 +59,7 @@ fun Route.refreshCookie() { val userService: UserService by closestDI().instance() val response = call.request.cookies["jwt"]?.let { tokenToCheck -> - val token = jwtService.getRefreshTokenVerifier().verify(tokenToCheck) + val token = jwtService.refreshTokenVerifier.verify(tokenToCheck) val id = token.getClaim("userid").asInt() val tokenVersion = token.getClaim("token_version").asInt() userService.getUserPrincipalByIDAndTokenVersion(id, tokenVersion)?.let { userPrincipal -> diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/BaseRoutes.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/BaseRoutes.kt index 20ca3721..56461be4 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/BaseRoutes.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/routes/BaseRoutes.kt @@ -10,7 +10,7 @@ import java.io.File fun Application.baseRoutes() { routing { static("/") { - staticRootFolder = File("files") + staticRootFolder = File("public") files(".") default("index.html") } @@ -29,18 +29,21 @@ fun Application.baseRoutes() { div { id = "swagger-ui" } - unsafe { - +""" - - - """.trimIndent() + """.trimIndent() + } } } } diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/JWTService.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/JWTService.kt index 5257d96d..158edb68 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/JWTService.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/JWTService.kt @@ -13,7 +13,7 @@ class JWTService(private val config: Config) { private val accessTokenValidationTime = 1000 * 60 * config.jwt.accessMinutes private val refreshTokenValidationTime = 1000 * 60 * 60 * 24 * config.jwt.refreshDays - private val accessTokenVerifier = JWT + val accessTokenVerifier: JWTVerifier = JWT .require(Algorithm.HMAC256(config.jwt.accessSecret)) .withAudience(config.jwt.audience) .withIssuer(config.jwt.issuer) @@ -21,7 +21,7 @@ class JWTService(private val config: Config) { .withClaimPresence("token_version") .build() - private val refreshTokenVerifier = JWT + val refreshTokenVerifier: JWTVerifier = JWT .require(Algorithm.HMAC256(config.jwt.refreshSecret)) .withAudience(config.jwt.audience) .withIssuer(config.jwt.issuer) @@ -29,14 +29,6 @@ class JWTService(private val config: Config) { .withClaimPresence("token_version") .build() - fun getAccessTokenVerifier(): JWTVerifier { - return accessTokenVerifier - } - - fun getRefreshTokenVerifier(): JWTVerifier { - return refreshTokenVerifier - } - fun getRealm(): String { return config.jwt.realm } diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/implementations/EntryServiceImpl.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/implementations/EntryServiceImpl.kt index 8aa95465..ca738ce4 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/implementations/EntryServiceImpl.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/services/implementations/EntryServiceImpl.kt @@ -49,40 +49,69 @@ class EntryServiceImpl : EntryService { }.toDto() } - private fun createOrChangeEntry(oldEntry: EntryEntity, repeat: Boolean?, amount: Float?): EntryEntity { + override fun changeEntry(userId: Int, entryId: Int, entry: Entry.Patch): Entry? = transaction { + var entryEntity = EntryEntity[entryId] + if (entryEntity.ended != null) { + return@transaction null + } + + if (entry.category == null) { + if (!canEntryBeChanged(entryEntity, entry.repeat, entry.amount)) + entryEntity = entryEntity.createChild() + } else { + val categoryEntity = entry.category?.let { getCategoryByID(userId, it.id) } + val maybeEntity = changeEntryWithCategoryChange(entryEntity, entry.repeat, entry.amount, categoryEntity) + maybeEntity?.let { + entryEntity = it + it.category = categoryEntity?.id + } ?: return@transaction null + } + + entry.name?.let { entryEntity.name = it } + entry.amount?.let { entryEntity.amount = it } + entry.repeat?.let { entryEntity.repeat = it } + entryEntity.toDto() + } + + private fun changeEntryWithCategoryChange( + oldEntry: EntryEntity, + repeat: Boolean?, + amount: Float?, + categoryEntity: CategoryEntity? + ): EntryEntity? { + if (!canEntryBeChanged(oldEntry, repeat, amount)) { + return oldEntry.createChild() + } val now = LocalDateTime.now() val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + val entryPeriod = LocalDateTime.of(oldEntry.created.year, oldEntry.created.month.value, 1, 0, 0) - if (!oldEntry.repeat || (repeat != false && amount == null) || oldEntry.created > period) { + if (entryPeriod == period) { return oldEntry } - val newEntry = oldEntry.createChild() - oldEntry.child = newEntry.id - oldEntry.ended = now - - return newEntry - } + if (oldEntry.repeat) { + return oldEntry.createChild() + } - override fun changeEntry(userId: Int, entryId: Int, entry: Entry.Patch): Entry? = transaction { - var entryEntity = EntryEntity[entryId] - if (entryEntity.ended != null) { - return@transaction null + if (categoryEntity == null) { + return oldEntry } - val categoryEntity = entry.category?.let { getCategoryByID(userId, it.id) } + val categoryPeriod = LocalDateTime.of(categoryEntity.created.year, categoryEntity.created.month.value, 1, 0, 0) - if (categoryEntity?.ended != null) { - return@transaction null + if (categoryPeriod > entryPeriod) { + return null } - entryEntity = createOrChangeEntry(entryEntity, entry.repeat, entry.amount) - entry.name?.let { entryEntity.name = it } - entry.amount?.let { entryEntity.amount = it } - entry.repeat?.let { entryEntity.repeat = it } - entry.category?.let { entryEntity.category = categoryEntity?.id } + return oldEntry + } - entryEntity.toDto() + private fun canEntryBeChanged(oldEntry: EntryEntity, repeat: Boolean?, amount: Float?): Boolean { + val now = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + + return !oldEntry.repeat || (repeat != false && amount == null) || oldEntry.created > period } override fun deleteEntry(entryId: Int): Entry? = transaction { diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/LocalDateTimeHelper.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/LocalDateTimeHelper.kt index 0dfbb64c..9dd00ece 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/LocalDateTimeHelper.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/LocalDateTimeHelper.kt @@ -17,7 +17,7 @@ fun parseParameterToLocalDateTimeOrErrorMessage(current: Boolean, param: String? if (param == null) return null to null - if (!param.matches("^(0?[1-9]|1[012])-([2-9]\\d[1-9]\\d|[1-9]\\d)\$".toRegex())) { + if (!param.matches("^(0?[1-9]|1[012])-([2-9]\\d\\d\\d)\$".toRegex())) { return "period has not the right pattern" to null } diff --git a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/UnauthorizedException.kt b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/UnauthorizedException.kt index b26dde45..8714fe84 100644 --- a/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/UnauthorizedException.kt +++ b/budget-binder-server/src/main/kotlin/de/hsfl/budgetBinder/server/utils/UnauthorizedException.kt @@ -1,3 +1,3 @@ package de.hsfl.budgetBinder.server.utils -class UnauthorizedException(override val message: String): Throwable() +class UnauthorizedException(override val message: String) : Throwable() diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/ApplicationTest.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/ApplicationTest.kt deleted file mode 100644 index a18e1311..00000000 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/ApplicationTest.kt +++ /dev/null @@ -1,324 +0,0 @@ -package de.hsfl.budgetBinder.server - -import de.hsfl.budgetBinder.common.APIResponse -import de.hsfl.budgetBinder.common.AuthToken -import de.hsfl.budgetBinder.common.User -import de.hsfl.budgetBinder.server.models.UserEntity -import io.ktor.client.call.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.plugins.cookies.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.net.HttpCookie -import kotlin.test.* - -class ApplicationTest { - @BeforeTest - fun registerTestUser() = customTestApplication { client -> - registerUser(client) - } - - @AfterTest - fun deleteTestUser() = transaction { - UserEntity.all().forEach { - it.delete() - } - } - - @Test - fun testRoot() = customTestApplication { client -> - client.get("/docs").let { response -> - assertEquals(HttpStatusCode.OK, response.status) - assertEquals(ContentType.Text.Html.withCharset(Charsets.UTF_8), response.contentType()) - } - - client.get("/favicon.ico").let { response -> - assertEquals(HttpStatusCode.OK, response.status) - assertEquals(ContentType.Image.XIcon, response.contentType()) - } - - client.get("/openapi.json").let { response -> - assertEquals(HttpStatusCode.OK, response.status) - assertEquals(ContentType.Application.Json, response.contentType()) - } - } - - @Test - fun testRegisterLoginAndLogout() = customTestApplication { - val client = createClient { - install(ContentNegotiation) { - json() - } - install(HttpCookies) - } - - client.post("/register").let { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldResponse, responseBody) - } - - client.post("/register") { - contentType(ContentType.Application.Json) - setBody(TestUser.userIn) - }.let { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Email already assigned. Please choose another.") - assertEquals(shouldResponse, responseBody) - } - - client.post("/login").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - client.post("/login") { - header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) - setBody( - listOf( - "username" to "falseTest@test.com", - "password" to "falsetest" - ).formUrlEncode() - ) - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - loginUser(client) { response -> - val setCookieHeader = response.headers[HttpHeaders.SetCookie] - assertNotNull(setCookieHeader) - val cookie = HttpCookie.parse(setCookieHeader) - assertNotNull(cookie) - assertEquals(1, cookie.size) - assertEquals("jwt", cookie[0].name) - assertNotEquals("", cookie[0].value) - } - - client.get("/me").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - client.get("/me") { - val bearer = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOjF9.OsLv52jTx-f-vcuQEJ6FJ-kTJ_DYm3XqVpjLwagQtM0" - header(HttpHeaders.Authorization, "Bearer $bearer") - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - checkMeSuccess(client) - - client.get("/refresh_token").let { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - assert(responseBody.success) - assertNotNull(responseBody.data) - } - - client.get("/logout").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/logout") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val logoutResponse: APIResponse = response.body() - val shouldResponse = wrapSuccess(AuthToken("")) - assertEquals(shouldResponse, logoutResponse) - - val setCookieHeader = response.headers[HttpHeaders.SetCookie] - assertNotNull(setCookieHeader) - val cookie = HttpCookie.parse(setCookieHeader) - assertNotNull(cookie) - assertEquals(1, cookie.size) - assertEquals("jwt", cookie[0].name) - assertEquals("", cookie[0].value) - } - - checkMeSuccess(client) - TestUser.accessToken = null - checkMeFailure(client) - } - - @Test - fun testRefreshTokenWithoutCookie() = customTestApplication { client -> - val response = client.get("/refresh_token") - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your refreshToken is absent.", 401) - assertEquals(shouldResponse, responseBody) - } - - @Test - fun testLogoutAll() = customTestApplication { - val client = createClient { - install(ContentNegotiation) { - json() - } - install(HttpCookies) - } - loginUser(client) - checkMeSuccess(client) - - val tokenVersion = transaction { - val tokenVersion = UserEntity.all().first().tokenVersion - assertEquals(1, tokenVersion) - tokenVersion - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/logout?all=true") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(AuthToken("")) - assertEquals(shouldResponse, responseBody) - } - - transaction { - val newTokenVersion = UserEntity.all().first().tokenVersion - assertNotEquals(tokenVersion, newTokenVersion) - } - - checkMeFailure(client) - - client.get("/refresh_token").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = - wrapFailure("Your refreshToken does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - } - - @Test - fun testUserEndpoints() = customTestApplicationWithLogin { client -> - checkMeSuccess(client) - - val userId = transaction { UserEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/me") { response -> - assertEquals(HttpStatusCode.OK, response.status) - - val user: APIResponse = response.body() - val shouldUser: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldUser, user) - } - - val patchedUser = User.Patch("changedTest", "changedSurname", "newPassword") - - client.patch("/me") { - header(HttpHeaders.ContentType, ContentType.Application.Json) - setBody(patchedUser) - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody(client, HttpMethod.Patch, "/me", patchedUser) { response -> - assertEquals(HttpStatusCode.OK, response.status) - - val user: APIResponse = response.body() - val shouldUser = wrapSuccess(User(userId, "changedTest", "changedSurname", TestUser.email)) - assertEquals(shouldUser, user) - - transaction { - val userEntity = UserEntity[userId] - assertEquals("changedTest", userEntity.firstName) - assertEquals("changedSurname", userEntity.name) - assertEquals("changedSurname", userEntity.name) - } - } - - client.post("/login") { - header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) - setBody( - listOf( - "username" to TestUser.email, - "password" to TestUser.password - ).formUrlEncode() - ) - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - client.post("/login") { - header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) - setBody( - listOf( - "username" to TestUser.email, - "password" to "newPassword" - ).formUrlEncode() - ) - }.let { response -> - assertEquals(HttpStatusCode.OK, response.status) - val token: APIResponse = response.body() - assert(token.success) - assertNotNull(token.data) - TestUser.accessToken = token.data!!.token - } - - client.delete("/me").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/me") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val user: APIResponse = response.body() - - val shouldUser = wrapSuccess(User(userId, "changedTest", "changedSurname", TestUser.email)) - assertEquals(shouldUser, user) - - transaction { - assertNull(UserEntity.findById(userId)) - } - } - - client.get("/me") { - header(HttpHeaders.Authorization, "Bearer ${TestUser.accessToken ?: ""}") - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - client.post("/login") { - header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) - setBody( - listOf( - "username" to TestUser.email, - "password" to "newPassword" - ).formUrlEncode() - ) - }.let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) - assertEquals(shouldResponse, responseBody) - } - } -} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/BaseRoutesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/BaseRoutesTests.kt new file mode 100644 index 00000000..9719c928 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/BaseRoutesTests.kt @@ -0,0 +1,32 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.server.utils.customTestApplication +import io.ktor.client.request.* +import io.ktor.http.* +import kotlin.test.* + +class BaseRoutesTests { + @Test + fun testGetOpenApi() = customTestApplication { client -> + client.get("/openapi.json").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertEquals(ContentType.Application.Json, response.contentType()) + } + } + + @Test + fun testFavicon() = customTestApplication { client -> + client.get("/favicon.ico").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertEquals(ContentType.Image.XIcon, response.contentType()) + } + } + + @Test + fun testDocs() = customTestApplication { client -> + client.get("/docs").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertEquals(ContentType.Text.Html.withCharset(Charsets.UTF_8), response.contentType()) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryEntryTest.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryEntryTest.kt deleted file mode 100644 index c5ad1eac..00000000 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryEntryTest.kt +++ /dev/null @@ -1,961 +0,0 @@ -package de.hsfl.budgetBinder.server - -import de.hsfl.budgetBinder.common.APIResponse -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.server.models.CategoryEntity -import de.hsfl.budgetBinder.server.models.Entries -import de.hsfl.budgetBinder.server.models.EntryEntity -import de.hsfl.budgetBinder.server.models.UserEntity -import io.ktor.client.call.* -import io.ktor.client.request.* -import io.ktor.http.* -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction -import java.time.LocalDateTime -import kotlin.test.* - -class CategoryEntryTest { - - @BeforeTest - fun before() = customTestApplication { client -> - registerUser(client) - - val userEntity = transaction { UserEntity.all().first() } - val now = LocalDateTime.now() - - transaction { - val internetCategory = CategoryEntity.new { - name = "Internet" - color = TestCategories.color - image = TestCategories.image - budget = 50f - created = now.minusMonths(3) - ended = now.minusMonths(2) - child = null - user = userEntity - } - - val internetPhoneCategory = CategoryEntity.new { - name = "Internet-Phone" - color = TestCategories.color - image = TestCategories.image - budget = 100f - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - } - - internetCategory.child = internetPhoneCategory.id - - val internetEntry = EntryEntity.new { - name = "Internet" - amount = -50f - repeat = true - created = now.minusMonths(3) - ended = now.minusMonths(2) - child = null - user = userEntity - category = internetCategory.id - } - - EntryEntity.new { - name = "Internet" - amount = -50f - repeat = true - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - category = internetPhoneCategory.id - }.let { internetEntry.child = it.id } - - EntryEntity.new { - name = "Phone" - amount = -50f - repeat = true - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - category = internetPhoneCategory.id - } - - EntryEntity.new { - name = "Phone one Time" - amount = -250f - repeat = false - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - category = internetPhoneCategory.id - } - - EntryEntity.new { - name = "Monthly Pay" - amount = 3000f - repeat = true - created = now.minusMonths(3) - ended = null - child = null - user = userEntity - category = null - } - } - } - - - @AfterTest - fun after() = transaction { - EntryEntity.all().forEach { it.delete() } - UserEntity.all().forEach { - it.delete() - } - CategoryEntity.all().forEach { it.delete() } - } - - - @Test - fun testGetEntriesByCategory() = customTestApplicationWithLogin { client -> - client.get("/categories/1/entries").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = - wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/test/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/5000/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - val categoryId = transaction { CategoryEntity.all().first().id.value } - val entryId = transaction { EntryEntity.all().first().id.value } - - val entryList = listOf( - Entry(entryId, "Internet", -50f, true, categoryId), - Entry(entryId + 1, "Internet", -50f, true, categoryId + 1), - Entry(entryId + 2, "Phone", -50f, true, categoryId + 1), - Entry(entryId + 3, "Phone one Time", -250f, false, categoryId + 1), - Entry(entryId + 4, "Monthly Pay", 3000f, true, null), - ) - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/${categoryId - 1}/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/$categoryId/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[0])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/${categoryId + 1}/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2], entryList[3])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/null/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[4])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries?current=true") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2], entryList[4])) - assertEquals(shouldResponse, responseBody) - } - } - - @Test - fun testGetEntriesByCategoryWithPeriod() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value } - val entryId = transaction { EntryEntity.all().first().id.value } - - val now = LocalDateTime.now() - - val entryList = listOf( - Entry(entryId, "Internet", -50f, true, categoryId), - Entry(entryId + 1, "Internet", -50f, true, categoryId + 1), - Entry(entryId + 2, "Phone", -50f, true, categoryId + 1), - Entry(entryId + 3, "Phone one Time", -250f, false, categoryId + 1), - Entry(entryId + 4, "Monthly Pay", 3000f, true, null), - ) - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/${categoryId}/entries?current=true") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(1))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(2))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(3))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[0])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId + 1}/entries?current=true" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId + 1}/entries?period=${formatToPeriod(now.minusMonths(1))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId + 1}/entries?period=${formatToPeriod(now.minusMonths(2))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2], entryList[3])) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories/${categoryId + 1}/entries?period=${formatToPeriod(now.minusMonths(3))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun createEntryWithCategory() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Post, "/entries", - Entry.In("Second Phone", -50f, true, 5000) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - EntryEntity.all().last().let { - assertEquals("Second Phone", it.name) - assertEquals(-50f, it.amount) - assert(it.repeat) - assertNull(it.category) - it.id.value - } - } - val shouldResponse = wrapSuccess(Entry(id, "Second Phone", -50f, true, null)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Post, "/entries", - Entry.In("Second Phone", -50f, true, categoryId) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - EntryEntity.all().last().let { - assertEquals("Second Phone", it.name) - assertEquals(-50f, it.amount) - assert(it.repeat) - assertEquals(categoryId, it.category?.value) - it.id.value - } - } - val shouldResponse = wrapSuccess(Entry(id, "Second Phone", -50f, true, categoryId)) - assertEquals(shouldResponse, responseBody) - } - - } - - @Test - fun testChangeCategoryInEntry() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - val entryId = transaction { EntryEntity.all().last().id.value } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/$entryId", - Entry.Patch(category = Entry.Category(categoryId - 1)) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("you can't change this Entry") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/$entryId", - Entry.Patch(category = Entry.Category(categoryId)) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - assertEquals(categoryId, EntryEntity[entryId].category?.value) - } - val shouldResponse = wrapSuccess(Entry(entryId, "Monthly Pay", 3000f, true, categoryId)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/$entryId", - Entry.Patch(category = Entry.Category(5000)) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - assertNull(EntryEntity[entryId].category) - } - val shouldResponse = wrapSuccess(Entry(entryId, "Monthly Pay", 3000f, true, null)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testChangeOldCategoryHasOldEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - val entryId = transaction { EntryEntity.all().first().id.value + 1 } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/categories/$categoryId", - Category.Patch(budget = 200f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - val oldCategory = CategoryEntity[categoryId] - assertNotNull(oldCategory.ended) - assertNotNull(oldCategory.child) - val newCategory = CategoryEntity[oldCategory.child!!] - - assertEquals(100f, oldCategory.budget) - assertEquals(200f, newCategory.budget) - - val oldInternetEntry = EntryEntity[entryId] - val oldPhoneEntry = EntryEntity[entryId + 1] - val oldPhoneOneTimeEntry = EntryEntity[entryId + 2] - - assertNotNull(oldInternetEntry.child) - assertNotNull(oldInternetEntry.ended) - val newInternetEntry = EntryEntity[oldInternetEntry.child!!] - - assertNotNull(oldPhoneEntry.child) - assertNotNull(oldPhoneEntry.ended) - val newPhoneEntry = EntryEntity[oldPhoneEntry.child!!] - - assertEquals(oldInternetEntry.name, newInternetEntry.name) - assertEquals(oldInternetEntry.repeat, newInternetEntry.repeat) - assertNotEquals(oldInternetEntry.category, newInternetEntry.category) - assertEquals(newInternetEntry.id, oldInternetEntry.child) - assertNull(newInternetEntry.child) - assertNull(newInternetEntry.ended) - - assertEquals(oldPhoneEntry.name, newPhoneEntry.name) - assertEquals(oldPhoneEntry.repeat, newPhoneEntry.repeat) - assertNotEquals(oldPhoneEntry.category, newPhoneEntry.category) - assertEquals(oldPhoneEntry.child, newPhoneEntry.id) - assertNull(newPhoneEntry.child) - assertNull(newPhoneEntry.ended) - - assert(!oldPhoneOneTimeEntry.repeat) - assertNull(oldPhoneOneTimeEntry.child) - assertNull(oldPhoneOneTimeEntry.ended) - - newCategory.id.value - } - - val shouldResponse = - wrapSuccess(Category(id, "Internet-Phone", TestCategories.color, TestCategories.image, 200f)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testChangeOldCategoryHasNewEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - transaction { - val userEntity = UserEntity.all().first() - val categoryEntity = CategoryEntity[categoryId] - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/categories/$categoryId", - Category.Patch(budget = 200f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - val oldCategory = CategoryEntity[categoryId] - assertNotNull(oldCategory.ended) - assertNotNull(oldCategory.child) - val newCategory = CategoryEntity[oldCategory.child!!] - - assertEquals(100f, oldCategory.budget) - assertEquals(200f, newCategory.budget) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assert(mobileEntry.repeat) - assertEquals(newCategory.id, mobileEntry.category) - - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - assert(!mobileOneEntry.repeat) - assertEquals(newCategory.id, mobileOneEntry.category) - - newCategory.id.value - } - - val shouldResponse = wrapSuccess( - Category( - id, - "Internet-Phone", - TestCategories.color, - TestCategories.image, - 200f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testChangeOldCategoryHasOnlyNewEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - transaction { - val userEntity = UserEntity.all().first() - val categoryEntity = CategoryEntity[categoryId] - - Entries.deleteWhere { Entries.category eq categoryEntity.id } - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/categories/$categoryId", - Category.Patch(budget = 200f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - val categoryEntity = CategoryEntity[categoryId] - assertNull(categoryEntity.ended) - assertNull(categoryEntity.child) - - assertEquals(200f, categoryEntity.budget) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assert(mobileEntry.repeat) - - assertEquals(categoryEntity.id, mobileEntry.category) - assertEquals(categoryEntity.id, mobileOneEntry.category) - - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - assert(!mobileOneEntry.repeat) - - categoryEntity.id.value - } - - val shouldResponse = wrapSuccess( - Category( - id, - "Internet-Phone", - TestCategories.color, - TestCategories.image, - 200f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testChangeNewCategoryHasNewEntries() = customTestApplicationWithLogin { client -> - transaction { - val userEntity = UserEntity.all().first() - val now = LocalDateTime.now() - - val categoryEntity = CategoryEntity.new { - name = "Mobile" - color = TestCategories.color - image = TestCategories.image - budget = 50f - created = now - ended = null - child = null - user = userEntity - } - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = now - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val categoryId = transaction { CategoryEntity.all().last().id.value } - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/categories/$categoryId", - Category.Patch(budget = 200f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - val categoryEntity = CategoryEntity[categoryId] - assertNull(categoryEntity.ended) - assertNull(categoryEntity.child) - assertEquals(200f, categoryEntity.budget) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assert(mobileEntry.repeat) - - assertEquals(categoryEntity.id, mobileEntry.category) - assertEquals(categoryEntity.id, mobileOneEntry.category) - - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - assert(!mobileOneEntry.repeat) - } - - val shouldResponse = wrapSuccess( - Category( - categoryId, - "Mobile", - TestCategories.color, - TestCategories.image, - 200f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testDeleteOldCategoryHasOldEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - val entryId = transaction { EntryEntity.all().first().id.value + 1 } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/$categoryId") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - val categoryEntity = CategoryEntity.findById(categoryId) - assertNotNull(categoryEntity) - assertNotNull(categoryEntity.ended) - assertNull(categoryEntity.child) - - val oldInternetEntry = EntryEntity[entryId] - val oldPhoneEntry = EntryEntity[entryId + 1] - val oldPhoneOneTimeEntry = EntryEntity[entryId + 2] - assertNull(oldPhoneOneTimeEntry.child) - assertNull(oldPhoneOneTimeEntry.ended) - - assertNotNull(oldInternetEntry.child) - assertNotNull(oldInternetEntry.ended) - val newInternetEntry = EntryEntity[oldInternetEntry.child!!] - assertNull(newInternetEntry.child) - assertNull(newInternetEntry.ended) - - assertNotNull(oldPhoneEntry.child) - assertNotNull(oldPhoneEntry.ended) - val newPhoneEntry = EntryEntity[oldPhoneEntry.child!!] - assertNull(newPhoneEntry.child) - assertNull(newPhoneEntry.ended) - - assertEquals(categoryEntity.id, oldInternetEntry.category) - assertEquals(categoryEntity.id, oldPhoneEntry.category) - assertNull(newInternetEntry.category) - assertNull(newPhoneEntry.category) - - assertEquals(categoryEntity.id, oldPhoneOneTimeEntry.category) - } - - val shouldResponse = wrapSuccess( - Category( - categoryId, - "Internet-Phone", - TestCategories.color, - TestCategories.image, - 100f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testDeleteOldCategoryHasNewEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - transaction { - val userEntity = UserEntity.all().first() - val categoryEntity = CategoryEntity[categoryId] - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/$categoryId") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - val categoryEntity = CategoryEntity.findById(categoryId) - assertNotNull(categoryEntity) - assertNotNull(categoryEntity.ended) - assertNull(categoryEntity.child) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - - assertNull(mobileEntry.category) - assertNull(mobileOneEntry.category) - } - - val shouldResponse = wrapSuccess( - Category( - categoryId, - "Internet-Phone", - TestCategories.color, - TestCategories.image, - 100f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testDeleteOldCategoryHasOnlyNewEntries() = customTestApplicationWithLogin { client -> - val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } - transaction { - val userEntity = UserEntity.all().first() - val categoryEntity = CategoryEntity[categoryId] - - Entries.deleteWhere { Entries.category eq categoryEntity.id } - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/$categoryId") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - val categoryEntity = CategoryEntity.findById(categoryId) - assertNull(categoryEntity) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - - assertNull(mobileEntry.category) - assertNull(mobileOneEntry.category) - } - - val shouldResponse = wrapSuccess( - Category( - categoryId, - "Internet-Phone", - TestCategories.color, - TestCategories.image, - 100f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testDeleteNewCategoryHasNewEntries() = customTestApplicationWithLogin { client -> - transaction { - val userEntity = UserEntity.all().first() - val now = LocalDateTime.now() - - val categoryEntity = CategoryEntity.new { - name = "Mobile" - color = TestCategories.color - image = TestCategories.image - budget = 50f - created = now - ended = null - child = null - user = userEntity - } - - EntryEntity.new { - name = "Mobile" - amount = -50f - repeat = true - created = LocalDateTime.now() - ended = null - child = null - user = userEntity - category = categoryEntity.id - }.id.value - - EntryEntity.new { - name = "Mobile One" - amount = -250f - repeat = false - created = now - ended = null - child = null - user = userEntity - category = categoryEntity.id - } - } - - val categoryId = transaction { CategoryEntity.all().last().id.value } - val entryId = transaction { EntryEntity.all().last().id.value - 1 } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/$categoryId") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - transaction { - val categoryEntity = CategoryEntity.findById(categoryId) - assertNull(categoryEntity) - - val mobileEntry = EntryEntity[entryId] - val mobileOneEntry = EntryEntity[entryId + 1] - - assertNull(mobileEntry.child) - assertNull(mobileEntry.ended) - assertNull(mobileOneEntry.child) - assertNull(mobileOneEntry.ended) - - assertNull(mobileEntry.category) - assertNull(mobileOneEntry.category) - } - - val shouldResponse = wrapSuccess( - Category( - categoryId, - "Mobile", - TestCategories.color, - TestCategories.image, - 50f - ) - ) - assertEquals(shouldResponse, responseBody) - } - } -} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryTest.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryTest.kt deleted file mode 100644 index 0acab05e..00000000 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/CategoryTest.kt +++ /dev/null @@ -1,482 +0,0 @@ -package de.hsfl.budgetBinder.server - -import de.hsfl.budgetBinder.common.APIResponse -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.server.models.CategoryEntity -import de.hsfl.budgetBinder.server.models.UserEntity -import io.ktor.client.call.* -import io.ktor.client.request.* -import io.ktor.http.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.time.LocalDateTime -import kotlin.test.* - -class CategoryTest { - - @BeforeTest - fun before() = customTestApplication { client -> - registerUser(client) - - val userEntity = transaction { UserEntity.all().first() } - val now = LocalDateTime.now() - - transaction { - CategoryEntity.new { - name = "test" - color = TestCategories.color - image = TestCategories.image - budget = 10f - created = now.minusMonths(5) - ended = now.minusMonths(3) - child = null - user = userEntity - } - - val internet = CategoryEntity.new { - name = "Internet" - color = TestCategories.color - image = TestCategories.image - budget = 50f - created = now.minusMonths(3) - ended = now.minusMonths(2) - child = null - user = userEntity - } - - val internetPhone = CategoryEntity.new { - name = "Internet-Phone" - color = TestCategories.color - image = TestCategories.image - budget = 100f - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - } - - internet.child = internetPhone.id - - CategoryEntity.new { - name = "Insurance" - color = TestCategories.color - image = TestCategories.image - budget = 100f - created = now.minusMonths(3) - ended = null - child = null - user = userEntity - } - - CategoryEntity.new { - name = "Hobbies" - color = TestCategories.color - image = TestCategories.image - budget = 150f - created = now.minusMonths(2) - ended = null - child = null - user = userEntity - } - } - } - - - @AfterTest - fun after() = transaction { - UserEntity.all().forEach { - it.delete() - } - CategoryEntity.all().forEach { - it.delete() - } - } - - - @Test - fun testCreateCategory() = customTestApplicationWithLogin { client -> - client.get("/categories").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Post, "/categories") { response -> - assertEquals(HttpStatusCode.OK, response.status) - - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Post, "/categories", - Category.In("Test", TestCategories.color, TestCategories.image, 50f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - - val responseBody: APIResponse = response.body() - val id = transaction { - val categoryEntity = CategoryEntity.all().last() - - assertEquals("Test", categoryEntity.name) - assertEquals(TestCategories.color, categoryEntity.color) - assertEquals(TestCategories.image, categoryEntity.image) - assertEquals(50f, categoryEntity.budget) - - categoryEntity.id.value - } - val shouldResponse = wrapSuccess(Category(id, "Test", TestCategories.color, TestCategories.image, 50f)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testGetCategories() = customTestApplicationWithLogin { client -> - client.get("/categories").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { CategoryEntity.all().first().id.value } - - val categoryList = listOf( - Category(id, "test", TestCategories.color, TestCategories.image, 10f), - Category(id + 1, "Internet-Phone", TestCategories.color, TestCategories.image, 50f), - Category(id + 2, "Internet-Phone", TestCategories.color, TestCategories.image, 100f), - Category(id + 3, "Insurance", TestCategories.color, TestCategories.image, 100f), - Category(id + 4, "Hobbies", TestCategories.color, TestCategories.image, 150f), - ) - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(5, responseBody.data!!.size) - val shouldResponse = wrapSuccess(categoryList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories?current=true") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(3, responseBody.data!!.size) - - val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories?period=508346") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse = wrapFailure>("period has not the right pattern") - assertEquals(shouldResponse, responseBody) - } - - val now = LocalDateTime.now() - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories?period=${formatToPeriod(now)}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(3, responseBody.data!!.size) - - val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories?period=${formatToPeriod(now.minusMonths(2))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(3, responseBody.data!!.size) - - val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories?period=${formatToPeriod(now.minusMonths(3))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(2, responseBody.data!!.size) - - val currentList = listOf(categoryList[1], categoryList[3]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories?period=${formatToPeriod(now.minusMonths(4))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(1, responseBody.data!!.size) - - val currentList = listOf(categoryList[0]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories?period=${formatToPeriod(now.minusMonths(5))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(1, responseBody.data!!.size) - - val currentList = listOf(categoryList[0]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/categories?period=${formatToPeriod(now.minusMonths(6))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(0, responseBody.data!!.size) - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testGetCategoryById() = customTestApplicationWithLogin { client -> - client.get("/categories/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/null") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { CategoryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/${id - 1}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/categories/${id}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Category(id, "test", TestCategories.color, TestCategories.image, 10f)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testPatchCategory() = customTestApplicationWithLogin { client -> - client.patch("/categories/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/categories/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/categories/null") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/categories/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { CategoryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/categories/${id}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, - "/categories/${id}", - Category.Patch(name = "patchedTest") - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("you can't change an old category.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, - "/categories/${id - 1}", - Category.Patch(name = "patchedTest") - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, - "/categories/${id + 4}", - Category.Patch(name = "Fishing") - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = - wrapSuccess(Category(id + 4, "Fishing", TestCategories.color, TestCategories.image, 150f)) - assertEquals(shouldResponse, responseBody) - - transaction { - val categoryEntity = CategoryEntity[id + 4] - assertEquals("Fishing", categoryEntity.name) - assertNull(categoryEntity.ended) - assertNull(categoryEntity.child) - } - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, - "/categories/${id + 4}", - Category.Patch(name = "Fishing", budget = 100f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = - wrapSuccess(Category(id + 4, "Fishing", TestCategories.color, TestCategories.image, 100f)) - assertEquals(shouldResponse, responseBody) - - transaction { - val newCategoryEntity = CategoryEntity[id + 4] - assertEquals("Fishing", newCategoryEntity.name) - assertNull(newCategoryEntity.ended) - assertNull(newCategoryEntity.child) - } - } - } - - - @Test - fun testDeleteCategory() = customTestApplicationWithLogin { client -> - client.get("categories/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/null") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { CategoryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/${id - 1}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your category was not found.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/${id}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("you can't delete an old category.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/categories/${id + 4}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = - wrapSuccess(Category(id + 4, "Hobbies", TestCategories.color, TestCategories.image, 150f)) - assertEquals(shouldResponse, responseBody) - - transaction { - val categoryEntity = CategoryEntity.findById(id + 4) - assertNull(categoryEntity) - } - } - } -} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/EntryTest.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/EntryTest.kt deleted file mode 100644 index 6c2801ce..00000000 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/EntryTest.kt +++ /dev/null @@ -1,529 +0,0 @@ -package de.hsfl.budgetBinder.server - -import de.hsfl.budgetBinder.common.APIResponse -import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.server.models.EntryEntity -import de.hsfl.budgetBinder.server.models.UserEntity -import io.ktor.client.call.* -import io.ktor.client.request.* -import io.ktor.http.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.time.LocalDateTime -import kotlin.test.* - -class EntryTest { - - @BeforeTest - fun before() = customTestApplication { client -> - - registerUser(client) - - val userEntity = transaction { UserEntity.all().first() } - val now = LocalDateTime.now() - - transaction { - val oldPay = EntryEntity.new { - name = "Monthly Pay" - amount = 3000f - repeat = true - created = now.minusMonths(3) - ended = now.minusMonths(2) - child = null - - user = userEntity - category = null - } - - val newPay = EntryEntity.new { - name = "Monthly Job Pay" - amount = 3500f - repeat = true - created = now.minusMonths(2) - ended = null - child = null - - user = userEntity - category = null - } - - oldPay.child = newPay.id - - EntryEntity.new { - name = "Phone" - amount = -50f - repeat = true - created = now.minusMonths(3) - ended = now.minusMonths(1) - child = null - - user = userEntity - category = null - } - - EntryEntity.new { - name = "Internet" - amount = -50f - repeat = true - created = now.minusMonths(2) - ended = null - child = null - - user = userEntity - category = null - } - - EntryEntity.new { - name = "Bike" - amount = -1500f - repeat = false - created = now.minusMonths(1) - ended = null - child = null - - user = userEntity - category = null - } - - EntryEntity.new { - name = "Ikea" - amount = -200f - repeat = false - created = now - ended = null - child = null - - user = userEntity - category = null - } - - EntryEntity.new { - name = "new Phone" - amount = -50f - repeat = true - created = now - ended = null - child = null - - user = userEntity - category = null - } - } - } - - - @AfterTest - fun after() = transaction { - EntryEntity.all().forEach { - it.delete() - } - UserEntity.all().forEach { - it.delete() - } - } - - - @Test - fun testCreateEntry() = customTestApplicationWithLogin { client -> - client.get("/entries").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Post, "/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Post, "/entries", - Entry.In("Bafög", 700f, true, null) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - - val id = transaction { - EntryEntity.all().last().let { - assertEquals("Bafög", it.name) - assertEquals(700f, it.amount) - assert(it.repeat) - assertNull(it.category) - it.id.value - } - } - val shouldResponse = wrapSuccess(Entry(id, "Bafög", 700f, true, null)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testGetEntries() = customTestApplicationWithLogin { client -> - client.get("/entries").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { EntryEntity.all().first().id.value } - - val entryList = listOf( - Entry(id, "Monthly Job Pay", 3000f, true, null), - Entry(id + 1, "Monthly Job Pay", 3500f, true, null), - Entry(id + 2, "Phone", -50f, true, null), - Entry(id + 3, "Internet", -50f, true, null), - Entry(id + 4, "Bike", -1500f, false, null), - Entry(id + 5, "Ikea", -200f, false, null), - Entry(id + 6, "new Phone", -50f, true, null), - ) - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(7, responseBody.data!!.size) - val shouldResponse = wrapSuccess(entryList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries?current=true") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(4, responseBody.data!!.size) - - val currentList = listOf(entryList[1], entryList[3], entryList[5], entryList[6]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries?period=50-8346") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - val shouldResponse: APIResponse> = wrapFailure("period has not the right pattern") - assertEquals(shouldResponse, responseBody) - } - - val now = LocalDateTime.now() - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries?period=${formatToPeriod(now)}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(4, responseBody.data!!.size) - - val currentList = listOf(entryList[1], entryList[3], entryList[5], entryList[6]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/entries?period=${formatToPeriod(now.minusMonths(1))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(3, responseBody.data!!.size) - - val currentList = listOf(entryList[1], entryList[3], entryList[4]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/entries?period=${formatToPeriod(now.minusMonths(2))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(3, responseBody.data!!.size) - - val currentList = listOf(entryList[1], entryList[2], entryList[3]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/entries?period=${formatToPeriod(now.minusMonths(3))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(2, responseBody.data!!.size) - - val currentList = listOf(entryList[0], entryList[2]) - val shouldResponse = wrapSuccess(currentList) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest( - client, - HttpMethod.Get, - "/entries?period=${formatToPeriod(now.minusMonths(4))}" - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse> = response.body() - assert(responseBody.success) - assertEquals(0, responseBody.data!!.size) - - val shouldResponse: APIResponse> = wrapSuccess(emptyList()) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testGetEntryById() = customTestApplicationWithLogin { client -> - client.get("/entries/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { EntryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Get, "/entries/$id") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id, "Monthly Job Pay", 3000f, true, null)) - assertEquals(shouldResponse, responseBody) - } - } - - - @Test - fun testPatchEntry() = customTestApplicationWithLogin { client -> - client.patch("/entries/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/entries/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/entries/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { EntryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Patch, "/entries/$id") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/$id", - Entry.Patch(name = "Pay") - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("you can't change this Entry") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 5}", - Entry.Patch(name = "Ikea Shopping") - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 5, "Ikea Shopping", -200f, false, null)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 4}", - Entry.Patch(amount = -1700f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 4, "Bike", -1700f, false, null)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 5}", - Entry.Patch(name = "Ikea", repeat = true) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 5, "Ikea", -200f, true, null)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 6}", - Entry.Patch(repeat = false) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 6, "new Phone", -50f, false, null)) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 3}", - Entry.Patch(repeat = false) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 7, "Internet", -50f, false, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val oldEntry = EntryEntity[id + 3] - val newEntry = EntryEntity[id + 7] - assertNotNull(oldEntry.ended) - assertEquals(newEntry.id, oldEntry.child) - assertNull(newEntry.ended) - assertNull(newEntry.child) - } - } - - sendAuthenticatedRequestWithBody( - client, - HttpMethod.Patch, "/entries/${id + 1}", - Entry.Patch(amount = 3700f) - ) { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 8, "Monthly Job Pay", 3700f, true, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val oldEntry = EntryEntity[id + 1] - val newEntry = EntryEntity[id + 8] - assertNotNull(oldEntry.ended) - assertEquals(newEntry.id, oldEntry.child) - assertNull(newEntry.ended) - assertNull(newEntry.child) - } - } - } - - - @Test - fun testDeleteEntry() = customTestApplicationWithLogin { client -> - client.delete("/entries/1").let { response -> - assertEquals(HttpStatusCode.Unauthorized, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/test") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/5000") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") - assertEquals(shouldResponse, responseBody) - } - - val id = transaction { EntryEntity.all().first().id.value } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/$id") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse: APIResponse = wrapFailure("you can't delete this Entry") - assertEquals(shouldResponse, responseBody) - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/${id + 1}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 1, "Monthly Job Pay", 3500f, true, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val entry = EntryEntity[id + 1] - assertNotNull(entry.ended) - } - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/${id + 6}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 6, "new Phone", -50f, true, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val entry = EntryEntity.findById(id + 6) - assertNull(entry) - } - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/${id + 4}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 4, "Bike", -1500f, false, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val entry = EntryEntity.findById(id + 4) - assertNull(entry) - } - } - - sendAuthenticatedRequest(client, HttpMethod.Delete, "/entries/${id + 5}") { response -> - assertEquals(HttpStatusCode.OK, response.status) - val responseBody: APIResponse = response.body() - val shouldResponse = wrapSuccess(Entry(id + 5, "Ikea", -200f, false, null)) - assertEquals(shouldResponse, responseBody) - - transaction { - val entry = EntryEntity.findById(id + 5) - assertNull(entry) - } - } - } -} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LoginTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LoginTests.kt new file mode 100644 index 00000000..da0baa63 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LoginTests.kt @@ -0,0 +1,68 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.AuthToken +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.customTestApplication +import de.hsfl.budgetBinder.server.utils.loginUser +import de.hsfl.budgetBinder.server.utils.registerUser +import de.hsfl.budgetBinder.server.utils.wrapFailure +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.net.HttpCookie +import kotlin.test.* + +class LoginTests { + @BeforeTest + fun registerTestUser() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun deleteTestUser() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testLoginUnauthorized() = customTestApplication { client -> + client.post("/login").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testLoginFalseData() = customTestApplication { client -> + client.post("/login") { + header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) + setBody( + listOf( + "username" to "falseTest@test.com", + "password" to "falsetest" + ).formUrlEncode() + ) + }.let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testLoginSuccessFull() = customTestApplication { client -> + client.loginUser { response -> + val setCookieHeader = response.headers[HttpHeaders.SetCookie] + assertNotNull(setCookieHeader) + val cookie = HttpCookie.parse(setCookieHeader) + assertNotNull(cookie) + assertEquals(1, cookie.size) + assertEquals("jwt", cookie[0].name) + assertNotEquals("", cookie[0].value) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LogoutAndRefreshTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LogoutAndRefreshTests.kt new file mode 100644 index 00000000..3e94b186 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/LogoutAndRefreshTests.kt @@ -0,0 +1,198 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.AuthToken +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.cookies.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.net.HttpCookie +import kotlin.test.* + +class LogoutAndRefreshTests { + + private suspend fun HttpClient.logoutUser(all: Boolean) { + this.sendAuthenticatedRequest(HttpMethod.Get, "/logout?all=$all") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapSuccess(AuthToken("")) + assertEquals(shouldResponse, responseBody) + + val setCookieHeader = response.headers[HttpHeaders.SetCookie] + assertNotNull(setCookieHeader) + val cookie = HttpCookie.parse(setCookieHeader) + assertNotNull(cookie) + assertEquals(1, cookie.size) + assertEquals("jwt", cookie[0].name) + assertEquals("", cookie[0].value) + } + } + + @BeforeTest + fun registerTestUser() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun deleteTestUser() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testRefreshTokenUnauthorized() = customTestApplication { client -> + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your refreshToken is absent.", 401) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testRefreshToken() = customTestApplication { + val client = createClient { + install(ContentNegotiation) { + json() + } + install(HttpCookies) { + storage = CustomCookieStorage() + } + } + + client.loginUser() + + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + assert(responseBody.success) + assertNull(responseBody.error) + assertNotNull(responseBody.data) + + val setCookieHeader = response.headers[HttpHeaders.SetCookie] + assertNotNull(setCookieHeader) + val cookie = HttpCookie.parse(setCookieHeader) + assertNotNull(cookie) + assertEquals(1, cookie.size) + assertEquals("jwt", cookie[0].name) + assertNotEquals("", cookie[0].value) + } + } + + @Test + fun testGetMeAfterLogout() = customTestApplicationWithLogin { client -> + client.checkMeSuccess() + client.logoutUser(false) + client.checkMeSuccess() + TestUser.accessToken = "" + client.checkMeFailure() + } + + @Test + fun testGetMeAfterLogoutAll() = customTestApplicationWithLogin { client -> + client.checkMeSuccess() + + val tokenVersion = transaction { + val tokenVersion = UserEntity.all().first().tokenVersion + assertEquals(1, tokenVersion) + tokenVersion + } + + client.logoutUser(true) + + transaction { + val newTokenVersion = UserEntity.all().first().tokenVersion + assertNotEquals(tokenVersion, newTokenVersion) + } + + client.checkMeFailure() + } + + @Test + fun testRefreshTokenAfterLogout() = customTestApplication { + var client = createClient { + install(ContentNegotiation) { + json() + } + install(HttpCookies) { + storage = CustomCookieStorage() + } + } + client.loginUser() + client.checkMeSuccess() + val cookies = client.cookies("http://localhost/refresh_token") + assertEquals(1, cookies.size) + client.logoutUser(false) + + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your refreshToken is absent.", 401) + assertEquals(shouldResponse, responseBody) + } + + assertEquals(0, client.cookies("http://localhost/refresh_token").size) + + client = createClient { + install(ContentNegotiation) { + json() + } + install(HttpCookies) { + storage = ConstantCookiesStorage(cookies[0]) + } + } + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + assert(responseBody.success) + assertNull(responseBody.error) + assertNotNull(responseBody.data) + } + } + + @Test + fun testRefreshTokenAfterLogoutAll() = customTestApplication { + var client = createClient { + install(ContentNegotiation) { + json() + } + install(HttpCookies) { + storage = CustomCookieStorage() + } + } + client.loginUser() + client.checkMeSuccess() + val cookies = client.cookies("http://localhost/refresh_token") + assertEquals(1, cookies.size) + client.logoutUser(true) + assertEquals(0, client.cookies("http://localhost/refresh_token").size) + + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your refreshToken is absent.", 401) + assertEquals(shouldResponse, responseBody) + } + + client = createClient { + install(ContentNegotiation) { + json() + } + install(HttpCookies) { + storage = ConstantCookiesStorage(cookies[0]) + } + } + + client.get("refresh_token").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your refreshToken does not match.", 401) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/RegisterTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/RegisterTests.kt new file mode 100644 index 00000000..dde9a7e8 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/RegisterTests.kt @@ -0,0 +1,51 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.User +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.TestUser +import de.hsfl.budgetBinder.server.utils.customTestApplication +import de.hsfl.budgetBinder.server.utils.registerUser +import de.hsfl.budgetBinder.server.utils.wrapFailure +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* +import kotlin.test.assertEquals + +class RegisterTests { + @AfterTest + fun deleteTestUser() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testRegisterWithoutBody() = customTestApplication { client -> + client.post("/register").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testRegisterUser() = customTestApplication { client -> + client.registerUser() + } + + @Test + fun testRegisterSameUser() = customTestApplication { client -> + client.registerUser() + client.post("/register") { + contentType(ContentType.Application.Json) + setBody(TestUser.userIn) + }.let { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Email already assigned. Please choose another.") + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestModels.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestModels.kt deleted file mode 100644 index 533de973..00000000 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestModels.kt +++ /dev/null @@ -1,26 +0,0 @@ -package de.hsfl.budgetBinder.server - -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.User - -object TestUser { - const val email = "test@test.com" - const val password = "test-test" - const val firstName = "test" - const val surName = "Test" - - val userIn = User.In(firstName, surName, email, password) - - fun getTestUser(id: Int): User { - return User(id, firstName, surName, email) - } - - var accessToken: String? = null -} - -object TestCategories { - - const val color = "111111" - val image = Category.Image.DEFAULT - -} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UnauthorizedTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UnauthorizedTests.kt new file mode 100644 index 00000000..5512ccac --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UnauthorizedTests.kt @@ -0,0 +1,94 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.common.* +import de.hsfl.budgetBinder.server.utils.customTestApplication +import de.hsfl.budgetBinder.server.utils.wrapFailure +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlin.test.* + +class UnauthorizedTests { + private suspend inline fun HttpResponse.checkUnauthorized() { + assertEquals(HttpStatusCode.Unauthorized, this.status) + val responseBody: APIResponse = this.body() + val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) + assertEquals(shouldResponse, responseBody) + } + + @Test + fun testLogout() = customTestApplication { client -> + client.get("/logout").checkUnauthorized() + } + + @Test + fun testGetMe() = customTestApplication { client -> + client.get("/me").checkUnauthorized() + } + + @Test + fun testPatchMe() = customTestApplication { client -> + client.patch("/me").checkUnauthorized() + } + + @Test + fun testDeleteMe() = customTestApplication { client -> + client.delete("/me").checkUnauthorized() + } + + @Test + fun testGetCategories() = customTestApplication { client -> + client.get("/categories").checkUnauthorized>() + } + + @Test + fun testPostCategory() = customTestApplication { client -> + client.post("/categories").checkUnauthorized() + } + + @Test + fun testGetCategoryByID() = customTestApplication { client -> + client.get("/categories/1").checkUnauthorized() + } + + @Test + fun testPatchCategoryByID() = customTestApplication { client -> + client.patch("/categories/1").checkUnauthorized() + } + + @Test + fun testDeleteCategoryByID() = customTestApplication { client -> + client.delete("/categories/1").checkUnauthorized() + } + + @Test + fun testGetEntriesByCategoryID() = customTestApplication { client -> + client.get("/categories/1/entries").checkUnauthorized>() + } + + @Test + fun testGetEntries() = customTestApplication { client -> + client.get("/entries").checkUnauthorized>() + } + + @Test + fun testPostEntry() = customTestApplication { client -> + client.post("/entries").checkUnauthorized() + } + + @Test + fun testGetEntryByID() = customTestApplication { client -> + client.get("/entries/1").checkUnauthorized() + } + + @Test + fun testPatchEntryByID() = customTestApplication { client -> + client.patch("/entries/1").checkUnauthorized() + } + + @Test + fun testDeleteEntryByID() = customTestApplication { client -> + client.delete("/entries/1").checkUnauthorized() + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UserTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UserTests.kt new file mode 100644 index 00000000..36637b59 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UserTests.kt @@ -0,0 +1,129 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.AuthToken +import de.hsfl.budgetBinder.common.User +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* + +class UserTests { + @BeforeTest + fun registerTestUser() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun deleteTestUser() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testGetMe() = customTestApplicationWithLogin { client -> + client.checkMeSuccess() + } + + @Test + fun testPatchMeWithoutBody() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/me") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val user: APIResponse = response.body() + val shouldUser: APIResponse = wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldUser, user) + } + } + + @Test + fun testPatchMe() = customTestApplicationWithLogin { client -> + val userId = transaction { UserEntity.all().first().id.value } + val patchedUser = User.Patch("changedTest", "changedSurname") + + client.sendAuthenticatedRequestWithBody(HttpMethod.Patch, "/me", patchedUser) { response -> + assertEquals(HttpStatusCode.OK, response.status) + + val user: APIResponse = response.body() + val shouldUser = wrapSuccess(User(userId, "changedTest", "changedSurname", TestUser.email)) + assertEquals(shouldUser, user) + + transaction { + val userEntity = UserEntity[userId] + assertEquals("changedTest", userEntity.firstName) + assertEquals("changedSurname", userEntity.name) + } + } + } + + @Test + fun testChangePassword() = customTestApplicationWithLogin { client -> + val userId = transaction { UserEntity.all().first().id.value } + val userPassword = transaction { UserEntity.all().first().passwordHash } + + val patchedUser = User.Patch(password = "newPassword") + + client.sendAuthenticatedRequestWithBody(HttpMethod.Patch, "/me", patchedUser) { response -> + assertEquals(HttpStatusCode.OK, response.status) + + val user: APIResponse = response.body() + val shouldUser = wrapSuccess(TestUser.getTestUser(userId)) + assertEquals(shouldUser, user) + + transaction { + val userEntity = UserEntity[userId] + assertNotEquals(userPassword, userEntity.passwordHash) + } + } + + client.checkMeSuccess() + + client.post("/login") { + header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) + setBody( + listOf( + "username" to TestUser.email, + "password" to TestUser.password + ).formUrlEncode() + ) + }.let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteMe() = customTestApplicationWithLogin { client -> + val userId = transaction { UserEntity.all().first().id.value } + client.sendAuthenticatedRequest(HttpMethod.Delete, "/me") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val user: APIResponse = response.body() + val shouldUser = wrapSuccess(TestUser.getTestUser(userId)) + assertEquals(shouldUser, user) + + transaction { + assertNull(UserEntity.findById(userId)) + } + } + + client.checkMeFailure() + + client.post("/login") { + header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded) + setBody( + listOf( + "username" to TestUser.email, + "password" to TestUser.password + ).formUrlEncode() + ) + }.let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your username and/or password do not match.", 401) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UtilsTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UtilsTests.kt new file mode 100644 index 00000000..c1f05a45 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/UtilsTests.kt @@ -0,0 +1,185 @@ +package de.hsfl.budgetBinder.server + +import de.hsfl.budgetBinder.server.utils.formatToPeriod +import de.hsfl.budgetBinder.server.utils.isCreatedAndEndedInPeriod +import de.hsfl.budgetBinder.server.utils.parseParameterToLocalDateTimeOrErrorMessage +import java.time.LocalDateTime +import kotlin.test.* + +class UtilsTests { + private val errorString = "period has not the right pattern" + + @Test + fun testParsePeriodCurrentTruePeriodNull() { + val now = LocalDateTime.now() + val shouldPeriod = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + + val pair = parseParameterToLocalDateTimeOrErrorMessage(true, null) + assertNull(pair.first) + assertEquals(shouldPeriod, pair.second) + } + + @Test + fun testParsePeriodCurrentFalsePeriodNull() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, null) + assertNull(pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodCurrentTruePeriodRight() { + val now = LocalDateTime.now() + val shouldPeriod = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + + val pair = parseParameterToLocalDateTimeOrErrorMessage(true, "06-2022") + assertNull(pair.first) + assertEquals(shouldPeriod, pair.second) + } + + @Test + fun testParsePeriodCurrentTruePeriodFalse() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "65416546") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWithCharakters() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "sjdvhkf434") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodMoreNumbers() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "65416546") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWithoutDash() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "0135970") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWithDashButWrongPlace() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "01359-0") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWithDashRightPlaceWrongNumbers() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "34-0001") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWrongMonth() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "34-2000") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodWrongYear() { + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, "01-1999") + assertEquals(errorString, pair.first) + assertNull(pair.second) + } + + @Test + fun testParsePeriodRightPatternMonth() { + repeat(12) { + val month = it + 1 + val shouldPeriod = LocalDateTime.of(2000, month, 1, 0, 0) + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, formatToPeriod(shouldPeriod)) + + assertNull(pair.first) + assertEquals(shouldPeriod, pair.second) + } + } + + @Test + fun testParsePeriodRightPatternYear() { + repeat(8000) { + val year = it + 2000 + val shouldPeriod = LocalDateTime.of(year, 1, 1, 0, 0) + val pair = parseParameterToLocalDateTimeOrErrorMessage(false, formatToPeriod(shouldPeriod)) + + assertNull(pair.first) + assertEquals(shouldPeriod, pair.second) + } + } + + @Test + fun testIsCreatedAndEndedInPeriodAllNow() { + val now = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(!isCreatedAndEndedInPeriod(now, now, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedNowEndedNullPeriodNow() { + val now = LocalDateTime.now() + val created = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(isCreatedAndEndedInPeriod(created, null, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedNowEndedNullPeriodFuture() { + val now = LocalDateTime.now().plusMonths(1) + val created = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(isCreatedAndEndedInPeriod(created, null, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedNowEndedNullPeriodPast() { + val now = LocalDateTime.now().minusMonths(1) + val created = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(!isCreatedAndEndedInPeriod(created, null, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedNowEndedPastPeriodNow() { + val now = LocalDateTime.now() + val created = LocalDateTime.now() + val ended = LocalDateTime.now().minusMonths(1) + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(!isCreatedAndEndedInPeriod(created, ended, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedPastEndedNowPeriodNow() { + val now = LocalDateTime.now() + val created = LocalDateTime.now().minusMonths(1) + val ended = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(!isCreatedAndEndedInPeriod(created, ended, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedPastEndedNowPeriodPast() { + val now = LocalDateTime.now().minusMonths(1) + val created = LocalDateTime.now().minusMonths(1) + val ended = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(isCreatedAndEndedInPeriod(created, ended, period)) + } + + @Test + fun testIsCreatedAndEndedInPeriodCreatedPastEndedNowPeriodFuture() { + val now = LocalDateTime.now().plusMonths(1) + val created = LocalDateTime.now().minusMonths(1) + val ended = LocalDateTime.now() + val period = LocalDateTime.of(now.year, now.month.value, 1, 0, 0) + assert(!isCreatedAndEndedInPeriod(created, ended, period)) + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/NewCategoryHasNewEntriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/NewCategoryHasNewEntriesTests.kt new file mode 100644 index 00000000..05353518 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/NewCategoryHasNewEntriesTests.kt @@ -0,0 +1,150 @@ +package de.hsfl.budgetBinder.server.alterCategoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class NewCategoryHasNewEntriesTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val shoppingCategory = CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = now + ended = null + child = null + user = userEntity + } + + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = true + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Ikea" + amount = -200f + repeat = false + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/categories/$categoryId", + Category.Patch(budget = 400f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 400f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[categoryId] + assertNull(categoryEntity.ended) + assertNull(categoryEntity.child) + assertEquals(400f, categoryEntity.budget) + + val mobileEntry = EntryEntity[entryId] + val mobileOneEntry = EntryEntity[entryId + 1] + + assertNull(mobileEntry.child) + assertNull(mobileEntry.ended) + assert(mobileEntry.repeat) + + assertEquals(categoryEntity.id, mobileEntry.category) + assertEquals(categoryEntity.id, mobileOneEntry.category) + + assertNull(mobileOneEntry.child) + assertNull(mobileOneEntry.ended) + assert(!mobileOneEntry.repeat) + } + } + } + + @Test + fun testDeleteCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$categoryId") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 300f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity.findById(categoryId) + assertNull(categoryEntity) + + val entryEntity1 = EntryEntity[entryId] + val entryEntity2 = EntryEntity[entryId + 1] + + assertNull(entryEntity1.child) + assertNull(entryEntity1.ended) + assert(entryEntity1.repeat) + + assertNull(entryEntity1.category) + assertNull(entryEntity2.category) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldAndNewEntriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldAndNewEntriesTests.kt new file mode 100644 index 00000000..2288d41e --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldAndNewEntriesTests.kt @@ -0,0 +1,222 @@ +package de.hsfl.budgetBinder.server.alterCategoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class OldCategoryHasOldAndNewEntriesTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val shoppingCategory = CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + } + + EntryEntity.new { + name = "Aldi Old" + amount = -200f + repeat = true + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Ikea Old" + amount = -200f + repeat = false + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Aldi New" + amount = -200f + repeat = true + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Ikea New" + amount = -200f + repeat = false + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/categories/$categoryId", + Category.Patch(budget = 400f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + + val newCategoryId = transaction { CategoryEntity.all().last().id.value } + val newEntryId = transaction { EntryEntity.all().last().id.value } + + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + newCategoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 400f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[categoryId] + val newCategoryEntity = CategoryEntity[newCategoryId] + + val entryEntity1 = EntryEntity[entryId] + val entryEntity2 = EntryEntity[entryId + 1] + val entryEntity3 = EntryEntity[entryId + 2] + val entryEntity4 = EntryEntity[entryId + 3] + + val newEntryEntity1 = EntryEntity[newEntryId] + + assertNotNull(categoryEntity.ended) + assertNull(newCategoryEntity.ended) + assertNull(newCategoryEntity.child) + assertEquals(newCategoryEntity.id, categoryEntity.child) + assertEquals(300f, categoryEntity.budget) + assertEquals(400f, newCategoryEntity.budget) + + assertNotNull(entryEntity1.ended) + assertEquals(newEntryEntity1.id, entryEntity1.child) + assert(entryEntity1.repeat) + assert(newEntryEntity1.repeat) + + assertNull(newEntryEntity1.ended) + assertNull(newEntryEntity1.child) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + + assertNull(entryEntity3.child) + assertNull(entryEntity3.ended) + assert(entryEntity3.repeat) + + assertNull(entryEntity4.child) + assertNull(entryEntity4.ended) + assert(!entryEntity4.repeat) + + assertEquals(categoryEntity.id, entryEntity1.category) + assertEquals(categoryEntity.id, entryEntity2.category) + assertEquals(newCategoryEntity.id, entryEntity3.category) + assertEquals(newCategoryEntity.id, entryEntity4.category) + assertEquals(newCategoryEntity.id, newEntryEntity1.category) + } + } + } + + @Test + fun testDeleteCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$categoryId") { response -> + val newEntryId = transaction { EntryEntity.all().last().id.value } + + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 300f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[categoryId] + + val entryEntity1 = EntryEntity[entryId] + val entryEntity2 = EntryEntity[entryId + 1] + val entryEntity3 = EntryEntity[entryId + 2] + val entryEntity4 = EntryEntity[entryId + 3] + + val newEntryEntity1 = EntryEntity[newEntryId] + + assertNotNull(categoryEntity.ended) + assertNull(categoryEntity.child) + + assertNotNull(entryEntity1.ended) + assertEquals(newEntryEntity1.id, entryEntity1.child) + assert(entryEntity1.repeat) + assert(newEntryEntity1.repeat) + + assertNull(newEntryEntity1.ended) + assertNull(newEntryEntity1.child) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + + assertNull(entryEntity3.child) + assertNull(entryEntity3.ended) + assert(entryEntity3.repeat) + + assertNull(entryEntity4.child) + assertNull(entryEntity4.ended) + assert(!entryEntity4.repeat) + + assertEquals(categoryEntity.id, entryEntity1.category) + assertEquals(categoryEntity.id, entryEntity2.category) + assertNull(entryEntity3.category) + assertNull(entryEntity4.category) + assertNull(newEntryEntity1.category) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldEntriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldEntriesTests.kt new file mode 100644 index 00000000..d1b25aa9 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOldEntriesTests.kt @@ -0,0 +1,173 @@ +package de.hsfl.budgetBinder.server.alterCategoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class OldCategoryHasOldEntriesTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val shoppingCategory = CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + } + + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = true + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Ikea" + amount = -200f + repeat = false + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/categories/$categoryId", + Category.Patch(budget = 400f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + + val newCategoryId = transaction { CategoryEntity.all().last().id.value } + val newEntryId = transaction { EntryEntity.all().last().id.value } + + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + newCategoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 400f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[categoryId] + val newCategoryEntity = CategoryEntity[newCategoryId] + assertNotNull(categoryEntity.ended) + assertNull(newCategoryEntity.ended) + assertNull(newCategoryEntity.child) + assertEquals(newCategoryEntity.id, categoryEntity.child) + assertEquals(300f, categoryEntity.budget) + assertEquals(400f, newCategoryEntity.budget) + + val entryEntity1 = EntryEntity[entryId] + val newEntryEntity1 = EntryEntity[newEntryId] + val entryEntity2 = EntryEntity[entryId + 1] + + assertNotNull(entryEntity1.ended) + assertEquals(newEntryEntity1.id, entryEntity1.child) + assert(entryEntity1.repeat) + assert(newEntryEntity1.repeat) + + assertNull(newEntryEntity1.ended) + assertNull(newEntryEntity1.child) + + assertEquals(categoryEntity.id, entryEntity1.category) + assertEquals(newCategoryEntity.id, newEntryEntity1.category) + assertEquals(categoryEntity.id, entryEntity2.category) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + } + } + } + + @Test + fun testDeleteCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$categoryId") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 300f + ) + ) + assertEquals(shouldResponse, responseBody) + + val newEntryId = transaction { EntryEntity.all().last().id.value } + + transaction { + val categoryEntity = CategoryEntity[categoryId] + assertNotNull(categoryEntity.ended) + assertNull(categoryEntity.child) + + val entryEntity1 = EntryEntity[entryId] + val newEntryEntity1 = EntryEntity[newEntryId] + val entryEntity2 = EntryEntity[entryId + 1] + + assertNotNull(entryEntity1.ended) + assertEquals(newEntryEntity1.id, entryEntity1.child) + assert(entryEntity1.repeat) + assert(newEntryEntity1.repeat) + + assertNull(newEntryEntity1.ended) + assertNull(newEntryEntity1.child) + + assertEquals(categoryEntity.id, entryEntity1.category) + assertNull(newEntryEntity1.category) + assertEquals(categoryEntity.id, entryEntity2.category) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOnlyNewEntriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOnlyNewEntriesTests.kt new file mode 100644 index 00000000..0c69dc55 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterCategoriesWithEntries/OldCategoryHasOnlyNewEntriesTests.kt @@ -0,0 +1,150 @@ +package de.hsfl.budgetBinder.server.alterCategoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class OldCategoryHasOnlyNewEntriesTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val shoppingCategory = CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = now.minusMonths(1) + ended = null + child = null + user = userEntity + } + + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = true + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + + EntryEntity.new { + name = "Ikea" + amount = -200f + repeat = false + created = now + ended = null + child = null + user = userEntity + category = shoppingCategory.id + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/categories/$categoryId", + Category.Patch(budget = 400f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 400f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[categoryId] + assertNull(categoryEntity.ended) + assertNull(categoryEntity.child) + assertEquals(400f, categoryEntity.budget) + + val entryEntity1 = EntryEntity[entryId] + val entryEntity2 = EntryEntity[entryId + 1] + + assertNull(entryEntity1.child) + assertNull(entryEntity1.ended) + assert(entryEntity1.repeat) + + assertEquals(categoryEntity.id, entryEntity1.category) + assertEquals(categoryEntity.id, entryEntity2.category) + + assertNull(entryEntity2.child) + assertNull(entryEntity2.ended) + assert(!entryEntity2.repeat) + } + } + } + + @Test + fun testDeleteCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$categoryId") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess( + Category( + categoryId, + "Shopping", + TestCategories.color, + TestCategories.image, + 300f + ) + ) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity.findById(categoryId) + assertNull(categoryEntity) + + val mobileEntry = EntryEntity[entryId] + val mobileOneEntry = EntryEntity[entryId + 1] + + assertNull(mobileEntry.child) + assertNull(mobileEntry.ended) + assert(mobileEntry.repeat) + + assertNull(mobileEntry.category) + assertNull(mobileOneEntry.category) + + assertNull(mobileOneEntry.child) + assertNull(mobileOneEntry.ended) + assert(!mobileOneEntry.repeat) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNewCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNewCategoryTests.kt new file mode 100644 index 00000000..fe3e0b89 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNewCategoryTests.kt @@ -0,0 +1,153 @@ +package de.hsfl.budgetBinder.server.alterEntryCategory + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class ToNewCategoryTests { + + private fun createEntry(isRepeated: Boolean, time: LocalDateTime): Int = transaction { + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = isRepeated + created = time + ended = null + child = null + user = UserEntity.all().first() + category = null + }.id.value + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + transaction { + CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = LocalDateTime.now() + ended = null + child = null + user = UserEntity.all().first() + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testOldEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now().minusMonths(1)) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't change this Entry") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testOldEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now().minusMonths(1)) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + val newEntryId = transaction { EntryEntity.all().last().id.value } + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newEntryId, "Aldi", -200f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + val newEntryEntity = EntryEntity[newEntryId] + + assertNotNull(entryEntity.ended) + assertEquals(newEntryEntity.id, entryEntity.child) + assert(entryEntity.repeat) + assert(newEntryEntity.repeat) + assertNull(entryEntity.category) + assertEquals(categoryId, newEntryEntity.category?.value) + } + } + } + + @Test + fun testNewEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now()) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, false, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(!entryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + } + } + } + + @Test + fun testNewEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now()) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(entryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNullCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNullCategoryTests.kt new file mode 100644 index 00000000..ace9bba4 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToNullCategoryTests.kt @@ -0,0 +1,161 @@ +package de.hsfl.budgetBinder.server.alterEntryCategory + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class ToNullCategoryTests { + private fun createEntry(isRepeated: Boolean, time: LocalDateTime, categoryId: Int): Int = transaction { + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = isRepeated + created = time + ended = null + child = null + user = UserEntity.all().first() + category = CategoryEntity[categoryId].id + }.id.value + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + transaction { + CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = LocalDateTime.now().minusMonths(2) + ended = null + child = null + user = UserEntity.all().first() + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testOldEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now().minusMonths(1), categoryId) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(null)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(!entryEntity.repeat) + assertNull(entryEntity.category) + } + } + } + + @Test + fun testOldEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now().minusMonths(1), categoryId) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(null)) + ) { response -> + val newEntryId = transaction { EntryEntity.all().last().id.value } + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newEntryId, "Aldi", -200f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + val newEntryEntity = EntryEntity[newEntryId] + + assertNotNull(entryEntity.ended) + assertEquals(newEntryEntity.id, entryEntity.child) + assert(entryEntity.repeat) + assert(newEntryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + assertNull(newEntryEntity.category) + } + } + } + + @Test + fun testNewEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now(), categoryId) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(null)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(!entryEntity.repeat) + assertNull(entryEntity.category) + } + } + } + + @Test + fun testNewEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now(), categoryId) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(null)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(entryEntity.repeat) + assertNull(entryEntity.category) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToOldCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToOldCategoryTests.kt new file mode 100644 index 00000000..3ecaee40 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToOldCategoryTests.kt @@ -0,0 +1,100 @@ +package de.hsfl.budgetBinder.server.alterEntryCategory + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class ToOldCategoryTests { + private fun createEntry(isRepeated: Boolean): Int = transaction { + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = isRepeated + created = LocalDateTime.now().minusMonths(2) + ended = null + child = null + user = UserEntity.all().first() + category = null + }.id.value + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + transaction { + CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = LocalDateTime.now().minusMonths(1) + ended = null + child = null + user = UserEntity.all().first() + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testOldEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't change this Entry") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testOldEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + val newEntryId = transaction { EntryEntity.all().last().id.value } + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newEntryId, "Aldi", -200f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + val newEntryEntity = EntryEntity[newEntryId] + + assertNotNull(entryEntity.ended) + assertEquals(newEntryEntity.id, entryEntity.child) + assert(entryEntity.repeat) + assert(newEntryEntity.repeat) + assertNull(entryEntity.category) + assertEquals(categoryId, newEntryEntity.category?.value) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToVeryOldCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToVeryOldCategoryTests.kt new file mode 100644 index 00000000..73e6b7c7 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/alterEntryCategory/ToVeryOldCategoryTests.kt @@ -0,0 +1,161 @@ +package de.hsfl.budgetBinder.server.alterEntryCategory + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class ToVeryOldCategoryTests { + private fun createEntry(isRepeated: Boolean, time: LocalDateTime): Int = transaction { + EntryEntity.new { + name = "Aldi" + amount = -200f + repeat = isRepeated + created = time + ended = null + child = null + user = UserEntity.all().first() + category = null + }.id.value + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + transaction { + CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = LocalDateTime.now().minusMonths(2) + ended = null + child = null + user = UserEntity.all().first() + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testOldEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now().minusMonths(1)) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, false, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(!entryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + } + } + } + + @Test + fun testOldEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now().minusMonths(1)) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + val newEntryId = transaction { EntryEntity.all().last().id.value } + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newEntryId, "Aldi", -200f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + val newEntryEntity = EntryEntity[newEntryId] + + assertNotNull(entryEntity.ended) + assertEquals(newEntryEntity.id, entryEntity.child) + assert(entryEntity.repeat) + assert(newEntryEntity.repeat) + assertNull(entryEntity.category) + assertEquals(categoryId, newEntryEntity.category?.value) + } + } + } + + @Test + fun testNewEntryNoRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(false, LocalDateTime.now()) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, false, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(!entryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + } + } + } + + @Test + fun testNewEntryRepeat() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = createEntry(true, LocalDateTime.now()) + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/entries/$entryId", + Entry.Patch(category = Entry.Category(categoryId)) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(entryId, "Aldi", -200f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entryEntity = EntryEntity[entryId] + + assertNull(entryEntity.ended) + assertNull(entryEntity.child) + assert(entryEntity.repeat) + assertEquals(categoryId, entryEntity.category?.value) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CategoryFalseValueTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CategoryFalseValueTests.kt new file mode 100644 index 00000000..488c04d7 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CategoryFalseValueTests.kt @@ -0,0 +1,133 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* + +class CategoryFalseValueTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testCreateCategoryFalseBody() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Post, "/categories") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = + wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCategoriesByPeriod() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories?period=508346") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapFailure>("period has not the right pattern") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCategoryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCategoryByIDNull() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/null") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCategoryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your category was not found.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchCategoryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/categories/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchCategoryByIDNull() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/categories/null") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchCategoryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/categories/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your category was not found.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteCategoryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteCategoryByIDNull() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/null") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteCategoryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/categories/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your category was not found.") + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CreateCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CreateCategoryTests.kt new file mode 100644 index 00000000..b63b2077 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/CreateCategoryTests.kt @@ -0,0 +1,48 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* + +class CreateCategoryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + } + + @Test + fun testCreateCategory() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequestWithBody( + HttpMethod.Post, "/categories", + Category.In("Test", TestCategories.color, TestCategories.image, 50f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + + val responseBody: APIResponse = response.body() + val id = transaction { + val categoryEntity = CategoryEntity.all().last() + + assertEquals("Test", categoryEntity.name) + assertEquals(TestCategories.color, categoryEntity.color) + assertEquals(TestCategories.image, categoryEntity.image) + assertEquals(50f, categoryEntity.budget) + + categoryEntity.id.value + } + val shouldResponse = wrapSuccess(Category(id, "Test", TestCategories.color, TestCategories.image, 50f)) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/DeleteCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/DeleteCategoryTests.kt new file mode 100644 index 00000000..722d5de1 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/DeleteCategoryTests.kt @@ -0,0 +1,95 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class DeleteCategoryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val internet = CategoryEntity.new { + name = "Internet" + color = TestCategories.color + image = TestCategories.image + budget = 50f + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + } + + val internetPhone = CategoryEntity.new { + name = "Internet-Phone" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + + internet.child = internetPhone.id + + CategoryEntity.new { + name = "Hobbies" + color = TestCategories.color + image = TestCategories.image + budget = 150f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + } + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + } + + @Test + fun testDeleteCategoryOld() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't delete an old category.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteCategory() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value + 2 } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/categories/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = + wrapSuccess(Category(id, "Hobbies", TestCategories.color, TestCategories.image, 150f)) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity.findById(id) + assertNull(categoryEntity) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoriesTests.kt new file mode 100644 index 00000000..f88e2c06 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoriesTests.kt @@ -0,0 +1,215 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetCategoriesTests { + private fun getCategoryListFromID(id: Int): List { + return listOf( + Category(id, "test", TestCategories.color, TestCategories.image, 10f), + Category(id + 1, "Internet-Phone", TestCategories.color, TestCategories.image, 50f), + Category(id + 2, "Internet-Phone", TestCategories.color, TestCategories.image, 100f), + Category(id + 3, "Insurance", TestCategories.color, TestCategories.image, 100f), + Category(id + 4, "Hobbies", TestCategories.color, TestCategories.image, 150f), + ) + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + CategoryEntity.new { + name = "test" + color = TestCategories.color + image = TestCategories.image + budget = 10f + created = now.minusMonths(5) + ended = now.minusMonths(3) + child = null + user = userEntity + } + + val internet = CategoryEntity.new { + name = "Internet" + color = TestCategories.color + image = TestCategories.image + budget = 50f + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + } + + val internetPhone = CategoryEntity.new { + name = "Internet-Phone" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + + internet.child = internetPhone.id + + CategoryEntity.new { + name = "Insurance" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(3) + ended = null + child = null + user = userEntity + } + + CategoryEntity.new { + name = "Hobbies" + color = TestCategories.color + image = TestCategories.image + budget = 150f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + } + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + } + + @Test + fun testGetAllCategories() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + val categoryList = getCategoryListFromID(id) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(5, responseBody.data!!.size) + val shouldResponse = wrapSuccess(categoryList) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCurrentCategories() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + val categoryList = getCategoryListFromID(id) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories?current=true") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(3, responseBody.data!!.size) + + val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCategoriesByPeriod() = customTestApplicationWithLogin { client -> + val now = LocalDateTime.now() + val id = transaction { CategoryEntity.all().first().id.value } + val categoryList = getCategoryListFromID(id) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories?period=${formatToPeriod(now)}") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(3, responseBody.data!!.size) + + val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories?period=${formatToPeriod(now.minusMonths(2))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(3, responseBody.data!!.size) + + val currentList = listOf(categoryList[2], categoryList[3], categoryList[4]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories?period=${formatToPeriod(now.minusMonths(3))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(2, responseBody.data!!.size) + + val currentList = listOf(categoryList[1], categoryList[3]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories?period=${formatToPeriod(now.minusMonths(4))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(1, responseBody.data!!.size) + + val currentList = listOf(categoryList[0]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories?period=${formatToPeriod(now.minusMonths(5))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(1, responseBody.data!!.size) + + val currentList = listOf(categoryList[0]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories?period=${formatToPeriod(now.minusMonths(6))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(0, responseBody.data!!.size) + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoryByIDTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoryByIDTests.kt new file mode 100644 index 00000000..1fa16c65 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/GetCategoryByIDTests.kt @@ -0,0 +1,53 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetCategoryByIDTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + CategoryEntity.new { + name = "test" + color = TestCategories.color + image = TestCategories.image + budget = 10f + created = now.minusMonths(5) + ended = now.minusMonths(3) + child = null + user = userEntity + } + } + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + } + + @Test + fun testGetCategoryByID() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/${id}") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Category(id, "test", TestCategories.color, TestCategories.image, 10f)) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/PatchCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/PatchCategoryTests.kt new file mode 100644 index 00000000..dd01fcdb --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categories/PatchCategoryTests.kt @@ -0,0 +1,142 @@ +package de.hsfl.budgetBinder.server.categories + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class PatchCategoryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val internet = CategoryEntity.new { + name = "Internet" + color = TestCategories.color + image = TestCategories.image + budget = 50f + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + } + + val internetPhone = CategoryEntity.new { + name = "Internet-Phone" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + + internet.child = internetPhone.id + + CategoryEntity.new { + name = "Hobbies" + color = TestCategories.color + image = TestCategories.image + budget = 150f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + } + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchCategoryFalseBody() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Patch, "/categories/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = + wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchCategoryOld() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/categories/${id}", + Category.Patch(name = "patchedTest") + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't change an old category.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchCategory() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value + 2 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/categories/$id", + Category.Patch(name = "Fishing") + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = + wrapSuccess(Category(id, "Fishing", TestCategories.color, TestCategories.image, 150f)) + assertEquals(shouldResponse, responseBody) + + transaction { + val categoryEntity = CategoryEntity[id] + assertEquals("Fishing", categoryEntity.name) + assertNull(categoryEntity.ended) + assertNull(categoryEntity.child) + } + } + } + + @Test + fun testPatchCategoryBudget() = customTestApplicationWithLogin { client -> + val id = transaction { CategoryEntity.all().first().id.value + 2 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, + "/categories/$id", + Category.Patch(name = "Fishing", budget = 100f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = + wrapSuccess(Category(id, "Fishing", TestCategories.color, TestCategories.image, 100f)) + assertEquals(shouldResponse, responseBody) + + transaction { + val newCategoryEntity = CategoryEntity[id] + assertEquals("Fishing", newCategoryEntity.name) + assertNull(newCategoryEntity.ended) + assertNull(newCategoryEntity.child) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/CreateEntryWithCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/CreateEntryWithCategoryTests.kt new file mode 100644 index 00000000..e395dc6f --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/CreateEntryWithCategoryTests.kt @@ -0,0 +1,87 @@ +package de.hsfl.budgetBinder.server.categoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class CreateEntryWithCategoryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + transaction { + CategoryEntity.new { + name = "Shopping" + color = TestCategories.color + image = TestCategories.image + budget = 300f + created = LocalDateTime.now() + ended = null + child = null + user = UserEntity.all().first() + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testCreateEntryWithCategoryNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequestWithBody( + HttpMethod.Post, "/entries", + Entry.In("Second Phone", -50f, true, 5000) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + + val id = transaction { + EntryEntity.all().last().let { + assertEquals("Second Phone", it.name) + assertEquals(-50f, it.amount) + assert(it.repeat) + assertNull(it.category) + it.id.value + } + } + val shouldResponse = wrapSuccess(Entry(id, "Second Phone", -50f, true, null)) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testCreateEntryWithCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + client.sendAuthenticatedRequestWithBody( + HttpMethod.Post, "/entries", + Entry.In("Second Phone", -50f, true, categoryId) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + + val id = transaction { + EntryEntity.all().last().let { + assertEquals("Second Phone", it.name) + assertEquals(-50f, it.amount) + assert(it.repeat) + assertEquals(categoryId, it.category?.value) + it.id.value + } + } + val shouldResponse = wrapSuccess(Entry(id, "Second Phone", -50f, true, categoryId)) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryTests.kt new file mode 100644 index 00000000..d50a13c8 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryTests.kt @@ -0,0 +1,177 @@ +package de.hsfl.budgetBinder.server.categoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetEntriesByCategoryTests { + + private fun getEntryListFromID(entryId: Int, categoryId: Int): List { + return listOf( + Entry(entryId, "Internet", -50f, true, categoryId), + Entry(entryId + 1, "Internet", -50f, true, categoryId + 1), + Entry(entryId + 2, "Phone", -50f, true, categoryId + 1), + Entry(entryId + 3, "Phone one Time", -250f, false, categoryId + 1), + Entry(entryId + 4, "Monthly Pay", 3000f, true, null), + ) + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val internetCategory = CategoryEntity.new { + name = "Internet" + color = TestCategories.color + image = TestCategories.image + budget = 50f + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + } + + val internetPhoneCategory = CategoryEntity.new { + name = "Internet-Phone" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + + internetCategory.child = internetPhoneCategory.id + + val internetEntry = EntryEntity.new { + name = "Internet" + amount = -50f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + category = internetCategory.id + } + + EntryEntity.new { + name = "Internet" + amount = -50f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + }.let { internetEntry.child = it.id } + + EntryEntity.new { + name = "Phone" + amount = -50f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + } + + EntryEntity.new { + name = "Phone one Time" + amount = -250f + repeat = false + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + } + + EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = null + child = null + user = userEntity + category = null + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testGetEntriesByCategoryString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/test/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByCategoryNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/5000/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapFailure("Your category was not found.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(entryId, categoryId) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/$categoryId/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[0])) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/${categoryId + 1}/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2], entryList[3])) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByCategoryNull() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(entryId, categoryId) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/null/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[4])) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryWithPeriodTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryWithPeriodTests.kt new file mode 100644 index 00000000..becc90ad --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/categoriesWithEntries/GetEntriesByCategoryWithPeriodTests.kt @@ -0,0 +1,238 @@ +package de.hsfl.budgetBinder.server.categoriesWithEntries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.CategoryEntity +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetEntriesByCategoryWithPeriodTests { + + private fun getEntryListFromID(entryId: Int, categoryId: Int): List { + return listOf( + Entry(entryId, "Internet", -50f, true, categoryId), + Entry(entryId + 1, "Internet", -50f, true, categoryId + 1), + Entry(entryId + 2, "Phone", -50f, true, categoryId + 1), + Entry(entryId + 3, "Phone one Time", -250f, false, categoryId + 1), + Entry(entryId + 4, "Monthly Pay", 3000f, true, null), + ) + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val internetCategory = CategoryEntity.new { + name = "Internet" + color = TestCategories.color + image = TestCategories.image + budget = 50f + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + } + + val internetPhoneCategory = CategoryEntity.new { + name = "Internet-Phone" + color = TestCategories.color + image = TestCategories.image + budget = 100f + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + } + + internetCategory.child = internetPhoneCategory.id + + val internetEntry = EntryEntity.new { + name = "Internet" + amount = -50f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + category = internetCategory.id + } + + EntryEntity.new { + name = "Internet" + amount = -50f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + }.let { internetEntry.child = it.id } + + EntryEntity.new { + name = "Phone" + amount = -50f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + } + + EntryEntity.new { + name = "Phone one Time" + amount = -250f + repeat = false + created = now.minusMonths(2) + ended = null + child = null + user = userEntity + category = internetPhoneCategory.id + } + + EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = null + child = null + user = userEntity + category = null + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + CategoryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testGetEntriesByCategoryFalsePeriod() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Get, "/categories/$categoryId/entries?period=508346") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapFailure>("period has not the right pattern") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCurrentEntriesByCategory() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(entryId, categoryId) + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/$categoryId/entries?current=true" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/${categoryId + 1}/entries?current=true" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2])) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByCategoryFirst() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value } + val entryId = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(entryId, categoryId) + val now = LocalDateTime.now() + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(1))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(2))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/${categoryId}/entries?period=${formatToPeriod(now.minusMonths(3))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[0])) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByCategorySecond() = customTestApplicationWithLogin { client -> + val categoryId = transaction { CategoryEntity.all().first().id.value + 1 } + val entryId = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(entryId, categoryId - 1) + val now = LocalDateTime.now() + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/$categoryId/entries?period=${formatToPeriod(now.minusMonths(1))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2])) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/$categoryId/entries?period=${formatToPeriod(now.minusMonths(2))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse = wrapSuccess(listOf(entryList[1], entryList[2], entryList[3])) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/categories/$categoryId/entries?period=${formatToPeriod(now.minusMonths(3))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/CreateEntryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/CreateEntryTests.kt new file mode 100644 index 00000000..96d97f5a --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/CreateEntryTests.kt @@ -0,0 +1,47 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* + +class CreateEntryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testCreateEntry() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequestWithBody( + HttpMethod.Post, "/entries", + Entry.In("Bafög", 700f, true, null) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + + val id = transaction { + EntryEntity.all().last().let { + assertEquals("Bafög", it.name) + assertEquals(700f, it.amount) + assert(it.repeat) + assertNull(it.category) + it.id.value + } + } + val shouldResponse = wrapSuccess(Entry(id, "Bafög", 700f, true, null)) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/DeleteEntryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/DeleteEntryTests.kt new file mode 100644 index 00000000..3f38e475 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/DeleteEntryTests.kt @@ -0,0 +1,146 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class DeleteEntryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val oldPay = EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + + user = userEntity + category = null + } + + val newPay = EntryEntity.new { + name = "Monthly Job Pay" + amount = 3500f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + + user = userEntity + category = null + } + + oldPay.child = newPay.id + + EntryEntity.new { + name = "Bike" + amount = -1500f + repeat = false + created = now.minusMonths(1) + ended = null + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "new Phone" + amount = -50f + repeat = true + created = now + ended = null + child = null + + user = userEntity + category = null + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testDeleteEntryOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't delete this Entry") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteEntryNotRepeat() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 2 } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Bike", -1500f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity.findById(id) + assertNull(entry) + } + } + } + + @Test + fun testDeleteEntryRepeatNew() = customTestApplicationWithLogin { client -> + println(transaction { EntryEntity.all().toList().map { it.toDto() } }) + + + val id = transaction { EntryEntity.all().first().id.value + 3 } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "new Phone", -50f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity.findById(id) + assertNull(entry) + } + } + } + + @Test + fun testDeleteEntryRepeatOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 1 } + + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Monthly Job Pay", 3500f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity[id] + assertNotNull(entry.ended) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/EntryFalseValueTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/EntryFalseValueTests.kt new file mode 100644 index 00000000..ba2d4707 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/EntryFalseValueTests.kt @@ -0,0 +1,102 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.* + +class EntryFalseValueTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + } + + @AfterTest + fun after() = transaction { + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testCreateEntryFalseBody() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Post, "/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByPeriod() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries?period=50-8346") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + val shouldResponse: APIResponse> = wrapFailure("period has not the right pattern") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchEntryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/entries/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchEntryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Patch, "/entries/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteEntryByIDString() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/test") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The ID you provided is not a number.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testDeleteEntryByIDNotFound() = customTestApplicationWithLogin { client -> + client.sendAuthenticatedRequest(HttpMethod.Delete, "/entries/5000") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("Your entry was not found.") + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntriesTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntriesTests.kt new file mode 100644 index 00000000..525c4383 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntriesTests.kt @@ -0,0 +1,233 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetEntriesTests { + private fun getEntryListFromID(id: Int): List { + return listOf( + Entry(id, "Monthly Job Pay", 3000f, true, null), + Entry(id + 1, "Monthly Job Pay", 3500f, true, null), + Entry(id + 2, "Phone", -50f, true, null), + Entry(id + 3, "Internet", -50f, true, null), + Entry(id + 4, "Bike", -1500f, false, null), + Entry(id + 5, "Ikea", -200f, false, null), + Entry(id + 6, "new Phone", -50f, true, null), + ) + } + + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val oldPay = EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + + user = userEntity + category = null + } + + val newPay = EntryEntity.new { + name = "Monthly Job Pay" + amount = 3500f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + + user = userEntity + category = null + } + + oldPay.child = newPay.id + + EntryEntity.new { + name = "Phone" + amount = -50f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(1) + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "Internet" + amount = -50f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "Bike" + amount = -1500f + repeat = false + created = now.minusMonths(1) + ended = null + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "Ikea" + amount = -200f + repeat = false + created = now + ended = null + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "new Phone" + amount = -50f + repeat = true + created = now + ended = null + child = null + + user = userEntity + category = null + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testGetAllCategories() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(id) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(7, responseBody.data!!.size) + val shouldResponse = wrapSuccess(entryList) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetCurrentEntries() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(id) + + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries?current=true") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(4, responseBody.data!!.size) + + val currentList = listOf(entryList[1], entryList[3], entryList[5], entryList[6]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntriesByPeriod() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + val entryList = getEntryListFromID(id) + val now = LocalDateTime.now() + + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries?period=${formatToPeriod(now)}") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(4, responseBody.data!!.size) + + val currentList = listOf(entryList[1], entryList[3], entryList[5], entryList[6]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/entries?period=${formatToPeriod(now.minusMonths(1))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(3, responseBody.data!!.size) + + val currentList = listOf(entryList[1], entryList[3], entryList[4]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/entries?period=${formatToPeriod(now.minusMonths(2))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(3, responseBody.data!!.size) + + val currentList = listOf(entryList[1], entryList[2], entryList[3]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/entries?period=${formatToPeriod(now.minusMonths(3))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(2, responseBody.data!!.size) + + val currentList = listOf(entryList[0], entryList[2]) + val shouldResponse = wrapSuccess(currentList) + assertEquals(shouldResponse, responseBody) + } + + client.sendAuthenticatedRequest( + HttpMethod.Get, + "/entries?period=${formatToPeriod(now.minusMonths(4))}" + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse> = response.body() + assert(responseBody.success) + assertEquals(0, responseBody.data!!.size) + + val shouldResponse: APIResponse> = wrapSuccess(emptyList()) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntryByIDTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntryByIDTests.kt new file mode 100644 index 00000000..6ef72784 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/GetEntryByIDTests.kt @@ -0,0 +1,79 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class GetEntryByIDTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val oldPay = EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + user = userEntity + category = null + } + + val newPay = EntryEntity.new { + name = "Monthly Job Pay" + amount = 3500f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + + user = userEntity + category = null + } + + oldPay.child = newPay.id + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testGetEntryByIDNew() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 1 } + + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Monthly Job Pay", 3500f, true, null)) + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testGetEntryByIDOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Get, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Monthly Job Pay", 3000f, true, null)) + assertEquals(shouldResponse, responseBody) + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/PatchEntryTests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/PatchEntryTests.kt new file mode 100644 index 00000000..c07b9538 --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/entries/PatchEntryTests.kt @@ -0,0 +1,237 @@ +package de.hsfl.budgetBinder.server.entries + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.server.models.EntryEntity +import de.hsfl.budgetBinder.server.models.UserEntity +import de.hsfl.budgetBinder.server.utils.* +import io.ktor.client.call.* +import io.ktor.http.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime +import kotlin.test.* + +class PatchEntryTests { + @BeforeTest + fun before() = customTestApplication { client -> + client.registerUser() + + val userEntity = transaction { UserEntity.all().first() } + val now = LocalDateTime.now() + + transaction { + val oldPay = EntryEntity.new { + name = "Monthly Pay" + amount = 3000f + repeat = true + created = now.minusMonths(3) + ended = now.minusMonths(2) + child = null + + user = userEntity + category = null + } + + val newPay = EntryEntity.new { + name = "Monthly Job Pay" + amount = 3500f + repeat = true + created = now.minusMonths(2) + ended = null + child = null + + user = userEntity + category = null + } + + oldPay.child = newPay.id + + EntryEntity.new { + name = "Bike" + amount = -1500f + repeat = false + created = now.minusMonths(1) + ended = null + child = null + + user = userEntity + category = null + } + + EntryEntity.new { + name = "new Phone" + amount = -50f + repeat = true + created = now + ended = null + child = null + + user = userEntity + category = null + } + } + } + + @AfterTest + fun after() = transaction { + EntryEntity.all().forEach { it.delete() } + UserEntity.all().forEach { it.delete() } + } + + @Test + fun testPatchEntryFalseBody() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequest(HttpMethod.Patch, "/entries/$id") { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("The object you provided it not in the right format.") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchEntryOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(name = "Pay") + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse: APIResponse = wrapFailure("you can't change this Entry") + assertEquals(shouldResponse, responseBody) + } + } + + @Test + fun testPatchEntryName() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 1 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(name = "Pay") + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Pay", 3500f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity[id] + assertEquals("Pay", entry.name) + } + } + } + + @Test + fun testPatchEntryAmountNotRepeat() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 2 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(amount = -1700f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "Bike", -1700f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity[id] + assertEquals(-1700f, entry.amount) + } + } + } + + @Test + fun testPatchEntryAmountRepeatNew() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 3 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(amount = -60f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "new Phone", -60f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity[id] + assertEquals(-60f, entry.amount) + } + } + } + + @Test + fun testPatchEntryAmountRepeatOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 1 } + val newId = transaction { EntryEntity.all().last().id.value + 1 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(amount = 3700f) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newId, "Monthly Job Pay", 3700f, true, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val oldEntry = EntryEntity[id] + val newEntry = EntryEntity[newId] + assertNotNull(oldEntry.ended) + assertEquals(newEntry.id, oldEntry.child) + assertNull(newEntry.ended) + assertNull(newEntry.child) + } + } + } + + @Test + fun testPatchEntryRepeatToFalseNew() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 3 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(repeat = false) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(id, "new Phone", -50f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val entry = EntryEntity[id] + assert(!entry.repeat) + } + } + } + + @Test + fun testPatchEntryRepeatToFalseOld() = customTestApplicationWithLogin { client -> + val id = transaction { EntryEntity.all().first().id.value + 1 } + val newId = transaction { EntryEntity.all().last().id.value + 1 } + + client.sendAuthenticatedRequestWithBody( + HttpMethod.Patch, "/entries/$id", + Entry.Patch(repeat = false) + ) { response -> + assertEquals(HttpStatusCode.OK, response.status) + val responseBody: APIResponse = response.body() + val shouldResponse = wrapSuccess(Entry(newId, "Monthly Job Pay", 3500f, false, null)) + assertEquals(shouldResponse, responseBody) + + transaction { + val oldEntry = EntryEntity[id] + val newEntry = EntryEntity[newId] + assertNotNull(oldEntry.ended) + assertEquals(newEntry.id, oldEntry.child) + assertNull(newEntry.ended) + assertNull(newEntry.child) + } + } + } +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestHelpers.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestHelpers.kt similarity index 56% rename from budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestHelpers.kt rename to budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestHelpers.kt index 9c7b8458..fd1fd8ae 100644 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestHelpers.kt +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestHelpers.kt @@ -1,32 +1,40 @@ -package de.hsfl.budgetBinder.server +package de.hsfl.budgetBinder.server.utils import de.hsfl.budgetBinder.common.APIResponse import de.hsfl.budgetBinder.common.ErrorModel -import de.hsfl.budgetBinder.server.config.getServerConfig +import de.hsfl.budgetBinder.server.config.Config +import de.hsfl.budgetBinder.server.config.ConfigIntermediate +import de.hsfl.budgetBinder.server.mainModule import io.ktor.client.* import io.ktor.serialization.kotlinx.json.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.server.testing.* +import kotlinx.serialization.json.Json import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun customTestApplication(block: suspend ApplicationTestBuilder.(client: HttpClient) -> Unit) { testApplication { application { - val configString = """ - dataBase: - dbType: SQLITE - sqlitePath: file:test?mode=memory&cache=shared - jwt: - accessSecret: testSecret - refreshSecret: testSecret2 - accessMinutes: 10 - """.trimIndent() - mainModule(getServerConfig(configString = configString)) + val intermediate = ConfigIntermediate( + database = ConfigIntermediate.Database( + dbType = Config.DBType.SQLITE, + sqlitePath = "file:test?mode=memory&cache=shared" + ), + jwt = ConfigIntermediate.JWT( + accessSecret = "testSecret", + refreshSecret = "testSecret2", + accessMinutes = 10 + ) + ) + mainModule(Config.createFromIntermediate(intermediate)) } val client = createClient { install(ContentNegotiation) { - json() + json(Json { + encodeDefaults = true + prettyPrint = true + }) } } block(client) @@ -35,7 +43,7 @@ fun customTestApplication(block: suspend ApplicationTestBuilder.(client: HttpCli fun customTestApplicationWithLogin(block: suspend ApplicationTestBuilder.(client: HttpClient) -> Unit) = customTestApplication { client -> - loginUser(client) + client.loginUser() block(client) } diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestModels.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestModels.kt new file mode 100644 index 00000000..74a13eda --- /dev/null +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestModels.kt @@ -0,0 +1,137 @@ +package de.hsfl.budgetBinder.server.utils + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.User +import io.ktor.client.plugins.cookies.* +import io.ktor.http.* +import io.ktor.util.* +import io.ktor.util.date.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.math.min + +object TestUser { + const val email = "test@test.com" + const val password = "test-test" + const val firstName = "test" + const val surName = "Test" + + val userIn = User.In(firstName, surName, email, password) + + fun getTestUser(id: Int): User { + return User(id, firstName, surName, email) + } + + var accessToken: String? = null +} + +object TestCategories { + + const val color = "111111" + val image = Category.Image.DEFAULT + +} + +class CustomCookieStorage : CookiesStorage { + private val container: MutableList = mutableListOf() + private var oldestCookie: Long = 0L + private val mutex = Mutex() + + override suspend fun get(requestUrl: Url): List = mutex.withLock { + val date = GMTDate() + if (date.timestamp >= oldestCookie) cleanup(date.timestamp) + + return@withLock container.filter { isCookieForUrl(it, requestUrl) } + } + + override suspend fun addCookie(requestUrl: Url, cookie: Cookie): Unit = mutex.withLock { + with(cookie) { + if (name.isBlank()) return@withLock + } + + val newCookie = cookie.fillDefaults(requestUrl) + + container.removeAll { areSameCookies(it, newCookie) } + container.add(newCookie) + + cookie.expires?.timestamp?.let { expires -> + if (oldestCookie > expires) { + oldestCookie = expires + } + } + cleanup(GMTDate().timestamp) + } + + private fun areSameCookies(cookie: Cookie, other: Cookie): Boolean { + val domain = cookie.domain?.toLowerCasePreservingASCIIRules()?.trimStart('.') + ?: error("Domain field should have the default value") + + val otherDomain = other.domain?.toLowerCasePreservingASCIIRules()?.trimStart('.') + ?: error("Domain field should have the default value") + + val path = cookie.path?.let { + if (it.endsWith('/')) it else "$it/" + } ?: error("Path field should have the default value") + + val otherPath = other.path?.let { + if (it.endsWith('/')) it else "$it/" + } ?: error("Path field should have the default value") + + return domain == otherDomain && path == otherPath && cookie.name == other.name + } + + private fun isCookieForUrl(cookie: Cookie, requestUrl: Url): Boolean { + val domain = cookie.domain?.toLowerCasePreservingASCIIRules()?.trimStart('.') + ?: error("Domain field should have the default value") + + val path = cookie.path?.let { + if (it.endsWith('/')) it else "$it/" + } ?: error("Path field should have the default value") + + val host = requestUrl.host.toLowerCasePreservingASCIIRules() + val requestPath = let { + val pathInRequest = requestUrl.encodedPath + if (pathInRequest.endsWith('/')) pathInRequest else "$pathInRequest/" + } + + if (host != domain && (hostIsIp(host) || !host.endsWith(".$domain"))) { + return false + } + + if (path != "/" && + requestPath != path && + !requestPath.startsWith(path) + ) return false + + return !(cookie.secure && !requestUrl.protocol.isSecure()) + } + + private fun Cookie.fillDefaults(requestUrl: Url): Cookie { + var result = this + + if (result.path?.startsWith("/") != true) { + result = result.copy(path = requestUrl.encodedPath) + } + + if (result.domain.isNullOrBlank()) { + result = result.copy(domain = requestUrl.host) + } + + return result + } + + private fun cleanup(timestamp: Long) { + container.removeAll { cookie -> + val expires = cookie.expires?.timestamp ?: return@removeAll false + expires < timestamp + } + + val newOldest = container.fold(Long.MAX_VALUE) { acc, cookie -> + cookie.expires?.timestamp?.let { min(acc, it) } ?: acc + } + + oldestCookie = newOldest + } + + override fun close() {} +} diff --git a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestRequests.kt b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestRequests.kt similarity index 79% rename from budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestRequests.kt rename to budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestRequests.kt index 170ca677..07705974 100644 --- a/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/TestRequests.kt +++ b/budget-binder-server/src/test/kotlin/de/hsfl/budgetBinder/server/utils/TestRequests.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.server +package de.hsfl.budgetBinder.server.utils import de.hsfl.budgetBinder.common.APIResponse import de.hsfl.budgetBinder.common.AuthToken @@ -13,8 +13,8 @@ import org.jetbrains.exposed.sql.transactions.transaction import kotlin.test.assertEquals import kotlin.test.assertNotNull -suspend fun registerUser(client: HttpClient) { - val response = client.post("/register") { +suspend fun HttpClient.registerUser() { + val response = this.post("/register") { contentType(ContentType.Application.Json) setBody(TestUser.userIn) } @@ -36,8 +36,8 @@ suspend fun registerUser(client: HttpClient) { assertEquals(shouldResponse, responseBody) } -suspend fun loginUser(client: HttpClient, block: (response: HttpResponse) -> Unit = {}) { - val response = client.post("/login") { +suspend fun HttpClient.loginUser(block: (response: HttpResponse) -> Unit = {}) { + val response = this.post("/login") { contentType(ContentType.Application.FormUrlEncoded) setBody( listOf( @@ -54,29 +54,27 @@ suspend fun loginUser(client: HttpClient, block: (response: HttpResponse) -> Uni block(response) } -suspend inline fun sendAuthenticatedRequest( - client: HttpClient, +suspend inline fun HttpClient.sendAuthenticatedRequest( sendMethod: HttpMethod, path: String, block: (response: HttpResponse) -> Unit ) { block( - client.request(path) { + this.request(path) { method = sendMethod header(HttpHeaders.Authorization, "Bearer ${TestUser.accessToken ?: ""}") } ) } -suspend inline fun sendAuthenticatedRequestWithBody( - client: HttpClient, +suspend inline fun HttpClient.sendAuthenticatedRequestWithBody( sendMethod: HttpMethod, path: String, body: T, block: (response: HttpResponse) -> Unit ) { block( - client.request(path) { + this.request(path) { method = sendMethod header(HttpHeaders.Authorization, "Bearer ${TestUser.accessToken ?: ""}") header(HttpHeaders.ContentType, ContentType.Application.Json) @@ -85,8 +83,8 @@ suspend inline fun sendAuthenticatedRequestWithBody( ) } -suspend fun checkMeSuccess(client: HttpClient) { - sendAuthenticatedRequest(client, HttpMethod.Get, "/me") { response -> +suspend fun HttpClient.checkMeSuccess() { + this.sendAuthenticatedRequest(HttpMethod.Get, "/me") { response -> assertEquals(HttpStatusCode.OK, response.status) val responseBody: APIResponse = response.body() @@ -97,8 +95,8 @@ suspend fun checkMeSuccess(client: HttpClient) { } } -suspend fun checkMeFailure(client: HttpClient) { - sendAuthenticatedRequest(client, HttpMethod.Get, "/me") { response -> +suspend fun HttpClient.checkMeFailure() { + this.sendAuthenticatedRequest(HttpMethod.Get, "/me") { response -> assertEquals(HttpStatusCode.Unauthorized, response.status) val responseBody: APIResponse = response.body() val shouldResponse: APIResponse = wrapFailure("Your accessToken is absent or does not match.", 401) diff --git a/build.gradle.kts b/build.gradle.kts index 95c1c864..a4819cf5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,4 +23,4 @@ buildscript { // Keep 'com.android.tools.lint:lint' @30.0.3 classpath("com.android.tools.lint:lint:30.0.3") } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 57128a59..89ce27f9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ rootProject.name = "budget-binder" include(":budget-binder-common") include(":budget-binder-multiplatform-app") -include(":budget-binder-server") \ No newline at end of file +include(":budget-binder-server")