diff --git a/config/config.exs b/config/config.exs index 277e0f5ed..d523bfa37 100644 --- a/config/config.exs +++ b/config/config.exs @@ -69,7 +69,7 @@ config :groupher_server, :article, min_length: 10, max_length: 20_000, # NOTE: do not change unless you know what you are doing - threads: [:post, :job, :repo, :blog, :works, :radar, :guide], + threads: [:post, :job, :repo, :blog, :works, :radar, :guide, :meetup], # in this period, paged articles will sort front if non-article-author commented # 在此时间段内,一旦有非文章作者的用户评论,该文章就会排到前面 active_period_days: %{ @@ -79,7 +79,8 @@ config :groupher_server, :article, blog: 10, works: 10, radar: 10, - guide: 10 + guide: 10, + meetup: 10 }, # NOTE: if you want to add/remove emotion, just edit the list below diff --git a/lib/groupher_server/cms/models/meetup.ex b/lib/groupher_server/cms/models/meetup.ex new file mode 100644 index 000000000..66cc28d2a --- /dev/null +++ b/lib/groupher_server/cms/models/meetup.ex @@ -0,0 +1,48 @@ +defmodule GroupherServer.CMS.Model.Meetup do + @moduledoc false + alias __MODULE__ + + use Ecto.Schema + use Accessible + + import Ecto.Changeset + import GroupherServer.CMS.Helper.Macros + + alias GroupherServer.CMS + alias CMS.Model.Embeds + + @timestamps_opts [type: :utc_datetime_usec] + + @required_fields ~w(title digest)a + @article_cast_fields general_article_cast_fields() + @optional_fields @article_cast_fields + + @type t :: %Meetup{} + schema "cms_meetups" do + article_tags_field(:meetup) + article_communities_field(:meetup) + general_article_fields(:meetup) + end + + @doc false + def changeset(%Meetup{} = meetup, attrs) do + meetup + |> cast(attrs, @optional_fields ++ @required_fields) + |> validate_required(@required_fields) + |> cast_embed(:meta, required: false, with: &Embeds.ArticleMeta.changeset/2) + |> generl_changeset + end + + @doc false + def update_changeset(%Meetup{} = meetup, attrs) do + meetup + |> cast(attrs, @optional_fields ++ @required_fields) + |> generl_changeset + end + + defp generl_changeset(changeset) do + changeset + |> validate_length(:title, min: 3, max: 50) + |> cast_embed(:emotions, with: &Embeds.ArticleEmotion.changeset/2) + end +end diff --git a/lib/groupher_server/cms/models/meetup_document.ex b/lib/groupher_server/cms/models/meetup_document.ex new file mode 100644 index 000000000..755997cb8 --- /dev/null +++ b/lib/groupher_server/cms/models/meetup_document.ex @@ -0,0 +1,47 @@ +defmodule GroupherServer.CMS.Model.MeetupDocument do + @moduledoc """ + mainly for full-text search + """ + alias __MODULE__ + + use Ecto.Schema + use Accessible + + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] + + alias GroupherServer.CMS + alias CMS.Model.Meetup + + @timestamps_opts [type: :utc_datetime_usec] + + @max_body_length get_config(:article, :max_length) + @min_body_length get_config(:article, :min_length) + + @required_fields ~w(body body_html meetup_id)a + @optional_fields [] + + @type t :: %MeetupDocument{} + schema "meetup_documents" do + belongs_to(:meetup, Meetup, foreign_key: :meetup_id) + + field(:body, :string) + field(:body_html, :string) + field(:toc, :map) + end + + @doc false + def changeset(%MeetupDocument{} = meetup, attrs) do + meetup + |> cast(attrs, @optional_fields ++ @required_fields) + |> validate_required(@required_fields) + |> validate_length(:body, min: @min_body_length, max: @max_body_length) + end + + @doc false + def update_changeset(%MeetupDocument{} = meetup, attrs) do + meetup + |> cast(attrs, @optional_fields ++ @required_fields) + |> validate_length(:body, min: @min_body_length, max: @max_body_length) + end +end diff --git a/lib/groupher_server_web/schema/cms/cms_metrics.ex b/lib/groupher_server_web/schema/cms/cms_metrics.ex index 744bae67f..d35d887f8 100644 --- a/lib/groupher_server_web/schema/cms/cms_metrics.ex +++ b/lib/groupher_server_web/schema/cms/cms_metrics.ex @@ -219,6 +219,13 @@ defmodule GroupherServerWeb.Schema.CMS.Metrics do field(:sort, :sort_enum) end + @desc "meetup_filter doc" + input_object :paged_meetups_filter do + pagination_args() + article_filter_fields() + field(:sort, :sort_enum) + end + @desc "article_filter doc" input_object :paged_repos_filter do @desc "limit of records (default 20), if first > 30, only return 30 at most" diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 2453bf01a..3207e2b46 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -129,6 +129,18 @@ defmodule GroupherServerWeb.Schema.CMS.Types do timestamp_fields(:article) end + object :meetup do + interface(:article) + + general_article_fields() + comments_fields() + + field(:length, :integer) + field(:link_addr, :string) + + timestamp_fields(:article) + end + object :repo do interface(:article) diff --git a/lib/groupher_server_web/schema/cms/mutations/meetup.ex b/lib/groupher_server_web/schema/cms/mutations/meetup.ex new file mode 100644 index 000000000..9ace7625e --- /dev/null +++ b/lib/groupher_server_web/schema/cms/mutations/meetup.ex @@ -0,0 +1,52 @@ +defmodule GroupherServerWeb.Schema.CMS.Mutations.Meetup do + @moduledoc """ + CMS mutations for meetup + """ + use Helper.GqlSchemaSuite + import GroupherServerWeb.Schema.Helper.Mutations + + object :cms_meetup_mutations do + @desc "create a meetup" + field :create_meetup, :meetup do + arg(:title, non_null(:string)) + arg(:body, non_null(:string)) + arg(:digest, non_null(:string)) + arg(:community_id, non_null(:id)) + arg(:thread, :thread, default_value: :meetup) + arg(:article_tags, list_of(:id)) + + middleware(M.Authorize, :login) + middleware(M.PublishThrottle) + resolve(&R.CMS.create_article/3) + middleware(M.Statistics.MakeContribute, for: [:user, :community]) + end + + @desc "update a cms/meetup" + field :update_meetup, :meetup do + arg(:id, non_null(:id)) + arg(:title, :string) + arg(:body, :string) + arg(:digest, :string) + + arg(:article_tags, list_of(:id)) + # ... + + middleware(M.Authorize, :login) + middleware(M.PassportLoader, source: :meetup) + middleware(M.Passport, claim: "owner;cms->c?->meetup.edit") + + resolve(&R.CMS.update_article/3) + end + + article_react_mutations(:meetup, [ + :upvote, + :pin, + :mark_delete, + :delete, + :emotion, + :report, + :sink, + :lock_comment + ]) + end +end diff --git a/priv/repo/migrations/20210626081546_create_meetup.exs b/priv/repo/migrations/20210626081546_create_meetup.exs new file mode 100644 index 000000000..9b108a3c5 --- /dev/null +++ b/priv/repo/migrations/20210626081546_create_meetup.exs @@ -0,0 +1,32 @@ +defmodule GroupherServer.Repo.Migrations.CreateMeetup do + use Ecto.Migration + + def change do + create table(:cms_meetups) do + add(:thread, :string) + add(:title, :string) + add(:digest, :string) + add(:views, :integer, default: 0) + add(:mark_delete, :boolean, default: false) + add(:meta, :map) + add(:emotions, :map) + add(:original_community_id, references(:communities, on_delete: :delete_all)) + add(:author_id, references(:cms_authors, on_delete: :delete_all), null: false) + + add(:active_at, :utc_datetime) + + # reaction + add(:upvotes_count, :integer, default: 0) + add(:collects_count, :integer, default: 0) + + # comments + add(:comments_participants_count, :integer, default: 0) + add(:comments_count, :integer, default: 0) + add(:comments_participants, :map) + + timestamps() + end + + create(index(:cms_meetups, [:author_id])) + end +end diff --git a/priv/repo/migrations/20210626081857_create_meetup_document.exs b/priv/repo/migrations/20210626081857_create_meetup_document.exs new file mode 100644 index 000000000..2824e3d9b --- /dev/null +++ b/priv/repo/migrations/20210626081857_create_meetup_document.exs @@ -0,0 +1,13 @@ +defmodule GroupherServer.Repo.Migrations.CreateMeetupDocument do + use Ecto.Migration + + def change do + create table(:meetup_documents) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all), null: false) + add(:body, :text) + add(:body_html, :text) + add(:markdown, :text) + add(:toc, :map) + end + end +end diff --git a/priv/repo/migrations/20210626082045_create_communities_join_meetups.exs b/priv/repo/migrations/20210626082045_create_communities_join_meetups.exs new file mode 100644 index 000000000..71b67f6d7 --- /dev/null +++ b/priv/repo/migrations/20210626082045_create_communities_join_meetups.exs @@ -0,0 +1,12 @@ +defmodule GroupherServer.Repo.Migrations.CreateCommunitiesJoinMeetups do + use Ecto.Migration + + def change do + create table(:communities_join_meetups) do + add(:community_id, references(:communities, on_delete: :delete_all), null: false) + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all), null: false) + end + + create(unique_index(:communities_join_meetups, [:community_id, :meetup_id])) + end +end diff --git a/priv/repo/migrations/20210626082257_add_meetup_to_others.exs b/priv/repo/migrations/20210626082257_add_meetup_to_others.exs new file mode 100644 index 000000000..642267962 --- /dev/null +++ b/priv/repo/migrations/20210626082257_add_meetup_to_others.exs @@ -0,0 +1,41 @@ +defmodule GroupherServer.Repo.Migrations.AddMeetupToOthers do + use Ecto.Migration + + def change do + alter table(:articles_join_tags) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:abuse_reports) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:article_collects) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:article_upvotes) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:comments) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:pinned_comments) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:articles_users_emotions) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:pinned_articles) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + + alter table(:cited_artiments) do + add(:meetup_id, references(:cms_meetups, on_delete: :delete_all)) + end + end +end diff --git a/test/groupher_server/accounts/published/published_meetup_test.exs b/test/groupher_server/accounts/published/published_meetup_test.exs new file mode 100644 index 000000000..46d83febe --- /dev/null +++ b/test/groupher_server/accounts/published/published_meetup_test.exs @@ -0,0 +1,93 @@ +defmodule GroupherServer.Test.Accounts.Published.Meetup do + use GroupherServer.TestTools + + alias GroupherServer.{Accounts, CMS} + alias Accounts.Model.User + alias Helper.ORM + + @publish_count 10 + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + + {:ok, ~m(user user2 meetup community community2)a} + end + + describe "[publised meetups]" do + test "create meetup should update user published meta", ~m(community user)a do + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, _meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, user} = ORM.find(User, user.id) + assert user.meta.published_meetups_count == 2 + end + + test "fresh user get empty paged published meetups", ~m(user)a do + {:ok, results} = Accounts.paged_published_articles(user, :meetup, %{page: 1, size: 20}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == 0 + end + + test "user can get paged published meetups", ~m(user user2 community community2)a do + pub_meetups = + Enum.reduce(1..@publish_count, [], fn _, acc -> + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + acc ++ [meetup] + end) + + pub_meetups2 = + Enum.reduce(1..@publish_count, [], fn _, acc -> + meetup_attrs = mock_attrs(:meetup, %{community_id: community2.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + acc ++ [meetup] + end) + + # unrelated other user + Enum.reduce(1..5, [], fn _, acc -> + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user2) + + acc ++ [meetup] + end) + + {:ok, results} = Accounts.paged_published_articles(user, :meetup, %{page: 1, size: 20}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == @publish_count * 2 + + random_meetup_id = pub_meetups |> Enum.random() |> Map.get(:id) + random_meetup_id2 = pub_meetups2 |> Enum.random() |> Map.get(:id) + assert results.entries |> Enum.any?(&(&1.id == random_meetup_id)) + assert results.entries |> Enum.any?(&(&1.id == random_meetup_id2)) + end + end + + describe "[publised meetup comments]" do + test "can get published article comments", ~m(meetup user)a do + total_count = 10 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + acc ++ [comment] + end) + + filter = %{page: 1, size: 20} + {:ok, articles} = Accounts.paged_published_comments(user, :meetup, filter) + + entries = articles.entries + article = entries |> List.first() + + assert article.article.id == meetup.id + assert article.article.title == meetup.title + end + end +end diff --git a/test/groupher_server/cms/abuse_reports/meetup_report_test.exs b/test/groupher_server/cms/abuse_reports/meetup_report_test.exs new file mode 100644 index 000000000..b3978504f --- /dev/null +++ b/test/groupher_server/cms/abuse_reports/meetup_report_test.exs @@ -0,0 +1,139 @@ +defmodule GroupherServer.Test.CMS.AbuseReports.MeetupReport do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Meetup + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + {:ok, community} = db_insert(:community) + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 community meetup_attrs)a} + end + + describe "[article meetup report/unreport]" do + test "list article reports should work", ~m(community user user2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user2) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + report = all_reports.entries |> List.first() + assert report.article.id == meetup.id + assert report.article.thread == "MEETUP" + end + + test "report a meetup should have a abuse report record", ~m(community user meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + report = List.first(all_reports.entries) + report_cases = report.report_cases + + assert report.article.id == meetup.id + assert all_reports.total_count == 1 + assert report.report_cases_count == 1 + assert List.first(report_cases).user.login == user.login + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.reported_count == 1 + assert user.id in meetup.meta.reported_user_ids + end + + test "can undo a report", ~m(community user meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + {:ok, _report} = CMS.undo_report_article(:meetup, meetup.id, user) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 0 + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert user.id not in meetup.meta.reported_user_ids + end + + test "can undo a existed report", ~m(community user user2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user2) + {:ok, _report} = CMS.undo_report_article(:meetup, meetup.id, user) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert user2.id in meetup.meta.reported_user_ids + assert user.id not in meetup.meta.reported_user_ids + end + + test "can undo a report with other user report it too", + ~m(community user user2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user2) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 2 + assert Enum.any?(report.report_cases, &(&1.user.login == user.login)) + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + + {:ok, _report} = CMS.undo_report_article(:meetup, meetup.id, user) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 1 + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + end + + test "different user report a comment should have same report with different report cases", + ~m(community user user2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason2", "attr_info 2", user2) + + filter = %{content_type: :meetup, content_id: meetup.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + report = List.first(all_reports.entries) + report_cases = report.report_cases + + assert all_reports.total_count == 1 + assert length(report_cases) == 2 + assert report.report_cases_count == 2 + + assert List.first(report_cases).user.login == user.login + assert List.last(report_cases).user.login == user2.login + end + + test "same user can not report a comment twice", ~m(community meetup_attrs user)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _report} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + + assert {:error, _report} = + CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + end + end +end diff --git a/test/groupher_server/cms/article_community/meetup_test.exs b/test/groupher_server/cms/article_community/meetup_test.exs new file mode 100644 index 000000000..e2047bd1d --- /dev/null +++ b/test/groupher_server/cms/article_community/meetup_test.exs @@ -0,0 +1,85 @@ +defmodule GroupherServer.Test.CMS.ArticleCommunity.Meetup do + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Meetup + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + {:ok, community3} = db_insert(:community) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 community community2 community3 meetup meetup_attrs)a} + end + + describe "[article mirror/move]" do + test "created meetup has origial community info", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :original_community) + + assert meetup.original_community_id == community.id + end + + test "meetup can be move to other community", ~m(user community community2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + assert meetup.original_community_id == community.id + + {:ok, _} = CMS.move_article(:meetup, meetup.id, community2.id) + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: [:original_community, :communities]) + + assert meetup.original_community.id == community2.id + assert not is_nil(Enum.find(meetup.communities, &(&1.id == community2.id))) + end + + test "meetup can be mirror to other community", ~m(user community community2 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :communities) + assert meetup.communities |> length == 1 + + assert not is_nil(Enum.find(meetup.communities, &(&1.id == community.id))) + + {:ok, _} = CMS.mirror_article(:meetup, meetup.id, community2.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :communities) + assert meetup.communities |> length == 2 + assert not is_nil(Enum.find(meetup.communities, &(&1.id == community.id))) + assert not is_nil(Enum.find(meetup.communities, &(&1.id == community2.id))) + end + + test "meetup can be unmirror from community", + ~m(user community community2 community3 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _} = CMS.mirror_article(:meetup, meetup.id, community2.id) + {:ok, _} = CMS.mirror_article(:meetup, meetup.id, community3.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :communities) + assert meetup.communities |> length == 3 + + {:ok, _} = CMS.unmirror_article(:meetup, meetup.id, community3.id) + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :communities) + assert meetup.communities |> length == 2 + + assert is_nil(Enum.find(meetup.communities, &(&1.id == community3.id))) + end + + test "meetup can not unmirror from original community", + ~m(user community community2 community3 meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _} = CMS.mirror_article(:meetup, meetup.id, community2.id) + {:ok, _} = CMS.mirror_article(:meetup, meetup.id, community3.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :communities) + assert meetup.communities |> length == 3 + + {:error, reason} = CMS.unmirror_article(:meetup, meetup.id, community.id) + assert reason |> is_error?(:mirror_article) + end + end +end diff --git a/test/groupher_server/cms/article_tags/meetup_tag_test.exs b/test/groupher_server/cms/article_tags/meetup_tag_test.exs new file mode 100644 index 000000000..eb850225c --- /dev/null +++ b/test/groupher_server/cms/article_tags/meetup_tag_test.exs @@ -0,0 +1,136 @@ +defmodule GroupherServer.Test.CMS.ArticleTag.MeetupTag do + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.{Community, ArticleTag, Meetup} + alias Helper.{ORM} + + setup do + {:ok, user} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, community} = db_insert(:community) + article_tag_attrs = mock_attrs(:article_tag) + article_tag_attrs2 = mock_attrs(:article_tag) + + meetup_attrs = mock_attrs(:meetup) + + {:ok, ~m(user community meetup meetup_attrs article_tag_attrs article_tag_attrs2)a} + end + + describe "[meetup tag CURD]" do + test "create article tag with valid data", ~m(community article_tag_attrs user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + assert article_tag.title == article_tag_attrs.title + assert article_tag.group == article_tag_attrs.group + end + + test "can update an article tag", ~m(community article_tag_attrs user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + + new_attrs = article_tag_attrs |> Map.merge(%{title: "new title"}) + + {:ok, article_tag} = CMS.update_article_tag(article_tag.id, new_attrs) + assert article_tag.title == "new title" + end + + test "create article tag with non-exsit community fails", ~m(article_tag_attrs user)a do + assert {:error, _} = + CMS.create_article_tag( + %Community{id: non_exsit_id()}, + :meetup, + article_tag_attrs, + user + ) + end + + test "tag can be deleted", ~m(community article_tag_attrs user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag} = ORM.find(ArticleTag, article_tag.id) + + {:ok, _} = CMS.delete_article_tag(article_tag.id) + + assert {:error, _} = ORM.find(ArticleTag, article_tag.id) + end + + test "assoc tag should be delete after tag deleted", + ~m(community meetup article_tag_attrs article_tag_attrs2 user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :meetup, article_tag_attrs2, user) + + {:ok, meetup} = CMS.set_article_tag(:meetup, meetup.id, article_tag.id) + {:ok, meetup} = CMS.set_article_tag(:meetup, meetup.id, article_tag2.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :article_tags) + assert exist_in?(article_tag, meetup.article_tags) + assert exist_in?(article_tag2, meetup.article_tags) + + {:ok, _} = CMS.delete_article_tag(article_tag.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :article_tags) + assert not exist_in?(article_tag, meetup.article_tags) + assert exist_in?(article_tag2, meetup.article_tags) + + {:ok, _} = CMS.delete_article_tag(article_tag2.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :article_tags) + assert not exist_in?(article_tag, meetup.article_tags) + assert not exist_in?(article_tag2, meetup.article_tags) + end + end + + describe "[create/update meetup with tags]" do + test "can create meetup with exsited article tags", + ~m(community user meetup_attrs article_tag_attrs article_tag_attrs2)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :meetup, article_tag_attrs2, user) + + meetup_with_tags = + Map.merge(meetup_attrs, %{article_tags: [article_tag.id, article_tag2.id]}) + + {:ok, created} = CMS.create_article(community, :meetup, meetup_with_tags, user) + {:ok, meetup} = ORM.find(Meetup, created.id, preload: :article_tags) + + assert exist_in?(article_tag, meetup.article_tags) + assert exist_in?(article_tag2, meetup.article_tags) + end + + test "can not create meetup with other community's article tags", + ~m(community user meetup_attrs article_tag_attrs article_tag_attrs2)a do + {:ok, community2} = db_insert(:community) + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community2, :meetup, article_tag_attrs2, user) + + meetup_with_tags = + Map.merge(meetup_attrs, %{article_tags: [article_tag.id, article_tag2.id]}) + + {:error, reason} = CMS.create_article(community, :meetup, meetup_with_tags, user) + is_error?(reason, :invalid_domain_tag) + end + end + + describe "[meetup tag set /unset]" do + test "can set a tag ", ~m(community meetup article_tag_attrs article_tag_attrs2 user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :meetup, article_tag_attrs2, user) + + {:ok, meetup} = CMS.set_article_tag(:meetup, meetup.id, article_tag.id) + assert meetup.article_tags |> length == 1 + assert exist_in?(article_tag, meetup.article_tags) + + {:ok, meetup} = CMS.set_article_tag(:meetup, meetup.id, article_tag2.id) + assert meetup.article_tags |> length == 2 + assert exist_in?(article_tag, meetup.article_tags) + assert exist_in?(article_tag2, meetup.article_tags) + + {:ok, meetup} = CMS.unset_article_tag(:meetup, meetup.id, article_tag.id) + assert meetup.article_tags |> length == 1 + assert not exist_in?(article_tag, meetup.article_tags) + assert exist_in?(article_tag2, meetup.article_tags) + + {:ok, meetup} = CMS.unset_article_tag(:meetup, meetup.id, article_tag2.id) + assert meetup.article_tags |> length == 0 + assert not exist_in?(article_tag, meetup.article_tags) + assert not exist_in?(article_tag2, meetup.article_tags) + end + end +end diff --git a/test/groupher_server/cms/articles/meetup_pin_test.exs b/test/groupher_server/cms/articles/meetup_pin_test.exs new file mode 100644 index 000000000..bc45d4ad4 --- /dev/null +++ b/test/groupher_server/cms/articles/meetup_pin_test.exs @@ -0,0 +1,54 @@ +defmodule GroupherServer.Test.CMS.Artilces.MeetupPin do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Community, PinnedArticle} + + @max_pinned_article_count_per_thread Community.max_pinned_article_count_per_thread() + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + {:ok, ~m(user community meetup)a} + end + + describe "[cms meetup pin]" do + test "can pin a meetup", ~m(community meetup)a do + {:ok, _} = CMS.pin_article(:meetup, meetup.id, community.id) + {:ok, pind_article} = ORM.find_by(PinnedArticle, %{meetup_id: meetup.id}) + + assert pind_article.meetup_id == meetup.id + end + + test "one community & thread can only pin certern count of meetup", ~m(community user)a do + Enum.reduce(1..@max_pinned_article_count_per_thread, [], fn _, acc -> + {:ok, new_meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + {:ok, _} = CMS.pin_article(:meetup, new_meetup.id, community.id) + acc + end) + + {:ok, new_meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + {:error, reason} = CMS.pin_article(:meetup, new_meetup.id, community.id) + assert reason |> Keyword.get(:code) == ecode(:too_much_pinned_article) + end + + test "can not pin a non-exsit meetup", ~m(community)a do + assert {:error, _} = CMS.pin_article(:meetup, 8848, community.id) + end + + test "can undo pin to a meetup", ~m(community meetup)a do + {:ok, _} = CMS.pin_article(:meetup, meetup.id, community.id) + + assert {:ok, unpinned} = CMS.undo_pin_article(:meetup, meetup.id, community.id) + + assert {:error, _} = ORM.find_by(PinnedArticle, %{meetup_id: meetup.id}) + end + end +end diff --git a/test/groupher_server/cms/articles/meetup_test.exs b/test/groupher_server/cms/articles/meetup_test.exs new file mode 100644 index 000000000..3ecad3f2b --- /dev/null +++ b/test/groupher_server/cms/articles/meetup_test.exs @@ -0,0 +1,205 @@ +defmodule GroupherServer.Test.Articles.Meetup do + use GroupherServer.TestTools + + alias GroupherServer.{CMS, Repo} + alias Helper.Converter.{EditorToHTML, HtmlSanitizer} + + alias EditorToHTML.{Class, Validator} + alias CMS.Model.{Author, Meetup, Community, ArticleDocument, MeetupDocument} + alias Helper.ORM + + @root_class Class.article() + @last_year Timex.shift(Timex.beginning_of_year(Timex.now()), days: -3, seconds: -1) + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, community} = db_insert(:community) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 community meetup_attrs)a} + end + + describe "[cms meetups curd]" do + test "can create meetup with valid attrs", ~m(user community meetup_attrs)a do + assert {:error, _} = ORM.find_by(Author, user_id: user.id) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + meetup = Repo.preload(meetup, :document) + + body_map = Jason.decode!(meetup.document.body) + + assert meetup.meta.thread == "MEETUP" + + assert meetup.title == meetup_attrs.title + assert body_map |> Validator.is_valid() + + assert meetup.document.body_html + |> String.contains?(~s(
List.first() |> get_in(["data", "text"]) + assert meetup.digest == paragraph_text |> HtmlSanitizer.strip_all_tags() + end + + test "created meetup should have a acitve_at field, same with inserted_at", + ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + assert meetup.active_at == meetup.inserted_at + end + + test "read meetup should update views and meta viewed_user_list", + ~m(meetup_attrs community user user2)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, _} = CMS.read_article(:meetup, meetup.id, user) + {:ok, _created} = ORM.find(Meetup, meetup.id) + + # same user duplicate case + {:ok, _} = CMS.read_article(:meetup, meetup.id, user) + {:ok, created} = ORM.find(Meetup, meetup.id) + + assert created.meta.viewed_user_ids |> length == 1 + assert user.id in created.meta.viewed_user_ids + + {:ok, _} = CMS.read_article(:meetup, meetup.id, user2) + {:ok, created} = ORM.find(Meetup, meetup.id) + + assert created.meta.viewed_user_ids |> length == 2 + assert user.id in created.meta.viewed_user_ids + assert user2.id in created.meta.viewed_user_ids + end + + test "read meetup should contains viewer_has_xxx state", + ~m(meetup_attrs community user user2)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = CMS.read_article(:meetup, meetup.id, user) + + assert not meetup.viewer_has_collected + assert not meetup.viewer_has_upvoted + assert not meetup.viewer_has_reported + + {:ok, meetup} = CMS.read_article(:meetup, meetup.id) + + assert not meetup.viewer_has_collected + assert not meetup.viewer_has_upvoted + assert not meetup.viewer_has_reported + + {:ok, meetup} = CMS.read_article(:meetup, meetup.id, user2) + + assert not meetup.viewer_has_collected + assert not meetup.viewer_has_upvoted + assert not meetup.viewer_has_reported + + {:ok, _} = CMS.upvote_article(:meetup, meetup.id, user) + {:ok, _} = CMS.collect_article(:meetup, meetup.id, user) + {:ok, _} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + + {:ok, meetup} = CMS.read_article(:meetup, meetup.id, user) + + assert meetup.viewer_has_collected + assert meetup.viewer_has_upvoted + assert meetup.viewer_has_reported + end + + test "create meetup with an exsit community fails", ~m(user)a do + invalid_attrs = mock_attrs(:meetup, %{community_id: non_exsit_id()}) + ivalid_community = %Community{id: non_exsit_id()} + + assert {:error, _} = CMS.create_article(ivalid_community, :meetup, invalid_attrs, user) + end + end + + describe "[cms meetup sink/undo_sink]" do + test "if a meetup is too old, read meetup should update can_undo_sink flag", + ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + assert meetup.meta.can_undo_sink + + {:ok, meetup_last_year} = db_insert(:meetup, %{title: "last year", inserted_at: @last_year}) + {:ok, meetup_last_year} = CMS.read_article(:meetup, meetup_last_year.id) + assert not meetup_last_year.meta.can_undo_sink + + {:ok, meetup_last_year} = CMS.read_article(:meetup, meetup_last_year.id, user) + assert not meetup_last_year.meta.can_undo_sink + end + + test "can sink a meetup", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + assert not meetup.meta.is_sinked + + {:ok, meetup} = CMS.sink_article(:meetup, meetup.id) + assert meetup.meta.is_sinked + assert meetup.active_at == meetup.inserted_at + end + + test "can undo sink meetup", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = CMS.sink_article(:meetup, meetup.id) + assert meetup.meta.is_sinked + assert meetup.meta.last_active_at == meetup.active_at + + {:ok, meetup} = CMS.undo_sink_article(:meetup, meetup.id) + assert not meetup.meta.is_sinked + assert meetup.active_at == meetup.meta.last_active_at + end + + test "can not undo sink to old meetup", ~m()a do + {:ok, meetup_last_year} = db_insert(:meetup, %{title: "last year", inserted_at: @last_year}) + + {:error, reason} = CMS.undo_sink_article(:meetup, meetup_last_year.id) + is_error?(reason, :undo_sink_old_article) + end + end + + describe "[cms meetup document]" do + test "will create related document after create", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = CMS.read_article(:meetup, meetup.id) + assert not is_nil(meetup.document.body_html) + {:ok, meetup} = CMS.read_article(:meetup, meetup.id, user) + assert not is_nil(meetup.document.body_html) + + {:ok, article_doc} = + ORM.find_by(ArticleDocument, %{article_id: meetup.id, thread: "MEETUP"}) + + {:ok, meetup_doc} = ORM.find_by(MeetupDocument, %{meetup_id: meetup.id}) + + assert meetup.document.body == meetup_doc.body + assert article_doc.body == meetup_doc.body + end + + @tag :wip + test "delete meetup should also delete related document", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _article_doc} = + ORM.find_by(ArticleDocument, %{article_id: meetup.id, thread: "MEETUP"}) + + {:ok, _meetup_doc} = ORM.find_by(MeetupDocument, %{meetup_id: meetup.id}) + + {:ok, _} = CMS.delete_article(meetup) + + {:error, _} = ORM.find(Meetup, meetup.id) + {:error, _} = ORM.find_by(ArticleDocument, %{article_id: meetup.id, thread: "MEETUP"}) + {:error, _} = ORM.find_by(MeetupDocument, %{meetup_id: meetup.id}) + end + + test "update meetup should also update related document", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + body = mock_rich_text(~s(new content)) + {:ok, meetup} = CMS.update_article(meetup, %{body: body}) + + {:ok, article_doc} = + ORM.find_by(ArticleDocument, %{article_id: meetup.id, thread: "MEETUP"}) + + {:ok, meetup_doc} = ORM.find_by(MeetupDocument, %{meetup_id: meetup.id}) + + assert String.contains?(meetup_doc.body, "new content") + assert String.contains?(article_doc.body, "new content") + end + end +end diff --git a/test/groupher_server/cms/comments/guide_comment_emotions_test.exs b/test/groupher_server/cms/comments/guide_comment_emotions_test.exs new file mode 100644 index 000000000..dbc3b5d5b --- /dev/null +++ b/test/groupher_server/cms/comments/guide_comment_emotions_test.exs @@ -0,0 +1,192 @@ +defmodule GroupherServer.Test.CMS.Comments.GuideCommentEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Comment, Embeds, CommentUserEmotion} + + @default_emotions Embeds.CommentEmotion.default_emotions() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + {:ok, guide} = db_insert(:guide) + + {:ok, ~m(user user2 user3 guide)a} + end + + describe "[emotion in paged article comment]" do + test "login user should got viewer has emotioned status", ~m(guide user)a do + total_count = 0 + page_number = 10 + page_size = 20 + + all_comment = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + acc ++ [comment] + end) + + first_comment = List.first(all_comment) + + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :popcorn, user) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies, + user + ) + + target = Enum.find(paged_comments.entries, &(&1.id == first_comment.id)) + + assert target.emotions.downvote_count == 1 + assert user_exist_in?(user, target.emotions.latest_downvote_users) + assert target.emotions.viewer_has_downvoteed + + assert target.emotions.beer_count == 1 + assert user_exist_in?(user, target.emotions.latest_beer_users) + assert target.emotions.viewer_has_beered + + assert target.emotions.popcorn_count == 1 + assert user_exist_in?(user, target.emotions.latest_popcorn_users) + assert target.emotions.viewer_has_popcorned + end + end + + describe "[basic article comment emotion]" do + test "comment has default emotions after created", ~m(guide user)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + emotions = parent_comment.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + test "can make emotion to comment", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "can undo emotion to comment", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + assert emotions.downvote_count == 0 + assert not user_exist_in?(user, emotions.latest_downvote_users) + assert not user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "same user make same emotion to same comment.", ~m(guide user)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert parent_comment.emotions.downvote_count == 1 + assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) + end + + test "same user same emotion to same comment only have one user_emotion record", + ~m(guide user)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + {:ok, records} = ORM.find_all(CommentUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = + ORM.find_by(CommentUserEmotion, %{ + comment_id: parent_comment.id, + user_id: user.id + }) + + assert record.downvote + assert record.heart + end + + test "different user can make same emotions on same comment", ~m(guide user user2 user3)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user2) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + # IO.inspect(emotions, label: "the parent_comment") + + assert emotions.beer_count == 3 + assert user_exist_in?(user, emotions.latest_beer_users) + assert user_exist_in?(user2, emotions.latest_beer_users) + assert user_exist_in?(user3, emotions.latest_beer_users) + end + + test "same user can make differcent emotions on same comment", ~m(guide user)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 1 + assert user_exist_in?(user, emotions.latest_downvote_users) + + assert emotions.beer_count == 1 + assert user_exist_in?(user, emotions.latest_beer_users) + + assert emotions.heart_count == 1 + assert user_exist_in?(user, emotions.latest_heart_users) + + assert emotions.orz_count == 1 + assert user_exist_in?(user, emotions.latest_orz_users) + + assert emotions.pill_count == 0 + assert not user_exist_in?(user, emotions.latest_pill_users) + + assert emotions.biceps_count == 0 + assert not user_exist_in?(user, emotions.latest_biceps_users) + + assert emotions.confused_count == 0 + assert not user_exist_in?(user, emotions.latest_confused_users) + end + end +end diff --git a/test/groupher_server/cms/comments/guide_comment_replies_test.exs b/test/groupher_server/cms/comments/guide_comment_replies_test.exs new file mode 100644 index 000000000..58e96ec0b --- /dev/null +++ b/test/groupher_server/cms/comments/guide_comment_replies_test.exs @@ -0,0 +1,204 @@ +defmodule GroupherServer.Test.CMS.Comments.GuideCommentReplies do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Comment, Guide} + + @max_parent_replies_count Comment.max_parent_replies_count() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, guide} = db_insert(:guide) + + {:ok, ~m(user user2 guide)a} + end + + describe "[basic article comment replies]" do + test "exsit comment can be reply", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, replyed_comment} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + assert replyed_comment.reply_to.id == parent_comment.id + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert exist_in?(replyed_comment, parent_comment.replies) + end + + test "deleted comment can not be reply", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.delete_comment(parent_comment) + + {:error, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + end + + test "multi reply should belong to one parent comment", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + end + + test "reply to reply inside a comment should belong same parent comment", + ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(replyed_comment_1.id, mock_comment(), user2) + + {:ok, replyed_comment_3} = CMS.reply_comment(replyed_comment_2.id, mock_comment(), user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + # IO.inspect(parent_comment.replies, label: "parent_comment.replies") + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + assert exist_in?(replyed_comment_3, parent_comment.replies) + + {:ok, replyed_comment_1} = ORM.find(Comment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(Comment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(Comment, replyed_comment_3.id) + + assert replyed_comment_1.reply_to_id == parent_comment.id + assert replyed_comment_2.reply_to_id == replyed_comment_1.id + assert replyed_comment_3.reply_to_id == replyed_comment_2.id + end + + test "reply to reply inside a comment should have is_reply_to_others flag in meta", + ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(replyed_comment_1.id, mock_comment(), user2) + + {:ok, replyed_comment_3} = CMS.reply_comment(replyed_comment_2.id, mock_comment(), user) + + {:ok, _parent_comment} = ORM.find(Comment, parent_comment.id) + + {:ok, replyed_comment_1} = ORM.find(Comment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(Comment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(Comment, replyed_comment_3.id) + + assert not replyed_comment_1.meta.is_reply_to_others + assert replyed_comment_2.meta.is_reply_to_others + assert replyed_comment_3.meta.is_reply_to_others + end + + test "comment replies only contains @max_parent_replies_count replies", ~m(guide user)a do + total_reply_count = @max_parent_replies_count + 1 + + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("reply_content_#{n}"), user) + + acc ++ [replyed_comment] + end) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert length(parent_comment.replies) == @max_parent_replies_count + assert exist_in?(Enum.at(reply_comment_list, 0), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 1), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 2), parent_comment.replies) + assert not exist_in?(List.last(reply_comment_list), parent_comment.replies) + end + + test "replyed user should appear in article comment participants", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, article} = ORM.find(Guide, guide.id) + + assert exist_in?(user, article.comments_participants) + assert exist_in?(user2, article.comments_participants) + end + + test "replies count should inc by 1 after got replyed", ~m(guide user user2)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + assert parent_comment.replies_count === 0 + + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + assert parent_comment.replies_count === 1 + + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + assert parent_comment.replies_count === 2 + end + end + + describe "[paged article comment replies]" do + test "can get paged replies of a parent comment", ~m(guide user)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, paged_replies} = CMS.paged_comment_replies(parent_comment.id, %{page: 1, size: 20}) + assert is_valid_pagination?(paged_replies, :raw, :empty) + + total_reply_count = 30 + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("reply_content_#{n}"), user) + + acc ++ [replyed_comment] + end) + + {:ok, paged_replies} = CMS.paged_comment_replies(parent_comment.id, %{page: 1, size: 20}) + + assert total_reply_count == paged_replies.total_count + assert is_valid_pagination?(paged_replies, :raw) + + assert exist_in?(Enum.at(reply_comment_list, 0), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 1), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 2), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 3), paged_replies.entries) + end + + test "can get reply_to info of a parent comment", ~m(guide user)a do + page_number = 1 + page_size = 10 + + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, reply_comment} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, reply_comment2} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :timeline + ) + + reply_comment = Enum.find(paged_comments.entries, &(&1.id == reply_comment.id)) + + assert reply_comment.reply_to.id == parent_comment.id + assert reply_comment.reply_to.body_html == parent_comment.body_html + assert reply_comment.reply_to.author.id == parent_comment.author_id + + reply_comment2 = Enum.find(paged_comments.entries, &(&1.id == reply_comment2.id)) + + assert reply_comment2.reply_to.id == parent_comment.id + assert reply_comment2.reply_to.body_html == parent_comment.body_html + assert reply_comment2.reply_to.author.id == parent_comment.author_id + end + end +end diff --git a/test/groupher_server/cms/comments/guide_comment_test.exs b/test/groupher_server/cms/comments/guide_comment_test.exs new file mode 100644 index 000000000..352b2efed --- /dev/null +++ b/test/groupher_server/cms/comments/guide_comment_test.exs @@ -0,0 +1,662 @@ +defmodule GroupherServer.Test.CMS.Comments.GuideComment do + @moduledoc false + + use GroupherServer.TestTools + import Helper.Utils, only: [get_config: 2] + + alias Helper.ORM + alias GroupherServer.{Accounts, CMS} + alias Accounts.Model.User + alias CMS.Model.{Comment, PinnedComment, Embeds, Guide} + + @active_period get_config(:article, :active_period_days) + + @delete_hint Comment.delete_hint() + @report_threshold_for_fold Comment.report_threshold_for_fold() + @default_comment_meta Embeds.CommentMeta.default_meta() + @pinned_comment_limit Comment.pinned_comment_limit() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + + {:ok, ~m(community user user2 guide)a} + end + + describe "[basic article comment]" do + test "guide are supported by article comment.", ~m(user guide)a do + {:ok, guide_comment_1} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, guide_comment_2} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :comments) + + assert exist_in?(guide_comment_1, guide.comments) + assert exist_in?(guide_comment_2, guide.comments) + end + + test "comment should have default meta after create", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + assert comment.meta |> Map.from_struct() |> Map.delete(:id) == @default_comment_meta + end + + test "create comment should update active timestamp of guide", ~m(user guide)a do + Process.sleep(1000) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, guide} = ORM.find(Guide, guide.id, preload: :comments) + + assert not is_nil(guide.active_at) + assert guide.active_at > guide.inserted_at + end + + test "guide author create comment will not update active timestamp", ~m(community user)a do + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = ORM.find(Guide, guide.id, preload: [author: :user]) + + Process.sleep(1000) + + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), guide.author.user) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :comments) + + assert not is_nil(guide.active_at) + assert guide.active_at == guide.inserted_at + end + + test "old guide will not update active after comment created", ~m(user)a do + active_period_days = Map.get(@active_period, :guide) + + inserted_at = + Timex.shift(Timex.now(), days: -(active_period_days - 1)) |> Timex.to_datetime() + + {:ok, guide} = db_insert(:guide, %{inserted_at: inserted_at}) + Process.sleep(1000) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, guide} = ORM.find(Guide, guide.id) + + assert guide.active_at |> DateTime.to_date() == DateTime.utc_now() |> DateTime.to_date() + + ##### + inserted_at = + Timex.shift(Timex.now(), days: -(active_period_days + 1)) |> Timex.to_datetime() + + {:ok, guide} = db_insert(:guide, %{inserted_at: inserted_at}) + Process.sleep(3000) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, guide} = ORM.find(Guide, guide.id) + + assert guide.active_at |> DateTime.to_unix() !== DateTime.utc_now() |> DateTime.to_unix() + end + + test "comment can be updated", ~m(guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, updated_comment} = CMS.update_comment(comment, mock_comment("updated content")) + + assert updated_comment.body_html |> String.contains?(~s(updated content
)) + end + end + + describe "[article comment floor]" do + test "comment will have a floor number after created", ~m(user guide)a do + {:ok, guide_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, guide_comment2} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, guide_comment} = ORM.find(Comment, guide_comment.id) + {:ok, guide_comment2} = ORM.find(Comment, guide_comment2.id) + + assert guide_comment.floor == 1 + assert guide_comment2.floor == 2 + end + end + + describe "[article comment participator for guide]" do + test "guide will have participator after comment created", ~m(user guide)a do + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, guide} = ORM.find(Guide, guide.id) + + participator = List.first(guide.comments_participants) + assert participator.id == user.id + end + + test "psot participator will not contains same user", ~m(user guide)a do + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, guide} = ORM.find(Guide, guide.id) + + assert 1 == length(guide.comments_participants) + end + + test "recent comment user should appear at first of the psot participants", + ~m(user user2 guide)a do + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user2) + + {:ok, guide} = ORM.find(Guide, guide.id) + + participator = List.first(guide.comments_participants) + + assert participator.id == user2.id + end + end + + describe "[article comment upvotes]" do + test "user can upvote a guide comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + CMS.upvote_comment(comment.id, user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + + assert 1 == length(comment.upvotes) + assert List.first(comment.upvotes).user_id == user.id + end + + test "article author upvote guide comment will have flag", ~m(guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, author_user} = ORM.find(User, guide.author.user.id) + + CMS.upvote_comment(comment.id, author_user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + assert comment.meta.is_article_author_upvoted + end + + test "user upvote guide comment will add id to upvoted_user_ids", ~m(guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = CMS.upvote_comment(comment.id, user) + + assert user.id in comment.meta.upvoted_user_ids + end + + test "user undo upvote guide comment will remove id from upvoted_user_ids", + ~m(guide user user2)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.upvote_comment(comment.id, user) + {:ok, comment} = CMS.upvote_comment(comment.id, user2) + + assert user2.id in comment.meta.upvoted_user_ids + assert user.id in comment.meta.upvoted_user_ids + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user2) + + assert user.id in comment.meta.upvoted_user_ids + assert user2.id not in comment.meta.upvoted_user_ids + end + + test "user upvote a already-upvoted comment fails", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + CMS.upvote_comment(comment.id, user) + {:error, _} = CMS.upvote_comment(comment.id, user) + end + + test "upvote comment should inc the comment's upvotes_count", ~m(user user2 guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.upvotes_count == 0 + + {:ok, _} = CMS.upvote_comment(comment.id, user) + {:ok, _} = CMS.upvote_comment(comment.id, user2) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.upvotes_count == 2 + end + + test "user can undo upvote a guide comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + CMS.upvote_comment(comment.id, user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + assert 1 == length(comment.upvotes) + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + + test "user can undo upvote a guide comment with no upvote", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + end + + describe "[article comment fold/unfold]" do + test "user can fold a comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert not comment.is_folded + + {:ok, comment} = CMS.fold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.is_folded + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.folded_comment_count == 1 + end + + test "user can unfold a comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.fold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert comment.is_folded + + {:ok, _comment} = CMS.unfold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert not comment.is_folded + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.folded_comment_count == 0 + end + end + + describe "[article comment pin/unpin]" do + test "user can pin a comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert not comment.is_pinned + + {:ok, comment} = CMS.pin_comment(comment.id) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert comment.is_pinned + + {:ok, pined_record} = PinnedComment |> ORM.find_by(%{guide_id: guide.id}) + assert pined_record.guide_id == guide.id + end + + test "user can unpin a comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _comment} = CMS.pin_comment(comment.id) + {:ok, comment} = CMS.undo_pin_comment(comment.id) + + assert not comment.is_pinned + assert {:error, _} = PinnedComment |> ORM.find_by(%{comment_id: comment.id}) + end + + test "pinned comments has a limit for each article", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + Enum.reduce(0..(@pinned_comment_limit - 1), [], fn _, _acc -> + {:ok, _comment} = CMS.pin_comment(comment.id) + end) + + assert {:error, _} = CMS.pin_comment(comment.id) + end + end + + describe "[article comment report/unreport]" do + # + # test "user can report a comment", ~m(user guide)a do + # {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + # {:ok, comment} = ORM.find(Comment, comment.id) + + # {:ok, comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + # {:ok, comment} = ORM.find(Comment, comment.id) + # end + + # + # test "user can unreport a comment", ~m(user guide)a do + # {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + # {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + # {:ok, comment} = ORM.find(Comment, comment.id) + + # {:ok, _comment} = CMS.undo_report_comment(comment.id, user) + # {:ok, comment} = ORM.find(Comment, comment.id) + # end + + test "can undo a report with other user report it too", ~m(user user2 guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user2) + + filter = %{content_type: :comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 2 + assert Enum.any?(report.report_cases, &(&1.user.login == user.login)) + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + + {:ok, _report} = CMS.undo_report_article(:comment, comment.id, user) + + filter = %{content_type: :comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 1 + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + end + + test "report user < @report_threshold_for_fold will not fold comment", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold - 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + end) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert not comment.is_folded + end + + test "report user > @report_threshold_for_fold will cause comment fold", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold + 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + end) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.is_folded + end + end + + describe "paged article comments" do + test "can load paged comments participants of a article", ~m(user guide)a do + total_count = 30 + page_size = 10 + thread = :guide + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, new_user} = db_insert(:user) + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), new_user) + + acc ++ [comment] + end) + + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, results} = + CMS.paged_comments_participants(thread, guide.id, %{page: 1, size: page_size}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == total_count + 1 + end + + test "paged article comments folded flag should be false", ~m(user guide)a do + total_count = 30 + page_number = 1 + page_size = 35 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies + ) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + assert not random_comment.is_folded + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + + test "paged article comments should contains pinned comments at top position", + ~m(user guide)a do + total_count = 20 + page_number = 1 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, random_comment_2} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, pined_comment_1} = CMS.pin_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert pined_comment_1.id == List.first(paged_comments.entries) |> Map.get(:id) + assert pined_comment_2.id == Enum.at(paged_comments.entries, 1) |> Map.get(:id) + + assert paged_comments.total_count == total_count + 2 + end + + test "only page 1 have pinned coments", + ~m(user guide)a do + total_count = 20 + page_number = 2 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, random_comment_2} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, pined_comment_1} = CMS.pin_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert not exist_in?(pined_comment_1, paged_comments.entries) + assert not exist_in?(pined_comment_2, paged_comments.entries) + + assert paged_comments.total_count == total_count + end + + test "paged article comments should not contains folded and repoted comments", + ~m(user guide)a do + total_count = 15 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment_1 = all_comments |> Enum.at(0) + random_comment_2 = all_comments |> Enum.at(1) + random_comment_3 = all_comments |> Enum.at(3) + + {:ok, _comment} = CMS.fold_comment(random_comment_1.id, user) + {:ok, _comment} = CMS.fold_comment(random_comment_2.id, user) + {:ok, _comment} = CMS.fold_comment(random_comment_3.id, user) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert not exist_in?(random_comment_1, paged_comments.entries) + assert not exist_in?(random_comment_2, paged_comments.entries) + assert not exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count - 3 == paged_comments.total_count + end + + test "can loaded paged folded comment", ~m(user guide)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_folded_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + CMS.fold_comment(comment.id, user) + + acc ++ [comment] + end) + + random_comment_1 = all_folded_comments |> Enum.at(1) + random_comment_2 = all_folded_comments |> Enum.at(3) + random_comment_3 = all_folded_comments |> Enum.at(5) + + {:ok, paged_comments} = + CMS.paged_folded_comments(:guide, guide.id, %{page: page_number, size: page_size}) + + assert exist_in?(random_comment_1, paged_comments.entries) + assert exist_in?(random_comment_2, paged_comments.entries) + assert exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + end + + describe "[article comment delete]" do + test "delete comment still exsit in paged list and content is gone", ~m(user guide)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, deleted_comment} = CMS.delete_comment(random_comment) + + {:ok, paged_comments} = + CMS.paged_comments( + :guide, + guide.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert exist_in?(deleted_comment, paged_comments.entries) + assert deleted_comment.is_deleted + assert deleted_comment.body_html == @delete_hint + end + + test "delete comment still update article's comments_count field", ~m(user guide)a do + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, guide} = ORM.find(Guide, guide.id) + + assert guide.comments_count == 5 + + {:ok, _} = CMS.delete_comment(comment) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.comments_count == 4 + end + + test "delete comment still delete pinned record if needed", ~m(user guide)a do + total_count = 10 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, _comment} = CMS.pin_comment(random_comment.id) + {:ok, _comment} = ORM.find(Comment, random_comment.id) + + {:ok, _} = CMS.delete_comment(random_comment) + assert {:error, _comment} = ORM.find(PinnedComment, random_comment.id) + end + end + + describe "[article comment info]" do + test "author of the article comment a comment should have flag", ~m(user guide)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + assert not comment.is_article_author + + author_user = guide.author.user + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), author_user) + assert comment.is_article_author + end + end + + describe "[lock/unlock guide comment]" do + test "locked guide can not be comment", ~m(user guide)a do + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.lock_article_comments(:guide, guide.id) + + {:error, reason} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + assert reason |> is_error?(:article_comments_locked) + + {:ok, _} = CMS.undo_lock_article_comments(:guide, guide.id) + {:ok, _} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + end + + test "locked guide can not by reply", ~m(user guide)a do + {:ok, parent_comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, _} = CMS.lock_article_comments(:guide, guide.id) + + {:error, reason} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + assert reason |> is_error?(:article_comments_locked) + + {:ok, _} = CMS.undo_lock_article_comments(:guide, guide.id) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + end + end +end diff --git a/test/groupher_server/cms/comments/meetup_comment_emotions_test.exs b/test/groupher_server/cms/comments/meetup_comment_emotions_test.exs new file mode 100644 index 000000000..bf61cf722 --- /dev/null +++ b/test/groupher_server/cms/comments/meetup_comment_emotions_test.exs @@ -0,0 +1,192 @@ +defmodule GroupherServer.Test.CMS.Comments.MeetupCommentEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Comment, Embeds, CommentUserEmotion} + + @default_emotions Embeds.CommentEmotion.default_emotions() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + {:ok, meetup} = db_insert(:meetup) + + {:ok, ~m(user user2 user3 meetup)a} + end + + describe "[emotion in paged article comment]" do + test "login user should got viewer has emotioned status", ~m(meetup user)a do + total_count = 0 + page_number = 10 + page_size = 20 + + all_comment = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + acc ++ [comment] + end) + + first_comment = List.first(all_comment) + + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :popcorn, user) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies, + user + ) + + target = Enum.find(paged_comments.entries, &(&1.id == first_comment.id)) + + assert target.emotions.downvote_count == 1 + assert user_exist_in?(user, target.emotions.latest_downvote_users) + assert target.emotions.viewer_has_downvoteed + + assert target.emotions.beer_count == 1 + assert user_exist_in?(user, target.emotions.latest_beer_users) + assert target.emotions.viewer_has_beered + + assert target.emotions.popcorn_count == 1 + assert user_exist_in?(user, target.emotions.latest_popcorn_users) + assert target.emotions.viewer_has_popcorned + end + end + + describe "[basic article comment emotion]" do + test "comment has default emotions after created", ~m(meetup user)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + emotions = parent_comment.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + test "can make emotion to comment", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "can undo emotion to comment", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + assert emotions.downvote_count == 0 + assert not user_exist_in?(user, emotions.latest_downvote_users) + assert not user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "same user make same emotion to same comment.", ~m(meetup user)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert parent_comment.emotions.downvote_count == 1 + assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) + end + + test "same user same emotion to same comment only have one user_emotion record", + ~m(meetup user)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + {:ok, records} = ORM.find_all(CommentUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = + ORM.find_by(CommentUserEmotion, %{ + comment_id: parent_comment.id, + user_id: user.id + }) + + assert record.downvote + assert record.heart + end + + test "different user can make same emotions on same comment", ~m(meetup user user2 user3)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user2) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + # IO.inspect(emotions, label: "the parent_comment") + + assert emotions.beer_count == 3 + assert user_exist_in?(user, emotions.latest_beer_users) + assert user_exist_in?(user2, emotions.latest_beer_users) + assert user_exist_in?(user3, emotions.latest_beer_users) + end + + test "same user can make differcent emotions on same comment", ~m(meetup user)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(Comment, parent_comment.id) + + assert emotions.downvote_count == 1 + assert user_exist_in?(user, emotions.latest_downvote_users) + + assert emotions.beer_count == 1 + assert user_exist_in?(user, emotions.latest_beer_users) + + assert emotions.heart_count == 1 + assert user_exist_in?(user, emotions.latest_heart_users) + + assert emotions.orz_count == 1 + assert user_exist_in?(user, emotions.latest_orz_users) + + assert emotions.pill_count == 0 + assert not user_exist_in?(user, emotions.latest_pill_users) + + assert emotions.biceps_count == 0 + assert not user_exist_in?(user, emotions.latest_biceps_users) + + assert emotions.confused_count == 0 + assert not user_exist_in?(user, emotions.latest_confused_users) + end + end +end diff --git a/test/groupher_server/cms/comments/meetup_comment_replies_test.exs b/test/groupher_server/cms/comments/meetup_comment_replies_test.exs new file mode 100644 index 000000000..0e6696ecc --- /dev/null +++ b/test/groupher_server/cms/comments/meetup_comment_replies_test.exs @@ -0,0 +1,204 @@ +defmodule GroupherServer.Test.CMS.Comments.MeetupCommentReplies do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Comment, Meetup} + + @max_parent_replies_count Comment.max_parent_replies_count() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + + {:ok, ~m(user user2 meetup)a} + end + + describe "[basic article comment replies]" do + test "exsit comment can be reply", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, replyed_comment} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + assert replyed_comment.reply_to.id == parent_comment.id + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert exist_in?(replyed_comment, parent_comment.replies) + end + + test "deleted comment can not be reply", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.delete_comment(parent_comment) + + {:error, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + end + + test "multi reply should belong to one parent comment", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + end + + test "reply to reply inside a comment should belong same parent comment", + ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(replyed_comment_1.id, mock_comment(), user2) + + {:ok, replyed_comment_3} = CMS.reply_comment(replyed_comment_2.id, mock_comment(), user) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + # IO.inspect(parent_comment.replies, label: "parent_comment.replies") + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + assert exist_in?(replyed_comment_3, parent_comment.replies) + + {:ok, replyed_comment_1} = ORM.find(Comment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(Comment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(Comment, replyed_comment_3.id) + + assert replyed_comment_1.reply_to_id == parent_comment.id + assert replyed_comment_2.reply_to_id == replyed_comment_1.id + assert replyed_comment_3.reply_to_id == replyed_comment_2.id + end + + test "reply to reply inside a comment should have is_reply_to_others flag in meta", + ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(replyed_comment_1.id, mock_comment(), user2) + + {:ok, replyed_comment_3} = CMS.reply_comment(replyed_comment_2.id, mock_comment(), user) + + {:ok, _parent_comment} = ORM.find(Comment, parent_comment.id) + + {:ok, replyed_comment_1} = ORM.find(Comment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(Comment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(Comment, replyed_comment_3.id) + + assert not replyed_comment_1.meta.is_reply_to_others + assert replyed_comment_2.meta.is_reply_to_others + assert replyed_comment_3.meta.is_reply_to_others + end + + test "comment replies only contains @max_parent_replies_count replies", ~m(meetup user)a do + total_reply_count = @max_parent_replies_count + 1 + + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("reply_content_#{n}"), user) + + acc ++ [replyed_comment] + end) + + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + + assert length(parent_comment.replies) == @max_parent_replies_count + assert exist_in?(Enum.at(reply_comment_list, 0), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 1), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 2), parent_comment.replies) + assert not exist_in?(List.last(reply_comment_list), parent_comment.replies) + end + + test "replyed user should appear in article comment participants", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, article} = ORM.find(Meetup, meetup.id) + + assert exist_in?(user, article.comments_participants) + assert exist_in?(user2, article.comments_participants) + end + + test "replies count should inc by 1 after got replyed", ~m(meetup user user2)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + assert parent_comment.replies_count === 0 + + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + assert parent_comment.replies_count === 1 + + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + {:ok, parent_comment} = ORM.find(Comment, parent_comment.id) + assert parent_comment.replies_count === 2 + end + end + + describe "[paged article comment replies]" do + test "can get paged replies of a parent comment", ~m(meetup user)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, paged_replies} = CMS.paged_comment_replies(parent_comment.id, %{page: 1, size: 20}) + assert is_valid_pagination?(paged_replies, :raw, :empty) + + total_reply_count = 30 + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("reply_content_#{n}"), user) + + acc ++ [replyed_comment] + end) + + {:ok, paged_replies} = CMS.paged_comment_replies(parent_comment.id, %{page: 1, size: 20}) + + assert total_reply_count == paged_replies.total_count + assert is_valid_pagination?(paged_replies, :raw) + + assert exist_in?(Enum.at(reply_comment_list, 0), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 1), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 2), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 3), paged_replies.entries) + end + + test "can get reply_to info of a parent comment", ~m(meetup user)a do + page_number = 1 + page_size = 10 + + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, reply_comment} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, reply_comment2} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :timeline + ) + + reply_comment = Enum.find(paged_comments.entries, &(&1.id == reply_comment.id)) + + assert reply_comment.reply_to.id == parent_comment.id + assert reply_comment.reply_to.body_html == parent_comment.body_html + assert reply_comment.reply_to.author.id == parent_comment.author_id + + reply_comment2 = Enum.find(paged_comments.entries, &(&1.id == reply_comment2.id)) + + assert reply_comment2.reply_to.id == parent_comment.id + assert reply_comment2.reply_to.body_html == parent_comment.body_html + assert reply_comment2.reply_to.author.id == parent_comment.author_id + end + end +end diff --git a/test/groupher_server/cms/comments/meetup_comment_test.exs b/test/groupher_server/cms/comments/meetup_comment_test.exs new file mode 100644 index 000000000..29935f2ad --- /dev/null +++ b/test/groupher_server/cms/comments/meetup_comment_test.exs @@ -0,0 +1,662 @@ +defmodule GroupherServer.Test.CMS.Comments.MeetupComment do + @moduledoc false + + use GroupherServer.TestTools + import Helper.Utils, only: [get_config: 2] + + alias Helper.ORM + alias GroupherServer.{Accounts, CMS} + alias Accounts.Model.User + alias CMS.Model.{Comment, PinnedComment, Embeds, Meetup} + + @active_period get_config(:article, :active_period_days) + + @delete_hint Comment.delete_hint() + @report_threshold_for_fold Comment.report_threshold_for_fold() + @default_comment_meta Embeds.CommentMeta.default_meta() + @pinned_comment_limit Comment.pinned_comment_limit() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, community} = db_insert(:community) + + {:ok, ~m(community user user2 meetup)a} + end + + describe "[basic article comment]" do + test "meetup are supported by article comment.", ~m(user meetup)a do + {:ok, meetup_comment_1} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, meetup_comment_2} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :comments) + + assert exist_in?(meetup_comment_1, meetup.comments) + assert exist_in?(meetup_comment_2, meetup.comments) + end + + test "comment should have default meta after create", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + assert comment.meta |> Map.from_struct() |> Map.delete(:id) == @default_comment_meta + end + + test "create comment should update active timestamp of meetup", ~m(user meetup)a do + Process.sleep(1000) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :comments) + + assert not is_nil(meetup.active_at) + assert meetup.active_at > meetup.inserted_at + end + + test "meetup author create comment will not update active timestamp", ~m(community user)a do + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: [author: :user]) + + Process.sleep(1000) + + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), meetup.author.user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id, preload: :comments) + + assert not is_nil(meetup.active_at) + assert meetup.active_at == meetup.inserted_at + end + + test "old meetup will not update active after comment created", ~m(user)a do + active_period_days = Map.get(@active_period, :meetup) + + inserted_at = + Timex.shift(Timex.now(), days: -(active_period_days - 1)) |> Timex.to_datetime() + + {:ok, meetup} = db_insert(:meetup, %{inserted_at: inserted_at}) + Process.sleep(1000) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert meetup.active_at |> DateTime.to_date() == DateTime.utc_now() |> DateTime.to_date() + + ##### + inserted_at = + Timex.shift(Timex.now(), days: -(active_period_days + 1)) |> Timex.to_datetime() + + {:ok, meetup} = db_insert(:meetup, %{inserted_at: inserted_at}) + Process.sleep(3000) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert meetup.active_at |> DateTime.to_unix() !== DateTime.utc_now() |> DateTime.to_unix() + end + + test "comment can be updated", ~m(meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, updated_comment} = CMS.update_comment(comment, mock_comment("updated content")) + + assert updated_comment.body_html |> String.contains?(~s(updated content)) + end + end + + describe "[article comment floor]" do + test "comment will have a floor number after created", ~m(user meetup)a do + {:ok, meetup_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, meetup_comment2} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, meetup_comment} = ORM.find(Comment, meetup_comment.id) + {:ok, meetup_comment2} = ORM.find(Comment, meetup_comment2.id) + + assert meetup_comment.floor == 1 + assert meetup_comment2.floor == 2 + end + end + + describe "[article comment participator for meetup]" do + test "meetup will have participator after comment created", ~m(user meetup)a do + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + participator = List.first(meetup.comments_participants) + assert participator.id == user.id + end + + test "psot participator will not contains same user", ~m(user meetup)a do + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert 1 == length(meetup.comments_participants) + end + + test "recent comment user should appear at first of the psot participants", + ~m(user user2 meetup)a do + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user2) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + participator = List.first(meetup.comments_participants) + + assert participator.id == user2.id + end + end + + describe "[article comment upvotes]" do + test "user can upvote a meetup comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + CMS.upvote_comment(comment.id, user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + + assert 1 == length(comment.upvotes) + assert List.first(comment.upvotes).user_id == user.id + end + + test "article author upvote meetup comment will have flag", ~m(meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, author_user} = ORM.find(User, meetup.author.user.id) + + CMS.upvote_comment(comment.id, author_user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + assert comment.meta.is_article_author_upvoted + end + + test "user upvote meetup comment will add id to upvoted_user_ids", ~m(meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = CMS.upvote_comment(comment.id, user) + + assert user.id in comment.meta.upvoted_user_ids + end + + test "user undo upvote meetup comment will remove id from upvoted_user_ids", + ~m(meetup user user2)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.upvote_comment(comment.id, user) + {:ok, comment} = CMS.upvote_comment(comment.id, user2) + + assert user2.id in comment.meta.upvoted_user_ids + assert user.id in comment.meta.upvoted_user_ids + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user2) + + assert user.id in comment.meta.upvoted_user_ids + assert user2.id not in comment.meta.upvoted_user_ids + end + + test "user upvote a already-upvoted comment fails", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + CMS.upvote_comment(comment.id, user) + {:error, _} = CMS.upvote_comment(comment.id, user) + end + + test "upvote comment should inc the comment's upvotes_count", ~m(user user2 meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.upvotes_count == 0 + + {:ok, _} = CMS.upvote_comment(comment.id, user) + {:ok, _} = CMS.upvote_comment(comment.id, user2) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.upvotes_count == 2 + end + + test "user can undo upvote a meetup comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + CMS.upvote_comment(comment.id, user) + + {:ok, comment} = ORM.find(Comment, comment.id, preload: :upvotes) + assert 1 == length(comment.upvotes) + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + + test "user can undo upvote a meetup comment with no upvote", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + end + + describe "[article comment fold/unfold]" do + test "user can fold a comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert not comment.is_folded + + {:ok, comment} = CMS.fold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.is_folded + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.folded_comment_count == 1 + end + + test "user can unfold a comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.fold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert comment.is_folded + + {:ok, _comment} = CMS.unfold_comment(comment.id, user) + {:ok, comment} = ORM.find(Comment, comment.id) + assert not comment.is_folded + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.folded_comment_count == 0 + end + end + + describe "[article comment pin/unpin]" do + test "user can pin a comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert not comment.is_pinned + + {:ok, comment} = CMS.pin_comment(comment.id) + {:ok, comment} = ORM.find(Comment, comment.id) + + assert comment.is_pinned + + {:ok, pined_record} = PinnedComment |> ORM.find_by(%{meetup_id: meetup.id}) + assert pined_record.meetup_id == meetup.id + end + + test "user can unpin a comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _comment} = CMS.pin_comment(comment.id) + {:ok, comment} = CMS.undo_pin_comment(comment.id) + + assert not comment.is_pinned + assert {:error, _} = PinnedComment |> ORM.find_by(%{comment_id: comment.id}) + end + + test "pinned comments has a limit for each article", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + Enum.reduce(0..(@pinned_comment_limit - 1), [], fn _, _acc -> + {:ok, _comment} = CMS.pin_comment(comment.id) + end) + + assert {:error, _} = CMS.pin_comment(comment.id) + end + end + + describe "[article comment report/unreport]" do + # + # test "user can report a comment", ~m(user meetup)a do + # {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + # {:ok, comment} = ORM.find(Comment, comment.id) + + # {:ok, comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + # {:ok, comment} = ORM.find(Comment, comment.id) + # end + + # + # test "user can unreport a comment", ~m(user meetup)a do + # {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + # {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + # {:ok, comment} = ORM.find(Comment, comment.id) + + # {:ok, _comment} = CMS.undo_report_comment(comment.id, user) + # {:ok, comment} = ORM.find(Comment, comment.id) + # end + + test "can undo a report with other user report it too", ~m(user user2 meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user2) + + filter = %{content_type: :comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 2 + assert Enum.any?(report.report_cases, &(&1.user.login == user.login)) + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + + {:ok, _report} = CMS.undo_report_article(:comment, comment.id, user) + + filter = %{content_type: :comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 1 + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + end + + test "report user < @report_threshold_for_fold will not fold comment", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold - 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + end) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert not comment.is_folded + end + + test "report user > @report_threshold_for_fold will cause comment fold", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold + 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_comment(comment.id, mock_comment(), "attr", user) + end) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.is_folded + end + end + + describe "paged article comments" do + test "can load paged comments participants of a article", ~m(user meetup)a do + total_count = 30 + page_size = 10 + thread = :meetup + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, new_user} = db_insert(:user) + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), new_user) + + acc ++ [comment] + end) + + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, results} = + CMS.paged_comments_participants(thread, meetup.id, %{page: 1, size: page_size}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == total_count + 1 + end + + test "paged article comments folded flag should be false", ~m(user meetup)a do + total_count = 30 + page_number = 1 + page_size = 35 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies + ) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + assert not random_comment.is_folded + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + + test "paged article comments should contains pinned comments at top position", + ~m(user meetup)a do + total_count = 20 + page_number = 1 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, random_comment_2} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, pined_comment_1} = CMS.pin_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert pined_comment_1.id == List.first(paged_comments.entries) |> Map.get(:id) + assert pined_comment_2.id == Enum.at(paged_comments.entries, 1) |> Map.get(:id) + + assert paged_comments.total_count == total_count + 2 + end + + test "only page 1 have pinned coments", + ~m(user meetup)a do + total_count = 20 + page_number = 2 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, random_comment_2} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, pined_comment_1} = CMS.pin_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert not exist_in?(pined_comment_1, paged_comments.entries) + assert not exist_in?(pined_comment_2, paged_comments.entries) + + assert paged_comments.total_count == total_count + end + + test "paged article comments should not contains folded and repoted comments", + ~m(user meetup)a do + total_count = 15 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment_1 = all_comments |> Enum.at(0) + random_comment_2 = all_comments |> Enum.at(1) + random_comment_3 = all_comments |> Enum.at(3) + + {:ok, _comment} = CMS.fold_comment(random_comment_1.id, user) + {:ok, _comment} = CMS.fold_comment(random_comment_2.id, user) + {:ok, _comment} = CMS.fold_comment(random_comment_3.id, user) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert not exist_in?(random_comment_1, paged_comments.entries) + assert not exist_in?(random_comment_2, paged_comments.entries) + assert not exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count - 3 == paged_comments.total_count + end + + test "can loaded paged folded comment", ~m(user meetup)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_folded_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + CMS.fold_comment(comment.id, user) + + acc ++ [comment] + end) + + random_comment_1 = all_folded_comments |> Enum.at(1) + random_comment_2 = all_folded_comments |> Enum.at(3) + random_comment_3 = all_folded_comments |> Enum.at(5) + + {:ok, paged_comments} = + CMS.paged_folded_comments(:meetup, meetup.id, %{page: page_number, size: page_size}) + + assert exist_in?(random_comment_1, paged_comments.entries) + assert exist_in?(random_comment_2, paged_comments.entries) + assert exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + end + + describe "[article comment delete]" do + test "delete comment still exsit in paged list and content is gone", ~m(user meetup)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, deleted_comment} = CMS.delete_comment(random_comment) + + {:ok, paged_comments} = + CMS.paged_comments( + :meetup, + meetup.id, + %{page: page_number, size: page_size}, + :replies + ) + + assert exist_in?(deleted_comment, paged_comments.entries) + assert deleted_comment.is_deleted + assert deleted_comment.body_html == @delete_hint + end + + test "delete comment still update article's comments_count field", ~m(user meetup)a do + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert meetup.comments_count == 5 + + {:ok, _} = CMS.delete_comment(comment) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.comments_count == 4 + end + + test "delete comment still delete pinned record if needed", ~m(user meetup)a do + total_count = 10 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, _comment} = CMS.pin_comment(random_comment.id) + {:ok, _comment} = ORM.find(Comment, random_comment.id) + + {:ok, _} = CMS.delete_comment(random_comment) + assert {:error, _comment} = ORM.find(PinnedComment, random_comment.id) + end + end + + describe "[article comment info]" do + test "author of the article comment a comment should have flag", ~m(user meetup)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + assert not comment.is_article_author + + author_user = meetup.author.user + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), author_user) + assert comment.is_article_author + end + end + + describe "[lock/unlock meetup comment]" do + test "locked meetup can not be comment", ~m(user meetup)a do + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.lock_article_comments(:meetup, meetup.id) + + {:error, reason} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + assert reason |> is_error?(:article_comments_locked) + + {:ok, _} = CMS.undo_lock_article_comments(:meetup, meetup.id) + {:ok, _} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + end + + test "locked meetup can not by reply", ~m(user meetup)a do + {:ok, parent_comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + + {:ok, _} = CMS.lock_article_comments(:meetup, meetup.id) + + {:error, reason} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + assert reason |> is_error?(:article_comments_locked) + + {:ok, _} = CMS.undo_lock_article_comments(:meetup, meetup.id) + {:ok, _} = CMS.reply_comment(parent_comment.id, mock_comment(), user) + end + end +end diff --git a/test/groupher_server/cms/emotions/meetup_emotions_test.exs b/test/groupher_server/cms/emotions/meetup_emotions_test.exs new file mode 100644 index 000000000..441a55d9c --- /dev/null +++ b/test/groupher_server/cms/emotions/meetup_emotions_test.exs @@ -0,0 +1,178 @@ +defmodule GroupherServer.Test.CMS.Emotions.MeetupEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Meetup, Embeds, ArticleUserEmotion} + + @default_emotions Embeds.ArticleEmotion.default_emotions() + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 user3 community meetup_attrs)a} + end + + describe "[emotion in paged meetups]" do + test "login user should got viewer has emotioned status", + ~m(community meetup_attrs user)a do + total_count = 10 + page_number = 10 + page_size = 20 + + all_meetups = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + acc ++ [meetup] + end) + + random_meetup = all_meetups |> Enum.at(3) + + {:ok, _} = CMS.emotion_to_article(:meetup, random_meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, random_meetup.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:meetup, random_meetup.id, :popcorn, user) + + {:ok, paged_articles} = + CMS.paged_articles(:meetup, %{page: page_number, size: page_size}, user) + + target = Enum.find(paged_articles.entries, &(&1.id == random_meetup.id)) + + assert target.emotions.downvote_count == 1 + assert user_exist_in?(user, target.emotions.latest_downvote_users) + assert target.emotions.viewer_has_downvoteed + + assert target.emotions.beer_count == 1 + assert user_exist_in?(user, target.emotions.latest_beer_users) + assert target.emotions.viewer_has_beered + + assert target.emotions.popcorn_count == 1 + assert user_exist_in?(user, target.emotions.latest_popcorn_users) + assert target.emotions.viewer_has_popcorned + end + end + + describe "[basic article emotion]" do + test "meetup has default emotions after created", ~m(community meetup_attrs user)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + emotions = meetup.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + test "can make emotion to meetup", ~m(community meetup_attrs user user2)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Meetup, meetup.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "can undo emotion to meetup", ~m(community meetup_attrs user user2)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user2) + + {:ok, _} = CMS.undo_emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_article(:meetup, meetup.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Meetup, meetup.id) + + assert emotions.downvote_count == 0 + assert not user_exist_in?(user, emotions.latest_downvote_users) + assert not user_exist_in?(user2, emotions.latest_downvote_users) + end + + test "same user make same emotion to same meetup.", ~m(community meetup_attrs user)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + assert meetup.emotions.downvote_count == 1 + assert user_exist_in?(user, meetup.emotions.latest_downvote_users) + end + + test "same user same emotion to same meetup only have one user_emotion record", + ~m(community meetup_attrs user)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :heart, user) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + + {:ok, records} = ORM.find_all(ArticleUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = ORM.find_by(ArticleUserEmotion, %{meetup_id: meetup.id, user_id: user.id}) + assert record.downvote + assert record.heart + end + + test "different user can make same emotions on same meetup", + ~m(community meetup_attrs user user2 user3)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :beer, user2) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(Meetup, meetup.id) + + assert emotions.beer_count == 3 + assert user_exist_in?(user, emotions.latest_beer_users) + assert user_exist_in?(user2, emotions.latest_beer_users) + assert user_exist_in?(user3, emotions.latest_beer_users) + end + + test "same user can make differcent emotions on same meetup", + ~m(community meetup_attrs user)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :heart, user) + {:ok, _} = CMS.emotion_to_article(:meetup, meetup.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(Meetup, meetup.id) + + assert emotions.downvote_count == 1 + assert user_exist_in?(user, emotions.latest_downvote_users) + + assert emotions.beer_count == 1 + assert user_exist_in?(user, emotions.latest_beer_users) + + assert emotions.heart_count == 1 + assert user_exist_in?(user, emotions.latest_heart_users) + + assert emotions.orz_count == 1 + assert user_exist_in?(user, emotions.latest_orz_users) + + assert emotions.pill_count == 0 + assert not user_exist_in?(user, emotions.latest_pill_users) + + assert emotions.biceps_count == 0 + assert not user_exist_in?(user, emotions.latest_biceps_users) + + assert emotions.confused_count == 0 + assert not user_exist_in?(user, emotions.latest_confused_users) + end + end +end diff --git a/test/groupher_server/cms/hooks/cite_meetup_test.exs b/test/groupher_server/cms/hooks/cite_meetup_test.exs new file mode 100644 index 000000000..384be5e3d --- /dev/null +++ b/test/groupher_server/cms/hooks/cite_meetup_test.exs @@ -0,0 +1,230 @@ +defmodule GroupherServer.Test.CMS.Hooks.CiteMeetup do + use GroupherServer.TestTools + + import Helper.Utils, only: [get_config: 2] + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Meetup, Comment, CitedArtiment} + alias CMS.Delegate.Hooks + + @site_host get_config(:general, :site_host) + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, meetup2} = db_insert(:meetup) + {:ok, meetup3} = db_insert(:meetup) + {:ok, meetup4} = db_insert(:meetup) + {:ok, meetup5} = db_insert(:meetup) + + {:ok, community} = db_insert(:community) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 community meetup meetup2 meetup3 meetup4 meetup5 meetup_attrs)a} + end + + describe "[cite basic]" do + test "cited multi meetup should work", + ~m(user community meetup2 meetup3 meetup4 meetup5 meetup_attrs)a do + body = + mock_rich_text( + ~s(the and same la is awesome, the is awesome too.), + # second paragraph + ~s(the paragraph 2 again, the paragraph 2 again, the paragraph 2 again) + ) + + meetup_attrs = meetup_attrs |> Map.merge(%{body: body}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + body = mock_rich_text(~s(the )) + meetup_attrs = meetup_attrs |> Map.merge(%{body: body}) + {:ok, meetup_n} = CMS.create_article(community, :meetup, meetup_attrs, user) + + Hooks.Cite.handle(meetup) + Hooks.Cite.handle(meetup_n) + + {:ok, meetup2} = ORM.find(Meetup, meetup2.id) + {:ok, meetup3} = ORM.find(Meetup, meetup3.id) + {:ok, meetup4} = ORM.find(Meetup, meetup4.id) + {:ok, meetup5} = ORM.find(Meetup, meetup5.id) + + assert meetup2.meta.citing_count == 1 + assert meetup3.meta.citing_count == 2 + assert meetup4.meta.citing_count == 1 + assert meetup5.meta.citing_count == 1 + end + + test "cited meetup itself should not work", ~m(user community meetup_attrs)a do + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + body = mock_rich_text(~s(the )) + {:ok, meetup} = CMS.update_article(meetup, %{body: body}) + + Hooks.Cite.handle(meetup) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.citing_count == 0 + end + + test "cited comment itself should not work", ~m(user meetup)a do + {:ok, cited_comment} = CMS.create_comment(:meetup, meetup.id, mock_rich_text("hello"), user) + + {:ok, comment} = + CMS.update_comment( + cited_comment, + mock_comment( + ~s(the ) + ) + ) + + Hooks.Cite.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 0 + end + + test "can cite meetup's comment in meetup", ~m(community user meetup meetup2 meetup_attrs)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_rich_text("hello"), user) + + body = + mock_rich_text( + ~s(the ) + ) + + meetup_attrs = meetup_attrs |> Map.merge(%{body: body}) + + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + Hooks.Cite.handle(meetup) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.meta.citing_count == 1 + + {:ok, cited_content} = ORM.find_by(CitedArtiment, %{cited_by_id: comment.id}) + + # 被 meetup 以 comment link 的方式引用了 + assert cited_content.meetup_id == meetup.id + assert cited_content.cited_by_type == "COMMENT" + end + + test "can cite a comment in a comment", ~m(user meetup)a do + {:ok, cited_comment} = CMS.create_comment(:meetup, meetup.id, mock_rich_text("hello"), user) + + comment_body = + mock_rich_text( + ~s(the ) + ) + + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, comment_body, user) + + Hooks.Cite.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 1 + + {:ok, cited_content} = ORM.find_by(CitedArtiment, %{cited_by_id: cited_comment.id}) + assert comment.id == cited_content.comment_id + assert cited_comment.id == cited_content.cited_by_id + assert cited_content.cited_by_type == "COMMENT" + end + + test "can cited meetup inside a comment", ~m(user meetup meetup2 meetup3 meetup4 meetup5)a do + comment_body = + mock_rich_text( + ~s(the and same la is awesome, the is awesome too.), + # second paragraph + ~s(the paragraph 2 again, the paragraph 2 again, the paragraph 2 again) + ) + + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, comment_body, user) + Hooks.Cite.handle(comment) + + comment_body = mock_rich_text(~s(the )) + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, comment_body, user) + + Hooks.Cite.handle(comment) + + {:ok, meetup2} = ORM.find(Meetup, meetup2.id) + {:ok, meetup3} = ORM.find(Meetup, meetup3.id) + {:ok, meetup4} = ORM.find(Meetup, meetup4.id) + {:ok, meetup5} = ORM.find(Meetup, meetup5.id) + + assert meetup2.meta.citing_count == 1 + assert meetup3.meta.citing_count == 2 + assert meetup4.meta.citing_count == 1 + assert meetup5.meta.citing_count == 1 + end + end + + describe "[cite pagi]" do + test "can get paged cited articles.", ~m(user community meetup2 meetup_attrs)a do + {:ok, comment} = + CMS.create_comment( + :meetup, + meetup2.id, + mock_comment(~s(the )), + user + ) + + Process.sleep(1000) + + body = + mock_rich_text( + ~s(the ), + ~s(the ) + ) + + meetup_attrs = meetup_attrs |> Map.merge(%{body: body}) + {:ok, meetup_x} = CMS.create_article(community, :meetup, meetup_attrs, user) + + Process.sleep(1000) + body = mock_rich_text(~s(the )) + meetup_attrs = meetup_attrs |> Map.merge(%{body: body}) + {:ok, meetup_y} = CMS.create_article(community, :meetup, meetup_attrs, user) + + Hooks.Cite.handle(meetup_x) + Hooks.Cite.handle(comment) + Hooks.Cite.handle(meetup_y) + + {:ok, result} = CMS.paged_citing_contents("MEETUP", meetup2.id, %{page: 1, size: 10}) + + entries = result.entries + + result_comment = entries |> List.first() + result_meetup_x = entries |> Enum.at(1) + result_meetup_y = entries |> List.last() + + article_map_keys = [:block_linker, :id, :inserted_at, :thread, :title, :user] + + assert result_comment.comment_id == comment.id + assert result_comment.id == meetup2.id + assert result_comment.title == meetup2.title + + assert result_meetup_x.id == meetup_x.id + assert result_meetup_x.block_linker |> length == 2 + assert result_meetup_x |> Map.keys() == article_map_keys + + assert result_meetup_y.id == meetup_y.id + assert result_meetup_y.block_linker |> length == 1 + assert result_meetup_y |> Map.keys() == article_map_keys + + assert result |> is_valid_pagination?(:raw) + assert result.total_count == 3 + end + end +end diff --git a/test/groupher_server/cms/hooks/mention_in_meetup_test.exs b/test/groupher_server/cms/hooks/mention_in_meetup_test.exs new file mode 100644 index 000000000..42714b20d --- /dev/null +++ b/test/groupher_server/cms/hooks/mention_in_meetup_test.exs @@ -0,0 +1,98 @@ +defmodule GroupherServer.Test.CMS.Hooks.MentionInMeetup do + use GroupherServer.TestTools + + import GroupherServer.CMS.Delegate.Helper, only: [preload_author: 1] + + alias GroupherServer.{CMS, Delivery} + alias CMS.Delegate.Hooks + + @article_mention_class "cdx-mention" + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + + {:ok, community} = db_insert(:community) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, ~m(user user2 user3 community meetup meetup_attrs)a} + end + + describe "[mention in meetup basic]" do + test "mention multi user in meetup should work", + ~m(user user2 user3 community meetup_attrs)a do + body = + mock_rich_text( + ~s(hiString.contains?(~s(comment
)) + end + + @reply_comment_query """ + mutation($id: ID!, $body: String!) { + replyComment(id: $id, body: $body) { + id + bodyHtml + } + } + """ + test "login user can reply to a comment", ~m(meetup user user_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id, body: mock_comment("reply comment")} + + result = user_conn |> mutation_result(@reply_comment_query, variables, "replyComment") + + assert result["bodyHtml"] |> String.contains?(~s(String.contains?(~s(reply comment
)) + end + + @update_comment_query """ + mutation($id: ID!, $body: String!) { + updateComment(id: $id, body: $body) { + id + bodyHtml + } + } + """ + + test "only owner can update a exsit comment", + ~m(meetup user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id, body: mock_comment("updated comment")} + + assert user_conn |> mutation_get_error?(@update_comment_query, variables, ecode(:passport)) + + assert guest_conn + |> mutation_get_error?(@update_comment_query, variables, ecode(:account_login)) + + result = owner_conn |> mutation_result(@update_comment_query, variables, "updateComment") + + assert result["bodyHtml"] |> String.contains?(~s(String.contains?(~s(updated comment
)) + end + + @delete_comment_query """ + mutation($id: ID!) { + deleteComment(id: $id) { + id + isDeleted + } + } + """ + test "only owner can delete a exsit comment", + ~m(meetup user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id} + + assert user_conn |> mutation_get_error?(@delete_comment_query, variables, ecode(:passport)) + + assert guest_conn + |> mutation_get_error?(@delete_comment_query, variables, ecode(:account_login)) + + deleted = owner_conn |> mutation_result(@delete_comment_query, variables, "deleteComment") + + assert deleted["id"] == to_string(comment.id) + assert deleted["isDeleted"] + end + end + + describe "[article comment upvote]" do + @upvote_comment_query """ + mutation($id: ID!) { + upvoteComment(id: $id) { + id + upvotesCount + viewerHasUpvoted + } + } + """ + + test "login user can upvote a exsit meetup comment", ~m(meetup user guest_conn user_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id} + + assert guest_conn + |> mutation_get_error?(@upvote_comment_query, variables, ecode(:account_login)) + + result = user_conn |> mutation_result(@upvote_comment_query, variables, "upvoteComment") + + assert result["id"] == to_string(comment.id) + assert result["upvotesCount"] == 1 + assert result["viewerHasUpvoted"] + end + + @undo_upvote_comment_query """ + mutation($id: ID!) { + undoUpvoteComment(id: $id) { + id + upvotesCount + viewerHasUpvoted + } + } + """ + + test "login user can undo upvote a exsit meetup comment", + ~m(meetup user guest_conn user_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id} + user_conn |> mutation_result(@upvote_comment_query, variables, "upvoteComment") + + assert guest_conn + |> mutation_get_error?(@undo_upvote_comment_query, variables, ecode(:account_login)) + + result = + user_conn + |> mutation_result(@undo_upvote_comment_query, variables, "undoUpvoteComment") + + assert result["upvotesCount"] == 0 + assert not result["viewerHasUpvoted"] + end + end + + describe "[article comment emotion]" do + @emotion_comment_query """ + mutation($id: ID!, $emotion: CommentEmotion!) { + emotionToComment(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + test "login user can emotion to a comment", ~m(meetup user user_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id, emotion: "BEER"} + + comment = + user_conn |> mutation_result(@emotion_comment_query, variables, "emotionToComment") + + assert comment |> get_in(["emotions", "beerCount"]) == 1 + assert get_in(comment, ["emotions", "viewerHasBeered"]) + end + + @emotion_comment_query """ + mutation($id: ID!, $emotion: CommentEmotion!) { + undoEmotionToComment(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + test "login user can undo emotion to a comment", ~m(meetup user owner_conn)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.emotion_to_comment(comment.id, :beer, user) + + variables = %{id: comment.id, emotion: "BEER"} + + comment = + owner_conn |> mutation_result(@emotion_comment_query, variables, "undoEmotionToComment") + + assert comment |> get_in(["emotions", "beerCount"]) == 0 + assert not get_in(comment, ["emotions", "viewerHasBeered"]) + end + end + + describe "[article comment lock/unlock]" do + @query """ + mutation($id: ID!, $communityId: ID!){ + lockMeetupComment(id: $id, communityId: $communityId) { + id + } + } + """ + + test "can lock a meetup's comment", ~m(community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + passport_rules = %{community.raw => %{"meetup.lock_comment" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "lockMeetupComment") + assert result["id"] == to_string(meetup.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.is_comment_locked + end + + test "unauth user fails", ~m(guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + undoLockMeetupComment(id: $id, communityId: $communityId) { + id + } + } + """ + + test "can undo lock a meetup's comment", ~m(community meetup)a do + {:ok, _} = CMS.lock_article_comments(:meetup, meetup.id) + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.is_comment_locked + + variables = %{id: meetup.id, communityId: community.id} + passport_rules = %{community.raw => %{"meetup.undo_lock_comment" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "undoLockMeetupComment") + assert result["id"] == to_string(meetup.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert not meetup.meta.is_comment_locked + end + + test "unauth user undo fails", ~m(guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end + + describe "[article comment pin/unPin]" do + @query """ + mutation($id: ID!){ + pinComment(id: $id) { + id + isPinned + } + } + """ + + test "can pin a meetup's comment", ~m(owner_conn meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + variables = %{id: comment.id} + result = owner_conn |> mutation_result(@query, variables, "pinComment") + + assert result["id"] == to_string(comment.id) + assert result["isPinned"] + end + + test "unauth user fails.", ~m(guest_conn meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + variables = %{id: comment.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!){ + undoPinComment(id: $id) { + id + isPinned + } + } + """ + + test "can undo pin a meetup's comment", ~m(owner_conn meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.pin_comment(comment.id) + + variables = %{id: comment.id} + result = owner_conn |> mutation_result(@query, variables, "undoPinComment") + + assert result["id"] == to_string(comment.id) + assert not result["isPinned"] + end + + test "unauth user undo fails.", ~m(guest_conn meetup user)a do + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _} = CMS.pin_comment(comment.id) + variables = %{id: comment.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/flags/meetup_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/meetup_flag_test.exs new file mode 100644 index 000000000..42fc74900 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/flags/meetup_flag_test.exs @@ -0,0 +1,178 @@ +defmodule GroupherServer.Test.Mutation.Flags.MeetupFlag do + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.Community + + alias Helper.ORM + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn owner_conn community user meetup)a} + end + + describe "[mutation meetup flag curd]" do + @query """ + mutation($id: ID!){ + markDeleteMeetup(id: $id) { + id + markDelete + } + } + """ + test "auth user can markDelete meetup", ~m(meetup)a do + variables = %{id: meetup.id} + + passport_rules = %{"meetup.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "markDeleteMeetup") + + assert updated["id"] == to_string(meetup.id) + assert updated["markDelete"] == true + end + + test "mark delete meetup should update meetup's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.meetups_count == 1 + + variables = %{id: meetup.id} + passport_rules = %{"meetup.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@query, variables, "markDeleteMeetup") + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.meetups_count == 0 + end + + test "unauth user markDelete meetup fails", ~m(user_conn guest_conn meetup)a do + variables = %{id: meetup.id} + rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) + + assert user_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + assert rule_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + end + + @query """ + mutation($id: ID!){ + undoMarkDeleteMeetup(id: $id) { + id + markDelete + } + } + """ + test "auth user can undo markDelete meetup", ~m(meetup)a do + variables = %{id: meetup.id} + + {:ok, _} = CMS.mark_delete_article(:meetup, meetup.id) + + passport_rules = %{"meetup.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "undoMarkDeleteMeetup") + + assert updated["id"] == to_string(meetup.id) + assert updated["markDelete"] == false + end + + test "undo mark delete meetup should update meetup's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + {:ok, _} = CMS.mark_delete_article(:meetup, meetup.id) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.meetups_count == 0 + + variables = %{id: meetup.id} + passport_rules = %{"meetup.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + rule_conn |> mutation_result(@query, variables, "undoMarkDeleteMeetup") + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.meetups_count == 1 + end + + test "unauth user undo markDelete meetup fails", ~m(user_conn guest_conn meetup)a do + variables = %{id: meetup.id} + rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) + + assert user_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + assert rule_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + pinMeetup(id: $id, communityId: $communityId) { + id + } + } + """ + + test "auth user can pin meetup", ~m(community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + passport_rules = %{community.raw => %{"meetup.pin" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "pinMeetup") + + assert updated["id"] == to_string(meetup.id) + end + + test "unauth user pin meetup fails", ~m(user_conn guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) + + assert user_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + assert rule_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + undoPinMeetup(id: $id, communityId: $communityId) { + id + isPinned + } + } + """ + + test "auth user can undo pin meetup", ~m(community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + passport_rules = %{community.raw => %{"meetup.undo_pin" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + CMS.pin_article(:meetup, meetup.id, community.id) + updated = rule_conn |> mutation_result(@query, variables, "undoPinMeetup") + + assert updated["id"] == to_string(meetup.id) + end + + test "unauth user undo pin meetup fails", ~m(user_conn guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) + + assert user_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + assert rule_conn |> mutation_get_error?(@query, variables, ecode(:passport)) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/sink/meetup_sink_test.exs b/test/groupher_server_web/mutation/cms/sink/meetup_sink_test.exs new file mode 100644 index 000000000..fcd3b3707 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/sink/meetup_sink_test.exs @@ -0,0 +1,79 @@ +defmodule GroupherServer.Test.Mutation.Sink.MeetupSink do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.Meetup + + alias Helper.ORM + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn community meetup user)a} + end + + describe "[meetup sink]" do + @query """ + mutation($id: ID!, $communityId: ID!){ + sinkMeetup(id: $id, communityId: $communityId) { + id + } + } + """ + + test "login user can sink a meetup", ~m(community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + passport_rules = %{community.raw => %{"meetup.sink" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "sinkMeetup") + assert result["id"] == to_string(meetup.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert meetup.meta.is_sinked + assert meetup.active_at == meetup.inserted_at + end + + test "unauth user sink a meetup fails", ~m(guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + undoSinkMeetup(id: $id, communityId: $communityId) { + id + } + } + """ + + test "login user can undo sink to a meetup", ~m(community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + passport_rules = %{community.raw => %{"meetup.undo_sink" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, _} = CMS.sink_article(:meetup, meetup.id) + updated = rule_conn |> mutation_result(@query, variables, "undoSinkMeetup") + assert updated["id"] == to_string(meetup.id) + + {:ok, meetup} = ORM.find(Meetup, meetup.id) + assert not meetup.meta.is_sinked + end + + :wip2 + + test "unauth user undo sink a meetup fails", ~m(guest_conn community meetup)a do + variables = %{id: meetup.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/upvotes/meetup_upvote_test.exs b/test/groupher_server_web/mutation/cms/upvotes/meetup_upvote_test.exs new file mode 100644 index 000000000..208aad491 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/upvotes/meetup_upvote_test.exs @@ -0,0 +1,64 @@ +defmodule GroupherServer.Test.Mutation.Upvotes.MeetupUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, meetup} = db_insert(:meetup) + {:ok, user} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn meetup user)a} + end + + describe "[meetup upvote]" do + @query """ + mutation($id: ID!) { + upvoteMeetup(id: $id) { + id + } + } + """ + + test "login user can upvote a meetup", ~m(user_conn meetup)a do + variables = %{id: meetup.id} + created = user_conn |> mutation_result(@query, variables, "upvoteMeetup") + + assert created["id"] == to_string(meetup.id) + end + + test "unauth user upvote a meetup fails", ~m(guest_conn meetup)a do + variables = %{id: meetup.id} + + assert guest_conn + |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!) { + undoUpvoteMeetup(id: $id) { + id + } + } + """ + + test "login user can undo upvote to a meetup", ~m(user_conn meetup user)a do + {:ok, _} = CMS.upvote_article(:meetup, meetup.id, user) + + variables = %{id: meetup.id} + updated = user_conn |> mutation_result(@query, variables, "undoUpvoteMeetup") + + assert updated["id"] == to_string(meetup.id) + end + + test "unauth user undo upvote a meetup fails", ~m(guest_conn meetup)a do + variables = %{id: meetup.id} + + assert guest_conn + |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end +end diff --git a/test/groupher_server_web/query/accounts/published/published_meetups_test.exs b/test/groupher_server_web/query/accounts/published/published_meetups_test.exs new file mode 100644 index 000000000..1a0d29d70 --- /dev/null +++ b/test/groupher_server_web/query/accounts/published/published_meetups_test.exs @@ -0,0 +1,102 @@ +defmodule GroupherServer.Test.Query.Accounts.Published.Meetups do + use GroupherServer.TestTools + + alias GroupherServer.CMS + + @publish_count 10 + + setup do + {:ok, user} = db_insert(:user) + {:ok, meetup} = db_insert(:meetup) + {:ok, community} = db_insert(:community) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(guest_conn user_conn community meetup user)a} + end + + describe "[published meetups]" do + @query """ + query($login: String!, $filter: PagedFilter!) { + pagedPublishedMeetups(login: $login, filter: $filter) { + entries { + id + title + author { + id + } + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + + test "can get published meetups", ~m(guest_conn community user)a do + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup2} = CMS.create_article(community, :meetup, meetup_attrs, user) + + variables = %{login: user.login, filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedPublishedMeetups") + + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(meetup.id))) + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(meetup2.id))) + end + end + + describe "[account published comments on meetup]" do + @query """ + query($login: String!, $thread: Thread, $filter: PagedFilter!) { + pagedPublishedComments(login: $login, thread: $thread, filter: $filter) { + entries { + id + bodyHtml + author { + id + } + article { + id + title + author { + nickname + login + } + } + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + test "user can get paged published comments on meetup", ~m(guest_conn user meetup)a do + pub_comments = + Enum.reduce(1..@publish_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + acc ++ [comment] + end) + + random_comment_id = pub_comments |> Enum.random() |> Map.get(:id) |> to_string + + variables = %{login: user.login, thread: "MEETUP", filter: %{page: 1, size: 20}} + + results = guest_conn |> query_result(@query, variables, "pagedPublishedComments") + + entries = results["entries"] + assert results |> is_valid_pagination? + assert results["totalCount"] == @publish_count + + assert entries |> Enum.all?(&(not is_nil(&1["article"]["author"]))) + + assert entries |> Enum.all?(&(&1["article"]["id"] == to_string(meetup.id))) + assert entries |> Enum.all?(&(&1["author"]["id"] == to_string(user.id))) + assert entries |> Enum.any?(&(&1["id"] == random_comment_id)) + end + end +end diff --git a/test/groupher_server_web/query/cms/comments/meetup_comment_test.exs b/test/groupher_server_web/query/cms/comments/meetup_comment_test.exs new file mode 100644 index 000000000..abfb51eec --- /dev/null +++ b/test/groupher_server_web/query/cms/comments/meetup_comment_test.exs @@ -0,0 +1,672 @@ +defmodule GroupherServer.Test.Query.Comments.MeetupComment do + @moduledoc false + + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, meetup} = db_insert(:meetup) + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn meetup user user2)a} + end + + describe "[baisc article meetup comment]" do + @query """ + query($id: ID!) { + meetup(id: $id) { + id + title + commentsParticipants { + id + nickname + } + commentsParticipantsCount + } + } + """ + + test "guest user can get comment participants after comment created", + ~m(guest_conn meetup user user2)a do + total_count = 5 + thread = :meetup + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, _} = CMS.create_comment(thread, meetup.id, mock_comment(), user2) + + variables = %{id: meetup.id} + results = guest_conn |> query_result(@query, variables, "meetup") + + comments_participants = results["commentsParticipants"] + comments_participants_count = results["commentsParticipantsCount"] + + assert is_list(comments_participants) + assert length(comments_participants) == 2 + assert comments_participants_count == 2 + end + + @query """ + query($id: ID!, $thread: Thread, $mode: CommentsMode, $filter: CommentsFilter!) { + pagedComments(id: $id, thread: $thread, mode: $mode, filter: $filter) { + entries { + id + bodyHtml + author { + id + nickname + } + isPinned + floor + upvotesCount + + emotions { + downvoteCount + latestDownvoteUsers { + login + nickname + } + viewerHasDownvoteed + beerCount + latestBeerUsers { + login + nickname + } + viewerHasBeered + + popcornCount + viewerHasPopcorned + } + isArticleAuthor + meta { + isArticleAuthorUpvoted + } + replyTo { + id + bodyHtml + floor + isArticleAuthor + author { + id + login + } + } + viewerHasUpvoted + replies { + id + bodyHtml + author { + id + login + } + } + repliesCount + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + + test "list comments with default replies-mode", ~m(guest_conn meetup user user2)a do + total_count = 10 + page_size = 20 + thread = :meetup + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + {:ok, replyed_comment_1} = CMS.reply_comment(random_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(random_comment.id, mock_comment(), user2) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + assert results["entries"] |> length == total_count + + assert not exist_in?(replyed_comment_1, results["entries"], :string_key) + assert not exist_in?(replyed_comment_2, results["entries"], :string_key) + + random_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + assert random_comment["replies"] |> length == 2 + assert random_comment["repliesCount"] == 2 + + assert random_comment["replies"] |> List.first() |> Map.get("id") == + to_string(replyed_comment_1.id) + + assert random_comment["replies"] |> List.last() |> Map.get("id") == + to_string(replyed_comment_2.id) + end + + test "timeline-mode paged comments", ~m(guest_conn meetup user user2)a do + total_count = 3 + page_size = 20 + thread = :meetup + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + {:ok, replyed_comment_1} = CMS.reply_comment(random_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(random_comment.id, mock_comment(), user2) + + variables = %{ + id: meetup.id, + thread: "MEETUP", + mode: "TIMELINE", + filter: %{page: 1, size: page_size} + } + + results = guest_conn |> query_result(@query, variables, "pagedComments") + assert results["entries"] |> length == total_count + 2 + + assert exist_in?(replyed_comment_1, results["entries"], :string_key) + assert exist_in?(replyed_comment_2, results["entries"], :string_key) + + random_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + assert random_comment["replies"] |> length == 2 + assert random_comment["repliesCount"] == 2 + end + + test "comment should have reply_to content if need", ~m(guest_conn meetup user user2)a do + total_count = 2 + thread = :meetup + + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + {:ok, parent_comment} = + CMS.create_comment(:meetup, meetup.id, mock_comment("parent_comment"), user) + + {:ok, replyed_comment_1} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + {:ok, replyed_comment_2} = CMS.reply_comment(parent_comment.id, mock_comment(), user2) + + variables = %{ + id: meetup.id, + thread: "MEETUP", + filter: %{page: 1, size: 10}, + mode: "TIMELINE" + } + + results = guest_conn |> query_result(@query, variables, "pagedComments") + + replyed_comment_1 = + Enum.find(results["entries"], &(&1["id"] == to_string(replyed_comment_1.id))) + + assert replyed_comment_1 |> get_in(["replyTo", "id"]) == to_string(parent_comment.id) + + assert replyed_comment_1 |> get_in(["replyTo", "author", "id"]) == + to_string(parent_comment.author_id) + + replyed_comment_2 = + Enum.find(results["entries"], &(&1["id"] == to_string(replyed_comment_2.id))) + + assert replyed_comment_2 |> get_in(["replyTo", "id"]) == to_string(parent_comment.id) + + assert replyed_comment_2 |> get_in(["replyTo", "author", "id"]) == + to_string(parent_comment.author_id) + end + + test "guest user can get paged comment for meetup", ~m(guest_conn meetup user)a do + total_count = 30 + thread = :meetup + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, value} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + acc ++ [value] + end) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: 10}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert results |> is_valid_pagination? + assert results["totalCount"] == total_count + end + + test "guest user can get paged comment with pinned comment in it", + ~m(guest_conn meetup user)a do + total_count = 20 + thread = :meetup + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + {:ok, pinned_comment} = CMS.pin_comment(comment.id) + + Process.sleep(1000) + + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + {:ok, pinned_comment2} = CMS.pin_comment(comment.id) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: 10}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert results["entries"] |> List.first() |> Map.get("id") == to_string(pinned_comment2.id) + assert results["entries"] |> Enum.at(1) |> Map.get("id") == to_string(pinned_comment.id) + + assert results["totalCount"] == total_count + 2 + end + + test "guest user can get paged comment with floor it", ~m(guest_conn meetup user)a do + total_count = 5 + thread = :meetup + page_size = 10 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + Process.sleep(1000) + acc ++ [comment] + end) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert results["entries"] |> List.first() |> Map.get("floor") == 1 + assert results["entries"] |> List.last() |> Map.get("floor") == 5 + end + + test "the comments is loaded in default asc order", ~m(guest_conn meetup user)a do + page_size = 10 + thread = :meetup + + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + Process.sleep(1000) + {:ok, _comment2} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + Process.sleep(1000) + {:ok, comment3} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + variables = %{ + id: meetup.id, + thread: "MEETUP", + filter: %{page: 1, size: page_size}, + mode: "TIMELINE" + } + + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert List.first(results["entries"]) |> Map.get("id") == to_string(comment.id) + assert List.last(results["entries"]) |> Map.get("id") == to_string(comment3.id) + end + + test "the comments can be loaded in desc order in timeline-mode", + ~m(guest_conn meetup user)a do + page_size = 10 + thread = :meetup + + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + Process.sleep(1000) + {:ok, _comment2} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + Process.sleep(1000) + {:ok, comment3} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + variables = %{ + id: meetup.id, + thread: "MEETUP", + filter: %{page: 1, size: page_size, sort: "DESC_INSERTED"}, + mode: "TIMELINE" + } + + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert List.first(results["entries"]) |> Map.get("id") == to_string(comment3.id) + assert List.last(results["entries"]) |> Map.get("id") == to_string(comment.id) + end + + test "the comments can be loaded in desc order in replies-mode", + ~m(guest_conn meetup user user2)a do + page_size = 10 + thread = :meetup + + {:ok, comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment.id, mock_comment(), user2) + Process.sleep(1000) + {:ok, comment2} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment2.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment2.id, mock_comment(), user2) + Process.sleep(1000) + {:ok, comment3} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment3.id, mock_comment(), user) + {:ok, _reply_comment} = CMS.reply_comment(comment3.id, mock_comment(), user2) + + variables = %{ + id: meetup.id, + thread: "MEETUP", + filter: %{page: 1, size: page_size, sort: "DESC_INSERTED"} + } + + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert List.first(results["entries"]) |> Map.get("id") == to_string(comment3.id) + assert List.last(results["entries"]) |> Map.get("id") == to_string(comment.id) + end + + test "guest user can get paged comment with upvotes_count", + ~m(guest_conn meetup user user2)a do + total_count = 10 + page_size = 10 + thread = :meetup + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + Process.sleep(1000) + acc ++ [comment] + end) + + upvote_comment = all_comment |> Enum.at(3) + upvote_comment2 = all_comment |> Enum.at(4) + {:ok, _} = CMS.upvote_comment(upvote_comment.id, user) + {:ok, _} = CMS.upvote_comment(upvote_comment2.id, user) + {:ok, _} = CMS.upvote_comment(upvote_comment2.id, user2) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + assert results["entries"] |> Enum.at(3) |> Map.get("upvotesCount") == 1 + assert results["entries"] |> Enum.at(4) |> Map.get("upvotesCount") == 2 + assert results["entries"] |> List.first() |> Map.get("upvotesCount") == 0 + assert results["entries"] |> List.last() |> Map.get("upvotesCount") == 0 + end + + test "article author upvote a comment can get is_article_author and/or is_article_author_upvoted flag", + ~m(guest_conn meetup user)a do + total_count = 5 + page_size = 12 + thread = :meetup + + author_user = meetup.author.user + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + {:ok, _} = CMS.upvote_comment(random_comment.id, author_user) + + {:ok, author_comment} = CMS.create_comment(thread, meetup.id, mock_comment(), author_user) + + {:ok, _} = CMS.upvote_comment(author_comment.id, author_user) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + the_author_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(author_comment.id))) + + assert the_author_comment["isArticleAuthor"] + assert the_author_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) + + the_random_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + + assert not the_random_comment["isArticleAuthor"] + assert the_random_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) + end + + test "guest user can get paged comment with emotions info", + ~m(guest_conn meetup user user2)a do + total_count = 2 + page_size = 10 + thread = :meetup + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + Process.sleep(1000) + acc ++ [comment] + end) + + comment = all_comment |> Enum.at(0) + comment2 = all_comment |> Enum.at(1) + + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user2) + {:ok, _} = CMS.emotion_to_comment(comment2.id, :beer, user2) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedComments") + + comment_emotion = + Enum.find(results["entries"], &(&1["id"] == to_string(comment.id))) |> Map.get("emotions") + + assert comment_emotion["popcornCount"] == 0 + + assert comment_emotion["downvoteCount"] == 2 + assert comment_emotion["latestDownvoteUsers"] |> length == 2 + assert not comment_emotion["viewerHasDownvoteed"] + + latest_downvote_users_logins = + Enum.map(comment_emotion["latestDownvoteUsers"], & &1["login"]) + + assert user.login in latest_downvote_users_logins + assert user2.login in latest_downvote_users_logins + + comment2_emotion = + Enum.find(results["entries"], &(&1["id"] == to_string(comment2.id))) + |> Map.get("emotions") + + assert comment2_emotion["beerCount"] == 1 + assert comment2_emotion["latestBeerUsers"] |> length == 1 + assert not comment2_emotion["viewerHasBeered"] + + latest_beer_users_logins = Enum.map(comment2_emotion["latestBeerUsers"], & &1["login"]) + assert user2.login in latest_beer_users_logins + end + + test "user make emotion can get paged comment with emotions has_motioned field", + ~m(user_conn meetup user user2)a do + total_count = 10 + page_size = 12 + thread = :meetup + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + Process.sleep(1000) + acc ++ [comment] + end) + + comment = all_comment |> Enum.at(0) + comment2 = all_comment |> Enum.at(1) + + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(comment2.id, :downvote, user2) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = user_conn |> query_result(@query, variables, "pagedComments") + + assert Enum.find(results["entries"], &(&1["id"] == to_string(comment.id))) + |> get_in(["emotions", "viewerHasDownvoteed"]) + end + + test "comment should have viewer has upvoted flag", ~m(user_conn meetup user)a do + total_count = 10 + page_size = 12 + thread = :meetup + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, meetup.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + {:ok, _} = CMS.upvote_comment(random_comment.id, user) + + variables = %{id: meetup.id, thread: "MEETUP", filter: %{page: 1, size: page_size}} + results = user_conn |> query_result(@query, variables, "pagedComments") + + upvoted_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + + assert upvoted_comment["viewerHasUpvoted"] + end + end + + describe "paged participants" do + @query """ + query($id: ID!, $thread: Thread, $filter: PagedFilter!) { + pagedCommentsParticipants(id: $id, thread: $thread, filter: $filter) { + entries { + id + nickname + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + + test "guest user can get paged participants", ~m(guest_conn meetup user)a do + total_count = 30 + page_size = 10 + thread = "MEETUP" + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, new_user} = db_insert(:user) + {:ok, comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), new_user) + + acc ++ [comment] + end) + + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + {:ok, _comment} = CMS.create_comment(:meetup, meetup.id, mock_comment(), user) + + variables = %{id: meetup.id, thread: thread, filter: %{page: 1, size: page_size}} + + results = guest_conn |> query_result(@query, variables, "pagedCommentsParticipants") + + assert results |> is_valid_pagination? + assert results["totalCount"] == total_count + 1 + end + end + + describe "paged replies" do + @query """ + query($id: ID!, $filter: CommentsFilter!) { + pagedCommentReplies(id: $id, filter: $filter) { + entries { + id + bodyHtml + author { + id + nickname + } + upvotesCount + emotions { + downvoteCount + latestDownvoteUsers { + login + nickname + } + viewerHasDownvoteed + beerCount + latestBeerUsers { + login + nickname + } + viewerHasBeered + } + isArticleAuthor + meta { + isArticleAuthorUpvoted + } + replyTo { + id + bodyHtml + floor + isArticleAuthor + author { + id + login + } + } + repliesCount + viewerHasUpvoted + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + + test "guest user can get paged replies", ~m(guest_conn meetup user user2)a do + total_count = 2 + page_size = 10 + thread = :meetup + + author_user = meetup.author.user + {:ok, parent_comment} = CMS.create_comment(thread, meetup.id, mock_comment(), user) + + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, reply_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("reply #{i}"), user2) + + acc ++ [reply_comment] + end) + + {:ok, author_reply_comment} = + CMS.reply_comment(parent_comment.id, mock_comment("author reply"), author_user) + + variables = %{id: parent_comment.id, filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedCommentReplies") + + author_reply_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(author_reply_comment.id))) + + assert author_reply_comment["isArticleAuthor"] + assert results["entries"] |> length == total_count + 1 + end + end +end diff --git a/test/groupher_server_web/query/cms/paged_articles/paged_meetups_test.exs b/test/groupher_server_web/query/cms/paged_articles/paged_meetups_test.exs new file mode 100644 index 000000000..ab7b57916 --- /dev/null +++ b/test/groupher_server_web/query/cms/paged_articles/paged_meetups_test.exs @@ -0,0 +1,475 @@ +defmodule GroupherServer.Test.Query.PagedArticles.PagedMeetup do + @moduledoc false + use GroupherServer.TestTools + + import Helper.Utils, only: [get_config: 2] + import Ecto.Query, warn: false + + alias GroupherServer.CMS + alias GroupherServer.Repo + + alias CMS.Model.Meetup + + @page_size get_config(:general, :page_size) + + @cur_date Timex.now() + @last_week Timex.shift(Timex.beginning_of_week(@cur_date), days: -1, seconds: -1) + @last_month Timex.shift(Timex.beginning_of_month(@cur_date), days: -7, seconds: -1) + @last_year Timex.shift(Timex.beginning_of_year(@cur_date), days: -2, seconds: -1) + + @today_count 15 + + @last_week_count 1 + @last_month_count 1 + @last_year_count 1 + + @total_count @today_count + @last_week_count + @last_month_count + @last_year_count + + setup do + {:ok, user} = db_insert(:user) + + {:ok, meetup_last_week} = + db_insert(:meetup, %{title: "last week", inserted_at: @last_week, active_at: @last_week}) + + db_insert(:meetup, %{title: "last month", inserted_at: @last_month}) + + {:ok, meetup_last_year} = + db_insert(:meetup, %{title: "last year", inserted_at: @last_year, active_at: @last_year}) + + db_insert_multi(:meetup, @today_count) + guest_conn = simu_conn(:guest) + + {:ok, ~m(guest_conn user meetup_last_week meetup_last_year)a} + end + + describe "[query paged_meetups filter pagination]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + document { + bodyHtml + } + communities { + id + raw + } + articleTags { + id + } + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + test "should get pagination info", ~m(guest_conn)a do + variables = %{filter: %{page: 1, size: 10}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert results |> is_valid_pagination? + assert results["pageSize"] == 10 + assert results["totalCount"] == @total_count + assert results["entries"] |> List.first() |> Map.get("articleTags") |> is_list + end + + test "should get valid thread document", ~m(guest_conn)a do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + Process.sleep(2000) + {:ok, _meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + variables = %{filter: %{page: 1, size: 30}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + meetup = results["entries"] |> List.first() + assert not is_nil(get_in(meetup, ["document", "bodyHtml"])) + end + + test "support article_tag filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + article_tag_attrs = mock_attrs(:article_tag) + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, _} = CMS.set_article_tag(:meetup, meetup.id, article_tag.id) + + variables = %{filter: %{page: 1, size: 10, article_tag: article_tag.title}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + meetup = results["entries"] |> List.first() + assert results["totalCount"] == 1 + assert exist_in?(article_tag, meetup["articleTags"], :string_key) + end + + test "support multi-tag (article_tags) filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + article_tag_attrs = mock_attrs(:article_tag) + + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, article_tag3} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + + {:ok, _} = CMS.set_article_tag(:meetup, meetup.id, article_tag.id) + {:ok, _} = CMS.set_article_tag(:meetup, meetup.id, article_tag2.id) + + variables = %{ + filter: %{page: 1, size: 10, article_tags: [article_tag.title, article_tag2.title]} + } + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + meetup = results["entries"] |> List.first() + assert results["totalCount"] == 1 + assert exist_in?(article_tag, meetup["articleTags"], :string_key) + assert exist_in?(article_tag2, meetup["articleTags"], :string_key) + assert not exist_in?(article_tag3, meetup["articleTags"], :string_key) + end + + test "should not have pined meetups when filter have article_tag or article_tags", + ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, pinned_meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + {:ok, meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + + {:ok, _} = CMS.pin_article(:meetup, pinned_meetup.id, community.id) + + article_tag_attrs = mock_attrs(:article_tag) + {:ok, article_tag} = CMS.create_article_tag(community, :meetup, article_tag_attrs, user) + {:ok, _} = CMS.set_article_tag(:meetup, meetup.id, article_tag.id) + + variables = %{ + filter: %{page: 1, size: 10, community: community.raw, article_tag: article_tag.title} + } + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert not exist_in?(pinned_meetup, results["entries"], :string_key) + assert exist_in?(meetup, results["entries"], :string_key) + + variables = %{ + filter: %{page: 1, size: 10, community: community.raw, article_tags: [article_tag.title]} + } + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert not exist_in?(pinned_meetup, results["entries"], :string_key) + assert exist_in?(meetup, results["entries"], :string_key) + end + + test "support community filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + + meetup_attrs = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, _meetup} = CMS.create_article(community, :meetup, meetup_attrs, user) + meetup_attrs2 = mock_attrs(:meetup, %{community_id: community.id}) + {:ok, _meetup} = CMS.create_article(community, :meetup, meetup_attrs2, user) + + variables = %{filter: %{page: 1, size: 10, community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + meetup = results["entries"] |> List.first() + assert results["totalCount"] == 2 + assert exist_in?(%{id: to_string(community.id)}, meetup["communities"], :string_key) + end + + test "request large size fails", ~m(guest_conn)a do + variables = %{filter: %{page: 1, size: 200}} + assert guest_conn |> query_get_error?(@query, variables, ecode(:pagination)) + end + + test "request 0 or neg-size fails", ~m(guest_conn)a do + variables_0 = %{filter: %{page: 1, size: 0}} + variables_neg_1 = %{filter: %{page: 1, size: -1}} + + assert guest_conn |> query_get_error?(@query, variables_0) + assert guest_conn |> query_get_error?(@query, variables_neg_1) + end + + test "pagination should have default page and size arg", ~m(guest_conn)a do + variables = %{filter: %{}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + assert results |> is_valid_pagination? + assert results["pageSize"] == @page_size + assert results["totalCount"] == @total_count + end + end + + describe "[query paged_meetups filter has_xxx]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + viewerHasCollected + viewerHasUpvoted + viewerHasViewed + viewerHasReported + } + totalCount + } + } + """ + + test "has_xxx state should work", ~m(user)a do + user_conn = simu_conn(:user, user) + {:ok, community} = db_insert(:community) + + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + {:ok, _meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + {:ok, _meetup3} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + variables = %{filter: %{community: community.raw}} + results = user_conn |> query_result(@query, variables, "pagedMeetups") + assert results["totalCount"] == 3 + + the_meetup = Enum.find(results["entries"], &(&1["id"] == to_string(meetup.id))) + assert not the_meetup["viewerHasViewed"] + assert not the_meetup["viewerHasUpvoted"] + assert not the_meetup["viewerHasCollected"] + assert not the_meetup["viewerHasReported"] + + {:ok, _} = CMS.read_article(:meetup, meetup.id, user) + {:ok, _} = CMS.upvote_article(:meetup, meetup.id, user) + {:ok, _} = CMS.collect_article(:meetup, meetup.id, user) + {:ok, _} = CMS.report_article(:meetup, meetup.id, "reason", "attr_info", user) + + results = user_conn |> query_result(@query, variables, "pagedMeetups") + the_meetup = Enum.find(results["entries"], &(&1["id"] == to_string(meetup.id))) + assert the_meetup["viewerHasViewed"] + assert the_meetup["viewerHasUpvoted"] + assert the_meetup["viewerHasCollected"] + assert the_meetup["viewerHasReported"] + end + end + + describe "[query paged_meetups filter sort]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + inserted_at + active_at + author { + id + nickname + avatar + } + } + } + } + """ + + test "filter community should get meetups which belongs to that community", + ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + {:ok, meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + variables = %{filter: %{community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert length(results["entries"]) == 1 + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(meetup.id))) + end + + test "should have a active_at same with inserted_at", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + {:ok, _meetup} = CMS.create_article(community, :meetup, mock_attrs(:meetup), user) + + variables = %{filter: %{community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + meetup = results["entries"] |> List.first() + + assert meetup["inserted_at"] == meetup["active_at"] + end + + test "filter sort should have default :desc_active", ~m(guest_conn)a do + variables = %{filter: %{}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + active_timestamps = results["entries"] |> Enum.map(& &1["active_at"]) + + {:ok, first_active_time, 0} = active_timestamps |> List.first() |> DateTime.from_iso8601() + {:ok, last_active_time, 0} = active_timestamps |> List.last() |> DateTime.from_iso8601() + + assert :gt = DateTime.compare(first_active_time, last_active_time) + end + + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + views + } + } + } + """ + + test "filter sort MOST_VIEWS should work", ~m(guest_conn)a do + most_views_meetup = Meetup |> order_by(desc: :views) |> limit(1) |> Repo.one() + variables = %{filter: %{sort: "MOST_VIEWS"}} + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + find_meetup = results |> Map.get("entries") |> hd + + assert find_meetup["views"] == most_views_meetup |> Map.get(:views) + end + end + + # TODO test sort, tag, community, when ... + @doc """ + test: FILTER when [TODAY] [THIS_WEEK] [THIS_MONTH] [THIS_YEAR] + """ + describe "[query paged_meetups filter when]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + views + inserted_at + } + totalCount + } + } + """ + test "THIS_YEAR option should work", ~m(guest_conn meetup_last_year)a do + variables = %{filter: %{when: "THIS_YEAR"}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert results["entries"] |> Enum.any?(&(&1["id"] != meetup_last_year.id)) + end + + test "TODAY option should work", ~m(guest_conn)a do + variables = %{filter: %{when: "TODAY"}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + expect_count = @total_count - @last_year_count - @last_month_count - @last_week_count + + assert results |> Map.get("totalCount") == expect_count + end + + test "THIS_WEEK option should work", ~m(guest_conn)a do + variables = %{filter: %{when: "THIS_WEEK"}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert results |> Map.get("totalCount") == @today_count + end + + test "THIS_MONTH option should work", ~m(guest_conn)a do + variables = %{filter: %{when: "THIS_MONTH"}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + {_, cur_week_month, _} = @cur_date |> Date.to_erl() + {_, last_week_month, _} = @last_week |> Date.to_erl() + + expect_count = + case cur_week_month == last_week_month do + true -> + @total_count - @last_year_count - @last_month_count + + false -> + @total_count - @last_year_count - @last_month_count - @last_week_count + end + + assert results |> Map.get("totalCount") == expect_count + end + end + + describe "[query paged_meetups filter extra]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + title + } + totalCount + } + } + """ + test "basic filter should work", ~m(guest_conn)a do + {:ok, meetup} = db_insert(:meetup) + {:ok, meetup2} = db_insert(:meetup) + + variables = %{filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + + assert results["totalCount"] >= 1 + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(meetup.id))) + assert results["entries"] |> Enum.any?(&(&1["id"] != to_string(meetup2.id))) + end + end + + describe "[paged meetups active_at]" do + @query """ + query($filter: PagedMeetupFilter!) { + pagedMeetups(filter: $filter) { + entries { + id + insertedAt + activeAt + } + } + } + """ + + test "latest commented meetup should appear on top", ~m(guest_conn meetup_last_week user)a do + variables = %{filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + entries = results["entries"] + first_meetup = entries |> List.first() + assert first_meetup["id"] !== to_string(meetup_last_week.id) + + Process.sleep(1500) + {:ok, _comment} = CMS.create_comment(:meetup, meetup_last_week.id, mock_comment(), user) + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + entries = results["entries"] + first_meetup = entries |> List.first() + + assert first_meetup["id"] == to_string(meetup_last_week.id) + end + + test "comment on very old meetup have no effect", ~m(guest_conn meetup_last_year user)a do + variables = %{filter: %{page: 1, size: 20}} + + {:ok, _comment} = CMS.create_comment(:meetup, meetup_last_year.id, mock_comment(), user) + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + entries = results["entries"] + first_meetup = entries |> List.first() + + assert first_meetup["id"] !== to_string(meetup_last_year.id) + end + + test "latest meetup author commented meetup have no effect", + ~m(guest_conn meetup_last_week)a do + variables = %{filter: %{page: 1, size: 20}} + + {:ok, _comment} = + CMS.create_comment( + :meetup, + meetup_last_week.id, + mock_comment(), + meetup_last_week.author.user + ) + + results = guest_conn |> query_result(@query, variables, "pagedMeetups") + entries = results["entries"] + first_meetup = entries |> List.first() + + assert first_meetup["id"] !== to_string(meetup_last_week.id) + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index f083eca17..c2adacbad 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -279,6 +279,26 @@ defmodule GroupherServer.Support.Factory do } end + defp mock_meta(:meetup) do + text = Faker.Lorem.sentence(%Range{first: 80, last: 120}) + + %{ + meta: @default_article_meta |> Map.merge(%{thread: "MEETUP"}), + title: String.slice(text, 1, 49), + body: mock_rich_text(), + digest: String.slice(text, 1, 150), + # length: String.length(text), + author: mock(:author), + views: Enum.random(0..2000), + original_community: mock(:community), + communities: [ + mock(:community) + ], + emotions: @default_emotions, + active_at: Timex.shift(Timex.now(), seconds: +1) + } + end + defp mock_meta(:comment) do # text = Faker.Lorem.sentence(%Range{first: 30, last: 80})