From d3223923e4b9d71647ecdd473e1f10f084e1cdda Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 16 May 2026 11:01:54 +0200 Subject: [PATCH] feat(domain/testing): add ApiKeyService, EngagementRepository, list_paginated, find_by_ids to TestStore --- crates/domain/Cargo.toml | 6 +++- crates/domain/src/testing.rs | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/domain/Cargo.toml b/crates/domain/Cargo.toml index 1fbcab9..ab7acae 100644 --- a/crates/domain/Cargo.toml +++ b/crates/domain/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -test-helpers = [] +test-helpers = ["dep:sha2", "dep:hex"] [dependencies] async-trait = { workspace = true } @@ -14,6 +14,10 @@ chrono = { workspace = true } serde = { workspace = true } futures = { workspace = true } url = { workspace = true } +sha2 = { version = "0.10", optional = true } +hex = { version = "0.4", optional = true } [dev-dependencies] tokio = { workspace = true, features = ["full"] } +sha2 = "0.10" +hex = "0.4" diff --git a/crates/domain/src/testing.rs b/crates/domain/src/testing.rs index 763b2d9..11193f5 100644 --- a/crates/domain/src/testing.rs +++ b/crates/domain/src/testing.rs @@ -82,6 +82,23 @@ impl UserReader for TestStore { .filter(|u| u.local) .count() as i64) } + + async fn list_paginated(&self, page: PageParams) -> Result, DomainError> { + let all = self.list_with_stats().await?; + let total = all.len() as i64; + let start = page.offset() as usize; + let items: Vec = all.into_iter().skip(start).take(page.limit() as usize).collect(); + Ok(Paginated { items, total, page: page.page, per_page: page.per_page }) + } + + async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> { + let g = self.users.lock().unwrap(); + let map = g.iter() + .filter(|u| ids.contains(&u.id)) + .map(|u| (u.id.clone(), u.clone())) + .collect(); + Ok(map) + } } #[async_trait] @@ -271,6 +288,33 @@ impl BoostRepository for TestStore { } } +#[async_trait] +impl EngagementRepository for TestStore { + async fn get_for_thoughts( + &self, + thought_ids: &[ThoughtId], + viewer_id: Option<&UserId>, + ) -> Result)>, DomainError> { + use crate::models::feed::{EngagementStats, ViewerContext}; + let likes = self.likes.lock().unwrap(); + let boosts = self.boosts.lock().unwrap(); + let thoughts = self.thoughts.lock().unwrap(); + + let mut result = HashMap::new(); + for tid in thought_ids { + let like_count = likes.iter().filter(|l| &l.thought_id == tid).count() as i64; + let boost_count = boosts.iter().filter(|b| &b.thought_id == tid).count() as i64; + let reply_count = thoughts.iter().filter(|t| t.in_reply_to_id.as_ref() == Some(tid)).count() as i64; + let viewer = viewer_id.map(|vid| ViewerContext { + liked: likes.iter().any(|l| &l.thought_id == tid && &l.user_id == vid), + boosted: boosts.iter().any(|b| &b.thought_id == tid && &b.user_id == vid), + }); + result.insert(tid.clone(), (EngagementStats { like_count, boost_count, reply_count }, viewer)); + } + Ok(result) + } +} + #[async_trait] impl FollowRepository for TestStore { async fn save(&self, follow: &Follow) -> Result<(), DomainError> { @@ -456,6 +500,21 @@ impl ApiKeyRepository for TestStore { } } +#[async_trait] +impl ApiKeyService for TestStore { + async fn validate_key(&self, raw_key: &str) -> Result, DomainError> { + use sha2::{Digest, Sha256}; + let hash = hex::encode(Sha256::digest(raw_key.as_bytes())); + Ok(self + .api_keys + .lock() + .unwrap() + .iter() + .find(|k| k.key_hash == hash) + .map(|k| k.user_id.clone())) + } +} + #[async_trait] impl TopFriendRepository for TestStore { async fn set_top_friends(