diff --git a/config/config.exs b/config/config.exs index 1be88aa08..277e0f5ed 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], + threads: [:post, :job, :repo, :blog, :works, :radar, :guide], # in this period, paged articles will sort front if non-article-author commented # 在此时间段内,一旦有非文章作者的用户评论,该文章就会排到前面 active_period_days: %{ @@ -78,7 +78,8 @@ config :groupher_server, :article, repo: 10, blog: 10, works: 10, - radar: 10 + radar: 10, + guide: 10 }, # NOTE: if you want to add/remove emotion, just edit the list below diff --git a/lib/groupher_server/cms/models/guide.ex b/lib/groupher_server/cms/models/guide.ex new file mode 100644 index 000000000..01422c0c1 --- /dev/null +++ b/lib/groupher_server/cms/models/guide.ex @@ -0,0 +1,48 @@ +defmodule GroupherServer.CMS.Model.Guide 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 :: %Guide{} + schema "cms_guides" do + article_tags_field(:guide) + article_communities_field(:guide) + general_article_fields(:guide) + end + + @doc false + def changeset(%Guide{} = guide, attrs) do + guide + |> 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(%Guide{} = guide, attrs) do + guide + |> 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/guide_document.ex b/lib/groupher_server/cms/models/guide_document.ex new file mode 100644 index 000000000..1af2d0f4b --- /dev/null +++ b/lib/groupher_server/cms/models/guide_document.ex @@ -0,0 +1,47 @@ +defmodule GroupherServer.CMS.Model.GuideDocument 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.Guide + + @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 guide_id)a + @optional_fields [] + + @type t :: %GuideDocument{} + schema "guide_documents" do + belongs_to(:guide, Guide, foreign_key: :guide_id) + + field(:body, :string) + field(:body_html, :string) + field(:toc, :map) + end + + @doc false + def changeset(%GuideDocument{} = guide, attrs) do + guide + |> 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(%GuideDocument{} = guide, attrs) do + guide + |> 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 4277c3fbd..744bae67f 100644 --- a/lib/groupher_server_web/schema/cms/cms_metrics.ex +++ b/lib/groupher_server_web/schema/cms/cms_metrics.ex @@ -212,6 +212,13 @@ defmodule GroupherServerWeb.Schema.CMS.Metrics do field(:sort, :sort_enum) end + @desc "guide_filter doc" + input_object :paged_guides_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 366324fe9..2453bf01a 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -117,6 +117,18 @@ defmodule GroupherServerWeb.Schema.CMS.Types do timestamp_fields(:article) end + object :guide 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/guide.ex b/lib/groupher_server_web/schema/cms/mutations/guide.ex new file mode 100644 index 000000000..a7f19d69b --- /dev/null +++ b/lib/groupher_server_web/schema/cms/mutations/guide.ex @@ -0,0 +1,52 @@ +defmodule GroupherServerWeb.Schema.CMS.Mutations.Guide do + @moduledoc """ + CMS mutations for guide + """ + use Helper.GqlSchemaSuite + import GroupherServerWeb.Schema.Helper.Mutations + + object :cms_guide_mutations do + @desc "create a guide" + field :create_guide, :guide 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: :guide) + 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/guide" + field :update_guide, :guide 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: :guide) + middleware(M.Passport, claim: "owner;cms->c?->guide.edit") + + resolve(&R.CMS.update_article/3) + end + + article_react_mutations(:guide, [ + :upvote, + :pin, + :mark_delete, + :delete, + :emotion, + :report, + :sink, + :lock_comment + ]) + end +end diff --git a/priv/repo/migrations/20210626054252_create_guide.exs b/priv/repo/migrations/20210626054252_create_guide.exs new file mode 100644 index 000000000..aded7419d --- /dev/null +++ b/priv/repo/migrations/20210626054252_create_guide.exs @@ -0,0 +1,32 @@ +defmodule GroupherServer.Repo.Migrations.CreateGuide do + use Ecto.Migration + + def change do + create table(:cms_guides) 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_guides, [:author_id])) + end +end diff --git a/priv/repo/migrations/20210626054616_create_guide_document.exs b/priv/repo/migrations/20210626054616_create_guide_document.exs new file mode 100644 index 000000000..7e79c4ca6 --- /dev/null +++ b/priv/repo/migrations/20210626054616_create_guide_document.exs @@ -0,0 +1,13 @@ +defmodule GroupherServer.Repo.Migrations.CreateGuideDocument do + use Ecto.Migration + + def change do + create table(:guide_documents) do + add(:guide_id, references(:cms_guides, 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/20210626054717_create_communities_join_guides.exs b/priv/repo/migrations/20210626054717_create_communities_join_guides.exs new file mode 100644 index 000000000..334f99f5f --- /dev/null +++ b/priv/repo/migrations/20210626054717_create_communities_join_guides.exs @@ -0,0 +1,12 @@ +defmodule GroupherServer.Repo.Migrations.CreateCommunitiesJoinGuides do + use Ecto.Migration + + def change do + create table(:communities_join_guides) do + add(:community_id, references(:communities, on_delete: :delete_all), null: false) + add(:guide_id, references(:cms_guides, on_delete: :delete_all), null: false) + end + + create(unique_index(:communities_join_guides, [:community_id, :guide_id])) + end +end diff --git a/priv/repo/migrations/20210626054903_add_guide_id_to_others.exs b/priv/repo/migrations/20210626054903_add_guide_id_to_others.exs new file mode 100644 index 000000000..c9c73e448 --- /dev/null +++ b/priv/repo/migrations/20210626054903_add_guide_id_to_others.exs @@ -0,0 +1,41 @@ +defmodule GroupherServer.Repo.Migrations.AddGuideIdToOthers do + use Ecto.Migration + + def change do + alter table(:articles_join_tags) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:abuse_reports) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:article_collects) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:article_upvotes) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:comments) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:pinned_comments) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:articles_users_emotions) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:pinned_articles) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + + alter table(:cited_artiments) do + add(:guide_id, references(:cms_guides, on_delete: :delete_all)) + end + end +end diff --git a/test/groupher_server/accounts/published/published_guide_test.exs b/test/groupher_server/accounts/published/published_guide_test.exs new file mode 100644 index 000000000..1efb3e354 --- /dev/null +++ b/test/groupher_server/accounts/published/published_guide_test.exs @@ -0,0 +1,93 @@ +defmodule GroupherServer.Test.Accounts.Published.Guide 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, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + + {:ok, ~m(user user2 guide community community2)a} + end + + describe "[publised guides]" do + test "create guide should update user published meta", ~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} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, user} = ORM.find(User, user.id) + assert user.meta.published_guides_count == 2 + end + + test "fresh user get empty paged published guides", ~m(user)a do + {:ok, results} = Accounts.paged_published_articles(user, :guide, %{page: 1, size: 20}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == 0 + end + + test "user can get paged published guides", ~m(user user2 community community2)a do + pub_guides = + Enum.reduce(1..@publish_count, [], fn _, acc -> + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + acc ++ [guide] + end) + + pub_guides2 = + Enum.reduce(1..@publish_count, [], fn _, acc -> + guide_attrs = mock_attrs(:guide, %{community_id: community2.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + acc ++ [guide] + end) + + # unrelated other user + Enum.reduce(1..5, [], fn _, acc -> + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user2) + + acc ++ [guide] + end) + + {:ok, results} = Accounts.paged_published_articles(user, :guide, %{page: 1, size: 20}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == @publish_count * 2 + + random_guide_id = pub_guides |> Enum.random() |> Map.get(:id) + random_guide_id2 = pub_guides2 |> Enum.random() |> Map.get(:id) + assert results.entries |> Enum.any?(&(&1.id == random_guide_id)) + assert results.entries |> Enum.any?(&(&1.id == random_guide_id2)) + end + end + + describe "[publised guide comments]" do + test "can get published article comments", ~m(guide user)a do + total_count = 10 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + acc ++ [comment] + end) + + filter = %{page: 1, size: 20} + {:ok, articles} = Accounts.paged_published_comments(user, :guide, filter) + + entries = articles.entries + article = entries |> List.first() + + assert article.article.id == guide.id + assert article.article.title == guide.title + end + end +end diff --git a/test/groupher_server/cms/abuse_reports/guide_report_test.exs b/test/groupher_server/cms/abuse_reports/guide_report_test.exs new file mode 100644 index 000000000..8ae5024df --- /dev/null +++ b/test/groupher_server/cms/abuse_reports/guide_report_test.exs @@ -0,0 +1,137 @@ +defmodule GroupherServer.Test.CMS.AbuseReports.GuideReport do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Guide + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + {:ok, community} = db_insert(:community) + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 community guide_attrs)a} + end + + describe "[article guide report/unreport]" do + test "list article reports should work", ~m(community user user2 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user2) + + filter = %{content_type: :guide, content_id: guide.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + + report = all_reports.entries |> List.first() + assert report.article.id == guide.id + assert report.article.thread == "GUIDE" + end + + test "report a guide should have a abuse report record", ~m(community user guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + + filter = %{content_type: :guide, content_id: guide.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 == guide.id + assert all_reports.total_count == 1 + assert report.report_cases_count == 1 + assert List.first(report_cases).user.login == user.login + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.reported_count == 1 + assert user.id in guide.meta.reported_user_ids + end + + test "can undo a report", ~m(community user guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + {:ok, _report} = CMS.undo_report_article(:guide, guide.id, user) + + filter = %{content_type: :guide, content_id: guide.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 0 + + {:ok, guide} = ORM.find(Guide, guide.id) + assert user.id not in guide.meta.reported_user_ids + end + + test "can undo a existed report", ~m(community user user2 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user2) + {:ok, _report} = CMS.undo_report_article(:guide, guide.id, user) + + filter = %{content_type: :guide, content_id: guide.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + {:ok, guide} = ORM.find(Guide, guide.id) + + assert user2.id in guide.meta.reported_user_ids + assert user.id not in guide.meta.reported_user_ids + end + + test "can undo a report with other user report it too", + ~m(community user user2 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user2) + + filter = %{content_type: :guide, content_id: guide.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(:guide, guide.id, user) + + filter = %{content_type: :guide, content_id: guide.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 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason2", "attr_info 2", user2) + + filter = %{content_type: :guide, content_id: guide.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 guide_attrs user)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + assert {:error, _report} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + end + end +end diff --git a/test/groupher_server/cms/article_community/guide_test.exs b/test/groupher_server/cms/article_community/guide_test.exs new file mode 100644 index 000000000..9ab9d98bb --- /dev/null +++ b/test/groupher_server/cms/article_community/guide_test.exs @@ -0,0 +1,85 @@ +defmodule GroupherServer.Test.CMS.ArticleCommunity.Guide do + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Guide + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + {:ok, community3} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 community community2 community3 guide guide_attrs)a} + end + + describe "[article mirror/move]" do + test "created guide has origial community info", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = ORM.find(Guide, guide.id, preload: :original_community) + + assert guide.original_community_id == community.id + end + + test "guide can be move to other community", ~m(user community community2 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + assert guide.original_community_id == community.id + + {:ok, _} = CMS.move_article(:guide, guide.id, community2.id) + {:ok, guide} = ORM.find(Guide, guide.id, preload: [:original_community, :communities]) + + assert guide.original_community.id == community2.id + assert not is_nil(Enum.find(guide.communities, &(&1.id == community2.id))) + end + + test "guide can be mirror to other community", ~m(user community community2 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :communities) + assert guide.communities |> length == 1 + + assert not is_nil(Enum.find(guide.communities, &(&1.id == community.id))) + + {:ok, _} = CMS.mirror_article(:guide, guide.id, community2.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :communities) + assert guide.communities |> length == 2 + assert not is_nil(Enum.find(guide.communities, &(&1.id == community.id))) + assert not is_nil(Enum.find(guide.communities, &(&1.id == community2.id))) + end + + test "guide can be unmirror from community", + ~m(user community community2 community3 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _} = CMS.mirror_article(:guide, guide.id, community2.id) + {:ok, _} = CMS.mirror_article(:guide, guide.id, community3.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :communities) + assert guide.communities |> length == 3 + + {:ok, _} = CMS.unmirror_article(:guide, guide.id, community3.id) + {:ok, guide} = ORM.find(Guide, guide.id, preload: :communities) + assert guide.communities |> length == 2 + + assert is_nil(Enum.find(guide.communities, &(&1.id == community3.id))) + end + + test "guide can not unmirror from original community", + ~m(user community community2 community3 guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _} = CMS.mirror_article(:guide, guide.id, community2.id) + {:ok, _} = CMS.mirror_article(:guide, guide.id, community3.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :communities) + assert guide.communities |> length == 3 + + {:error, reason} = CMS.unmirror_article(:guide, guide.id, community.id) + assert reason |> is_error?(:mirror_article) + end + end +end diff --git a/test/groupher_server/cms/article_tags/guide_tag_test.exs b/test/groupher_server/cms/article_tags/guide_tag_test.exs new file mode 100644 index 000000000..c26ed5363 --- /dev/null +++ b/test/groupher_server/cms/article_tags/guide_tag_test.exs @@ -0,0 +1,134 @@ +defmodule GroupherServer.Test.CMS.ArticleTag.GuideTag do + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.{Community, ArticleTag, Guide} + alias Helper.{ORM} + + setup do + {:ok, user} = db_insert(:user) + {:ok, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + article_tag_attrs = mock_attrs(:article_tag) + article_tag_attrs2 = mock_attrs(:article_tag) + + guide_attrs = mock_attrs(:guide) + + {:ok, ~m(user community guide guide_attrs article_tag_attrs article_tag_attrs2)a} + end + + describe "[guide 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, :guide, 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, :guide, 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()}, + :guide, + 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, :guide, 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 guide article_tag_attrs article_tag_attrs2 user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :guide, article_tag_attrs2, user) + + {:ok, guide} = CMS.set_article_tag(:guide, guide.id, article_tag.id) + {:ok, guide} = CMS.set_article_tag(:guide, guide.id, article_tag2.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :article_tags) + assert exist_in?(article_tag, guide.article_tags) + assert exist_in?(article_tag2, guide.article_tags) + + {:ok, _} = CMS.delete_article_tag(article_tag.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :article_tags) + assert not exist_in?(article_tag, guide.article_tags) + assert exist_in?(article_tag2, guide.article_tags) + + {:ok, _} = CMS.delete_article_tag(article_tag2.id) + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :article_tags) + assert not exist_in?(article_tag, guide.article_tags) + assert not exist_in?(article_tag2, guide.article_tags) + end + end + + describe "[create/update guide with tags]" do + test "can create guide with exsited article tags", + ~m(community user guide_attrs article_tag_attrs article_tag_attrs2)a do + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :guide, article_tag_attrs2, user) + + guide_with_tags = Map.merge(guide_attrs, %{article_tags: [article_tag.id, article_tag2.id]}) + + {:ok, created} = CMS.create_article(community, :guide, guide_with_tags, user) + {:ok, guide} = ORM.find(Guide, created.id, preload: :article_tags) + + assert exist_in?(article_tag, guide.article_tags) + assert exist_in?(article_tag2, guide.article_tags) + end + + test "can not create guide with other community's article tags", + ~m(community user guide_attrs article_tag_attrs article_tag_attrs2)a do + {:ok, community2} = db_insert(:community) + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community2, :guide, article_tag_attrs2, user) + + guide_with_tags = Map.merge(guide_attrs, %{article_tags: [article_tag.id, article_tag2.id]}) + + {:error, reason} = CMS.create_article(community, :guide, guide_with_tags, user) + is_error?(reason, :invalid_domain_tag) + end + end + + describe "[guide tag set /unset]" do + test "can set a tag ", ~m(community guide article_tag_attrs article_tag_attrs2 user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :guide, article_tag_attrs2, user) + + {:ok, guide} = CMS.set_article_tag(:guide, guide.id, article_tag.id) + assert guide.article_tags |> length == 1 + assert exist_in?(article_tag, guide.article_tags) + + {:ok, guide} = CMS.set_article_tag(:guide, guide.id, article_tag2.id) + assert guide.article_tags |> length == 2 + assert exist_in?(article_tag, guide.article_tags) + assert exist_in?(article_tag2, guide.article_tags) + + {:ok, guide} = CMS.unset_article_tag(:guide, guide.id, article_tag.id) + assert guide.article_tags |> length == 1 + assert not exist_in?(article_tag, guide.article_tags) + assert exist_in?(article_tag2, guide.article_tags) + + {:ok, guide} = CMS.unset_article_tag(:guide, guide.id, article_tag2.id) + assert guide.article_tags |> length == 0 + assert not exist_in?(article_tag, guide.article_tags) + assert not exist_in?(article_tag2, guide.article_tags) + end + end +end diff --git a/test/groupher_server/cms/articles/guide_pin_test.exs b/test/groupher_server/cms/articles/guide_pin_test.exs new file mode 100644 index 000000000..aaa1a1da2 --- /dev/null +++ b/test/groupher_server/cms/articles/guide_pin_test.exs @@ -0,0 +1,54 @@ +defmodule GroupherServer.Test.CMS.Artilces.GuidePin 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, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + {:ok, ~m(user community guide)a} + end + + describe "[cms guide pin]" do + test "can pin a guide", ~m(community guide)a do + {:ok, _} = CMS.pin_article(:guide, guide.id, community.id) + {:ok, pind_article} = ORM.find_by(PinnedArticle, %{guide_id: guide.id}) + + assert pind_article.guide_id == guide.id + end + + test "one community & thread can only pin certern count of guide", ~m(community user)a do + Enum.reduce(1..@max_pinned_article_count_per_thread, [], fn _, acc -> + {:ok, new_guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + {:ok, _} = CMS.pin_article(:guide, new_guide.id, community.id) + acc + end) + + {:ok, new_guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + {:error, reason} = CMS.pin_article(:guide, new_guide.id, community.id) + assert reason |> Keyword.get(:code) == ecode(:too_much_pinned_article) + end + + test "can not pin a non-exsit guide", ~m(community)a do + assert {:error, _} = CMS.pin_article(:guide, 8848, community.id) + end + + test "can undo pin to a guide", ~m(community guide)a do + {:ok, _} = CMS.pin_article(:guide, guide.id, community.id) + + assert {:ok, unpinned} = CMS.undo_pin_article(:guide, guide.id, community.id) + + assert {:error, _} = ORM.find_by(PinnedArticle, %{guide_id: guide.id}) + end + end +end diff --git a/test/groupher_server/cms/articles/guide_test.exs b/test/groupher_server/cms/articles/guide_test.exs new file mode 100644 index 000000000..af8cad980 --- /dev/null +++ b/test/groupher_server/cms/articles/guide_test.exs @@ -0,0 +1,198 @@ +defmodule GroupherServer.Test.Articles.Guide do + use GroupherServer.TestTools + + alias GroupherServer.{CMS, Repo} + alias Helper.Converter.{EditorToHTML, HtmlSanitizer} + + alias EditorToHTML.{Class, Validator} + alias CMS.Model.{Author, Guide, Community, ArticleDocument, GuideDocument} + 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) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 community guide_attrs)a} + end + + describe "[cms guides curd]" do + test "can create guide with valid attrs", ~m(user community guide_attrs)a do + assert {:error, _} = ORM.find_by(Author, user_id: user.id) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + guide = Repo.preload(guide, :document) + + body_map = Jason.decode!(guide.document.body) + + assert guide.meta.thread == "GUIDE" + + assert guide.title == guide_attrs.title + assert body_map |> Validator.is_valid() + + assert guide.document.body_html + |> String.contains?(~s(
)) + + assert guide.document.body_html |> String.contains?(~s(

List.first() |> get_in(["data", "text"]) + assert guide.digest == paragraph_text |> HtmlSanitizer.strip_all_tags() + end + + test "created guide should have a acitve_at field, same with inserted_at", + ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + assert guide.active_at == guide.inserted_at + end + + test "read guide should update views and meta viewed_user_list", + ~m(guide_attrs community user user2)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _} = CMS.read_article(:guide, guide.id, user) + {:ok, _created} = ORM.find(Guide, guide.id) + + # same user duplicate case + {:ok, _} = CMS.read_article(:guide, guide.id, user) + {:ok, created} = ORM.find(Guide, guide.id) + + assert created.meta.viewed_user_ids |> length == 1 + assert user.id in created.meta.viewed_user_ids + + {:ok, _} = CMS.read_article(:guide, guide.id, user2) + {:ok, created} = ORM.find(Guide, guide.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 guide should contains viewer_has_xxx state", + ~m(guide_attrs community user user2)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = CMS.read_article(:guide, guide.id, user) + + assert not guide.viewer_has_collected + assert not guide.viewer_has_upvoted + assert not guide.viewer_has_reported + + {:ok, guide} = CMS.read_article(:guide, guide.id) + + assert not guide.viewer_has_collected + assert not guide.viewer_has_upvoted + assert not guide.viewer_has_reported + + {:ok, guide} = CMS.read_article(:guide, guide.id, user2) + + assert not guide.viewer_has_collected + assert not guide.viewer_has_upvoted + assert not guide.viewer_has_reported + + {:ok, _} = CMS.upvote_article(:guide, guide.id, user) + {:ok, _} = CMS.collect_article(:guide, guide.id, user) + {:ok, _} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + + {:ok, guide} = CMS.read_article(:guide, guide.id, user) + + assert guide.viewer_has_collected + assert guide.viewer_has_upvoted + assert guide.viewer_has_reported + end + + test "create guide with an exsit community fails", ~m(user)a do + invalid_attrs = mock_attrs(:guide, %{community_id: non_exsit_id()}) + ivalid_community = %Community{id: non_exsit_id()} + + assert {:error, _} = CMS.create_article(ivalid_community, :guide, invalid_attrs, user) + end + end + + describe "[cms guide sink/undo_sink]" do + test "if a guide is too old, read guide should update can_undo_sink flag", + ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + assert guide.meta.can_undo_sink + + {:ok, guide_last_year} = db_insert(:guide, %{title: "last year", inserted_at: @last_year}) + {:ok, guide_last_year} = CMS.read_article(:guide, guide_last_year.id) + assert not guide_last_year.meta.can_undo_sink + + {:ok, guide_last_year} = CMS.read_article(:guide, guide_last_year.id, user) + assert not guide_last_year.meta.can_undo_sink + end + + test "can sink a guide", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + assert not guide.meta.is_sinked + + {:ok, guide} = CMS.sink_article(:guide, guide.id) + assert guide.meta.is_sinked + assert guide.active_at == guide.inserted_at + end + + test "can undo sink guide", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = CMS.sink_article(:guide, guide.id) + assert guide.meta.is_sinked + assert guide.meta.last_active_at == guide.active_at + + {:ok, guide} = CMS.undo_sink_article(:guide, guide.id) + assert not guide.meta.is_sinked + assert guide.active_at == guide.meta.last_active_at + end + + test "can not undo sink to old guide", ~m()a do + {:ok, guide_last_year} = db_insert(:guide, %{title: "last year", inserted_at: @last_year}) + + {:error, reason} = CMS.undo_sink_article(:guide, guide_last_year.id) + is_error?(reason, :undo_sink_old_article) + end + end + + describe "[cms guide document]" do + test "will create related document after create", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = CMS.read_article(:guide, guide.id) + assert not is_nil(guide.document.body_html) + {:ok, guide} = CMS.read_article(:guide, guide.id, user) + assert not is_nil(guide.document.body_html) + + {:ok, article_doc} = ORM.find_by(ArticleDocument, %{article_id: guide.id, thread: "GUIDE"}) + {:ok, guide_doc} = ORM.find_by(GuideDocument, %{guide_id: guide.id}) + + assert guide.document.body == guide_doc.body + assert article_doc.body == guide_doc.body + end + + @tag :wip + test "delete guide should also delete related document", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _article_doc} = ORM.find_by(ArticleDocument, %{article_id: guide.id, thread: "GUIDE"}) + {:ok, _guide_doc} = ORM.find_by(GuideDocument, %{guide_id: guide.id}) + + {:ok, _} = CMS.delete_article(guide) + + {:error, _} = ORM.find(Guide, guide.id) + {:error, _} = ORM.find_by(ArticleDocument, %{article_id: guide.id, thread: "GUIDE"}) + {:error, _} = ORM.find_by(GuideDocument, %{guide_id: guide.id}) + end + + test "update guide should also update related document", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + body = mock_rich_text(~s(new content)) + {:ok, guide} = CMS.update_article(guide, %{body: body}) + + {:ok, article_doc} = ORM.find_by(ArticleDocument, %{article_id: guide.id, thread: "GUIDE"}) + {:ok, guide_doc} = ORM.find_by(GuideDocument, %{guide_id: guide.id}) + + assert String.contains?(guide_doc.body, "new content") + assert String.contains?(article_doc.body, "new content") + end + end +end diff --git a/test/groupher_server/cms/emotions/guide_emotions_test.exs b/test/groupher_server/cms/emotions/guide_emotions_test.exs new file mode 100644 index 000000000..ada57da02 --- /dev/null +++ b/test/groupher_server/cms/emotions/guide_emotions_test.exs @@ -0,0 +1,178 @@ +defmodule GroupherServer.Test.CMS.Emotions.GuideEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Guide, 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) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 user3 community guide_attrs)a} + end + + describe "[emotion in paged guides]" do + test "login user should got viewer has emotioned status", + ~m(community guide_attrs user)a do + total_count = 10 + page_number = 10 + page_size = 20 + + all_guides = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + acc ++ [guide] + end) + + random_guide = all_guides |> Enum.at(3) + + {:ok, _} = CMS.emotion_to_article(:guide, random_guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, random_guide.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:guide, random_guide.id, :popcorn, user) + + {:ok, paged_articles} = + CMS.paged_articles(:guide, %{page: page_number, size: page_size}, user) + + target = Enum.find(paged_articles.entries, &(&1.id == random_guide.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 "guide has default emotions after created", ~m(community guide_attrs user)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + emotions = guide.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + test "can make emotion to guide", ~m(community guide_attrs user user2)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Guide, guide.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 guide", ~m(community guide_attrs user user2)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user2) + + {:ok, _} = CMS.undo_emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_article(:guide, guide.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(Guide, guide.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 guide.", ~m(community guide_attrs user)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + + {:ok, guide} = ORM.find(Guide, guide.id) + + assert guide.emotions.downvote_count == 1 + assert user_exist_in?(user, guide.emotions.latest_downvote_users) + end + + test "same user same emotion to same guide only have one user_emotion record", + ~m(community guide_attrs user)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :heart, user) + + {:ok, guide} = ORM.find(Guide, guide.id) + + {:ok, records} = ORM.find_all(ArticleUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = ORM.find_by(ArticleUserEmotion, %{guide_id: guide.id, user_id: user.id}) + assert record.downvote + assert record.heart + end + + test "different user can make same emotions on same guide", + ~m(community guide_attrs user user2 user3)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :beer, user2) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(Guide, guide.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 guide", + ~m(community guide_attrs user)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :heart, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(Guide, guide.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_guide_test.exs b/test/groupher_server/cms/hooks/cite_guide_test.exs new file mode 100644 index 000000000..d69c9f249 --- /dev/null +++ b/test/groupher_server/cms/hooks/cite_guide_test.exs @@ -0,0 +1,230 @@ +defmodule GroupherServer.Test.CMS.Hooks.CiteGuide do + use GroupherServer.TestTools + + import Helper.Utils, only: [get_config: 2] + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.Model.{Guide, 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, guide} = db_insert(:guide) + {:ok, guide2} = db_insert(:guide) + {:ok, guide3} = db_insert(:guide) + {:ok, guide4} = db_insert(:guide) + {:ok, guide5} = db_insert(:guide) + + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 community guide guide2 guide3 guide4 guide5 guide_attrs)a} + end + + describe "[cite basic]" do + test "cited multi guide should work", + ~m(user community guide2 guide3 guide4 guide5 guide_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) + ) + + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + body = mock_rich_text(~s(the )) + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide_n} = CMS.create_article(community, :guide, guide_attrs, user) + + Hooks.Cite.handle(guide) + Hooks.Cite.handle(guide_n) + + {:ok, guide2} = ORM.find(Guide, guide2.id) + {:ok, guide3} = ORM.find(Guide, guide3.id) + {:ok, guide4} = ORM.find(Guide, guide4.id) + {:ok, guide5} = ORM.find(Guide, guide5.id) + + assert guide2.meta.citing_count == 1 + assert guide3.meta.citing_count == 2 + assert guide4.meta.citing_count == 1 + assert guide5.meta.citing_count == 1 + end + + test "cited guide itself should not work", ~m(user community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + body = mock_rich_text(~s(the )) + {:ok, guide} = CMS.update_article(guide, %{body: body}) + + Hooks.Cite.handle(guide) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.citing_count == 0 + end + + test "cited comment itself should not work", ~m(user guide)a do + {:ok, cited_comment} = CMS.create_comment(:guide, guide.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 guide's comment in guide", ~m(community user guide guide2 guide_attrs)a do + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_rich_text("hello"), user) + + body = + mock_rich_text( + ~s(the ) + ) + + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + Hooks.Cite.handle(guide) + + {: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}) + + # 被 guide 以 comment link 的方式引用了 + assert cited_content.guide_id == guide.id + assert cited_content.cited_by_type == "COMMENT" + end + + test "can cite a comment in a comment", ~m(user guide)a do + {:ok, cited_comment} = CMS.create_comment(:guide, guide.id, mock_rich_text("hello"), user) + + comment_body = + mock_rich_text( + ~s(the ) + ) + + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide inside a comment", ~m(user guide guide2 guide3 guide4 guide5)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(:guide, guide.id, comment_body, user) + Hooks.Cite.handle(comment) + + comment_body = mock_rich_text(~s(the )) + {:ok, comment} = CMS.create_comment(:guide, guide.id, comment_body, user) + + Hooks.Cite.handle(comment) + + {:ok, guide2} = ORM.find(Guide, guide2.id) + {:ok, guide3} = ORM.find(Guide, guide3.id) + {:ok, guide4} = ORM.find(Guide, guide4.id) + {:ok, guide5} = ORM.find(Guide, guide5.id) + + assert guide2.meta.citing_count == 1 + assert guide3.meta.citing_count == 2 + assert guide4.meta.citing_count == 1 + assert guide5.meta.citing_count == 1 + end + end + + describe "[cite pagi]" do + test "can get paged cited articles.", ~m(user community guide2 guide_attrs)a do + {:ok, comment} = + CMS.create_comment( + :guide, + guide2.id, + mock_comment(~s(the )), + user + ) + + Process.sleep(1000) + + body = + mock_rich_text( + ~s(the ), + ~s(the ) + ) + + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide_x} = CMS.create_article(community, :guide, guide_attrs, user) + + Process.sleep(1000) + body = mock_rich_text(~s(the )) + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide_y} = CMS.create_article(community, :guide, guide_attrs, user) + + Hooks.Cite.handle(guide_x) + Hooks.Cite.handle(comment) + Hooks.Cite.handle(guide_y) + + {:ok, result} = CMS.paged_citing_contents("GUIDE", guide2.id, %{page: 1, size: 10}) + + entries = result.entries + + result_comment = entries |> List.first() + result_guide_x = entries |> Enum.at(1) + result_guide_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 == guide2.id + assert result_comment.title == guide2.title + + assert result_guide_x.id == guide_x.id + assert result_guide_x.block_linker |> length == 2 + assert result_guide_x |> Map.keys() == article_map_keys + + assert result_guide_y.id == guide_y.id + assert result_guide_y.block_linker |> length == 1 + assert result_guide_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_guide_test.exs b/test/groupher_server/cms/hooks/mention_in_guide_test.exs new file mode 100644 index 000000000..5747c02a6 --- /dev/null +++ b/test/groupher_server/cms/hooks/mention_in_guide_test.exs @@ -0,0 +1,98 @@ +defmodule GroupherServer.Test.CMS.Hooks.MentionInGuide 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, guide} = db_insert(:guide) + + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 user3 community guide guide_attrs)a} + end + + describe "[mention in guide basic]" do + test "mention multi user in guide should work", + ~m(user user2 user3 community guide_attrs)a do + body = + mock_rich_text( + ~s(hi

#{user2.login}
, and
#{user3.login}
), + ~s(hi
#{user2.login}
) + ) + + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = preload_author(guide) + + {:ok, _result} = Hooks.Mention.handle(guide) + + {:ok, result} = Delivery.fetch(:mention, user2, %{page: 1, size: 10}) + + mention = result.entries |> List.first() + assert mention.thread == "GUIDE" + assert mention.block_linker |> length == 2 + assert mention.article_id == guide.id + assert mention.title == guide.title + assert mention.user.login == guide.author.user.login + + {:ok, result} = Delivery.fetch(:mention, user3, %{page: 1, size: 10}) + + mention = result.entries |> List.first() + assert mention.thread == "GUIDE" + assert mention.block_linker |> length == 1 + assert mention.article_id == guide.id + assert mention.title == guide.title + assert mention.user.login == guide.author.user.login + end + + test "mention in guide's comment should work", ~m(user user2 guide)a do + comment_body = + mock_rich_text(~s(hi
#{user2.login}
)) + + {:ok, comment} = CMS.create_comment(:guide, guide.id, comment_body, user) + {:ok, comment} = preload_author(comment) + + {:ok, _result} = Hooks.Mention.handle(comment) + {:ok, result} = Delivery.fetch(:mention, user2, %{page: 1, size: 10}) + + mention = result.entries |> List.first() + assert mention.thread == "GUIDE" + assert mention.comment_id == comment.id + assert mention.block_linker |> length == 1 + assert mention.article_id == guide.id + assert mention.title == guide.title + assert mention.user.login == comment.author.login + end + + test "can not mention author self in guide or comment", ~m(community user guide_attrs)a do + body = mock_rich_text(~s(hi
#{user.login}
)) + guide_attrs = guide_attrs |> Map.merge(%{body: body}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, result} = Delivery.fetch(:mention, user, %{page: 1, size: 10}) + assert result.total_count == 0 + + comment_body = + mock_rich_text(~s(hi
#{user.login}
)) + + {:ok, comment} = CMS.create_comment(:guide, guide.id, comment_body, user) + + {:ok, _result} = Hooks.Mention.handle(comment) + {:ok, result} = Delivery.fetch(:mention, user, %{page: 1, size: 10}) + + assert result.total_count == 0 + end + end +end diff --git a/test/groupher_server/cms/hooks/notify_guide_test.exs b/test/groupher_server/cms/hooks/notify_guide_test.exs new file mode 100644 index 000000000..24e41877d --- /dev/null +++ b/test/groupher_server/cms/hooks/notify_guide_test.exs @@ -0,0 +1,174 @@ +defmodule GroupherServer.Test.CMS.Hooks.NotifyGuide do + use GroupherServer.TestTools + + import GroupherServer.CMS.Delegate.Helper, only: [preload_author: 1] + + alias GroupherServer.{CMS, Delivery, Repo} + alias CMS.Delegate.Hooks + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + + {:ok, ~m(user2 user3 guide comment)a} + end + + describe "[upvote notify]" do + test "upvote hook should work on guide", ~m(user2 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user2) + Hooks.Notify.handle(:upvote, article, user2) + + {:ok, notifications} = + Delivery.fetch(:notification, guide.author.user, %{page: 1, size: 20}) + + assert notifications.total_count == 1 + + notify = notifications.entries |> List.first() + assert notify.action == "UPVOTE" + assert notify.article_id == guide.id + assert notify.thread == "GUIDE" + assert notify.user_id == guide.author.user.id + assert user_exist_in?(user2, notify.from_users) + end + + test "upvote hook should work on guide comment", ~m(user2 guide comment)a do + {:ok, comment} = CMS.upvote_comment(comment.id, user2) + {:ok, comment} = preload_author(comment) + + Hooks.Notify.handle(:upvote, comment, user2) + + {:ok, notifications} = Delivery.fetch(:notification, comment.author, %{page: 1, size: 20}) + + assert notifications.total_count == 1 + + notify = notifications.entries |> List.first() + assert notify.action == "UPVOTE" + assert notify.article_id == guide.id + assert notify.thread == "GUIDE" + assert notify.user_id == comment.author.id + assert notify.comment_id == comment.id + assert user_exist_in?(user2, notify.from_users) + end + + test "undo upvote hook should work on guide", ~m(user2 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user2) + Hooks.Notify.handle(:upvote, article, user2) + + {:ok, article} = CMS.undo_upvote_article(:guide, guide.id, user2) + Hooks.Notify.handle(:undo, :upvote, article, user2) + + {:ok, notifications} = + Delivery.fetch(:notification, guide.author.user, %{page: 1, size: 20}) + + assert notifications.total_count == 0 + end + + test "undo upvote hook should work on guide comment", ~m(user2 comment)a do + {:ok, comment} = CMS.upvote_comment(comment.id, user2) + + Hooks.Notify.handle(:upvote, comment, user2) + + {:ok, comment} = CMS.undo_upvote_comment(comment.id, user2) + Hooks.Notify.handle(:undo, :upvote, comment, user2) + + {:ok, comment} = preload_author(comment) + + {:ok, notifications} = Delivery.fetch(:notification, comment.author, %{page: 1, size: 20}) + + assert notifications.total_count == 0 + end + end + + describe "[collect notify]" do + test "collect hook should work on guide", ~m(user2 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, _} = CMS.collect_article(:guide, guide.id, user2) + Hooks.Notify.handle(:collect, guide, user2) + + {:ok, notifications} = + Delivery.fetch(:notification, guide.author.user, %{page: 1, size: 20}) + + assert notifications.total_count == 1 + + notify = notifications.entries |> List.first() + assert notify.action == "COLLECT" + assert notify.article_id == guide.id + assert notify.thread == "GUIDE" + assert notify.user_id == guide.author.user.id + assert user_exist_in?(user2, notify.from_users) + end + + test "undo collect hook should work on guide", ~m(user2 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, _} = CMS.upvote_article(:guide, guide.id, user2) + Hooks.Notify.handle(:collect, guide, user2) + + {:ok, _} = CMS.undo_upvote_article(:guide, guide.id, user2) + Hooks.Notify.handle(:undo, :collect, guide, user2) + + {:ok, notifications} = + Delivery.fetch(:notification, guide.author.user, %{page: 1, size: 20}) + + assert notifications.total_count == 0 + end + end + + describe "[comment notify]" do + test "guide author should get notify after some one comment on it", ~m(user2 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user2) + Hooks.Notify.handle(:comment, comment, user2) + + {:ok, notifications} = + Delivery.fetch(:notification, guide.author.user, %{page: 1, size: 20}) + + assert notifications.total_count == 1 + + notify = notifications.entries |> List.first() + assert notify.action == "COMMENT" + assert notify.thread == "GUIDE" + assert notify.article_id == guide.id + assert notify.user_id == guide.author.user.id + assert user_exist_in?(user2, notify.from_users) + end + + test "guide comment author should get notify after some one reply it", + ~m(user2 user3 guide)a do + {:ok, guide} = preload_author(guide) + + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user2) + {:ok, replyed_comment} = CMS.reply_comment(comment.id, mock_comment(), user3) + + Hooks.Notify.handle(:reply, replyed_comment, user3) + + comment = Repo.preload(comment, :author) + {:ok, notifications} = Delivery.fetch(:notification, comment.author, %{page: 1, size: 20}) + + assert notifications.total_count == 1 + + notify = notifications.entries |> List.first() + + assert notify.action == "REPLY" + assert notify.thread == "GUIDE" + assert notify.article_id == guide.id + assert notify.comment_id == replyed_comment.id + + assert notify.user_id == comment.author_id + assert user_exist_in?(user3, notify.from_users) + end + end +end diff --git a/test/groupher_server/cms/upvotes/guide_upvote_test.exs b/test/groupher_server/cms/upvotes/guide_upvote_test.exs new file mode 100644 index 000000000..0d53bbd1e --- /dev/null +++ b/test/groupher_server/cms/upvotes/guide_upvote_test.exs @@ -0,0 +1,83 @@ +defmodule GroupherServer.Test.Upvotes.GuideUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, ~m(user user2 community guide_attrs)a} + end + + describe "[cms guide upvote]" do + test "guide can be upvote && upvotes_count should inc by 1", + ~m(user user2 community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user) + assert article.id == guide.id + assert article.upvotes_count == 1 + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user2) + assert article.upvotes_count == 2 + end + + test "guide can be undo upvote && upvotes_count should dec by 1", + ~m(user user2 community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user) + assert article.id == guide.id + assert article.upvotes_count == 1 + + {:ok, article} = CMS.undo_upvote_article(:guide, guide.id, user2) + assert article.upvotes_count == 0 + end + + test "can get upvotes_users", ~m(user user2 community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _article} = CMS.upvote_article(:guide, guide.id, user) + {:ok, _article} = CMS.upvote_article(:guide, guide.id, user2) + + {:ok, users} = CMS.upvoted_users(:guide, guide.id, %{page: 1, size: 2}) + + assert users |> is_valid_pagination?(:raw) + assert user_exist_in?(user, users.entries) + assert user_exist_in?(user2, users.entries) + end + + test "guide meta history should be updated after upvote", + ~m(user user2 community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, article} = CMS.upvote_article(:guide, guide.id, user) + assert user.id in article.meta.upvoted_user_ids + + {:ok, article} = CMS.upvote_article(:guide, guide.id, user2) + assert user.id in article.meta.upvoted_user_ids + assert user2.id in article.meta.upvoted_user_ids + end + + test "guide meta history should be updated after undo upvote", + ~m(user user2 community guide_attrs)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _article} = CMS.upvote_article(:guide, guide.id, user) + {:ok, article} = CMS.upvote_article(:guide, guide.id, user2) + + assert user.id in article.meta.upvoted_user_ids + assert user2.id in article.meta.upvoted_user_ids + + {:ok, article} = CMS.undo_upvote_article(:guide, guide.id, user2) + assert user2.id not in article.meta.upvoted_user_ids + + {:ok, article} = CMS.undo_upvote_article(:guide, guide.id, user) + assert user.id not in article.meta.upvoted_user_ids + end + end +end diff --git a/test/groupher_server_web/mutation/cms/abuse_reports/guide_report_test.exs b/test/groupher_server_web/mutation/cms/abuse_reports/guide_report_test.exs new file mode 100644 index 000000000..a2621c7e6 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/abuse_reports/guide_report_test.exs @@ -0,0 +1,62 @@ +defmodule GroupherServer.Test.Mutation.AbuseReports.GuideReport do + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn user guest_conn owner_conn community guide_attrs)a} + end + + describe "[guide report/undo_report]" do + @report_query """ + mutation($id: ID!, $reason: String!, $attr: String) { + reportGuide(id: $id, reason: $reason, attr: $attr) { + id + title + } + } + """ + + test "login user can report a guide", ~m(community guide_attrs user user_conn)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + variables = %{id: guide.id, reason: "reason"} + article = user_conn |> mutation_result(@report_query, variables, "reportGuide") + + assert article["id"] == to_string(guide.id) + end + + @undo_report_query """ + mutation($id: ID!) { + undoReportGuide(id: $id) { + id + title + } + } + """ + + test "login user can undo report a guide", ~m(community guide_attrs user user_conn)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + variables = %{id: guide.id, reason: "reason"} + article = user_conn |> mutation_result(@report_query, variables, "reportGuide") + + assert article["id"] == to_string(guide.id) + + variables = %{id: guide.id} + + article = user_conn |> mutation_result(@undo_report_query, variables, "undoReportGuide") + + assert article["id"] == to_string(guide.id) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/article_community/guide_test.exs b/test/groupher_server_web/mutation/cms/article_community/guide_test.exs new file mode 100644 index 000000000..84b0dc442 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/article_community/guide_test.exs @@ -0,0 +1,148 @@ +defmodule GroupherServer.Test.Mutation.ArticleCommunity.Guide do + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Guide + + setup do + {:ok, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:owner, guide) + + {:ok, ~m(user_conn guest_conn owner_conn community guide)a} + end + + describe "[mirror/unmirror/move guide to/from community]" do + @mirror_article_query """ + mutation($id: ID!, $thread: Thread, $communityId: ID!) { + mirrorArticle(id: $id, thread: $thread, communityId: $communityId) { + id + } + } + """ + test "auth user can mirror a guide to other community", ~m(guide)a do + passport_rules = %{"guide.community.mirror" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, community} = db_insert(:community) + variables = %{id: guide.id, thread: "GUIDE", communityId: community.id} + rule_conn |> mutation_result(@mirror_article_query, variables, "mirrorArticle") + {:ok, found} = ORM.find(Guide, guide.id, preload: :communities) + + assoc_communities = found.communities |> Enum.map(& &1.id) + assert community.id in assoc_communities + end + + test "unauth user cannot mirror a guide to a community", ~m(user_conn guest_conn guide)a do + {:ok, community} = db_insert(:community) + variables = %{id: guide.id, thread: "GUIDE", communityId: community.id} + rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) + + assert user_conn + |> mutation_get_error?(@mirror_article_query, variables, ecode(:passport)) + + assert guest_conn + |> mutation_get_error?(@mirror_article_query, variables, ecode(:account_login)) + + assert rule_conn + |> mutation_get_error?(@mirror_article_query, variables, ecode(:passport)) + end + + test "auth user can mirror multi guide to other communities", ~m(guide)a do + passport_rules = %{"guide.community.mirror" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + + variables = %{id: guide.id, thread: "GUIDE", communityId: community.id} + rule_conn |> mutation_result(@mirror_article_query, variables, "mirrorArticle") + + variables = %{id: guide.id, thread: "GUIDE", communityId: community2.id} + rule_conn |> mutation_result(@mirror_article_query, variables, "mirrorArticle") + + {:ok, found} = ORM.find(Guide, guide.id, preload: :communities) + + assoc_communities = found.communities |> Enum.map(& &1.id) + assert community.id in assoc_communities + assert community2.id in assoc_communities + end + + @unmirror_article_query """ + mutation($id: ID!, $thread: Thread, $communityId: ID!) { + unmirrorArticle(id: $id, thread: $thread, communityId: $communityId) { + id + } + } + """ + + test "auth user can unmirror guide to a community", ~m(guide)a do + passport_rules = %{"guide.community.mirror" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + + variables = %{id: guide.id, thread: "GUIDE", communityId: community.id} + rule_conn |> mutation_result(@mirror_article_query, variables, "mirrorArticle") + + variables2 = %{id: guide.id, thread: "GUIDE", communityId: community2.id} + rule_conn |> mutation_result(@mirror_article_query, variables2, "mirrorArticle") + + {:ok, found} = ORM.find(Guide, guide.id, preload: :communities) + + assoc_communities = found.communities |> Enum.map(& &1.id) + assert community.id in assoc_communities + assert community2.id in assoc_communities + + passport_rules = %{"guide.community.unmirror" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@unmirror_article_query, variables, "unmirrorArticle") + {:ok, found} = ORM.find(Guide, guide.id, preload: :communities) + assoc_communities = found.communities |> Enum.map(& &1.id) + assert community.id not in assoc_communities + assert community2.id in assoc_communities + end + + @move_article_query """ + mutation($id: ID!, $thread: Thread, $communityId: ID!) { + moveArticle(id: $id, thread: $thread, communityId: $communityId) { + id + } + } + """ + test "auth user can move guide to other community", ~m(guide)a do + passport_rules = %{"guide.community.mirror" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + + variables = %{id: guide.id, thread: "GUIDE", communityId: community.id} + rule_conn |> mutation_result(@mirror_article_query, variables, "mirrorArticle") + {:ok, found} = ORM.find(Guide, guide.id, preload: [:original_community, :communities]) + assoc_communities = found.communities |> Enum.map(& &1.id) + assert community.id in assoc_communities + + passport_rules = %{"guide.community.move" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + pre_original_community_id = found.original_community.id + + variables = %{id: guide.id, thread: "GUIDE", communityId: community2.id} + rule_conn |> mutation_result(@move_article_query, variables, "moveArticle") + {:ok, found} = ORM.find(Guide, guide.id, preload: [:original_community, :communities]) + assoc_communities = found.communities |> Enum.map(& &1.id) + assert pre_original_community_id not in assoc_communities + assert community2.id in assoc_communities + assert community2.id == found.original_community_id + + assert found.original_community.id == community2.id + end + end +end diff --git a/test/groupher_server_web/mutation/cms/article_tags/guide_tag_test.exs b/test/groupher_server_web/mutation/cms/article_tags/guide_tag_test.exs new file mode 100644 index 000000000..a9f6f6457 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/article_tags/guide_tag_test.exs @@ -0,0 +1,91 @@ +defmodule GroupherServer.Test.Mutation.ArticleTags.GuideTag do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + alias CMS.Model.Guide + + setup do + {:ok, guide} = db_insert(:guide) + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:owner, guide) + + article_tag_attrs = mock_attrs(:article_tag) + article_tag_attrs2 = mock_attrs(:article_tag) + + {:ok, + ~m(user_conn guest_conn owner_conn community guide article_tag_attrs article_tag_attrs2 user)a} + end + + describe "[mutation guide tag]" do + @set_tag_query """ + mutation($id: ID!, $thread: Thread, $articleTagId: ID!, $communityId: ID!) { + setArticleTag(id: $id, thread: $thread, articleTagId: $articleTagId, communityId: $communityId) { + id + } + } + """ + + test "auth user can set a valid tag to guide", ~m(community guide article_tag_attrs user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + + passport_rules = %{community.title => %{"guide.article_tag.set" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + variables = %{ + id: guide.id, + thread: "GUIDE", + articleTagId: article_tag.id, + communityId: community.id + } + + rule_conn |> mutation_result(@set_tag_query, variables, "setArticleTag") + {:ok, found} = ORM.find(Guide, guide.id, preload: :article_tags) + + assoc_tags = found.article_tags |> Enum.map(& &1.id) + assert article_tag.id in assoc_tags + end + + @unset_tag_query """ + mutation($id: ID!, $thread: Thread, $articleTagId: ID!, $communityId: ID!) { + unsetArticleTag(id: $id, thread: $thread, articleTagId: $articleTagId, communityId: $communityId) { + id + title + } + } + """ + + test "can unset tag to a guide", + ~m(community guide article_tag_attrs article_tag_attrs2 user)a do + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :guide, article_tag_attrs2, user) + + {:ok, _} = CMS.set_article_tag(:guide, guide.id, article_tag.id) + {:ok, _} = CMS.set_article_tag(:guide, guide.id, article_tag2.id) + + passport_rules = %{community.title => %{"guide.article_tag.unset" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + variables = %{ + id: guide.id, + thread: "GUIDE", + articleTagId: article_tag.id, + communityId: community.id + } + + rule_conn |> mutation_result(@unset_tag_query, variables, "unsetArticleTag") + + {:ok, guide} = ORM.find(Guide, guide.id, preload: :article_tags) + assoc_tags = guide.article_tags |> Enum.map(& &1.id) + + assert article_tag.id not in assoc_tags + assert article_tag2.id in assoc_tags + end + end +end diff --git a/test/groupher_server_web/mutation/cms/articles/guide_emotion_test.exs b/test/groupher_server_web/mutation/cms/articles/guide_emotion_test.exs new file mode 100644 index 000000000..35a838223 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/articles/guide_emotion_test.exs @@ -0,0 +1,73 @@ +defmodule GroupherServer.Test.Mutation.Articles.GuideEmotion do + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn user guest_conn owner_conn community guide_attrs)a} + end + + describe "[guide emotion]" do + @emotion_query """ + mutation($id: ID!, $emotion: ArticleEmotion!) { + emotionToGuide(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + + test "login user can emotion to a pguide", ~m(community guide_attrs user user_conn)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + variables = %{id: guide.id, emotion: "BEER"} + article = user_conn |> mutation_result(@emotion_query, variables, "emotionToGuide") + + assert article |> get_in(["emotions", "beerCount"]) == 1 + assert get_in(article, ["emotions", "viewerHasBeered"]) + end + + @emotion_query """ + mutation($id: ID!, $emotion: ArticleEmotion!) { + undoEmotionToGuide(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + + test "login user can undo emotion to a guide", ~m(community guide_attrs user owner_conn)a do + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, _} = CMS.emotion_to_article(:guide, guide.id, :beer, user) + + variables = %{id: guide.id, emotion: "BEER"} + article = owner_conn |> mutation_result(@emotion_query, variables, "undoEmotionToGuide") + + assert article |> get_in(["emotions", "beerCount"]) == 0 + assert not get_in(article, ["emotions", "viewerHasBeered"]) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/articles/guide_test.exs b/test/groupher_server_web/mutation/cms/articles/guide_test.exs new file mode 100644 index 000000000..079e7abfa --- /dev/null +++ b/test/groupher_server_web/mutation/cms/articles/guide_test.exs @@ -0,0 +1,226 @@ +defmodule GroupherServer.Test.Mutation.Articles.Guide do + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.{CMS, Repo} + + alias CMS.Model.Guide + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:owner, guide) + + {:ok, ~m(user_conn guest_conn owner_conn community user guide)a} + end + + describe "[mutation guide curd]" do + @create_guide_query """ + mutation ( + $title: String!, + $body: String, + $digest: String!, + $communityId: ID!, + $articleTags: [Id] + ) { + createGuide( + title: $title, + body: $body, + digest: $digest, + communityId: $communityId, + articleTags: $articleTags + ) { + id + title + document { + bodyHtml + } + originalCommunity { + id + } + communities { + id + title + } + } + } + """ + test "create guide with valid attrs and make sure author exsit" do + {:ok, user} = db_insert(:user) + user_conn = simu_conn(:user, user) + + {:ok, community} = db_insert(:community) + guide_attr = mock_attrs(:guide) + + variables = guide_attr |> Map.merge(%{communityId: community.id}) |> camelize_map_key + + created = user_conn |> mutation_result(@create_guide_query, variables, "createGuide") + + {:ok, found} = ORM.find(Guide, created["id"]) + + assert created["id"] == to_string(found.id) + assert created["originalCommunity"]["id"] == to_string(community.id) + + assert created["id"] == to_string(found.id) + end + + test "create guide with valid tags id list", ~m(user_conn user community)a do + article_tag_attrs = mock_attrs(:article_tag) + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + + guide_attr = mock_attrs(:guide) + + variables = + guide_attr |> Map.merge(%{communityId: community.id, articleTags: [article_tag.id]}) + + created = user_conn |> mutation_result(@create_guide_query, variables, "createGuide") + + {:ok, guide} = ORM.find(Guide, created["id"], preload: :article_tags) + + assert exist_in?(%{id: article_tag.id}, guide.article_tags) + end + + test "create guide should excape xss attracts" do + {:ok, user} = db_insert(:user) + user_conn = simu_conn(:user, user) + + {:ok, community} = db_insert(:community) + + guide_attr = mock_attrs(:guide, %{body: mock_xss_string()}) + variables = guide_attr |> Map.merge(%{communityId: community.id}) |> camelize_map_key + result = user_conn |> mutation_result(@create_guide_query, variables, "createGuide") + + {:ok, guide} = ORM.find(Guide, result["id"], preload: :document) + body_html = guide |> get_in([:document, :body_html]) + + assert not String.contains?(body_html, "script") + end + + test "create guide should excape xss attracts 2" do + {:ok, user} = db_insert(:user) + user_conn = simu_conn(:user, user) + + {:ok, community} = db_insert(:community) + + guide_attr = mock_attrs(:guide, %{body: mock_xss_string(:safe)}) + variables = guide_attr |> Map.merge(%{communityId: community.id}) |> camelize_map_key + result = user_conn |> mutation_result(@create_guide_query, variables, "createGuide") + {:ok, guide} = ORM.find(Guide, result["id"], preload: :document) + body_html = guide |> get_in([:document, :body_html]) + + assert String.contains?(body_html, "<script>blackmail</script>") + end + + @query """ + mutation($id: ID!, $title: String, $body: String, $articleTags: [Ids]){ + updateGuide(id: $id, title: $title, body: $body, articleTags: $articleTags) { + id + title + document { + bodyHtml + } + articleTags { + id + } + } + } + """ + test "update a guide without login user fails", ~m(guest_conn guide)a do + unique_num = System.unique_integer([:positive, :monotonic]) + + variables = %{ + id: guide.id, + title: "updated title #{unique_num}", + body: mock_rich_text("updated body #{unique_num}") + } + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + test "guide can be update by owner", ~m(owner_conn guide)a do + unique_num = System.unique_integer([:positive, :monotonic]) + + variables = %{ + id: guide.id, + title: "updated title #{unique_num}", + body: mock_rich_text("updated body #{unique_num}") + } + + result = owner_conn |> mutation_result(@query, variables, "updateGuide") + + assert result["title"] == variables.title + + assert result + |> get_in(["document", "bodyHtml"]) + |> String.contains?(~s(updated body #{unique_num})) + end + + test "login user with auth passport update a guide", ~m(guide)a do + guide = guide |> Repo.preload(:communities) + + guide_communities_0 = guide.communities |> List.first() |> Map.get(:title) + passport_rules = %{guide_communities_0 => %{"guide.edit" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + unique_num = System.unique_integer([:positive, :monotonic]) + + variables = %{ + id: guide.id, + title: "updated title #{unique_num}", + body: mock_rich_text("updated body #{unique_num}") + } + + updated = rule_conn |> mutation_result(@query, variables, "updateGuide") + + assert updated["id"] == to_string(guide.id) + end + + test "unauth user update guide fails", ~m(user_conn guest_conn guide)a do + unique_num = System.unique_integer([:positive, :monotonic]) + + variables = %{ + id: guide.id, + title: "updated title #{unique_num}", + body: mock_rich_text("updated body #{unique_num}") + } + + 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!){ + deleteGuide(id: $id) { + id + } + } + """ + + test "can delete a guide by guide's owner", ~m(owner_conn guide)a do + deleted = owner_conn |> mutation_result(@query, %{id: guide.id}, "deleteGuide") + + assert deleted["id"] == to_string(guide.id) + assert {:error, _} = ORM.find(Guide, deleted["id"]) + end + + test "can delete a guide by auth user", ~m(guide)a do + guide = guide |> Repo.preload(:communities) + belongs_community_title = guide.communities |> List.first() |> Map.get(:title) + rule_conn = simu_conn(:user, cms: %{belongs_community_title => %{"guide.delete" => true}}) + + deleted = rule_conn |> mutation_result(@query, %{id: guide.id}, "deleteGuide") + + assert deleted["id"] == to_string(guide.id) + assert {:error, _} = ORM.find(Guide, deleted["id"]) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/comments/guide_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/guide_comment_test.exs new file mode 100644 index 000000000..19783cbf5 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/comments/guide_comment_test.exs @@ -0,0 +1,329 @@ +defmodule GroupherServer.Test.Mutation.Comments.GuideComment do + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.Guide + + alias Helper.ORM + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + {:ok, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn user guest_conn owner_conn community guide)a} + end + + describe "[article comment CURD]" do + @write_comment_query """ + mutation($thread: Thread!, $id: ID!, $body: String!) { + createComment(thread: $thread,id: $id, body: $body) { + id + bodyHtml + } + } + """ + test "write article comment to a exsit guide", ~m(guide user_conn)a do + variables = %{thread: "GUIDE", id: guide.id, body: mock_comment()} + + result = user_conn |> mutation_result(@write_comment_query, variables, "createComment") + + assert result["bodyHtml"] |> String.contains?(~s(

String.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(guide user user_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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(guide user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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(guide user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide comment", ~m(guide user guest_conn user_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide comment", + ~m(guide user guest_conn user_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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(guide user user_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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(guide user owner_conn)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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!){ + lockGuideComment(id: $id, communityId: $communityId) { + id + } + } + """ + + test "can lock a guide's comment", ~m(community guide)a do + variables = %{id: guide.id, communityId: community.id} + passport_rules = %{community.raw => %{"guide.lock_comment" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "lockGuideComment") + assert result["id"] == to_string(guide.id) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.is_comment_locked + end + + test "unauth user fails", ~m(guest_conn community guide)a do + variables = %{id: guide.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + undoLockGuideComment(id: $id, communityId: $communityId) { + id + } + } + """ + + test "can undo lock a guide's comment", ~m(community guide)a do + {:ok, _} = CMS.lock_article_comments(:guide, guide.id) + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.is_comment_locked + + variables = %{id: guide.id, communityId: community.id} + passport_rules = %{community.raw => %{"guide.undo_lock_comment" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "undoLockGuideComment") + assert result["id"] == to_string(guide.id) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert not guide.meta.is_comment_locked + end + + test "unauth user undo fails", ~m(guest_conn community guide)a do + variables = %{id: guide.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 guide's comment", ~m(owner_conn guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide's comment", ~m(owner_conn guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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 guide user)a do + {:ok, comment} = CMS.create_comment(:guide, guide.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/guide_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/guide_flag_test.exs new file mode 100644 index 000000000..9e3085829 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/flags/guide_flag_test.exs @@ -0,0 +1,178 @@ +defmodule GroupherServer.Test.Mutation.Flags.GuideFlag 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, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), 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 guide)a} + end + + describe "[mutation guide flag curd]" do + @query """ + mutation($id: ID!){ + markDeleteGuide(id: $id) { + id + markDelete + } + } + """ + test "auth user can markDelete guide", ~m(guide)a do + variables = %{id: guide.id} + + passport_rules = %{"guide.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "markDeleteGuide") + + assert updated["id"] == to_string(guide.id) + assert updated["markDelete"] == true + end + + test "mark delete guide should update guide'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, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.guides_count == 1 + + variables = %{id: guide.id} + passport_rules = %{"guide.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@query, variables, "markDeleteGuide") + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.guides_count == 0 + end + + test "unauth user markDelete guide fails", ~m(user_conn guest_conn guide)a do + variables = %{id: guide.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!){ + undoMarkDeleteGuide(id: $id) { + id + markDelete + } + } + """ + test "auth user can undo markDelete guide", ~m(guide)a do + variables = %{id: guide.id} + + {:ok, _} = CMS.mark_delete_article(:guide, guide.id) + + passport_rules = %{"guide.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "undoMarkDeleteGuide") + + assert updated["id"] == to_string(guide.id) + assert updated["markDelete"] == false + end + + test "undo mark delete guide should update guide'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, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + {:ok, _} = CMS.mark_delete_article(:guide, guide.id) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.guides_count == 0 + + variables = %{id: guide.id} + passport_rules = %{"guide.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + rule_conn |> mutation_result(@query, variables, "undoMarkDeleteGuide") + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.guides_count == 1 + end + + test "unauth user undo markDelete guide fails", ~m(user_conn guest_conn guide)a do + variables = %{id: guide.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!){ + pinGuide(id: $id, communityId: $communityId) { + id + } + } + """ + + test "auth user can pin guide", ~m(community guide)a do + variables = %{id: guide.id, communityId: community.id} + + passport_rules = %{community.raw => %{"guide.pin" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + updated = rule_conn |> mutation_result(@query, variables, "pinGuide") + + assert updated["id"] == to_string(guide.id) + end + + test "unauth user pin guide fails", ~m(user_conn guest_conn community guide)a do + variables = %{id: guide.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!){ + undoPinGuide(id: $id, communityId: $communityId) { + id + isPinned + } + } + """ + + test "auth user can undo pin guide", ~m(community guide)a do + variables = %{id: guide.id, communityId: community.id} + + passport_rules = %{community.raw => %{"guide.undo_pin" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + CMS.pin_article(:guide, guide.id, community.id) + updated = rule_conn |> mutation_result(@query, variables, "undoPinGuide") + + assert updated["id"] == to_string(guide.id) + end + + test "unauth user undo pin guide fails", ~m(user_conn guest_conn community guide)a do + variables = %{id: guide.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/guide_sink_test.exs b/test/groupher_server_web/mutation/cms/sink/guide_sink_test.exs new file mode 100644 index 000000000..335bb79a3 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/sink/guide_sink_test.exs @@ -0,0 +1,79 @@ +defmodule GroupherServer.Test.Mutation.Sink.GuideSink do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + alias CMS.Model.Guide + + alias Helper.ORM + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + {:ok, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn community guide user)a} + end + + describe "[guide sink]" do + @query """ + mutation($id: ID!, $communityId: ID!){ + sinkGuide(id: $id, communityId: $communityId) { + id + } + } + """ + + test "login user can sink a guide", ~m(community guide)a do + variables = %{id: guide.id, communityId: community.id} + passport_rules = %{community.raw => %{"guide.sink" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + result = rule_conn |> mutation_result(@query, variables, "sinkGuide") + assert result["id"] == to_string(guide.id) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert guide.meta.is_sinked + assert guide.active_at == guide.inserted_at + end + + test "unauth user sink a guide fails", ~m(guest_conn community guide)a do + variables = %{id: guide.id, communityId: community.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!, $communityId: ID!){ + undoSinkGuide(id: $id, communityId: $communityId) { + id + } + } + """ + + test "login user can undo sink to a guide", ~m(community guide)a do + variables = %{id: guide.id, communityId: community.id} + + passport_rules = %{community.raw => %{"guide.undo_sink" => true}} + rule_conn = simu_conn(:user, cms: passport_rules) + + {:ok, _} = CMS.sink_article(:guide, guide.id) + updated = rule_conn |> mutation_result(@query, variables, "undoSinkGuide") + assert updated["id"] == to_string(guide.id) + + {:ok, guide} = ORM.find(Guide, guide.id) + assert not guide.meta.is_sinked + end + + :wip2 + + test "unauth user undo sink a guide fails", ~m(guest_conn community guide)a do + variables = %{id: guide.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/guide_upvote_test.exs b/test/groupher_server_web/mutation/cms/upvotes/guide_upvote_test.exs new file mode 100644 index 000000000..3f191f306 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/upvotes/guide_upvote_test.exs @@ -0,0 +1,64 @@ +defmodule GroupherServer.Test.Mutation.Upvotes.GuideUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, guide} = db_insert(:guide) + {:ok, user} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn guide user)a} + end + + describe "[guide upvote]" do + @query """ + mutation($id: ID!) { + upvoteGuide(id: $id) { + id + } + } + """ + + test "login user can upvote a guide", ~m(user_conn guide)a do + variables = %{id: guide.id} + created = user_conn |> mutation_result(@query, variables, "upvoteGuide") + + assert created["id"] == to_string(guide.id) + end + + test "unauth user upvote a guide fails", ~m(guest_conn guide)a do + variables = %{id: guide.id} + + assert guest_conn + |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!) { + undoUpvoteGuide(id: $id) { + id + } + } + """ + + test "login user can undo upvote to a guide", ~m(user_conn guide user)a do + {:ok, _} = CMS.upvote_article(:guide, guide.id, user) + + variables = %{id: guide.id} + updated = user_conn |> mutation_result(@query, variables, "undoUpvoteGuide") + + assert updated["id"] == to_string(guide.id) + end + + test "unauth user undo upvote a guide fails", ~m(guest_conn guide)a do + variables = %{id: guide.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_guides_test.exs b/test/groupher_server_web/query/accounts/published/published_guides_test.exs new file mode 100644 index 000000000..9c6e3069c --- /dev/null +++ b/test/groupher_server_web/query/accounts/published/published_guides_test.exs @@ -0,0 +1,102 @@ +defmodule GroupherServer.Test.Query.Accounts.Published.Guides do + use GroupherServer.TestTools + + alias GroupherServer.CMS + + @publish_count 10 + + setup do + {:ok, user} = db_insert(:user) + {:ok, guide} = db_insert(:guide) + {:ok, community} = db_insert(:community) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(guest_conn user_conn community guide user)a} + end + + describe "[published guides]" do + @query """ + query($login: String!, $filter: PagedFilter!) { + pagedPublishedGuides(login: $login, filter: $filter) { + entries { + id + title + author { + id + } + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + + test "can get published guides", ~m(guest_conn community user)a do + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide2} = CMS.create_article(community, :guide, guide_attrs, user) + + variables = %{login: user.login, filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedPublishedGuides") + + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(guide.id))) + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(guide2.id))) + end + end + + describe "[account published comments on guide]" 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 guide", ~m(guest_conn user guide)a do + pub_comments = + Enum.reduce(1..@publish_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(:guide, guide.id, mock_comment(), user) + acc ++ [comment] + end) + + random_comment_id = pub_comments |> Enum.random() |> Map.get(:id) |> to_string + + variables = %{login: user.login, thread: "GUIDE", 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(guide.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/guide_comment_test.exs b/test/groupher_server_web/query/cms/comments/guide_comment_test.exs new file mode 100644 index 000000000..e1b5b5d48 --- /dev/null +++ b/test/groupher_server_web/query/cms/comments/guide_comment_test.exs @@ -0,0 +1,666 @@ +defmodule GroupherServer.Test.Query.Comments.GuideComment do + @moduledoc false + + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, guide} = db_insert(:guide) + {: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 guide user user2)a} + end + + describe "[baisc article guide comment]" do + @query """ + query($id: ID!) { + guide(id: $id) { + id + title + commentsParticipants { + id + nickname + } + commentsParticipantsCount + } + } + """ + + test "guest user can get comment participants after comment created", + ~m(guest_conn guide user user2)a do + total_count = 5 + thread = :guide + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, _} = CMS.create_comment(thread, guide.id, mock_comment(), user2) + + variables = %{id: guide.id} + results = guest_conn |> query_result(@query, variables, "guide") + + 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 guide user user2)a do + total_count = 10 + page_size = 20 + thread = :guide + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, thread: "GUIDE", 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 guide user user2)a do + total_count = 3 + page_size = 20 + thread = :guide + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, + thread: "GUIDE", + 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 guide user user2)a do + total_count = 2 + thread = :guide + + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment("comment #{i}"), user) + + acc ++ [comment] + end) + + {:ok, parent_comment} = + CMS.create_comment(:guide, guide.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: guide.id, thread: "GUIDE", 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 guide", ~m(guest_conn guide user)a do + total_count = 30 + thread = :guide + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, value} = CMS.create_comment(thread, guide.id, mock_comment(), user) + + acc ++ [value] + end) + + variables = %{id: guide.id, thread: "GUIDE", 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 guide user)a do + total_count = 20 + thread = :guide + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + + acc ++ [comment] + end) + + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + {:ok, pinned_comment} = CMS.pin_comment(comment.id) + + Process.sleep(1000) + + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + {:ok, pinned_comment2} = CMS.pin_comment(comment.id) + + variables = %{id: guide.id, thread: "GUIDE", 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 guide user)a do + total_count = 5 + thread = :guide + page_size = 10 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + Process.sleep(1000) + acc ++ [comment] + end) + + variables = %{id: guide.id, thread: "GUIDE", 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 guide user)a do + page_size = 10 + thread = :guide + + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + Process.sleep(1000) + {:ok, _comment2} = CMS.create_comment(thread, guide.id, mock_comment(), user) + Process.sleep(1000) + {:ok, comment3} = CMS.create_comment(thread, guide.id, mock_comment(), user) + + variables = %{ + id: guide.id, + thread: "GUIDE", + 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 guide user)a do + page_size = 10 + thread = :guide + + {:ok, comment} = CMS.create_comment(thread, guide.id, mock_comment(), user) + Process.sleep(1000) + {:ok, _comment2} = CMS.create_comment(thread, guide.id, mock_comment(), user) + Process.sleep(1000) + {:ok, comment3} = CMS.create_comment(thread, guide.id, mock_comment(), user) + + variables = %{ + id: guide.id, + thread: "GUIDE", + 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 guide user user2)a do + page_size = 10 + thread = :guide + + {:ok, comment} = CMS.create_comment(thread, guide.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, guide.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, guide.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: guide.id, + thread: "GUIDE", + 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 guide user user2)a do + total_count = 10 + page_size = 10 + thread = :guide + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, thread: "GUIDE", 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 guide user)a do + total_count = 5 + page_size = 12 + thread = :guide + + author_user = guide.author.user + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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, guide.id, mock_comment(), author_user) + + {:ok, _} = CMS.upvote_comment(author_comment.id, author_user) + + variables = %{id: guide.id, thread: "GUIDE", 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 guide user user2)a do + total_count = 2 + page_size = 10 + thread = :guide + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, thread: "GUIDE", 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 guide user user2)a do + total_count = 10 + page_size = 12 + thread = :guide + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, thread: "GUIDE", 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 guide user)a do + total_count = 10 + page_size = 12 + thread = :guide + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = + CMS.create_comment(thread, guide.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: guide.id, thread: "GUIDE", 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 guide user)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) + + variables = %{id: guide.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 guide user user2)a do + total_count = 2 + page_size = 10 + thread = :guide + + author_user = guide.author.user + {:ok, parent_comment} = CMS.create_comment(thread, guide.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_guides_test.exs b/test/groupher_server_web/query/cms/paged_articles/paged_guides_test.exs new file mode 100644 index 000000000..01300fa4a --- /dev/null +++ b/test/groupher_server_web/query/cms/paged_articles/paged_guides_test.exs @@ -0,0 +1,474 @@ +defmodule GroupherServer.Test.Query.PagedArticles.PagedGuide 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.Guide + + @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, guide_last_week} = + db_insert(:guide, %{title: "last week", inserted_at: @last_week, active_at: @last_week}) + + db_insert(:guide, %{title: "last month", inserted_at: @last_month}) + + {:ok, guide_last_year} = + db_insert(:guide, %{title: "last year", inserted_at: @last_year, active_at: @last_year}) + + db_insert_multi(:guide, @today_count) + guest_conn = simu_conn(:guest) + + {:ok, ~m(guest_conn user guide_last_week guide_last_year)a} + end + + describe "[query paged_guides filter pagination]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(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, "pagedGuides") + + 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) + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + Process.sleep(2000) + {:ok, _guide} = CMS.create_article(community, :guide, guide_attrs, user) + + variables = %{filter: %{page: 1, size: 30}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + guide = results["entries"] |> List.first() + assert not is_nil(get_in(guide, ["document", "bodyHtml"])) + end + + test "support article_tag filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + article_tag_attrs = mock_attrs(:article_tag) + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, _} = CMS.set_article_tag(:guide, guide.id, article_tag.id) + + variables = %{filter: %{page: 1, size: 10, article_tag: article_tag.title}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + guide = results["entries"] |> List.first() + assert results["totalCount"] == 1 + assert exist_in?(article_tag, guide["articleTags"], :string_key) + end + + test "support multi-tag (article_tags) filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + article_tag_attrs = mock_attrs(:article_tag) + + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag2} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, article_tag3} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + + {:ok, _} = CMS.set_article_tag(:guide, guide.id, article_tag.id) + {:ok, _} = CMS.set_article_tag(:guide, guide.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, "pagedGuides") + + guide = results["entries"] |> List.first() + assert results["totalCount"] == 1 + assert exist_in?(article_tag, guide["articleTags"], :string_key) + assert exist_in?(article_tag2, guide["articleTags"], :string_key) + assert not exist_in?(article_tag3, guide["articleTags"], :string_key) + end + + test "should not have pined guides when filter have article_tag or article_tags", + ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, pinned_guide} = CMS.create_article(community, :guide, guide_attrs, user) + {:ok, guide} = CMS.create_article(community, :guide, guide_attrs, user) + + {:ok, _} = CMS.pin_article(:guide, pinned_guide.id, community.id) + + article_tag_attrs = mock_attrs(:article_tag) + {:ok, article_tag} = CMS.create_article_tag(community, :guide, article_tag_attrs, user) + {:ok, _} = CMS.set_article_tag(:guide, guide.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, "pagedGuides") + + assert not exist_in?(pinned_guide, results["entries"], :string_key) + assert exist_in?(guide, 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, "pagedGuides") + + assert not exist_in?(pinned_guide, results["entries"], :string_key) + assert exist_in?(guide, results["entries"], :string_key) + end + + test "support community filter", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + + guide_attrs = mock_attrs(:guide, %{community_id: community.id}) + {:ok, _guide} = CMS.create_article(community, :guide, guide_attrs, user) + guide_attrs2 = mock_attrs(:guide, %{community_id: community.id}) + {:ok, _guide} = CMS.create_article(community, :guide, guide_attrs2, user) + + variables = %{filter: %{page: 1, size: 10, community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + guide = results["entries"] |> List.first() + assert results["totalCount"] == 2 + assert exist_in?(%{id: to_string(community.id)}, guide["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, "pagedGuides") + assert results |> is_valid_pagination? + assert results["pageSize"] == @page_size + assert results["totalCount"] == @total_count + end + end + + describe "[query paged_guides filter has_xxx]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(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, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + {:ok, _guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + {:ok, _guide3} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + variables = %{filter: %{community: community.raw}} + results = user_conn |> query_result(@query, variables, "pagedGuides") + assert results["totalCount"] == 3 + + the_guide = Enum.find(results["entries"], &(&1["id"] == to_string(guide.id))) + assert not the_guide["viewerHasViewed"] + assert not the_guide["viewerHasUpvoted"] + assert not the_guide["viewerHasCollected"] + assert not the_guide["viewerHasReported"] + + {:ok, _} = CMS.read_article(:guide, guide.id, user) + {:ok, _} = CMS.upvote_article(:guide, guide.id, user) + {:ok, _} = CMS.collect_article(:guide, guide.id, user) + {:ok, _} = CMS.report_article(:guide, guide.id, "reason", "attr_info", user) + + results = user_conn |> query_result(@query, variables, "pagedGuides") + the_guide = Enum.find(results["entries"], &(&1["id"] == to_string(guide.id))) + assert the_guide["viewerHasViewed"] + assert the_guide["viewerHasUpvoted"] + assert the_guide["viewerHasCollected"] + assert the_guide["viewerHasReported"] + end + end + + describe "[query paged_guides filter sort]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(filter: $filter) { + entries { + id + inserted_at + active_at + author { + id + nickname + avatar + } + } + } + } + """ + + test "filter community should get guides which belongs to that community", + ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + {:ok, guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + variables = %{filter: %{community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + assert length(results["entries"]) == 1 + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(guide.id))) + end + + test "should have a active_at same with inserted_at", ~m(guest_conn user)a do + {:ok, community} = db_insert(:community) + {:ok, _guide} = CMS.create_article(community, :guide, mock_attrs(:guide), user) + + variables = %{filter: %{community: community.raw}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + guide = results["entries"] |> List.first() + + assert guide["inserted_at"] == guide["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, "pagedGuides") + 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: PagedGuideFilter!) { + pagedGuides(filter: $filter) { + entries { + id + views + } + } + } + """ + + test "filter sort MOST_VIEWS should work", ~m(guest_conn)a do + most_views_guide = Guide |> order_by(desc: :views) |> limit(1) |> Repo.one() + variables = %{filter: %{sort: "MOST_VIEWS"}} + + results = guest_conn |> query_result(@query, variables, "pagedGuides") + find_guide = results |> Map.get("entries") |> hd + + assert find_guide["views"] == most_views_guide |> 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_guides filter when]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(filter: $filter) { + entries { + id + views + inserted_at + } + totalCount + } + } + """ + test "THIS_YEAR option should work", ~m(guest_conn guide_last_year)a do + variables = %{filter: %{when: "THIS_YEAR"}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + assert results["entries"] |> Enum.any?(&(&1["id"] != guide_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, "pagedGuides") + + 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, "pagedGuides") + + 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, "pagedGuides") + + {_, 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_guides filter extra]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(filter: $filter) { + entries { + id + title + } + totalCount + } + } + """ + test "basic filter should work", ~m(guest_conn)a do + {:ok, guide} = db_insert(:guide) + {:ok, guide2} = db_insert(:guide) + + variables = %{filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + + assert results["totalCount"] >= 1 + assert results["entries"] |> Enum.any?(&(&1["id"] == to_string(guide.id))) + assert results["entries"] |> Enum.any?(&(&1["id"] != to_string(guide2.id))) + end + end + + describe "[paged guides active_at]" do + @query """ + query($filter: PagedGuideFilter!) { + pagedGuides(filter: $filter) { + entries { + id + insertedAt + activeAt + } + } + } + """ + + test "latest commented guide should appear on top", ~m(guest_conn guide_last_week user)a do + variables = %{filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "pagedGuides") + entries = results["entries"] + first_guide = entries |> List.first() + assert first_guide["id"] !== to_string(guide_last_week.id) + + Process.sleep(1500) + {:ok, _comment} = CMS.create_comment(:guide, guide_last_week.id, mock_comment(), user) + + results = guest_conn |> query_result(@query, variables, "pagedGuides") + entries = results["entries"] + first_guide = entries |> List.first() + + assert first_guide["id"] == to_string(guide_last_week.id) + end + + test "comment on very old guide have no effect", ~m(guest_conn guide_last_year user)a do + variables = %{filter: %{page: 1, size: 20}} + + {:ok, _comment} = CMS.create_comment(:guide, guide_last_year.id, mock_comment(), user) + + results = guest_conn |> query_result(@query, variables, "pagedGuides") + entries = results["entries"] + first_guide = entries |> List.first() + + assert first_guide["id"] !== to_string(guide_last_year.id) + end + + test "latest guide author commented guide have no effect", ~m(guest_conn guide_last_week)a do + variables = %{filter: %{page: 1, size: 20}} + + {:ok, _comment} = + CMS.create_comment( + :guide, + guide_last_week.id, + mock_comment(), + guide_last_week.author.user + ) + + results = guest_conn |> query_result(@query, variables, "pagedGuides") + entries = results["entries"] + first_guide = entries |> List.first() + + assert first_guide["id"] !== to_string(guide_last_week.id) + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index bc7919cfb..f083eca17 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -259,6 +259,26 @@ defmodule GroupherServer.Support.Factory do } end + defp mock_meta(:guide) do + text = Faker.Lorem.sentence(%Range{first: 80, last: 120}) + + %{ + meta: @default_article_meta |> Map.merge(%{thread: "GUIDE"}), + 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})