From e07a98ddbbb77957b1da63a60183cd76ff7d3b67 Mon Sep 17 00:00:00 2001 From: Julia Lange Date: Wed, 21 Jan 2026 12:47:47 -0800 Subject: [PATCH] Finalizing type schema and user tests --- koucha/src/feed.rs | 19 +++--- koucha/src/user.rs | 152 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/koucha/src/feed.rs b/koucha/src/feed.rs index 8e04a0d..1a38d91 100644 --- a/koucha/src/feed.rs +++ b/koucha/src/feed.rs @@ -10,41 +10,42 @@ use crate::{ }; use sqlx::SqlitePool; -pub struct FeedId(pub i64); -impl From for FeedId { fn from(id: i64) -> Self { FeedId(id) } } +#[derive(Copy, Clone)] +pub struct FeedId(i64); impl From for i64 { fn from(id: FeedId) -> Self { id.0 } } pub struct UnparsedFeed { pub id: i64, pub title: String, - pub user_id: i64 } impl UnparsedFeed { pub fn parse(self) -> Result { Ok(Feed { id: FeedId(self.id), title: self.title, - user_id: UserId(self.user_id), }) } } pub struct Feed { - pub id: FeedId, - pub title: String, - pub user_id: UserId, + id: FeedId, + title: String, } impl Feed { + pub fn id(&self) -> FeedId { self.id } + pub fn title(&self) -> &str { &self.title } + pub async fn create( pool: &SqlitePool, id: UserId, name: &str ) -> Result { + let int_id = i64::from(id); let new_feed = sqlx::query_as!( UnparsedFeed, "INSERT INTO feeds (user_id, title) VALUES (?, ?) - RETURNING id as `id!`, user_id, title", - id.0, name + RETURNING id as `id!`, title", + int_id, name ).fetch_one(pool).await?.parse(); new_feed diff --git a/koucha/src/user.rs b/koucha/src/user.rs index 5a038a0..f78de6e 100644 --- a/koucha/src/user.rs +++ b/koucha/src/user.rs @@ -1,26 +1,52 @@ use sqlx::SqlitePool; use crate::{ Result, - Feed + Feed, + feed::UnparsedFeed, }; -pub struct UserId(pub i64); -impl From for UserId { fn from(id: i64) -> Self { UserId(id) } } +#[derive(Copy, Clone)] +pub struct UserId(i64); impl From for i64 { fn from(id: UserId) -> Self { id.0 } } -pub struct User { - pub id: UserId, +pub struct UnparsedUser { + pub id: i64, pub name: String, } +impl UnparsedUser { + pub fn parse(self) -> Result { + Ok(User { + id: UserId(self.id), + name: self.name + }) + } +} + +pub struct User { + id: UserId, + name: String, +} impl User { - pub async fn get_all(pool: &SqlitePool) -> Result> { - let users = sqlx::query_as!( - User, - "SELECT id, name FROM users" - ).fetch_all(pool).await?; + pub fn id(&self) -> UserId { self.id } + pub fn name(&self) -> &str { &self.name } - Ok(users) + pub async fn get(pool: &SqlitePool, id: UserId) -> Result { + let user = sqlx::query_as!( + UnparsedUser, + "SELECT id, name FROM users WHERE id = ?", + id.0 + ).fetch_one(pool).await?.parse(); + + user + } + pub async fn get_all(pool: &SqlitePool) -> Result> { + let users: Result> = sqlx::query_as!( + UnparsedUser, + "SELECT id, name FROM users" + ).fetch_all(pool).await?.into_iter().map(UnparsedUser::parse).collect(); + + users } pub async fn create(pool: &SqlitePool, name: &str) -> Result { @@ -48,15 +74,103 @@ impl User { Ok(()) } - pub async fn get_feeds(pool: &SqlitePool, id: UserId) -> Result> { - let feeds = sqlx::query_as!( - Feed, - "SELECT id FROM feeds WHERE user_id = ?", - id.0 - ).fetch_all(pool).await?; + pub async fn get_feeds(&self, pool: &SqlitePool) -> Result> { + let feeds: Result> = sqlx::query_as!( + UnparsedFeed, + "SELECT id, title FROM feeds WHERE user_id = ?", + self.id.0 + ).fetch_all(pool).await?.into_iter() + .map(UnparsedFeed::parse).collect(); - Ok(feeds) + feeds + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::SqlitePool; + + async fn setup_test_db() -> SqlitePool { + let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); + sqlx::migrate!().run(&pool).await.unwrap(); + pool + } + + #[tokio::test] + async fn get_user() { + let pool = setup_test_db().await; + + let new_user = User::create(&pool, "Alice").await.unwrap(); + + let fetched_user = User::get(&pool, new_user.id).await.unwrap(); + assert_eq!(fetched_user.name, "Alice"); + assert!(fetched_user.id.0 > 0); } - pub fn name(&self) -> &str { &self.name } + #[tokio::test] + async fn create_user() { + let pool = setup_test_db().await; + + let user = User::create(&pool, "Alice").await.unwrap(); + + assert_eq!(user.name, "Alice"); + assert!(user.id.0 > 0); + } + + #[tokio::test] + async fn create_duplicate_user() { + let pool = setup_test_db().await; + + let _user = User::create(&pool, "Alice").await.unwrap(); + let duplicate_user = User::create(&pool, "Alice").await; + + assert!(duplicate_user.is_err()); + } + + #[tokio::test] + async fn get_all_users() { + let pool = setup_test_db().await; + + User::create(&pool, "Alice").await.unwrap(); + User::create(&pool, "Bob").await.unwrap(); + + let users = User::get_all(&pool).await.unwrap(); + + assert_eq!(users.len(), 2); + assert!(users.iter().any(|u| u.name == "Alice")); + assert!(users.iter().any(|u| u.name == "Bob")); + } + + #[tokio::test] + async fn update_name() { + let pool = setup_test_db().await; + + let user = User::create(&pool, "Alice").await.unwrap(); + User::update_name(&pool, user.id, "Alicia").await.unwrap(); + + let updated = User::get(&pool, user.id).await.unwrap(); + assert_eq!(updated.name, "Alicia"); + } + + #[tokio::test] + async fn update_name_to_duplicate() { + let pool = setup_test_db().await; + + let alice = User::create(&pool, "Alice").await.unwrap(); + let _sam = User::create(&pool, "Sam").await.unwrap(); + let status = User::update_name(&pool, alice.id, "Sam").await; + + assert!(status.is_err()); + } + + #[tokio::test] + async fn get_feeds_empty() { + let pool = setup_test_db().await; + + let user = User::create(&pool, "Alice").await.unwrap(); + let feeds = user.get_feeds(&pool).await.unwrap(); + + assert_eq!(feeds.len(), 0); + } }