diff --git a/elixir-api/test/api/post_posts_test.exs b/elixir-api/test/api/post_posts_test.exs new file mode 100644 index 000000000..3c922a96c --- /dev/null +++ b/elixir-api/test/api/post_posts_test.exs @@ -0,0 +1,301 @@ +defmodule PostPostsTest do + use ExUnit.Case, async: false + use Plug.Test + + import ExUnit.CaptureLog + import Mox + + setup :verify_on_exit! + + setup do + Application.put_env(:hexerei, :http_client, Hexerei.HTTP.MockClient) + + on_exit(fn -> + Application.delete_env(:hexerei, :http_client) + + Hexerei.Cache.QueryCache.clear_table() + Hexerei.Cache.TranslateCache.clear_table() + end) + + :ok + end + + @opts Hexerei.Router.init([]) + @authorization "Bearer 1234567890" + + test "GET '/query/post/:id' returns a post" do + fakeSlug = "some_post_id_" <> Integer.to_string(System.system_time(:millisecond)) + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 1, + fn url, _headers -> + assert String.contains?(url, fakeSlug) + + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(TestFixtures.stub_post(fakeSlug)), + status_code: 200 + }} + end + ) + |> Mox.expect( + :post, + 1, + fn url, body, _params -> + assert String.contains?(url, "mutate") + assert String.contains?(body, fakeSlug) + assert String.contains?(body, "inc") + + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(%{}), + status_code: 200 + }} + end + ) + + conn = + conn(:get, "/api/v1/query/post/#{fakeSlug}") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + assert conn.state == :sent + assert conn.status == 200 + + assert_receive {:increment_view_count_done, {:ok}}, 5000 + + body = Poison.decode!(conn.resp_body) + + assert body != [] + assert body["data"] != nil + assert body["data"]["result"]["slug"]["current"] == fakeSlug + end + + test "GET '/query/posts' returns posts" do + stub_posts = TestFixtures.stub_posts(["some_post_1", "some_post_2"]) + + stub_posts_count = + TestFixtures.stub_posts_count( + count: length(stub_posts["result"]), + total: length(stub_posts["result"]) + ) + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 2, + fn url, _headers -> + if String.contains?(url, "count") and String.contains?(url, "total") do + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_posts_count), + status_code: 200 + }} + else + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_posts), + status_code: 200 + }} + end + end + ) + + conn = + conn(:get, "/api/v1/query/posts") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + assert conn.state == :sent + assert conn.status == 200 + + body = Poison.decode!(conn.resp_body) + + assert body != [] + assert body["data"] != nil + assert body["data"]["result"] != nil + + assert length(body["data"]["result"]) == length(stub_posts["result"]) + end + + test "GET '/query/post/:id' with 'fr' lang should invoke Translate" do + fakeSlug = "some_post_id_" <> Integer.to_string(System.system_time(:millisecond)) + + stub_post = TestFixtures.stub_post(fakeSlug) + stub_post_translatable_calls = 3 + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 1, + fn url, _headers -> + assert String.contains?(url, fakeSlug) + + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_post), + status_code: 200 + }} + end + ) + |> Mox.expect( + :post, + stub_post_translatable_calls + 1, + fn url, body, _params -> + case url do + "https://translation.googleapis.com/" <> _ -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Poison.encode!(%{ + data: %{ + translations: [ + %{ + translatedText: "Translated text" + } + ] + } + }) + }} + + url -> + if String.contains?(url, "sanity.io/") do + assert String.contains?(url, "mutate") + assert String.contains?(body, fakeSlug) + assert String.contains?(body, "inc") + + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(%{}), + status_code: 200 + }} + else + flunk("Unexpected POST to URL: #{inspect(url)}") + end + end + end + ) + + Application.put_env(:hexerei, :gcloud_key, "some_key") + + log_output = + capture_log(fn -> + conn = + conn(:get, "/api/v1/query/post/#{fakeSlug}?lang=fr") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + Process.put(:temp_conn, conn) + end) + + conn = Process.get(:temp_conn) + Process.delete(:temp_conn) + + assert conn.state == :sent + assert conn.status == 200 + + assert_receive {:increment_view_count_done, {:ok}}, 5000 + + body = Poison.decode!(conn.resp_body) + + result = body["data"]["result"] + + assert result != nil + + assert String.contains?(log_output, "No gcp key accessible for translation, skipping") == + false + + assert result["title"] == "Translated text" + end + + test "GET '/query/posts' with 'fr' lang should invoke Translate for each title/description" do + stub_posts = TestFixtures.stub_posts(["some_post_1", "some_post_2"]) + + stub_posts_count = + TestFixtures.stub_posts_count( + count: length(stub_posts["result"]), + total: length(stub_posts["result"]) + ) + + stub_posts_translatable_calls = length(stub_posts["result"]) + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 2, + fn url, _headers -> + if String.contains?(url, "count") and String.contains?(url, "total") do + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_posts_count), + status_code: 200 + }} + else + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_posts), + status_code: 200 + }} + end + end + ) + |> Mox.expect( + :post, + stub_posts_translatable_calls, + fn url, body, _params -> + assert String.contains?(url, "https://translation.googleapis.com/") + + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Poison.encode!(%{ + data: %{ + translations: [ + %{ + translatedText: "Translated text" + } + ] + } + }) + }} + end + ) + + Application.put_env(:hexerei, :gcloud_key, "some_key") + + log_output = + capture_log(fn -> + conn = + conn(:get, "/api/v1/query/posts?lang=fr") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + Process.put(:temp_conn, conn) + end) + + conn = Process.get(:temp_conn) + + assert conn.state == :sent + + body = Poison.decode!(conn.resp_body) + + Process.delete(:temp_conn) + + result = body["data"]["result"] + + assert result != nil + + assert String.contains?(log_output, "No gcp key accessible for translation, skipping") == + false + + assert length(result) == length(stub_posts["result"]) + + assert Enum.all?(result, fn post -> + post["title"] == "Translated text" and post["desc"] == "Translated text" + end) + end +end diff --git a/elixir-api/test/api/project_projects_test.exs b/elixir-api/test/api/project_projects_test.exs new file mode 100644 index 000000000..fb78f27fd --- /dev/null +++ b/elixir-api/test/api/project_projects_test.exs @@ -0,0 +1,118 @@ +defmodule ProjectProjectsTest do + use ExUnit.Case, async: false + use Plug.Test + + import Mox + + setup :verify_on_exit! + + setup do + Application.put_env(:hexerei, :http_client, Hexerei.HTTP.MockClient) + + on_exit(fn -> + Application.delete_env(:hexerei, :http_client) + + Hexerei.Cache.QueryCache.clear_table() + Hexerei.Cache.TranslateCache.clear_table() + end) + + :ok + end + + @opts Hexerei.Router.init([]) + @authorization "Bearer 1234567890" + + test "GET '/query/project/:id' returns a project" do + fakeSlug = "some_project_id_" <> Integer.to_string(System.system_time(:millisecond)) + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 1, + fn _url, _headers -> + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(TestFixtures.stub_post()), + status_code: 200 + }} + end + ) + |> Mox.expect( + :post, + 1, + fn url, body, _params -> + assert String.contains?(url, "mutate") + assert String.contains?(body, fakeSlug) + assert String.contains?(body, "inc") + + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(%{}), + status_code: 200 + }} + end + ) + + conn = + conn(:get, "/api/v1/query/project/#{fakeSlug}") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + assert conn.state == :sent + assert conn.status == 200 + + assert_receive {:increment_view_count_done, {:ok}}, 5000 + + body = Poison.decode!(conn.resp_body) + + assert body != [] + assert body["data"] != nil + end + + test "GET '/query/projects' returns projects" do + stub_projects = TestFixtures.stub_projects(["some_project_1", "some_project_2"]) + + stub_projects_count = + TestFixtures.stub_projects_count( + count: length(stub_projects["result"]), + total: length(stub_projects["result"]) + ) + + Hexerei.HTTP.MockClient + |> Mox.expect( + :get, + 2, + fn url, _headers -> + if String.contains?(url, "count") and String.contains?(url, "total") do + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_projects_count), + status_code: 200 + }} + else + {:ok, + %HTTPoison.Response{ + body: Poison.encode!(stub_projects), + status_code: 200 + }} + end + end + ) + + conn = + conn(:get, "/api/v1/query/projects") + |> put_req_header("authorization", @authorization) + |> Hexerei.Router.call(@opts) + + assert conn.state == :sent + assert conn.status == 200 + + body = Poison.decode!(conn.resp_body) + + assert body != [] + assert body["data"] != nil + assert body["data"]["result"] != nil + + assert length(body["data"]["result"]) == length(stub_projects["result"]) + end +end diff --git a/elixir-api/test/api_test.exs b/elixir-api/test/api_test.exs index ea2981de4..42316ec70 100644 --- a/elixir-api/test/api_test.exs +++ b/elixir-api/test/api_test.exs @@ -2,7 +2,6 @@ defmodule ApiTest do use ExUnit.Case, async: false use Plug.Test - import ExUnit.CaptureLog import Mox @opts Hexerei.Router.init([]) @@ -195,189 +194,4 @@ defmodule ApiTest do assert body != [] assert body["data"] != nil end - - test "GET '/query/post/:id' should increment views" do - fakeID = "some_post_id_" <> Integer.to_string(System.system_time(:millisecond)) - - Hexerei.HTTP.MockClient - |> Mox.expect( - :get, - 1, - fn _url, _headers -> - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(TestFixtures.stub_post()), - status_code: 200 - }} - end - ) - |> Mox.expect( - :post, - 1, - fn url, body, _params -> - assert String.contains?(url, "mutate") - assert String.contains?(body, fakeID) - assert String.contains?(body, "inc") - - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(%{}), - status_code: 200 - }} - end - ) - - conn = - conn(:get, "/api/v1/query/post/#{fakeID}") - |> put_req_header("authorization", @authorization) - |> Hexerei.Router.call(@opts) - - assert conn.state == :sent - assert conn.status == 200 - - assert_receive {:increment_view_count_done, {:ok}}, 5000 - - body = Poison.decode!(conn.resp_body) - - assert body != [] - assert body["data"] != nil - end - - test "GET '/query/project/:id' should increment views" do - fakeID = "some_project_id_" <> Integer.to_string(System.system_time(:millisecond)) - - Hexerei.HTTP.MockClient - |> Mox.expect( - :get, - 1, - fn _url, _headers -> - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(TestFixtures.stub_post()), - status_code: 200 - }} - end - ) - |> Mox.expect( - :post, - 1, - fn url, body, _params -> - assert String.contains?(url, "mutate") - assert String.contains?(body, fakeID) - assert String.contains?(body, "inc") - - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(%{}), - status_code: 200 - }} - end - ) - - conn = - conn(:get, "/api/v1/query/project/#{fakeID}") - |> put_req_header("authorization", @authorization) - |> Hexerei.Router.call(@opts) - - assert conn.state == :sent - assert conn.status == 200 - - assert_receive {:increment_view_count_done, {:ok}}, 5000 - - body = Poison.decode!(conn.resp_body) - - assert body != [] - assert body["data"] != nil - end - - test "GET '/post/:id' with 'fr' lang should invoke Translate" do - fakeID = "some_post_id_" <> Integer.to_string(System.system_time(:millisecond)) - - stub_post = TestFixtures.stub_post() - stub_post_translatable_calls = 3 - - Hexerei.HTTP.MockClient - |> Mox.expect( - :get, - 1, - fn url, _headers -> - assert String.contains?(url, fakeID) - - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(stub_post), - status_code: 200 - }} - end - ) - |> Mox.expect( - :post, - stub_post_translatable_calls + 1, - fn url, body, _params -> - case url do - "https://translation.googleapis.com/" <> _ -> - {:ok, - %HTTPoison.Response{ - status_code: 200, - body: - Poison.encode!(%{ - data: %{ - translations: [ - %{ - translatedText: "Translated text" - } - ] - } - }) - }} - - url -> - if String.contains?(url, "sanity.io/") do - assert String.contains?(url, "mutate") - assert String.contains?(body, fakeID) - assert String.contains?(body, "inc") - - {:ok, - %HTTPoison.Response{ - body: Poison.encode!(%{}), - status_code: 200 - }} - else - flunk("Unexpected POST to URL: #{inspect(url)}") - end - end - end - ) - - Application.put_env(:hexerei, :gcloud_key, "some_key") - - log_output = - capture_log(fn -> - conn = - conn(:get, "/api/v1/query/post/#{fakeID}?lang=fr") - |> put_req_header("authorization", @authorization) - |> Hexerei.Router.call(@opts) - - Process.put(:temp_conn, conn) - end) - - conn = Process.get(:temp_conn) - Process.delete(:temp_conn) - - assert conn.state == :sent - assert conn.status == 200 - - assert_receive {:increment_view_count_done, {:ok}}, 5000 - - body = Poison.decode!(conn.resp_body) - - result = body["data"]["result"] - - assert result != nil - - assert String.contains?(log_output, "No gcp key accessible for translation, skipping") == - false - - assert result["title"] == "Translated text" - end end diff --git a/elixir-api/test/support/fixtures.exs b/elixir-api/test/support/fixtures.exs index b62940f6a..73c04e11f 100644 --- a/elixir-api/test/support/fixtures.exs +++ b/elixir-api/test/support/fixtures.exs @@ -143,64 +143,40 @@ defmodule TestFixtures do } end - def stub_posts_count() do + def stub_posts_count(opts \\ []) do %{ "query" => "{ 'total': count(*[!(_id in path('drafts.**')) && _type == 'post']{_id, 'objectID':_id, _rev, _type, title, publishedAt, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)}), 'count': count(*[!(_id in path('drafts.**')) && _type == 'post']{_id, 'objectID':_id, _rev, _type, title, publishedAt, 'author':{'_id':author->_id, '_type':author->_type, 'name':author->name, 'slug':author->slug, 'image':author->image}, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)} | order(date desc) [0...10])}", "result" => %{ - "total" => 1, - "count" => 1 + "total" => opts[:total] || 1, + "count" => opts[:count] || 1 }, "ms" => 1 } end - def stub_posts() do + def stub_projects_count(opts \\ []) do + %{ + "query" => + "{ 'total': count(*[!(_id in path('drafts.**')) && _type == 'project']{_id, 'objectID':_id, _rev, _type, title, publishedAt, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)}), 'count': count(*[!(_id in path('drafts.**')) && _type == 'project']{_id, 'objectID':_id, _rev, _type, title, publishedAt, 'author':{'_id':author->_id, '_type':author->_type, 'name':author->name, 'slug':author->slug, 'image':author->image}, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)} | order(date desc) [0...10])}", + "result" => %{ + "total" => opts[:total] || 1, + "count" => opts[:count] || 1 + }, + "ms" => 1 + } + end + + def stub_posts(fakeSlugs \\ ["some-post"]) do %{ "query" => "*[!(_id in path('drafts.**')) && _type == 'post']{_id, 'objectID':_id, _rev, _type, title, publishedAt, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)} | order(date desc) [0...10]", - "result" => [ - %{ - "estimatedWordCount" => 11, - "_type" => "post", - "tags" => nil, - "slug" => %{ - "_type" => "slug", - "current" => "some-post" - }, - "date" => "2022-08-26", - "views" => 99, - "numberOfCharacters" => 55, - "_id" => "b0a22943-b747-42df-84cd-573503332428", - "_rev" => "DFeMJDW0bXSE9MGpOVLqKO", - "title" => "Some post", - "objectID" => "b0a22943-b747-42df-84cd-573503332428", - "publishedAt" => nil, - "body" => [ - %{ - "markDefs" => [], - "children" => [ - %{ - "text" => "This post now has actual body content! Revolutionary...", - "_key" => "f231f8e8847b", - "_type" => "span", - "marks" => [] - } - ], - "_type" => "block", - "style" => "normal", - "_key" => "cda99f79ba00" - } - ], - "desc" => "Something interesting", - "estimatedReadingTime" => 0 - } - ], + "result" => fakeSlugs |> Enum.map(&stub_post/1) |> Enum.map(& &1["result"]), "ms" => 1 } end - def stub_post() do + def stub_post(fakeSlug \\ "some-post") do %{ "query" => "*[!(_id in path('drafts.**')) && _type == 'post' && slug.current == 'some-post'][0]", @@ -210,7 +186,7 @@ defmodule TestFixtures do "tags" => nil, "slug" => %{ "_type" => "slug", - "current" => "some-post" + "current" => "#{fakeSlug}" }, "date" => "2022-08-26", "numberOfCharacters" => 55, @@ -242,7 +218,7 @@ defmodule TestFixtures do } end - def stub_project() do + def stub_project(fakeSlug \\ "some-project") do %{ "query" => "*[!(_id in path('drafts.**')) && _type == 'project' && slug.current == 'some-project'][0]", @@ -256,7 +232,7 @@ defmodule TestFixtures do "title" => "elixir", "slug" => %{ "_type" => "slug", - "current" => "elixir" + "current" => "#{fakeSlug}" } } ], @@ -311,6 +287,15 @@ defmodule TestFixtures do } end + def stub_projects(fakeSlugs \\ ["some-project"]) do + %{ + "query" => + "*[!(_id in path('drafts.**')) && _type == 'project']{_id, 'objectID':_id, _rev, _type, title, publishedAt, tags[]->{_id, title, slug}, slug, body, desc, date, 'numberOfCharacters':length(pt::text(body)), 'estimatedWordCount':round(length(pt::text(body)) / 5), 'estimatedReadingTime':round(length(pt::text(body)) / 5 / 120)} | order(date desc) [0...10]", + "result" => fakeSlugs |> Enum.map(&stub_project/1) |> Enum.map(& &1["result"]), + "ms" => 1 + } + end + def mixed_headings() do [ %{