Adding tests for current aggregates.

This commit is contained in:
Dessalines 2020-12-09 11:52:10 -05:00
parent 742b78523a
commit dabcfca67b
7 changed files with 334 additions and 20 deletions

View file

@ -9,13 +9,258 @@ pub struct CommunityAggregates {
pub community_id: i32,
pub subscribers: i64,
pub posts: i64,
pub counts: i64,
pub comments: i64,
}
impl CommunityAggregates {
pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
community_aggregates::table.find(id).first::<Self>(conn)
pub fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
community_aggregates::table
.filter(community_aggregates::community_id.eq(community_id))
.first::<Self>(conn)
}
}
// TODO add unit tests, to make sure triggers are working
#[cfg(test)]
mod tests {
use crate::{
aggregates::community_aggregates::CommunityAggregates,
comment::{Comment, CommentForm},
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm},
post::{Post, PostForm},
tests::establish_unpooled_connection,
user::{UserForm, User_},
Crud,
Followable,
ListingType,
SortType,
};
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "thommy_community_agg".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None,
banner: None,
admin: false,
banned: Some(false),
published: None,
updated: None,
show_nsfw: false,
theme: "browser".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
actor_id: None,
bio: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let another_user = UserForm {
name: "jerry_community_agg".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None,
banner: None,
admin: false,
banned: Some(false),
published: None,
updated: None,
show_nsfw: false,
theme: "browser".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
actor_id: None,
bio: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
};
let another_inserted_user = User_::create(&conn, &another_user).unwrap();
let new_community = CommunityForm {
name: "TIL_community_agg".into(),
creator_id: inserted_user.id,
title: "nada".to_owned(),
description: None,
category_id: 1,
nsfw: false,
removed: None,
deleted: None,
updated: None,
actor_id: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
icon: None,
banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
let another_community = CommunityForm {
name: "TIL_community_agg_2".into(),
creator_id: inserted_user.id,
title: "nada".to_owned(),
description: None,
category_id: 1,
nsfw: false,
removed: None,
deleted: None,
updated: None,
actor_id: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
icon: None,
banner: None,
};
let another_inserted_community = Community::create(&conn, &another_community).unwrap();
let first_user_follow = CommunityFollowerForm {
community_id: inserted_community.id,
user_id: inserted_user.id,
pending: false,
};
CommunityFollower::follow(&conn, &first_user_follow).unwrap();
let second_user_follow = CommunityFollowerForm {
community_id: inserted_community.id,
user_id: another_inserted_user.id,
pending: false,
};
CommunityFollower::follow(&conn, &second_user_follow).unwrap();
let another_community_follow = CommunityFollowerForm {
community_id: another_inserted_community.id,
user_id: inserted_user.id,
pending: false,
};
CommunityFollower::follow(&conn, &another_community_follow).unwrap();
let new_post = PostForm {
name: "A test post".into(),
url: None,
body: None,
creator_id: inserted_user.id,
community_id: inserted_community.id,
removed: None,
deleted: None,
locked: None,
stickied: None,
nsfw: false,
updated: None,
embed_title: None,
embed_description: None,
embed_html: None,
thumbnail_url: None,
ap_id: None,
local: true,
published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
let comment_form = CommentForm {
content: "A test comment".into(),
creator_id: inserted_user.id,
post_id: inserted_post.id,
removed: None,
deleted: None,
read: None,
parent_id: None,
published: None,
updated: None,
ap_id: None,
local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
let child_comment_form = CommentForm {
content: "A test comment".into(),
creator_id: inserted_user.id,
post_id: inserted_post.id,
removed: None,
deleted: None,
read: None,
parent_id: Some(inserted_comment.id),
published: None,
updated: None,
ap_id: None,
local: true,
};
let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
let community_aggregates_before_delete =
CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(2, community_aggregates_before_delete.subscribers);
assert_eq!(1, community_aggregates_before_delete.posts);
assert_eq!(2, community_aggregates_before_delete.comments);
// Test the other community
let another_community_aggs =
CommunityAggregates::read(&conn, another_inserted_community.id).unwrap();
assert_eq!(1, another_community_aggs.subscribers);
assert_eq!(0, another_community_aggs.posts);
assert_eq!(0, another_community_aggs.comments);
// Unfollow test
CommunityFollower::unfollow(&conn, &second_user_follow).unwrap();
let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(1, after_unfollow.subscribers);
// Follow again just for the later tests
CommunityFollower::follow(&conn, &second_user_follow).unwrap();
let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(2, after_follow_again.subscribers);
// Remove a parent comment (the comment count should also be 0)
Post::delete(&conn, inserted_post.id).unwrap();
let after_parent_post_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(0, after_parent_post_delete.comments);
assert_eq!(0, after_parent_post_delete.posts);
// Remove the 2nd user
User_::delete(&conn, another_inserted_user.id).unwrap();
let after_user_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(1, after_user_delete.subscribers);
// This should delete all the associated rows, and fire triggers
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, user_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = CommunityAggregates::read(&conn, inserted_community.id);
assert!(after_delete.is_err());
}
}

View file

@ -108,7 +108,9 @@ mod tests {
published: None,
};
// Insert two of those posts
let inserted_post = Post::create(&conn, &new_post).unwrap();
let _inserted_post_again = Post::create(&conn, &new_post).unwrap();
let comment_form = CommentForm {
content: "A test comment".into(),
@ -124,6 +126,7 @@ mod tests {
local: true,
};
// Insert two of those comments
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
let child_comment_form = CommentForm {
@ -146,9 +149,15 @@ mod tests {
assert_eq!(1, site_aggregates_before_delete.users);
assert_eq!(1, site_aggregates_before_delete.communities);
assert_eq!(1, site_aggregates_before_delete.posts);
assert_eq!(2, site_aggregates_before_delete.posts);
assert_eq!(2, site_aggregates_before_delete.comments);
// Try a post delete
Post::delete(&conn, inserted_post.id).unwrap();
let site_aggregates_after_post_delete = SiteAggregates::read(&conn).unwrap();
assert_eq!(1, site_aggregates_after_post_delete.posts);
assert_eq!(0, site_aggregates_after_post_delete.comments);
// This shouuld delete all the associated rows, and fire triggers
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, user_num_deleted);

View file

@ -167,7 +167,7 @@ mod tests {
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
let comment_like = CommentLikeForm {
let mut comment_like = CommentLikeForm {
comment_id: inserted_comment.id,
user_id: inserted_user.id,
post_id: inserted_post.id,
@ -176,7 +176,7 @@ mod tests {
let _inserted_comment_like = CommentLike::like(&conn, &comment_like).unwrap();
let child_comment_form = CommentForm {
let mut child_comment_form = CommentForm {
content: "A test comment".into(),
creator_id: inserted_user.id,
post_id: inserted_post.id,
@ -219,6 +219,23 @@ mod tests {
assert_eq!(0, after_parent_comment_delete.comment_count);
assert_eq!(0, after_parent_comment_delete.comment_score);
// Add in the two comments again, then delete the post.
let new_parent_comment = Comment::create(&conn, &comment_form).unwrap();
child_comment_form.parent_id = Some(new_parent_comment.id);
Comment::create(&conn, &child_comment_form).unwrap();
comment_like.comment_id = new_parent_comment.id;
CommentLike::like(&conn, &comment_like).unwrap();
let after_comment_add = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(2, after_comment_add.comment_count);
assert_eq!(1, after_comment_add.comment_score);
Post::delete(&conn, inserted_post.id).unwrap();
let after_post_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(0, after_post_delete.comment_score);
assert_eq!(0, after_post_delete.comment_count);
assert_eq!(0, after_post_delete.post_score);
assert_eq!(0, after_post_delete.post_count);
// This should delete all the associated rows, and fire triggers
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, user_num_deleted);

View file

@ -31,6 +31,7 @@ end $$;
create trigger site_aggregates_user
after insert or delete on user_
for each row
execute procedure site_aggregates_user();
-- post

View file

@ -80,7 +80,7 @@ begin
left join post_like pl on p.id = pl.post_id
group by u.id
) pd
where ua.user_id = pd.id;
where ua.user_id = OLD.creator_id;
END IF;
return null;
@ -97,7 +97,6 @@ returns trigger language plpgsql
as $$
begin
IF (TG_OP = 'INSERT') THEN
-- TODO not sure if this is working right
-- Need to get the post creator, not the voter
update user_aggregates ua
set post_score = post_score + NEW.score
@ -143,7 +142,7 @@ begin
left join comment_like cl on c.id = cl.comment_id
group by u.id
) cd
where ua.user_id = cd.id;
where ua.user_id = OLD.creator_id;
END IF;
return null;
end $$;

View file

@ -1,9 +1,11 @@
-- community aggregates
drop table community_aggregates;
drop trigger community_aggregates_community on community;
drop trigger community_aggregates_post_count on post;
drop trigger community_aggregates_comment_count on comment;
drop trigger community_aggregates_subscriber_count on community_follower;
drop function
community_aggregates_community,
community_aggregates_post_count,
community_aggregates_comment_count,
community_aggregates_subscriber_count;

View file

@ -2,18 +2,18 @@
create table community_aggregates (
id serial primary key,
community_id int references community on update cascade on delete cascade not null,
subscribers bigint not null,
posts bigint not null,
comments bigint not null,
subscribers bigint not null default 0,
posts bigint not null default 0,
comments bigint not null default 0,
unique (community_id)
);
insert into community_aggregates (community_id, subscribers, posts, comments)
select
c.id,
coalesce(cf.subs, 0::bigint) as subscribers,
coalesce(cd.posts, 0::bigint) as posts,
coalesce(cd.comments, 0::bigint) as comments
coalesce(cf.subs, 0) as subscribers,
coalesce(cd.posts, 0) as posts,
coalesce(cd.comments, 0) as comments
from community c
left join (
select
@ -33,6 +33,24 @@ insert into community_aggregates (community_id, subscribers, posts, comments)
) cf on cf.community_id = c.id;
-- Add community aggregate triggers
-- initial community add
create function community_aggregates_community()
returns trigger language plpgsql
as $$
begin
IF (TG_OP = 'INSERT') THEN
insert into community_aggregates (community_id) values (NEW.id);
ELSIF (TG_OP = 'DELETE') THEN
delete from community_aggregates where community_id = OLD.id;
END IF;
return null;
end $$;
create trigger community_aggregates_community
after insert or delete on community
for each row
execute procedure community_aggregates_community();
-- post count
create function community_aggregates_post_count()
returns trigger language plpgsql
@ -44,6 +62,22 @@ begin
ELSIF (TG_OP = 'DELETE') THEN
update community_aggregates
set posts = posts - 1 where community_id = OLD.community_id;
-- Update the counts if the post got deleted
update community_aggregates ca
set posts = coalesce(cd.posts, 0),
comments = coalesce(cd.comments, 0)
from (
select
c.id,
count(distinct p.id) as posts,
count(distinct ct.id) as comments
from community c
left join post p on c.id = p.community_id
left join comment ct on p.id = ct.post_id
group by c.id
) cd
where ca.community_id = OLD.community_id;
END IF;
return null;
end $$;
@ -59,11 +93,18 @@ returns trigger language plpgsql
as $$
begin
IF (TG_OP = 'INSERT') THEN
update community_aggregates
set comments = comments + 1 from comment c join post p on p.id = c.post_id and p.id = NEW.post_id;
update community_aggregates ca
set comments = comments + 1 from comment c, post p
where p.id = c.post_id
and p.id = NEW.post_id
and ca.community_id = p.community_id;
ELSIF (TG_OP = 'DELETE') THEN
update community_aggregates
set comments = comments - 1 from comment c join post p on p.id = c.post_id and p.id = OLD.post_id;
update community_aggregates ca
set comments = comments - 1 from comment c, post p
where p.id = c.post_id
and p.id = OLD.post_id
and ca.community_id = p.community_id;
END IF;
return null;
end $$;