diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/Client.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/Client.kt index cbbed21..549c3e2 100644 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/Client.kt +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/Client.kt @@ -12,12 +12,14 @@ import io.ktor.http.HeadersBuilder import io.ktor.http.parameters import io.ktor.serialization.kotlinx.json.json import io.ktor.utils.io.ByteReadChannel -import io.wellmate.api.client.auth.Email -import io.wellmate.api.client.app.MealFields -import io.wellmate.api.client.app.Message import io.wellmate.api.client.auth.EmailPassword import io.wellmate.api.client.auth.OAuthToken import io.wellmate.api.client.auth.Token +import io.wellmate.api.client.entry.MealFields +import io.wellmate.api.client.entry.MealFieldsClient +import io.wellmate.api.client.entry.TimerFieldsClient +import io.wellmate.api.client.generics.Email +import io.wellmate.api.client.generics.Message import io.wellmate.api.client.userData.PotentialUser import io.wellmate.api.client.userData.PotentialUserFields import io.wellmate.api.client.userData.UserInfo @@ -129,10 +131,12 @@ object Client { headers: HeadersBuilder.() -> Unit = { } ): ResponseWrapper { - return endpoint.submitForm(formParameters = { parameters { - append("username", username) - append("password", password) - }}) { + return endpoint.submitForm(formParameters = { + parameters { + append("username", username) + append("password", password) + } + }) { io.ktor.http.headers { headers() append("sec-ch-ua-model", secChUaModel) @@ -252,7 +256,10 @@ object Client { private const val URL = "${User.URL}/potential" private val endpoint = Endpoint(client = client, url = URL) - suspend fun post(body: PotentialUserFields, headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { + suspend fun post( + body: PotentialUserFields, + headers: HeadersBuilder.() -> Unit = { } + ): ResponseWrapper { return endpoint.post(body = body) { headers() } } } @@ -262,7 +269,7 @@ object Client { private const val URL = "${Api.URL}/entry" private val endpoint = Endpoint(client = client, url = URL) - suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { + suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { return endpoint.get { headers() } } @@ -271,13 +278,13 @@ object Client { private val endpoint = Endpoint(client = client, url = URL) suspend fun post( - body: Any, + body: MealFieldsClient, headers: HeadersBuilder.() -> Unit = { } - ): ResponseWrapper { + ): ResponseWrapper { return endpoint.post(body = body) { headers() } } - suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { + suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { return endpoint.get { headers() } } @@ -297,13 +304,13 @@ object Client { private val endpoint = Endpoint(client = client, url = URL) suspend fun post( - body: Any, + body: TimerFieldsClient, headers: HeadersBuilder.() -> Unit = { } - ): ResponseWrapper { + ): ResponseWrapper { return endpoint.post(body = body) { headers() } } - suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { + suspend fun get(headers: HeadersBuilder.() -> Unit = { }): ResponseWrapper { return endpoint.get { headers() } } diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/Endpoint.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/Endpoint.kt index 4f05d75..a5bfe38 100644 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/Endpoint.kt +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/Endpoint.kt @@ -12,7 +12,6 @@ import io.ktor.client.statement.HttpResponse import io.ktor.http.ContentType import io.ktor.http.HeadersBuilder import io.ktor.http.HttpStatusCode -import io.ktor.http.Parameters import io.ktor.http.ParametersBuilder import io.ktor.http.contentType import io.ktor.http.isSuccess diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/app/IngredientFields.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/app/IngredientFields.kt deleted file mode 100644 index 6906007..0000000 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/app/IngredientFields.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.wellmate.api.client.app - -import kotlinx.serialization.Serializable - - -@Serializable -class IngredientFields( - val name: String, - val amount: Float, - - val kilocalories: Int, - val proteins: Float, - val carbohydrates: Float, - val fats: Float, - val sugars: Float, -) \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/app/MealFields.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/app/MealFields.kt deleted file mode 100644 index fe83be2..0000000 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/app/MealFields.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.wellmate.api.client.app - -import kotlinx.serialization.Serializable - -@Serializable -class MealFields( - val name: String, - val ingredients: List, -) \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Entry.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Entry.kt new file mode 100644 index 0000000..aba02cd --- /dev/null +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Entry.kt @@ -0,0 +1,37 @@ +package io.wellmate.api.client.entry + +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +interface EntryBaseFieldsClient { + val timestamp: LocalDateTime +} + + +interface EntryBase : EntryBaseFieldsClient { + val id: Int + val userId: Int + + val added: LocalDateTime +} + +@Serializable +enum class EntryType { + @SerialName("meal") + MEAL, + + @SerialName("timer") + TIMER, +} + +@Serializable +data class Entry( + val id: Int, + @Serializable(with = LocalDateTimeIso8601Serializer::class) val added: LocalDateTime, + @SerialName("user_id") val userId: Int, + @Serializable(with = LocalDateTimeIso8601Serializer::class) val timestamp: LocalDateTime, + val type: EntryType, +) \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Ingredient.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Ingredient.kt new file mode 100644 index 0000000..63b850a --- /dev/null +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Ingredient.kt @@ -0,0 +1,31 @@ +package io.wellmate.api.client.entry + +import kotlinx.serialization.Serializable + + +@Serializable +data class IngredientFields( + val name: String, + + val amount: Float, + + val kilocalories: Int, + val proteins: Float, + val carbohydrates: Float, + val fats: Float, + val sugars: Float, +) + +@Serializable +data class Ingredient( + val id: Int, + + val name: String, + val amount: Float, + + val kilocalories: Int, + val proteins: Float, + val carbohydrates: Float, + val fats: Float, + val sugars: Float, +) \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Meal.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Meal.kt new file mode 100644 index 0000000..3604eb3 --- /dev/null +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Meal.kt @@ -0,0 +1,37 @@ +package io.wellmate.api.client.entry + +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +interface MealInterface { + val name: String + val ingredients: List +} + +@Serializable +data class MealFields( + override val name: String, + override val ingredients: List, +) : MealInterface + +@Serializable +data class MealFieldsClient( + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val timestamp: LocalDateTime, + + override val name: String, + override val ingredients: List, +) : EntryBaseFieldsClient, MealInterface + +@Serializable +data class Meal( + override val id: Int, + @SerialName("user_id") override val userId: Int, + + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val added: LocalDateTime, + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val timestamp: LocalDateTime, + + override val name: String, + override val ingredients: List, +) : EntryBase, MealInterface \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Timer.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Timer.kt new file mode 100644 index 0000000..57196f9 --- /dev/null +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/entry/Timer.kt @@ -0,0 +1,29 @@ +package io.wellmate.api.client.entry + +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +interface TimerInterface { + val duration: Int +} + +@Serializable +data class TimerFieldsClient( + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val timestamp: LocalDateTime, + + override val duration: Int, +) : EntryBaseFieldsClient, TimerInterface + + +@Serializable +data class Timer( + override val id: Int, + @SerialName("user_id") override val userId: Int, + + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val timestamp: LocalDateTime, + @Serializable(with = LocalDateTimeIso8601Serializer::class) override val added: LocalDateTime, + + override val duration: Int, +) : EntryBase, TimerInterface diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/auth/Email.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/generics/Email.kt similarity index 70% rename from library/src/commonMain/kotlin/io/wellmate/api/client/auth/Email.kt rename to library/src/commonMain/kotlin/io/wellmate/api/client/generics/Email.kt index 712738f..8817d0e 100644 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/auth/Email.kt +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/generics/Email.kt @@ -1,4 +1,4 @@ -package io.wellmate.api.client.auth +package io.wellmate.api.client.generics import kotlinx.serialization.Serializable diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/app/Message.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/generics/Message.kt similarity index 70% rename from library/src/commonMain/kotlin/io/wellmate/api/client/app/Message.kt rename to library/src/commonMain/kotlin/io/wellmate/api/client/generics/Message.kt index 06082ce..2295487 100644 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/app/Message.kt +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/generics/Message.kt @@ -1,4 +1,4 @@ -package io.wellmate.api.client.app +package io.wellmate.api.client.generics import kotlinx.serialization.Serializable diff --git a/library/src/commonMain/kotlin/io/wellmate/api/client/userData/UserInfo.kt b/library/src/commonMain/kotlin/io/wellmate/api/client/userData/UserInfo.kt index 4a521a9..b0ee0a2 100644 --- a/library/src/commonMain/kotlin/io/wellmate/api/client/userData/UserInfo.kt +++ b/library/src/commonMain/kotlin/io/wellmate/api/client/userData/UserInfo.kt @@ -1,14 +1,17 @@ package io.wellmate.api.client.userData import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer @Serializable enum class Sex { - @SerialName("m") M, - @SerialName("f") F, + @SerialName("m") + M, + + @SerialName("f") + F, } @Serializable diff --git a/library/src/commonTest/kotlin/ApiTest.kt b/library/src/commonTest/kotlin/ApiTest.kt index 5a8ec43..a2eec88 100644 --- a/library/src/commonTest/kotlin/ApiTest.kt +++ b/library/src/commonTest/kotlin/ApiTest.kt @@ -3,47 +3,28 @@ package io.wellmate.api.client import io.ktor.http.HttpHeaders import io.ktor.http.isSuccess import io.wellmate.api.client.auth.EmailPassword -import kotlinx.coroutines.cancel +import io.wellmate.api.client.auth.Token +import io.wellmate.api.client.userData.Me import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest +import kotlin.properties.Delegates +import kotlin.random.Random import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -import kotlin.random.Random class ApiTest { private val testDispatcher = StandardTestDispatcher() - @AfterTest - fun tearDown() { - testDispatcher.cancel() - } - - @Test - fun `api-user-me get fails with no headers`() = runTest(testDispatcher) { - val endpoint = Client.Api.User.Me - assertFalse( - endpoint.get { }.status.isSuccess(), - "/api/user/me:get should not be successful when no header is provided" - ) - } + private lateinit var token: Token + private lateinit var me: Me - @Test - fun `api-user post fails with malformed body due to validation`() = runTest(testDispatcher) { - val endpoint = Client.Api.User - assertFailsWith( - IllegalArgumentException::class, - "/api/user:post should not be successful when body is empty" - ) { - endpoint.post(body = EmailPassword("", "")) - } - } - - @Test - fun `api-user post+delete succeeds with proper body + with me get`() = runTest(testDispatcher) { + @BeforeTest + fun `set up the user for testing`() = runTest(testDispatcher) { val endpoint = Client.Api.User // Fake credentials generated for test purposes only @@ -51,10 +32,10 @@ class ApiTest { email = "tester${Random.nextInt()}@wellmate.test", password = "nFbz\$Qc%!!@PgLl@5\\$^pH47XW9vl2D!SEp@b", ) - val resultCreate = endpoint.post(body = emailPassword) assertTrue(resultCreate.status.isSuccess()) - val token = resultCreate.body() + + token = resultCreate.body() val resultMe = Client.Api.User.Me.get { append( @@ -63,8 +44,11 @@ class ApiTest { ) } assertTrue(resultMe.status.isSuccess()) - val me = resultMe.body() + me = resultMe.body() + } + @AfterTest + fun `delete the user used for testing`() = runTest(testDispatcher) { val deleteEndpoint = Client.Api.User.UserId(userId = me.id) val resultDelete = deleteEndpoint.delete { append( @@ -74,4 +58,28 @@ class ApiTest { } assertTrue(resultDelete.status.isSuccess()) } + + @Test + fun `api-user-me get fails with no headers`() = runTest(testDispatcher) { + val endpoint = Client.Api.User.Me + assertFalse( + endpoint.get { }.status.isSuccess(), + "/api/user/me:get should not be successful when no header is provided" + ) + } + + @Test + fun `api-user post fails with malformed body due to validation`() = runTest(testDispatcher) { + val endpoint = Client.Api.User + assertFailsWith( + IllegalArgumentException::class, + "/api/user:post should not be successful when body is empty" + ) { + endpoint.post(body = EmailPassword("", "")) + } + } + + @Test + fun `api-user post+delete succeeds with proper body + with me get`() = runTest(testDispatcher) { + } } \ No newline at end of file