Finalizing type schema and user tests

This commit is contained in:
Julia Lange 2026-01-21 12:47:47 -08:00
parent 162ba78430
commit e07a98ddbb
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto
2 changed files with 143 additions and 28 deletions

View file

@ -10,41 +10,42 @@ use crate::{
}; };
use sqlx::SqlitePool; use sqlx::SqlitePool;
pub struct FeedId(pub i64); #[derive(Copy, Clone)]
impl From<i64> for FeedId { fn from(id: i64) -> Self { FeedId(id) } } pub struct FeedId(i64);
impl From<FeedId> for i64 { fn from(id: FeedId) -> Self { id.0 } } impl From<FeedId> for i64 { fn from(id: FeedId) -> Self { id.0 } }
pub struct UnparsedFeed { pub struct UnparsedFeed {
pub id: i64, pub id: i64,
pub title: String, pub title: String,
pub user_id: i64
} }
impl UnparsedFeed { impl UnparsedFeed {
pub fn parse(self) -> Result<Feed> { pub fn parse(self) -> Result<Feed> {
Ok(Feed { Ok(Feed {
id: FeedId(self.id), id: FeedId(self.id),
title: self.title, title: self.title,
user_id: UserId(self.user_id),
}) })
} }
} }
pub struct Feed { pub struct Feed {
pub id: FeedId, id: FeedId,
pub title: String, title: String,
pub user_id: UserId,
} }
impl Feed { impl Feed {
pub fn id(&self) -> FeedId { self.id }
pub fn title(&self) -> &str { &self.title }
pub async fn create( pub async fn create(
pool: &SqlitePool, id: UserId, name: &str pool: &SqlitePool, id: UserId, name: &str
) -> Result<Self> { ) -> Result<Self> {
let int_id = i64::from(id);
let new_feed = sqlx::query_as!( let new_feed = sqlx::query_as!(
UnparsedFeed, UnparsedFeed,
"INSERT INTO feeds (user_id, title) "INSERT INTO feeds (user_id, title)
VALUES (?, ?) VALUES (?, ?)
RETURNING id as `id!`, user_id, title", RETURNING id as `id!`, title",
id.0, name int_id, name
).fetch_one(pool).await?.parse(); ).fetch_one(pool).await?.parse();
new_feed new_feed

View file

@ -1,26 +1,52 @@
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::{ use crate::{
Result, Result,
Feed Feed,
feed::UnparsedFeed,
}; };
pub struct UserId(pub i64); #[derive(Copy, Clone)]
impl From<i64> for UserId { fn from(id: i64) -> Self { UserId(id) } } pub struct UserId(i64);
impl From<UserId> for i64 { fn from(id: UserId) -> Self { id.0 } } impl From<UserId> for i64 { fn from(id: UserId) -> Self { id.0 } }
pub struct User { pub struct UnparsedUser {
pub id: UserId, pub id: i64,
pub name: String, pub name: String,
} }
impl UnparsedUser {
pub fn parse(self) -> Result<User> {
Ok(User {
id: UserId(self.id),
name: self.name
})
}
}
pub struct User {
id: UserId,
name: String,
}
impl User { impl User {
pub async fn get_all(pool: &SqlitePool) -> Result<Vec<Self>> { pub fn id(&self) -> UserId { self.id }
let users = sqlx::query_as!( pub fn name(&self) -> &str { &self.name }
User,
"SELECT id, name FROM users"
).fetch_all(pool).await?;
Ok(users) pub async fn get(pool: &SqlitePool, id: UserId) -> Result<Self> {
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<Vec<Self>> {
let users: Result<Vec<Self>> = 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<Self> { pub async fn create(pool: &SqlitePool, name: &str) -> Result<Self> {
@ -48,15 +74,103 @@ impl User {
Ok(()) Ok(())
} }
pub async fn get_feeds(pool: &SqlitePool, id: UserId) -> Result<Vec<Feed>> { pub async fn get_feeds(&self, pool: &SqlitePool) -> Result<Vec<Feed>> {
let feeds = sqlx::query_as!( let feeds: Result<Vec<Feed>> = sqlx::query_as!(
Feed, UnparsedFeed,
"SELECT id FROM feeds WHERE user_id = ?", "SELECT id, title FROM feeds WHERE user_id = ?",
id.0 self.id.0
).fetch_all(pool).await?; ).fetch_all(pool).await?.into_iter()
.map(UnparsedFeed::parse).collect();
Ok(feeds) feeds
}
} }
pub fn name(&self) -> &str { &self.name } #[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);
}
#[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);
}
} }