god this sucks

This commit is contained in:
Hamcha 2025-01-25 18:29:55 +01:00
parent 97498d3c65
commit 24f6052ed0
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
5 changed files with 331 additions and 288 deletions
src
domain/entities
main.rs
outbound
repository
services

View file

@ -20,7 +20,7 @@ pub struct Page {
pub content: PageContent, pub content: PageContent,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum PageContent { pub enum PageContent {
Single { Single {
content: Post, content: Post,
@ -31,7 +31,7 @@ pub enum PageContent {
}, },
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum CollectionKind { pub enum CollectionKind {
Blog, Blog,
Gallery, Gallery,

View file

@ -19,67 +19,86 @@ fn main() {
use outbound::services::site::SiteService; use outbound::services::site::SiteService;
builder = builder.with_context_provider(|| { builder = builder.with_context_provider(|| {
let mut store = InMemoryStore::new();
store.add_site(outbound::repository::site::SiteMetadata { let store = tokio::runtime::Builder::new_multi_thread()
domain: "localhost".to_string(), .enable_all()
title: "Test site".to_string(), .build()
}); .unwrap()
store.add_page( .block_on(async move {
"localhost", let store = InMemoryStore::new();
domain::entities::site::Page { store
info: domain::entities::site::PageInfo { .add_site(outbound::repository::site::SiteMetadata {
title: "Home".to_string(), domain: "localhost".to_string(),
name: "/".to_string(), title: "Test site".to_string(),
order: 0, })
}, .await;
content: domain::entities::site::PageContent::Single { store
content: domain::entities::site::Post { .add_page(
blocks: vec![domain::entities::site::Block::Text { "localhost",
text: "Hello, world!".to_string(), domain::entities::site::Page {
}], info: domain::entities::site::PageInfo {
title: "Home".to_string(),
name: "/".to_string(),
order: 0,
},
content: domain::entities::site::PageContent::Single {
content: domain::entities::site::Post {
blocks: vec![domain::entities::site::Block::Text {
text: "Hello, world!".to_string(),
}],
},
}, },
}, },
}, )
); .await;
store.add_page( store
"localhost", .add_page(
domain::entities::site::Page { "localhost",
info: domain::entities::site::PageInfo { domain::entities::site::Page {
title: "Cool page".to_string(), info: domain::entities::site::PageInfo {
name: "cool".to_string(), title: "Cool page".to_string(),
order: 10, name: "cool".to_string(),
}, order: 10,
content: domain::entities::site::PageContent::Collection {
kind: domain::entities::site::CollectionKind::Blog,
collection_id: "cool-posts".to_string(),
},
},
);
store.add_post(
"localhost",
"cool-posts",
"test_id",
domain::entities::site::Post {
blocks: vec![domain::entities::site::Block::Text {
text: "This is a cool post!".to_string(),
}],
},
);
store.add_post(
"localhost",
"cool-posts",
"test_id_2",
domain::entities::site::Post {
blocks: vec![
domain::entities::site::Block::Text {
text: "This is another cool post!".to_string(),
}, },
domain::entities::site::Block::Text { content: domain::entities::site::PageContent::Collection {
text: "With two blocks!".to_string(), kind: domain::entities::site::CollectionKind::Blog,
collection_id: "cool-posts".to_string(),
}, },
], },
}, )
); .await;
store
.add_post(
"localhost",
"cool-posts",
"test_id",
domain::entities::site::Post {
blocks: vec![domain::entities::site::Block::Text {
text: "This is a cool post!".to_string(),
}],
},
)
.await;
store
.add_post(
"localhost",
"cool-posts",
"test_id_2",
domain::entities::site::Post {
blocks: vec![
domain::entities::site::Block::Text {
text: "This is another cool post!".to_string(),
},
domain::entities::site::Block::Text {
text: "With two blocks!".to_string(),
},
],
},
)
.await;
store
});
Box::new(SiteService::new(store)) Box::new(SiteService::new(store))
}); });

View file

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::sync::Mutex;
use crate::{ use crate::{
domain::entities::{ domain::entities::{
@ -13,103 +14,130 @@ use crate::{
}, },
}; };
pub struct InMemoryStore { struct InMemoryStoreData {
sites: HashMap<String, SiteMetadata>, sites: HashMap<String, SiteMetadata>,
pages: HashMap<(String, String), Page>, pages: HashMap<(String, String), Page>,
posts: HashMap<(String, String), HashMap<String, Post>>, posts: HashMap<(String, String), HashMap<String, Post>>,
} }
pub struct InMemoryStore {
data: Arc<Mutex<InMemoryStoreData>>,
}
impl InMemoryStore { impl InMemoryStore {
pub fn new() -> InMemoryStore { pub fn new() -> InMemoryStore {
InMemoryStore { InMemoryStore {
sites: HashMap::new(), data: Arc::new(Mutex::new(InMemoryStoreData {
pages: HashMap::new(), sites: HashMap::new(),
posts: HashMap::new(), pages: HashMap::new(),
posts: HashMap::new(),
})),
} }
} }
pub fn add_site(&mut self, site: SiteMetadata) { pub async fn add_site(&self, site: SiteMetadata) {
self.sites.insert(site.domain.clone(), site); self.data
.lock()
.await
.sites
.insert(site.domain.clone(), site);
} }
pub fn add_page(&mut self, site: &str, page: Page) { pub async fn add_page(&self, site: &str, page: Page) {
self.pages self.data
.lock()
.await
.pages
.insert((site.to_string(), page.info.name.clone()), page); .insert((site.to_string(), page.info.name.clone()), page);
} }
pub fn add_post(&mut self, site: &str, collection_id: &str, post_id: &str, post: Post) { pub async fn add_post(&self, site: &str, collection_id: &str, post_id: &str, post: Post) {
self.posts self.data
.lock()
.await
.posts
.entry((site.to_string(), collection_id.to_string())) .entry((site.to_string(), collection_id.to_string()))
.or_insert_with(HashMap::new) .or_insert_with(HashMap::new)
.insert(post_id.to_string(), post); .insert(post_id.to_string(), post);
} }
#[cfg(test)] #[cfg(test)]
pub fn with_test_data() -> InMemoryStore { pub async fn with_test_data() -> InMemoryStore {
use crate::domain::entities::site::{Block, PageContent, Post}; use crate::domain::entities::site::{Block, PageContent, Post};
let mut store = InMemoryStore::new(); let store = InMemoryStore::new();
store.add_site(SiteMetadata {
domain: "example.com".to_string(), store
title: "Test site".to_string(), .add_site(SiteMetadata {
}); domain: "example.com".to_string(),
store.add_page( title: "Test site".to_string(),
"example.com", })
Page { .await;
info: PageInfo { store
title: "Home".to_string(), .add_page(
name: "/".to_string(), "example.com",
order: 0, Page {
}, info: PageInfo {
content: PageContent::Single { title: "Home".to_string(),
content: Post { name: "/".to_string(),
blocks: vec![Block::Text { order: 0,
text: "Hello, world!".to_string(), },
}], content: PageContent::Single {
content: Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
}, },
}, },
}, )
); .await;
store.add_page( store
"example.com", .add_page(
Page { "example.com",
info: PageInfo { Page {
title: "About".to_string(), info: PageInfo {
name: "/about".to_string(), title: "About".to_string(),
order: 10, name: "about".to_string(),
}, order: 10,
content: PageContent::Single { },
content: Post { content: PageContent::Single {
blocks: vec![Block::Text { content: Post {
text: "This is the about page.".to_string(), blocks: vec![Block::Text {
}], text: "This is the about page.".to_string(),
}],
},
}, },
}, },
}, )
); .await;
store.add_post( store
"example.com", .add_post(
"home_posts", "example.com",
"post_id", "home_posts",
Post { "post_id",
blocks: vec![Block::Text { Post {
text: "Hello, world!".to_string(), blocks: vec![Block::Text {
}], text: "Hello, world!".to_string(),
}, }],
); },
)
.await;
store.add_post( store
"example.com", .add_post(
"home_posts", "example.com",
"post_id2", "home_posts",
Post { "post_id2",
blocks: vec![Block::Text { Post {
text: "Hello again!".to_string(), blocks: vec![Block::Text {
}], text: "Hello again!".to_string(),
}, }],
); },
)
.await;
store store
} }
@ -118,7 +146,10 @@ impl InMemoryStore {
#[async_trait] #[async_trait]
impl SiteRepository for InMemoryStore { impl SiteRepository for InMemoryStore {
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> { async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> {
self.sites self.data
.lock()
.await
.sites
.get(domain) .get(domain)
.ok_or(repository::site::Error::NotFound) .ok_or(repository::site::Error::NotFound)
.cloned() .cloned()
@ -126,6 +157,9 @@ impl SiteRepository for InMemoryStore {
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>> { async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>> {
let mut pages = self let mut pages = self
.data
.lock()
.await
.pages .pages
.iter() .iter()
.filter(|((site, _), _)| site == domain) .filter(|((site, _), _)| site == domain)
@ -138,14 +172,30 @@ impl SiteRepository for InMemoryStore {
} }
async fn get_page(&self, domain: &str, page: &str) -> Result<Page> { async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
self.pages self.data
.lock()
.await
.pages
.get(&(domain.to_string(), page.to_string())) .get(&(domain.to_string(), page.to_string()))
.ok_or(repository::site::Error::NotFound) .ok_or(repository::site::Error::NotFound)
.cloned() .cloned()
} }
async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
self.data
.lock()
.await
.pages
.insert((domain.to_string(), page.info.name.clone()), page);
Ok(())
}
async fn get_post(&self, domain: &str, collection_id: &str, post_id: &str) -> Result<Post> { async fn get_post(&self, domain: &str, collection_id: &str, post_id: &str) -> Result<Post> {
self.posts self.data
.lock()
.await
.posts
.get(&(domain.to_string(), collection_id.to_string())) .get(&(domain.to_string(), collection_id.to_string()))
.and_then(|posts| posts.get(post_id)) .and_then(|posts| posts.get(post_id))
.cloned() .cloned()
@ -159,6 +209,9 @@ impl SiteRepository for InMemoryStore {
cursor: CursorOptions<String>, cursor: CursorOptions<String>,
) -> Result<Paginated<Post, String>> { ) -> Result<Paginated<Post, String>> {
let posts = self let posts = self
.data
.lock()
.await
.posts .posts
.get(&(domain.to_string(), collection_id.to_string())) .get(&(domain.to_string(), collection_id.to_string()))
.ok_or(repository::site::Error::NotFound) .ok_or(repository::site::Error::NotFound)
@ -220,14 +273,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_site_by_domain_works() { async fn get_site_by_domain_works() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_site(SiteMetadata { let info = store.get_site_by_domain("example.com").await.unwrap();
domain: "test.com".to_string(), assert_eq!(info.domain, "example.com");
title: "Some test site".to_string(), assert_eq!(info.title, "Test site");
});
let info = store.get_site_by_domain("test.com").await.unwrap();
assert_eq!(info.domain, "test.com");
assert_eq!(info.title, "Some test site");
} }
#[tokio::test] #[tokio::test]
@ -243,77 +292,32 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_pages_for_site_works() { async fn get_pages_for_site_works() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_site(SiteMetadata {
domain: "example.com".to_string(),
title: "Test site".to_string(),
});
store.add_page(
"example.com",
Page {
info: PageInfo {
title: "Home".to_string(),
name: "/".to_string(),
order: 0,
},
content: PageContent::Single {
content: Post { blocks: vec![] },
},
},
);
store.add_page(
"example.com",
Page {
info: PageInfo {
title: "Cool page".to_string(),
name: "cool".to_string(),
order: 10,
},
content: PageContent::Single {
content: Post { blocks: vec![] },
},
},
);
let pages = store.get_pages_for_site("example.com").await.unwrap(); let pages = store.get_pages_for_site("example.com").await.unwrap();
assert_eq!(pages.len(), 2); assert_eq!(pages.len(), 2);
assert_eq!(pages[0].title, "Home"); assert_eq!(pages[0].title, "Home");
assert_eq!(pages[0].name, "/"); assert_eq!(pages[0].name, "/");
assert_eq!(pages[1].title, "Cool page"); assert_eq!(pages[1].title, "About");
assert_eq!(pages[1].name, "cool"); assert_eq!(pages[1].name, "about");
assert!(pages[0].order < pages[1].order); assert!(pages[0].order < pages[1].order);
} }
#[tokio::test] #[tokio::test]
async fn get_page_works() { async fn get_page_works() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_page(
"example.com",
Page {
info: PageInfo {
title: "Home".to_string(),
name: "/".to_string(),
order: 0,
},
content: PageContent::Single {
content: Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
},
},
);
let page = store.get_page("example.com", "/").await.unwrap(); let page = store.get_page("example.com", "/").await.unwrap();
let PageContent::Single { content } = page.content else { assert_eq!(page.info.title, "Home");
panic!("page content must be a single text block") assert_eq!(page.info.name, "/");
}; assert_eq!(
let Post { blocks } = content; page.content,
assert_eq!(blocks.len(), 1); PageContent::Single {
content: Post {
let Block::Text { text } = &blocks[0] else { blocks: vec![Block::Text {
panic!("page content must be a single text block") text: "Hello, world!".to_string()
}; }]
assert_eq!(text, "Hello, world!"); }
}
);
} }
#[tokio::test] #[tokio::test]
@ -329,29 +333,26 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_post_works() { async fn get_post_works() {
let mut store = InMemoryStore::new(); let expected_post = Post {
store.add_post( blocks: vec![Block::Text {
"example.com", text: "Hello, world!".to_string(),
"home_posts", }],
"test_id", };
Post { let store = InMemoryStore::new();
blocks: vec![Block::Text { store
text: "Hello, world!".to_string(), .add_post(
}], "example.com",
}, "home_posts",
); "test_id",
expected_post.clone(),
)
.await;
let post = store let post = store
.get_post("example.com", "home_posts", "test_id") .get_post("example.com", "home_posts", "test_id")
.await .await
.unwrap(); .unwrap();
let Post { blocks } = post; assert_eq!(post, expected_post);
assert_eq!(blocks.len(), 1);
let Block::Text { text } = &blocks[0] else {
panic!("page content must be a single text block")
};
assert_eq!(text, "Hello, world!");
} }
#[tokio::test] #[tokio::test]
@ -367,59 +368,31 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_posts_for_page_works() { async fn get_posts_for_page_works() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_post(
"example.com",
"home_posts",
"test_id",
Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
);
let posts = store let posts = store
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(10)) .get_posts_for_collection("example.com", "home_posts", CursorOptions::none(10))
.await .await
.unwrap(); .unwrap();
assert_eq!(posts.data.len(), 1); assert_eq!(posts.data.len(), 2);
assert_eq!(posts.next, None); assert_eq!(posts.next, None);
assert_eq!(
let Post { blocks } = &posts.data[0]; posts.data[0],
let Block::Text { text } = &blocks[0] else { Post {
panic!("page content must be a single text block") blocks: vec![Block::Text {
}; text: "Hello, world!".to_string()
assert_eq!(text, "Hello, world!"); }]
}
);
} }
#[tokio::test] #[tokio::test]
async fn get_posts_for_page_with_cursor_works() { async fn get_posts_for_page_with_cursor_works() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_post(
"example.com",
"home_posts",
"test_id",
Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
);
store.add_post(
"example.com",
"home_posts",
"test_id_2",
Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
);
let posts = store let posts = store
.get_posts_for_collection( .get_posts_for_collection(
"example.com", "example.com",
"home_posts", "home_posts",
CursorOptions::after("test_id".to_string(), 10), CursorOptions::after("post_id".to_string(), 10),
) )
.await .await
.unwrap(); .unwrap();
@ -429,33 +402,13 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn get_posts_for_page_returns_cursor() { async fn get_posts_for_page_returns_cursor() {
let mut store = InMemoryStore::new(); let store = InMemoryStore::with_test_data().await;
store.add_post(
"example.com",
"home_posts",
"test_id",
Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
);
store.add_post(
"example.com",
"home_posts",
"test_id_2",
Post {
blocks: vec![Block::Text {
text: "Hello, world!".to_string(),
}],
},
);
let posts = store let posts = store
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(1)) .get_posts_for_collection("example.com", "home_posts", CursorOptions::none(1))
.await .await
.unwrap(); .unwrap();
assert_eq!(posts.data.len(), 1); assert_eq!(posts.data.len(), 1);
assert_eq!(posts.next, Some("test_id".to_string())); assert_eq!(posts.next, Some("post_id".to_string()));
} }
#[tokio::test] #[tokio::test]
@ -470,4 +423,33 @@ mod tests {
repository::site::Error::NotFound repository::site::Error::NotFound
)); ));
} }
#[tokio::test]
async fn set_page_works() {
let store = InMemoryStore::new();
let content = PageContent::Single {
content: Post {
blocks: vec![Block::Text {
text: "hello!".to_string(),
}],
},
};
let _ = store
.set_page(
"example.com",
Page {
info: PageInfo {
title: "New page".to_string(),
name: "new-page".to_string(),
order: 0,
},
content: content.clone(),
},
)
.await;
let page = store.get_page("example.com", "new-page").await.unwrap();
assert_eq!(page.info.title, "New page");
assert_eq!(page.info.name, "new-page");
assert_eq!(page.content, content);
}
} }

View file

@ -16,7 +16,10 @@ pub struct SiteMetadata {
pub trait SiteRepository: Send + Sync + 'static { pub trait SiteRepository: Send + Sync + 'static {
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>; async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>;
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>; async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>;
async fn get_page(&self, domain: &str, name: &str) -> Result<Page>; async fn get_page(&self, domain: &str, name: &str) -> Result<Page>;
async fn set_page(&self, domain: &str, page: Page) -> Result<()>;
async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post>; async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post>;
async fn get_posts_for_collection( async fn get_posts_for_collection(
&self, &self,

View file

@ -74,6 +74,13 @@ impl SiteService {
Ok(posts) Ok(posts)
} }
pub async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
let info = self.site_repository.get_site_by_domain(domain).await?;
self.site_repository.set_page(&info.domain, page).await?;
Ok(())
}
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -100,13 +107,13 @@ impl From<repository::site::Error> for Error {
mod tests { mod tests {
use super::{Error, SiteService}; use super::{Error, SiteService};
use crate::{ use crate::{
domain::entities::site::{Block, PageContent}, domain::entities::site::{Block, Page, PageContent, PageInfo, Post},
outbound::repository::adapters::memory::InMemoryStore, outbound::repository::adapters::memory::InMemoryStore,
}; };
#[tokio::test] #[tokio::test]
async fn gets_site_info() { async fn gets_site_info() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let info = service.get_site("example.com").await.unwrap(); let info = service.get_site("example.com").await.unwrap();
assert_eq!(info.domain, "example.com"); assert_eq!(info.domain, "example.com");
assert_eq!(info.title, "Test site"); assert_eq!(info.title, "Test site");
@ -115,7 +122,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_nonexistent_site() { async fn gets_nonexistent_site() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let result = service.get_site("nonexistent.com").await; let result = service.get_site("nonexistent.com").await;
assert!(result.is_err()); assert!(result.is_err());
assert!(matches!(result.err().unwrap(), Error::NotFound)); assert!(matches!(result.err().unwrap(), Error::NotFound));
@ -123,7 +130,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_page_data() { async fn gets_page_data() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let page = service.get_page("example.com", "/").await.unwrap(); let page = service.get_page("example.com", "/").await.unwrap();
assert_eq!(page.info.title, "Home"); assert_eq!(page.info.title, "Home");
@ -146,7 +153,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_nonexistent_page() { async fn gets_nonexistent_page() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let result = service.get_page("example.com", "nonexistent").await; let result = service.get_page("example.com", "nonexistent").await;
assert!(result.is_err()); assert!(result.is_err());
assert!(matches!(result.err().unwrap(), Error::NotFound)); assert!(matches!(result.err().unwrap(), Error::NotFound));
@ -154,7 +161,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_a_single_post() { async fn gets_a_single_post() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let post = service let post = service
.get_post("example.com", "home_posts", "post_id") .get_post("example.com", "home_posts", "post_id")
.await .await
@ -169,7 +176,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_nonexistent_post() { async fn gets_nonexistent_post() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let result = service let result = service
.get_post("example.com", "home_posts", "nonexistent") .get_post("example.com", "home_posts", "nonexistent")
.await; .await;
@ -179,7 +186,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_posts() { async fn gets_posts() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let posts = service let posts = service
.get_posts("example.com", "home_posts", None) .get_posts("example.com", "home_posts", None)
.await .await
@ -190,7 +197,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_posts_with_cursor() { async fn gets_posts_with_cursor() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let posts = service let posts = service
.get_posts("example.com", "home_posts", Some("post_id".to_string())) .get_posts("example.com", "home_posts", Some("post_id".to_string()))
.await .await
@ -201,9 +208,41 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn gets_nonexistent_posts() { async fn gets_nonexistent_posts() {
let service = SiteService::new(InMemoryStore::with_test_data()); let service = SiteService::new(InMemoryStore::with_test_data().await);
let result = service.get_posts("example.com", "nonexistent", None).await; let result = service.get_posts("example.com", "nonexistent", None).await;
assert!(result.is_err()); assert!(result.is_err());
assert!(matches!(result.err().unwrap(), Error::NotFound)); assert!(matches!(result.err().unwrap(), Error::NotFound));
} }
#[tokio::test]
async fn sets_a_page() {
let content = PageContent::Single {
content: Post {
blocks: vec![Block::Text {
text: "Hello my new page!".to_string(),
}],
},
};
let service = SiteService::new(InMemoryStore::with_test_data().await);
service
.set_page(
"example.com",
Page {
info: PageInfo {
name: "new_page".to_string(),
title: "New page".to_string(),
order: 10,
},
content: content.clone(),
},
)
.await
.unwrap();
let page = service.get_page("example.com", "new_page").await.unwrap();
assert_eq!(page.info.title, "New page");
assert_eq!(page.info.name, "new_page");
assert_eq!(page.content, content);
}
} }