auth tests fix
This commit is contained in:
parent
fba10fa426
commit
14e2b420e2
1
api-core/.gitignore
vendored
Normal file
1
api-core/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
52
api-core/build.gradle.kts
Normal file
52
api-core/build.gradle.kts
Normal file
@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.api.core"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 33
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.retrofit2.kotlinx.serialization.converter)
|
||||
implementation(libs.retrofit2)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.logging.interceptor)
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockwebserver)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
}
|
0
api-core/consumer-rules.pro
Normal file
0
api-core/consumer-rules.pro
Normal file
21
api-core/proguard-rules.pro
vendored
Normal file
21
api-core/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,24 @@
|
||||
package com.example.api.core
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ApiRepositoryTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.api.core.test", appContext.packageName)
|
||||
}
|
||||
}
|
4
api-core/src/main/AndroidManifest.xml
Normal file
4
api-core/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,27 @@
|
||||
package com.example.api.core.data.core
|
||||
|
||||
import com.example.api.core.domain.ApiRepository
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
|
||||
object ApiFactory {
|
||||
|
||||
fun provideRepository(dataStoreRepository: DataStoreRepository): ApiRepository {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.matule.ru/api/")
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}).build())
|
||||
.build()
|
||||
|
||||
|
||||
return BaseApiRepository(retrofit.create<ApiService>(), dataStoreRepository)
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.example.api.core.data.core
|
||||
|
||||
import com.example.api.core.data.dto.AuthUserResponseDto
|
||||
import com.example.api.core.data.dto.RegisterUserDto
|
||||
import com.example.api.core.data.dto.SalesAndActionsDto
|
||||
import com.example.api.core.data.dto.SignInUserDto
|
||||
import com.example.api.core.data.dto.UserResponseDto
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
internal interface ApiService {
|
||||
|
||||
@POST("collections/users/records")
|
||||
suspend fun register(@Body registerUserDto: RegisterUserDto): UserResponseDto
|
||||
|
||||
@POST("collections/users/auth-with-password")
|
||||
suspend fun auth(@Body signInUserDto: SignInUserDto): AuthUserResponseDto
|
||||
|
||||
@GET("collections/project/records")
|
||||
suspend fun salesAndProjects(): SalesAndActionsDto
|
||||
|
||||
// @GET("collections/products/records")
|
||||
// suspend fun products():
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.example.api.core.data.core
|
||||
|
||||
import com.example.api.core.domain.ApiRepository
|
||||
import com.example.api.core.domain.AuthUserResponse
|
||||
import com.example.api.core.domain.FetchResult
|
||||
import com.example.api.core.domain.RegisterUser
|
||||
import retrofit2.HttpException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
internal class BaseApiRepository(
|
||||
private val service: ApiService,
|
||||
private val dataStoreRepository: DataStoreRepository
|
||||
) : RemoteMapper(), ApiRepository {
|
||||
|
||||
override suspend fun auth(registerUser: RegisterUser): FetchResult<AuthUserResponse> {
|
||||
return try {
|
||||
service.register(registerUser.toRegisterUserDto())
|
||||
val response = service.auth(registerUser.toSignInUserDto())
|
||||
dataStoreRepository.saveToken(response.token)
|
||||
|
||||
FetchResult.Success(response.toUserResponse())
|
||||
} catch (e: UnknownHostException) {
|
||||
FetchResult.Error(null, "no internet connection")
|
||||
} catch (e: HttpException) {
|
||||
FetchResult.Error(null, e.message())
|
||||
} catch (e: Exception) {
|
||||
FetchResult.Error(null, e.message!!)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.example.api.core.data.core
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
interface DataStoreRepository {
|
||||
|
||||
suspend fun saveToken(token: String)
|
||||
|
||||
fun token(): Flow<String>
|
||||
|
||||
class Base(private val preferences: DataStore<Preferences>) : DataStoreRepository {
|
||||
override suspend fun saveToken(token: String) {
|
||||
preferences.edit { settings ->
|
||||
settings[TOKEN_KEY] = token
|
||||
}
|
||||
}
|
||||
|
||||
override fun token(): Flow<String> {
|
||||
return preferences.data.map { preferences ->
|
||||
preferences[TOKEN_KEY] ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val TOKEN_KEY = stringPreferencesKey("token_key")
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.example.api.core.data.core
|
||||
|
||||
import com.example.api.core.data.dto.AuthUserResponseDto
|
||||
import com.example.api.core.data.dto.RegisterUserDto
|
||||
import com.example.api.core.data.dto.SignInUserDto
|
||||
import com.example.api.core.data.dto.SignInUserResponseDto
|
||||
import com.example.api.core.data.dto.UserResponseDto
|
||||
import com.example.api.core.domain.AuthUserResponse
|
||||
import com.example.api.core.domain.RegisterUser
|
||||
import com.example.api.core.domain.SignInUserResponse
|
||||
import com.example.api.core.domain.UserResponse
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
internal abstract class RemoteMapper {
|
||||
protected fun RegisterUser.toRegisterUserDto(): RegisterUserDto {
|
||||
return RegisterUserDto(
|
||||
email = email,
|
||||
password = password,
|
||||
passwordConfirm = passwordConfirm
|
||||
)
|
||||
}
|
||||
|
||||
protected fun RegisterUser.toSignInUserDto(): SignInUserDto {
|
||||
return SignInUserDto(
|
||||
identity = email,
|
||||
password = password
|
||||
)
|
||||
}
|
||||
|
||||
protected fun AuthUserResponseDto.toUserResponse(): AuthUserResponse {
|
||||
return AuthUserResponse(
|
||||
record = record.toUserResponse(),
|
||||
token = token
|
||||
)
|
||||
}
|
||||
|
||||
protected fun SignInUserResponseDto.toUserResponse(): SignInUserResponse {
|
||||
return SignInUserResponse(
|
||||
collectionId = collectionId,
|
||||
collectionName = collectionName,
|
||||
created = created,
|
||||
dateBirthday = dateBirthday,
|
||||
emailVisibility = emailVisibility,
|
||||
email = email,
|
||||
firstname = firstname,
|
||||
gender = gender,
|
||||
id = id,
|
||||
lastname = lastname,
|
||||
secondname = secondname,
|
||||
updated = updated,
|
||||
verified = verified
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class AuthUserResponseDto(
|
||||
val record: SignInUserResponseDto,
|
||||
val token: String
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class CatalogResponseDto(
|
||||
val items: List<SaleItemDto>
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
//data class ProductDto()
|
@ -0,0 +1,10 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class RegisterUserDto(
|
||||
val email: String,
|
||||
val password: String,
|
||||
val passwordConfirm: String
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class SaleItemDto(
|
||||
val id: String,
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val updated: String,
|
||||
val title: String,
|
||||
val dateStart: String,
|
||||
val dateEnd: String,
|
||||
val gender: String,
|
||||
@SerialName("description_source") val descriptionSource: String,
|
||||
val category: String,
|
||||
val image: String,
|
||||
@SerialName("user_id") val userId: String
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class SalesAndActionsDto(
|
||||
val page: Int,
|
||||
val perPage: Int,
|
||||
val totalPage: Int,
|
||||
val totalItems: Int,
|
||||
val items: List<SaleItemDto>
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class SignInUserDto(
|
||||
val identity: String,
|
||||
val password: String
|
||||
)
|
@ -0,0 +1,20 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SignInUserResponseDto(
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val dateBirthday: String,
|
||||
val email: String,
|
||||
val emailVisibility: Boolean,
|
||||
val firstname: String,
|
||||
val gender: String,
|
||||
val id: String,
|
||||
val lastname: String,
|
||||
val secondname: String,
|
||||
val updated: String,
|
||||
val verified: Boolean
|
||||
)
|
@ -0,0 +1,19 @@
|
||||
package com.example.api.core.data.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class UserResponseDto(
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val dateBirthday: String,
|
||||
val emailVisibility: Boolean,
|
||||
val firstname: String,
|
||||
val gender: String,
|
||||
val id: String,
|
||||
val lastname: String,
|
||||
val secondname: String,
|
||||
val updated: String,
|
||||
val verified: Boolean
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
interface ApiRepository {
|
||||
|
||||
suspend fun auth(registerUser: RegisterUser): FetchResult<AuthUserResponse>
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class AuthUserResponse(
|
||||
val record: SignInUserResponse,
|
||||
val token: String
|
||||
)
|
@ -0,0 +1,5 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class CatalogResponse(
|
||||
val items: List<SaleItem>
|
||||
)
|
@ -0,0 +1,31 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
interface FetchResult<D> {
|
||||
|
||||
fun <Ui> map(mapper: Mapper<D, Ui>): Ui
|
||||
|
||||
interface Mapper<D, Ui> {
|
||||
|
||||
fun mapSuccess(data: D): Ui
|
||||
|
||||
fun mapError(data: D?, message: String): Ui
|
||||
}
|
||||
|
||||
class Success<D>(private val data: D) : FetchResult<D> {
|
||||
override fun <Ui> map(mapper: Mapper<D, Ui>): Ui {
|
||||
return mapper.mapSuccess(data)
|
||||
}
|
||||
}
|
||||
|
||||
class Error<D>(private val data: D?, private val message: String) : FetchResult<D> {
|
||||
override fun <Ui> map(mapper: Mapper<D, Ui>): Ui {
|
||||
return mapper.mapError(data, message)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class RegisterUser(
|
||||
val email: String,
|
||||
val password: String,
|
||||
val passwordConfirm: String
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class SaleItem(
|
||||
val id: String,
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val updated: String,
|
||||
val title: String,
|
||||
val dateStart: String,
|
||||
val dateEnd: String,
|
||||
val gender: String,
|
||||
val descriptionSource: String,
|
||||
val category: String,
|
||||
val image: String,
|
||||
val userId: String
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
internal data class SalesAndActions(
|
||||
val page: Int,
|
||||
val perPage: Int,
|
||||
val totalPage: Int,
|
||||
val totalItems: Int,
|
||||
val items: List<SaleItem>
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class SignInUser(
|
||||
val identity: String,
|
||||
val password: String
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class SignInUserResponse(
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val dateBirthday: String,
|
||||
val emailVisibility: Boolean,
|
||||
val email: String,
|
||||
val firstname: String,
|
||||
val gender: String,
|
||||
val id: String,
|
||||
val lastname: String,
|
||||
val secondname: String,
|
||||
val updated: String,
|
||||
val verified: Boolean
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.api.core.domain
|
||||
|
||||
data class UserResponse(
|
||||
val collectionId: String,
|
||||
val collectionName: String,
|
||||
val created: String,
|
||||
val dateBirthday: String,
|
||||
val emailVisibility: Boolean,
|
||||
val firstname: String,
|
||||
val gender: String,
|
||||
val id: String,
|
||||
val lastname: String,
|
||||
val secondname: String,
|
||||
val updated: String,
|
||||
val verified: Boolean
|
||||
)
|
133
api-core/src/test/java/com/example/api/core/ServiceTest.kt
Normal file
133
api-core/src/test/java/com/example/api/core/ServiceTest.kt
Normal file
@ -0,0 +1,133 @@
|
||||
package com.example.api.core
|
||||
|
||||
import com.example.api.core.data.core.ApiService
|
||||
import com.example.api.core.data.dto.RegisterUserDto
|
||||
import com.example.api.core.data.dto.SignInUserDto
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
class ServiceTest {
|
||||
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
private lateinit var service: ApiService
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockWebServer = MockWebServer()
|
||||
mockWebServer.start()
|
||||
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
service = Retrofit.Builder()
|
||||
.baseUrl(mockWebServer.url("/"))
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}).build())
|
||||
.build().create<ApiService>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_registration() = runBlocking {
|
||||
val response = """
|
||||
{
|
||||
"collectionId": "_pb_users_auth_",
|
||||
"collectionName": "users",
|
||||
"created": "2025-05-27 06:08:13.833Z",
|
||||
"dateBirthday": "",
|
||||
"emailVisibility": false,
|
||||
"firstname": "",
|
||||
"gender": "",
|
||||
"id": "w20q0pj1e2824oz",
|
||||
"lastname": "",
|
||||
"secondname": "",
|
||||
"updated": "2025-05-27 06:08:13.833Z",
|
||||
"verified": false
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(response))
|
||||
|
||||
val responseResult = service.register(
|
||||
RegisterUserDto(
|
||||
email = "email@test.com",
|
||||
password = "12345678",
|
||||
passwordConfirm = "12345678"
|
||||
)
|
||||
)
|
||||
|
||||
val request = mockWebServer.takeRequest()
|
||||
|
||||
assertEquals("w20q0pj1e2824oz", responseResult.id)
|
||||
assertEquals("2025-05-27 06:08:13.833Z", responseResult.created)
|
||||
|
||||
assertEquals("POST", request.method)
|
||||
assertEquals("/collections/users/records", request.path)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sign_in() = runBlocking {
|
||||
val response = """
|
||||
{
|
||||
"record": {
|
||||
"collectionId": "_pb_users_auth_",
|
||||
"collectionName": "users",
|
||||
"created": "2025-05-27 06:08:13.833Z",
|
||||
"dateBirthday": "",
|
||||
"email": "example123@test.ru",
|
||||
"emailVisibility": false,
|
||||
"firstname": "",
|
||||
"gender": "",
|
||||
"id": "w20q0pj1e2824oz",
|
||||
"lastname": "",
|
||||
"secondname": "",
|
||||
"updated": "2025-05-27 06:08:13.833Z",
|
||||
"verified": false
|
||||
},
|
||||
"token": "eyJhbGciOiJIUzIqwwe1NiIsInR5cCI6IkpXVCJg9.eyJjb2xsZWN0aW9uSWheQiOiJfqcGggJfdXNlcnNfYXV0aFasd8iLCJleHAiOjE3NDg5MzA5MjAsImlkIjoidzIwcTBwajFlMjgyNG96IiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.7Sc_NB_CgnwEfXWp0NjsnDT35gGVXzFJwwLUVD730Gw"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(response))
|
||||
|
||||
val responseResult = service.auth(
|
||||
SignInUserDto(
|
||||
identity = "example123@test.ru",
|
||||
password = "12345678"
|
||||
)
|
||||
)
|
||||
|
||||
val request = mockWebServer.takeRequest()
|
||||
|
||||
assertEquals("example123@test.ru", responseResult.record.email)
|
||||
assertEquals(
|
||||
"eyJhbGciOiJIUzIqwwe1NiIsInR5cCI6IkpXVCJg9.eyJjb2xsZWN0aW9uSWheQiOiJfqcGggJfdXNlcnNfYXV0aFasd8iLCJleHAiOjE3NDg5MzA5MjAsImlkIjoidzIwcTBwajFlMjgyNG96IiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.7Sc_NB_CgnwEfXWp0NjsnDT35gGVXzFJwwLUVD730Gw",
|
||||
responseResult.token
|
||||
)
|
||||
|
||||
assertEquals("POST", request.method)
|
||||
assertEquals("/collections/users/auth-with-password", request.path)
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.dagger.hilt.android)
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -40,6 +42,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
kapt(libs.hilt.android.compiler)
|
||||
implementation(libs.hilt.android)
|
||||
implementation(libs.hilt.navigation.compose)
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
@ -56,4 +62,6 @@ dependencies {
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
|
||||
implementation(project(":api-core"))
|
||||
}
|
@ -2,7 +2,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
12
app/src/main/java/com/example/api/App.kt
Normal file
12
app/src/main/java/com/example/api/App.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.example.api
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
@HiltAndroidApp
|
||||
class App : Application()
|
51
app/src/main/java/com/example/api/CoreModule.kt
Normal file
51
app/src/main/java/com/example/api/CoreModule.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package com.example.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.example.api.core.data.core.ApiFactory
|
||||
import com.example.api.core.data.core.DataStoreRepository
|
||||
import com.example.api.core.domain.ApiRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class CoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideContent(@ApplicationContext context: Context): Context = context
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDataStore(context: Context): DataStore<Preferences> {
|
||||
return context.dataStore
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDataStoreRepository(preferences: DataStore<Preferences>): DataStoreRepository {
|
||||
return DataStoreRepository.Base(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRepository(dataStoreRepository: DataStoreRepository): ApiRepository {
|
||||
return ApiFactory.provideRepository(dataStoreRepository)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val Context.dataStore by preferencesDataStore("settings")
|
||||
}
|
||||
}
|
18
app/src/main/java/com/example/api/FetchResultMapper.kt
Normal file
18
app/src/main/java/com/example/api/FetchResultMapper.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package com.example.api
|
||||
|
||||
import com.example.api.core.domain.FetchResult
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
class FetchResultMapper<T> : FetchResult.Mapper<T, FetchResultUiState<T>> {
|
||||
override fun mapSuccess(data: T): FetchResultUiState<T> {
|
||||
return FetchResultUiState.Success(data)
|
||||
}
|
||||
|
||||
override fun mapError(data: T?, message: String): FetchResultUiState<T> {
|
||||
return FetchResultUiState.Error(data, message)
|
||||
}
|
||||
}
|
62
app/src/main/java/com/example/api/FetchResultUiState.kt
Normal file
62
app/src/main/java/com/example/api/FetchResultUiState.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package com.example.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
interface FetchResultUiState<T> {
|
||||
|
||||
@Composable
|
||||
fun Show(
|
||||
onSuccess: @Composable (T) -> Unit,
|
||||
onError: @Composable (T?, String) -> Unit,
|
||||
onLoading: @Composable (T?) -> Unit
|
||||
)
|
||||
|
||||
class Success<T>(val data: T) : FetchResultUiState<T> {
|
||||
@Composable
|
||||
override fun Show(
|
||||
onSuccess: @Composable (T) -> Unit,
|
||||
onError: @Composable (T?, String) -> Unit,
|
||||
onLoading: @Composable (T?) -> Unit
|
||||
) {
|
||||
onSuccess(data)
|
||||
}
|
||||
}
|
||||
|
||||
class Error<T>(private val data: T?, private val message: String) : FetchResultUiState<T> {
|
||||
@Composable
|
||||
override fun Show(
|
||||
onSuccess: @Composable (T) -> Unit,
|
||||
onError: @Composable (T?, String) -> Unit,
|
||||
onLoading: @Composable (T?) -> Unit
|
||||
) {
|
||||
onError(data, message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Loading<T>(private val data: T? = null) : FetchResultUiState<T> {
|
||||
@Composable
|
||||
override fun Show(
|
||||
onSuccess: @Composable (T) -> Unit,
|
||||
onError: @Composable (T?, String) -> Unit,
|
||||
onLoading: @Composable (T?) -> Unit
|
||||
) {
|
||||
onLoading(data)
|
||||
}
|
||||
}
|
||||
|
||||
class Initial<T> : FetchResultUiState<T> {
|
||||
@Composable
|
||||
override fun Show(
|
||||
onSuccess: @Composable (T) -> Unit,
|
||||
onError: @Composable (T?, String) -> Unit,
|
||||
onLoading: @Composable (T?) -> Unit
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
@ -4,44 +4,18 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.example.api.ui.theme.ApiTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
ApiTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Greeting(
|
||||
name = "Android",
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
}
|
||||
TestScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Hello $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
ApiTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
47
app/src/main/java/com/example/api/MainViewModel.kt
Normal file
47
app/src/main/java/com/example/api/MainViewModel.kt
Normal file
@ -0,0 +1,47 @@
|
||||
package com.example.api
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.api.core.domain.ApiRepository
|
||||
import com.example.api.core.domain.AuthUserResponse
|
||||
import com.example.api.core.domain.RegisterUser
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val apiRepository: ApiRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _loadResultUiState =
|
||||
MutableStateFlow<FetchResultUiState<AuthUserResponse>>(FetchResultUiState.Initial())
|
||||
val loadResultUiState: StateFlow<FetchResultUiState<AuthUserResponse>>
|
||||
get() = _loadResultUiState.asStateFlow()
|
||||
|
||||
fun auth(email: String, password: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val result = apiRepository.auth(
|
||||
RegisterUser(
|
||||
email = email,
|
||||
password = password,
|
||||
passwordConfirm = password
|
||||
)
|
||||
)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_loadResultUiState.value = result.map(FetchResultMapper())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
app/src/main/java/com/example/api/TestScreen.kt
Normal file
41
app/src/main/java/com/example/api/TestScreen.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package com.example.api
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
||||
/**
|
||||
* Автор: Манякин Дмитрий (user5)
|
||||
* Дата создания: 27.05.2025
|
||||
* */
|
||||
|
||||
@Composable
|
||||
fun TestScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: MainViewModel = hiltViewModel()
|
||||
) {
|
||||
val state by viewModel.loadResultUiState.collectAsState()
|
||||
|
||||
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column {
|
||||
Button(onClick = { viewModel.auth("someemael@te3st1.com", "12345678") }) {
|
||||
Text("register")
|
||||
}
|
||||
|
||||
state.Show(
|
||||
onSuccess = { Text(text = it.token) },
|
||||
onError = { _, message -> Text(text = message) },
|
||||
onLoading = { CircularProgressIndicator() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,4 +3,6 @@ plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.dagger.hilt.android) apply false
|
||||
}
|
@ -8,8 +8,30 @@ espressoCore = "3.6.1"
|
||||
lifecycleRuntimeKtx = "2.9.0"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2024.09.00"
|
||||
appcompat = "1.7.0"
|
||||
material = "1.12.0"
|
||||
retrofit = "2.11.0"
|
||||
okhttp3 = "4.12.0"
|
||||
kotlinxSerializationConverter = "1.0.0"
|
||||
kotlinxSerializationJson = "1.8.0"
|
||||
kotlinxCoroutinesTest = "1.10.2"
|
||||
datastorePreferences = "1.1.6"
|
||||
dagger = "2.55"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
|
||||
[libraries]
|
||||
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "dagger" }
|
||||
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "dagger" }
|
||||
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
||||
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
|
||||
mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp3" }
|
||||
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
|
||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||
retrofit2-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinxSerializationConverter" }
|
||||
logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" }
|
||||
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" }
|
||||
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
@ -24,9 +46,14 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
|
||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
|
||||
[plugins]
|
||||
dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "dagger" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
|
||||
|
@ -21,4 +21,4 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "api"
|
||||
include(":app")
|
||||
|
||||
include(":api-core")
|
||||
|
Loading…
x
Reference in New Issue
Block a user