Fix failures in UserRouteTest

This commit is contained in:
William Brawner 2024-03-09 09:00:56 -07:00
parent aa96cbbf3a
commit 4ae19c09c2
4 changed files with 126 additions and 170 deletions

View file

@ -25,9 +25,15 @@ fun Application.userRoutes(
post("/login") { post("/login") {
val request = call.receive<LoginRequest>() val request = call.receive<LoginRequest>()
val user = val user =
userRepository.findAll(nameOrEmail = request.username, password = passwordHasher.hash(request.password)) userRepository.findAll(
nameOrEmail = request.username,
password = passwordHasher.hash(request.password)
)
.firstOrNull() .firstOrNull()
?: userRepository.findAll(nameOrEmail = request.username, password = passwordHasher.hash(request.password)) ?: userRepository.findAll(
nameOrEmail = request.username,
password = passwordHasher.hash(request.password)
)
.firstOrNull() .firstOrNull()
?: run { ?: run {
errorResponse(HttpStatusCode.Unauthorized, "Invalid credentials") errorResponse(HttpStatusCode.Unauthorized, "Invalid credentials")
@ -73,16 +79,18 @@ fun Application.userRoutes(
authenticate(optional = false) { authenticate(optional = false) {
get { get {
val query = call.request.queryParameters["query"] val query = call.request.queryParameters["query"]
val budgetIds = call.request.queryParameters.getAll("budgetId")
if (query != null) { if (query != null) {
if (query.isBlank()) { if (query.isBlank()) {
errorResponse(HttpStatusCode.BadRequest, "query cannot be empty") errorResponse(HttpStatusCode.BadRequest, "query cannot be empty")
} }
call.respond(userRepository.findAll(nameLike = query).map { it.asResponse() }) call.respond(userRepository.findAll(nameLike = query).map { it.asResponse() })
return@get return@get
} else if (budgetIds == null || budgetIds.all { it.isBlank() }) {
errorResponse(HttpStatusCode.BadRequest, "query or budgetId required but absent")
} }
permissionRepository.findAll( permissionRepository.findAll(budgetIds = budgetIds)
budgetIds = call.request.queryParameters.getAll("budgetId") .mapNotNull {
).mapNotNull {
userRepository.findAll(ids = listOf(it.userId)) userRepository.findAll(ids = listOf(it.userId))
.firstOrNull() .firstOrNull()
?.asResponse() ?.asResponse()

View file

@ -4,8 +4,8 @@ import com.wbrawner.twigs.ErrorResponse
import com.wbrawner.twigs.PasswordResetRequest import com.wbrawner.twigs.PasswordResetRequest
import com.wbrawner.twigs.ResetPasswordRequest import com.wbrawner.twigs.ResetPasswordRequest
import com.wbrawner.twigs.model.PasswordResetToken import com.wbrawner.twigs.model.PasswordResetToken
import com.wbrawner.twigs.model.User
import com.wbrawner.twigs.randomString import com.wbrawner.twigs.randomString
import com.wbrawner.twigs.test.helpers.repository.FakeUserRepository.Companion.TEST_USER
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
@ -17,7 +17,7 @@ import java.util.*
class PasswordResetRouteTest : ApiTest() { class PasswordResetRouteTest : ApiTest() {
@Test @Test
fun `reset password with invalid username returns 202`() = apiTest { client -> fun `reset password with invalid username returns 202`() = apiTest { client ->
val request = ResetPasswordRequest(username = "testuser") val request = ResetPasswordRequest(username = "invaliduser")
val response = client.post("/api/resetpassword") { val response = client.post("/api/resetpassword") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -28,14 +28,6 @@ class PasswordResetRouteTest : ApiTest() {
@Test @Test
fun `reset password with valid username returns 202`() = apiTest { client -> fun `reset password with valid username returns 202`() = apiTest { client ->
val users = listOf(
User(
name = "testuser",
email = "test@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"
),
)
users.forEach { userRepository.save(it) }
val request = ResetPasswordRequest(username = "testuser") val request = ResetPasswordRequest(username = "testuser")
val response = client.post("/api/resetpassword") { val response = client.post("/api/resetpassword") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
@ -44,10 +36,10 @@ class PasswordResetRouteTest : ApiTest() {
assertEquals(HttpStatusCode.Accepted, response.status) assertEquals(HttpStatusCode.Accepted, response.status)
assertEquals(1, emailService.emails.size) assertEquals(1, emailService.emails.size)
val email = emailService.emails.first() val email = emailService.emails.first()
assertEquals(users.first().email, email.to) assertEquals(TEST_USER.email, email.to)
assertEquals(1, passwordResetRepository.entities.size) assertEquals(1, passwordResetRepository.entities.size)
val passwordReset = passwordResetRepository.entities.first() val passwordReset = passwordResetRepository.entities.first()
assertEquals(users.first().id, passwordReset.userId) assertEquals(TEST_USER.id, passwordReset.userId)
} }
@Test @Test
@ -77,11 +69,7 @@ class PasswordResetRouteTest : ApiTest() {
@Test @Test
fun `password reset with valid token returns 200`() = apiTest { client -> fun `password reset with valid token returns 200`() = apiTest { client ->
val users = listOf( val token = passwordResetRepository.save(PasswordResetToken(userId = userRepository.findAll("testuser").first().id))
User(name = "testuser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"),
)
users.forEach { userRepository.save(it) }
val token = passwordResetRepository.save(PasswordResetToken(userId = users.first().id))
val request = PasswordResetRequest(token = token.id, password = "newpass") val request = PasswordResetRequest(token = token.id, password = "newpass")
val response = client.post("/api/passwordreset") { val response = client.post("/api/passwordreset") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
@ -89,8 +77,8 @@ class PasswordResetRouteTest : ApiTest() {
} }
assertEquals(HttpStatusCode.NoContent, response.status) assertEquals(HttpStatusCode.NoContent, response.status)
assertEquals( assertEquals(
"\$2a\$10\$bETxbFPja1PyXVLybETxb.E7dYGWCalFjrgd3ofAfKD8MqR0Ukua6", "newpass",
userRepository.entities.first().password userRepository.findAll(TEST_USER.name).first().password
) )
assert(passwordResetRepository.entities.isEmpty()) assert(passwordResetRepository.entities.isEmpty())
} }

View file

@ -3,10 +3,13 @@ package com.wbrawner.twigs.server.api
import com.wbrawner.twigs.* import com.wbrawner.twigs.*
import com.wbrawner.twigs.model.Session import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.model.User import com.wbrawner.twigs.model.User
import com.wbrawner.twigs.test.helpers.repository.FakeUserRepository.Companion.OTHER_USER
import com.wbrawner.twigs.test.helpers.repository.FakeUserRepository.Companion.TEST_USER
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class UserRouteTest : ApiTest() { class UserRouteTest : ApiTest() {
@ -36,11 +39,7 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `login with invalid password returns 401`() = apiTest { client -> fun `login with invalid password returns 401`() = apiTest { client ->
val users = listOf( val request = LoginRequest(TEST_USER.name, "pass")
User(name = "testuser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"),
)
users.forEach { userRepository.save(it) }
val request = LoginRequest("testuser", "pass")
val response = client.post("/api/users/login") { val response = client.post("/api/users/login") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -52,11 +51,7 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `login with empty password returns 401`() = apiTest { client -> fun `login with empty password returns 401`() = apiTest { client ->
val users = listOf( val request = LoginRequest(TEST_USER.name, "")
User(name = "testuser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"),
)
users.forEach { userRepository.save(it) }
val request = LoginRequest("testuser", "")
val response = client.post("/api/users/login") { val response = client.post("/api/users/login") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -68,41 +63,27 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `login with valid username and password returns 200`() = apiTest { client -> fun `login with valid username and password returns 200`() = apiTest { client ->
val users = listOf( val request = LoginRequest(TEST_USER.name, TEST_USER.password)
User(name = "testuser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"),
User(name = "otheruser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb..rhfIeOkP4qil1Drj29LDUhBxVkm6fS"),
)
users.forEach { userRepository.save(it) }
val request = LoginRequest("testuser", "testpassword")
val response = client.post("/api/users/login") { val response = client.post("/api/users/login") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
} }
assertEquals(HttpStatusCode.OK, response.status) assertEquals(HttpStatusCode.OK, response.status)
val session = response.body<SessionResponse>() val session = response.body<SessionResponse>()
assertEquals(users.first().id, session.userId) assertEquals(TEST_USER.id, session.userId)
assert(session.token.isNotBlank()) assert(session.token.isNotBlank())
} }
@Test @Test
fun `login with valid email and password returns 200`() = apiTest { client -> fun `login with valid email and password returns 200`() = apiTest { client ->
val users = listOf( val request = LoginRequest(TEST_USER.email, TEST_USER.password)
User(
name = "testuser",
email = "test@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"
),
User(name = "otheruser", password = "\$2a\$10\$bETxbFPja1PyXVLybETxb..rhfIeOkP4qil1Drj29LDUhBxVkm6fS"),
)
users.forEach { userRepository.save(it) }
val request = LoginRequest("test@example.com", "testpassword")
val response = client.post("/api/users/login") { val response = client.post("/api/users/login") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
} }
assertEquals(HttpStatusCode.OK, response.status) assertEquals(HttpStatusCode.OK, response.status)
val session = response.body<SessionResponse>() val session = response.body<SessionResponse>()
assertEquals(users.first().id, session.userId) assertEquals(TEST_USER.id, session.userId)
assert(session.token.isNotBlank()) assert(session.token.isNotBlank())
} }
@ -156,15 +137,7 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `register with existing username returns 400`() = apiTest { client -> fun `register with existing username returns 400`() = apiTest { client ->
val users = listOf( val request = UserRequest(username = TEST_USER.name, password = "password")
User(
name = "testuser",
email = "test@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"
),
)
users.forEach { userRepository.save(it) }
val request = UserRequest(username = "testuser", password = "password")
val response = client.post("/api/users/register") { val response = client.post("/api/users/register") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -176,15 +149,7 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `register with existing email returns 400`() = apiTest { client -> fun `register with existing email returns 400`() = apiTest { client ->
val users = listOf( val request = UserRequest(username = "testuser2", email = TEST_USER.email, password = "password")
User(
name = "testuser",
email = "test@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2"
),
)
users.forEach { userRepository.save(it) }
val request = UserRequest(username = "testuser2", email = "test@example.com", password = "password")
val response = client.post("/api/users/register") { val response = client.post("/api/users/register") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -196,7 +161,8 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `register with valid username and password returns 200`() = apiTest { client -> fun `register with valid username and password returns 200`() = apiTest { client ->
val request = UserRequest("testuser", "testpassword") val initialUserCount = userRepository.entities.size
val request = UserRequest("newuser", "newpass")
val response = client.post("/api/users/register") { val response = client.post("/api/users/register") {
header("Content-Type", "application/json") header("Content-Type", "application/json")
setBody(request) setBody(request)
@ -206,12 +172,14 @@ class UserRouteTest : ApiTest() {
assert(userResponse.id.isNotBlank()) assert(userResponse.id.isNotBlank())
assertEquals(request.username, userResponse.username) assertEquals(request.username, userResponse.username)
assertEquals("", userResponse.email) assertEquals("", userResponse.email)
assertEquals(1, userRepository.entities.size) assertEquals(initialUserCount + 1, userRepository.entities.size)
val savedUser: User = userRepository.entities.first() val savedUser: User? = userRepository.findAll("newuser").firstOrNull()
assertNotNull(savedUser)
requireNotNull(savedUser)
assertEquals(userResponse.id, savedUser.id) assertEquals(userResponse.id, savedUser.id)
assertEquals(request.username, savedUser.name) assertEquals(request.username, savedUser.name)
assertEquals("", savedUser.email) assertEquals("", savedUser.email)
assertEquals("\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2", savedUser.password) assertEquals("newpass", savedUser.password)
} }
@Test @Test
@ -256,12 +224,7 @@ class UserRouteTest : ApiTest() {
@Test @Test
fun `get users with valid query and matches returns list`() = apiTest { client -> fun `get users with valid query and matches returns list`() = apiTest { client ->
val users = listOf( val session = Session(userId = TEST_USER.id)
User(name = "testuser", password = "testpassword"),
User(name = "otheruser", password = "otherpassword"),
)
users.forEach { userRepository.save(it) }
val session = Session(userId = users.first().id)
sessionRepository.save(session) sessionRepository.save(session)
val response = client.get("/api/users?query=user") { val response = client.get("/api/users?query=user") {
header("Authorization", "Bearer ${session.token}") header("Authorization", "Bearer ${session.token}")
@ -269,25 +232,17 @@ class UserRouteTest : ApiTest() {
assertEquals(HttpStatusCode.OK, response.status) assertEquals(HttpStatusCode.OK, response.status)
val userQueryResponse: List<UserResponse> = response.body() val userQueryResponse: List<UserResponse> = response.body()
assertEquals(2, userQueryResponse.size) assertEquals(2, userQueryResponse.size)
repeat(2) { i -> assertEquals(TEST_USER.asResponse(), userQueryResponse[0])
assertEquals(users[i].id, userQueryResponse[i].id, "User IDs at index $i don't match") assertEquals(OTHER_USER.asResponse(), userQueryResponse[1])
assertEquals(users[i].name, userQueryResponse[i].username, "Usernames at index $i don't match")
assertEquals(users[i].email, userQueryResponse[i].email, "User emails at index $i don't match")
}
} }
@Test @Test
fun `get users with empty budgetId returns 400`() = apiTest { client -> fun `get users with empty budgetId returns 400`() = apiTest { client ->
val session = Session(userId = TEST_USER.id)
sessionRepository.save(session)
val response = client.get("/api/users?budgetId=") {
header("Authorization", "Bearer ${session.token}")
}
assertEquals(HttpStatusCode.BadRequest, response.status)
} }
// @Test
// fun ``() = apiTest { client ->
//
// }
// @Test
// fun ``() = apiTest { client ->
//
// }
} }

View file

@ -4,18 +4,7 @@ import com.wbrawner.twigs.model.User
import com.wbrawner.twigs.storage.UserRepository import com.wbrawner.twigs.storage.UserRepository
class FakeUserRepository : FakeRepository<User>(), UserRepository { class FakeUserRepository : FakeRepository<User>(), UserRepository {
override val entities: MutableList<User> = mutableListOf( override val entities: MutableList<User> = mutableListOf(TEST_USER, OTHER_USER)
User(
name = "testuser",
email = "test@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb.CWBYzyYdZpmCcA7NSIN8dkdzidt1Xv2" // testpass
),
User(
name = "otheruser",
email = "other@example.com",
password = "\$2a\$10\$bETxbFPja1PyXVLybETxb..rhfIeOkP4qil1Drj29LDUhBxVkm6fS"
),
)
override fun findAll(nameOrEmail: String, password: String?): List<User> { override fun findAll(nameOrEmail: String, password: String?): List<User> {
return entities.filter { user -> return entities.filter { user ->
@ -29,4 +18,20 @@ class FakeUserRepository : FakeRepository<User>(), UserRepository {
override fun findAll(nameLike: String): List<User> { override fun findAll(nameLike: String): List<User> {
return entities.filter { it.name.contains(nameLike, ignoreCase = true) } return entities.filter { it.name.contains(nameLike, ignoreCase = true) }
} }
companion object {
val TEST_USER = User(
id = "id-test-user",
name = "testuser",
email = "test@example.com",
password = "testpass"
)
val OTHER_USER = User(
id = "id-other-user",
name = "otheruser",
email = "other@example.com",
password = "otherpass"
)
}
} }