From ff5345e8888f9294f2c9522b8b92e63fa2a23363 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 29 Apr 2024 12:34:33 -0500 Subject: [PATCH] start ripping out tests --- config/test.exs | 1 + lib/cms/api/static.ex | 547 ++++++++++++++++++++++ lib/cms/search_results/file.ex | 19 +- lib/dotcom_web/controllers/helpers.ex | 11 +- test/cms/api/http_client_test.exs | 201 -------- test/cms/api/time_request_test.exs | 81 ---- test/cms/{lib => }/blurb_test.exs | 0 test/cms/{lib => }/breadcrumbs_test.exs | 0 test/cms/lib/config_test.exs | 53 --- test/cms/lib/external_request_test.exs | 184 -------- test/cms/repo_test.exs | 588 +----------------------- 11 files changed, 569 insertions(+), 1116 deletions(-) create mode 100644 lib/cms/api/static.ex delete mode 100644 test/cms/api/http_client_test.exs delete mode 100644 test/cms/api/time_request_test.exs rename test/cms/{lib => }/blurb_test.exs (100%) rename test/cms/{lib => }/breadcrumbs_test.exs (100%) delete mode 100644 test/cms/lib/config_test.exs delete mode 100644 test/cms/lib/external_request_test.exs diff --git a/config/test.exs b/config/test.exs index b4453b13e8..33dcc3896b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -4,6 +4,7 @@ config :dotcom, :cache, Dotcom.Cache.TestCache config :dotcom, :httpoison, HTTPoison.Mock +config :dotcom, :cms_api_module, CMS.Api.Mock config :dotcom, :mbta_api_module, MBTA.Api.Mock config :dotcom, :redis, Dotcom.Redis.Mock diff --git a/lib/cms/api/static.ex b/lib/cms/api/static.ex new file mode 100644 index 0000000000..224a5ed10a --- /dev/null +++ b/lib/cms/api/static.ex @@ -0,0 +1,547 @@ +defmodule CMS.API.Static do + @moduledoc """ + This module provides static responses for the CMS API. + + Should be removed when we rewrite all tests in this module. + """ + + @behaviour CMS.Api.Behaviour + + alias CMS.Helpers + alias CMS.Page.NewsEntry + alias Poison.Parser + + # Views REST export responses with fully-loaded objects + + def events_response do + parse_json("cms/events.json") + end + + def banners_response do + parse_json("cms/banners.json") + end + + def search_response do + parse_json("cms/search.json") + end + + def search_response_empty do + parse_json("cms/search-empty.json") + end + + def whats_happening_response do + parse_json("cms/whats-happening.json") + end + + def route_pdfs_response do + parse_json("cms/route-pdfs.json") + end + + def schedule_pdfs_response do + parse_json("cms/schedule-pdfs.json") + end + + # Teaser responses from CMS API (minimal data) + + def teaser_response do + parse_json("cms/teasers.json") + end + + def teaser_basic_page_response do + parse_json("cms/teasers_page.json") + end + + def teaser_news_entry_response do + parse_json("cms/teasers_news_entry.json") + end + + def teaser_event_response do + parse_json("cms/teasers_event.json") + end + + def teaser_project_response do + parse_json("cms/teasers_project.json") + end + + def teaser_featured_projects_response do + parse_json("cms/teasers_project_featured.json") + end + + def teaser_project_update_response do + parse_json("cms/teasers_project_update.json") + end + + def teaser_diversion_response do + parse_json("cms/teasers_diversion.json") + end + + def teaser_empty_response do + [] + end + + # Repositories of multiple, full-object responses (maximum data) + + def news_repo do + parse_json("repo/news.json") + end + + def project_repo do + parse_json("repo/projects.json") + end + + def project_update_repo do + parse_json("repo/project-updates.json") + end + + # Core (entity:node) responses + + def all_paragraphs_response do + parse_json("landing_page_with_all_paragraphs.json") + end + + def event_agenda_response do + parse_json("event_agenda.json") + end + + def basic_page_response do + parse_json("basic_page_no_sidebar.json") + end + + def basic_page_with_sidebar_response do + parse_json("basic_page_with_sidebar.json") + end + + def basic_page_no_alias_response do + parse_json("basic_page_with_sidebar_no_alias.json") + end + + def diversion_response do + parse_json("diversion.json") + end + + def landing_page_response do + parse_json("landing_page.json") + end + + def person_response do + parse_json("person.json") + end + + def redirect_response do + parse_json("redirect.json") + end + + def redirect_with_query_response do + parse_json("redirect_with_query%3Fid%3D5.json") + end + + # Partials (paragraph library) response + + def paragraph_response do + parse_json("_paragraph.json") + end + + @impl true + def view(path, params) + + def view("/cms/recent-news", current_id: id) do + filtered_recent_news = Enum.reject(news_repo(), &match?(%{"nid" => [%{"value" => ^id}]}, &1)) + + recent_news = Enum.take(filtered_recent_news, NewsEntry.number_of_recent_news_suggestions()) + + {:ok, recent_news} + end + + def view("/cms/recent-news", _) do + {:ok, Enum.take(news_repo(), NewsEntry.number_of_recent_news_suggestions())} + end + + def view("/basic_page_no_sidebar", _) do + {:ok, basic_page_response()} + end + + def view("/event_agenda", _) do + {:ok, event_agenda_response()} + end + + def view("/cms/news", id: id) do + news_entry = filter_by(news_repo(), "nid", id) + {:ok, news_entry} + end + + def view("/cms/news", migration_id: "multiple-records") do + {:ok, news_repo()} + end + + def view("/cms/news", migration_id: id) do + news = filter_by(news_repo(), "field_migration_id", id) + {:ok, news} + end + + def view("/cms/news", page: _page) do + record = List.first(news_repo()) + {:ok, [record]} + end + + def view("/news/incorrect-pattern", _) do + {:ok, Enum.at(news_repo(), 1)} + end + + def view("/news/date/title", _) do + {:ok, Enum.at(news_repo(), 1)} + end + + def view("/news/2018/news-entry", _) do + {:ok, List.first(news_repo())} + end + + def view("/news/redirected-url", params) do + redirect("/news/date/title", params, 301) + end + + def view("/cms/events", meeting_id: "multiple-records") do + {:ok, events_response()} + end + + def view("/cms/events", meeting_id: id) do + events = filter_by(events_response(), "field_meeting_id", id) + {:ok, events} + end + + def view("/cms/events", id: id) do + {:ok, filter_by(events_response(), "nid", id)} + end + + def view("/events/incorrect-pattern", _) do + {:ok, Enum.at(events_response(), 1)} + end + + def view("/events/date/title", _) do + {:ok, Enum.at(events_response(), 1)} + end + + def view("/events/redirected-url", params) do + redirect("/events/date/title", params, 301) + end + + def view("/cms/events", _opts) do + {:ok, events_response()} + end + + def view("/cms/search", q: "empty", page: 0) do + {:ok, search_response_empty()} + end + + def view("/cms/search", _opts) do + {:ok, search_response()} + end + + def view("/projects/project-name", _) do + {:ok, Enum.at(project_repo(), 1)} + end + + def view("/projects/project-with-paragraphs", _) do + {:ok, Enum.at(project_repo(), 0)} + end + + def view("/porjects/project-name", _) do + {:ok, Enum.at(project_repo(), 1)} + end + + def view("/projects/redirected-project", params) do + redirect("/projects/project-name", params, 301) + end + + def view("/projects/3004/update/3174", _) do + {:ok, Enum.at(project_update_repo(), 1)} + end + + def view("/projects/project-name/update/project-progress", _) do + {:ok, Enum.at(project_update_repo(), 1)} + end + + def view("/projects/project-name/update/update-with-paragraphs", _) do + {:ok, Enum.at(project_update_repo(), 5)} + end + + def view("/projects/redirected-project/update/update-with-paragraphs", _) do + {:ok, Enum.at(project_update_repo(), 7)} + end + + def view("/projects/project-name/update/redirected-update-with-paragraphs", params) do + redirect("/projects/project-name/update/update-with-paragraphs", params, 301) + end + + def view("/projects/DNE/update/update-no-project-with-paragraphs", _) do + {:ok, Enum.at(project_update_repo(), 6)} + end + + def view("/projects/project-deleted/update/project-deleted-progress", _) do + project_info = %{ + "field_project" => [ + %{ + "target_id" => 3004, + "target_type" => "node", + "target_uuid" => "5d55a7f8-22da-4ce8-9861-09602c64c7e4", + "url" => "/projects/project-deleted" + } + ] + } + + {:ok, + project_update_repo() + |> Enum.at(0) + |> Map.merge(project_info)} + end + + def view("/projects/redirected-project/update/not-redirected-update", _) do + {:ok, Enum.at(project_update_repo(), 2)} + end + + def view("/projects/project-name/update/redirected-update", params) do + redirect("/projects/project-name/update/project-progress", params, 301) + end + + def view("/cms/whats-happening", _) do + {:ok, whats_happening_response()} + end + + def view("/cms/important-notices", _) do + {:ok, banners_response()} + end + + def view("/landing_page_with_all_paragraphs", _) do + {:ok, all_paragraphs_response()} + end + + def view("/landing_page", _) do + {:ok, landing_page_response()} + end + + def view("/basic_page_with_sidebar", _) do + {:ok, basic_page_with_sidebar_response()} + end + + def view("/diversions/diversion", _) do + {:ok, diversion_response()} + end + + def view("/redirect_node", _) do + {:ok, redirect_response()} + end + + def view("/redirect_node_with_query%3Fid%3D5", _) do + {:ok, redirect_with_query_response()} + end + + def view("/redirect_node_with_query", %{"id" => "6"}) do + {:ok, redirect_with_query_response()} + end + + def view("/person", _) do + {:ok, person_response()} + end + + # Nodes without a path alias OR CMS redirect + def view("/node/3183", _) do + {:ok, basic_page_no_alias_response()} + end + + def view("/node/3519", _) do + {:ok, Enum.at(news_repo(), 0)} + end + + def view("/node/3268", _) do + {:ok, Enum.at(events_response(), 0)} + end + + def view("/node/3004", _) do + {:ok, Enum.at(project_repo(), 0)} + end + + def view("/node/3005", _) do + {:ok, Enum.at(project_update_repo(), 0)} + end + + # Paths that return CMS redirects (path alias exists) + def view("/node/3518", params) do + redirect("/news/2018/news-entry", params, 301) + end + + def view("/node/3458", params) do + redirect("/events/date/title", params, 301) + end + + def view("/node/3480", params) do + redirect("/projects/project-name", params, 301) + end + + def view("/node/3174", params) do + redirect("/projects/project-name/updates/project-progress", params, 301) + end + + def view("/cms/route-pdfs/87", _) do + {:ok, route_pdfs_response()} + end + + def view("/cms/schedules/87", _) do + {:ok, schedule_pdfs_response()} + end + + def view("/cms/route-pdfs/error", _) do + {:error, :invalid_response} + end + + def view("/cms/route-pdfs/" <> _route_id, _) do + {:ok, []} + end + + def view("/cms/teasers/guides" <> _, _) do + {:ok, teaser_basic_page_response()} + end + + def view("/cms/teasers/guides/red", sticky: 0) do + {:ok, []} + end + + def view("/cms/teasers", %{type: [:project], sticky: 1}) do + {:ok, teaser_featured_projects_response()} + end + + def view("/cms/teasers", %{type: [:project]}) do + {:ok, teaser_project_response()} + end + + def view("/cms/teasers", %{type: [:project_update]}) do + {:ok, teaser_project_update_response()} + end + + def view("/cms/teasers", %{type: [:project, :project_update]}) do + {:ok, teaser_project_response() ++ teaser_project_update_response()} + end + + def view("/cms/teasers", %{promoted: 0}) do + {:ok, teaser_empty_response()} + end + + def view("/cms/teasers", %{type: [:diversion]}) do + {:ok, teaser_diversion_response()} + end + + def view("/cms/teasers", %{type: [:news_entry], except: 3518, items_per_page: 4}) do + {:ok, teaser_news_entry_response() |> Enum.take(4)} + end + + def view("/cms/teasers", %{type: [:news_entry]}) do + {:ok, teaser_news_entry_response()} + end + + def view("/cms/teasers", %{type: [:event]}) do + {:ok, teaser_event_response()} + end + + def view("/cms/teasers/" <> arguments, params) when arguments != "any/NotFound" do + filtered = + teaser_response() + |> filter_teasers(params) + + case Map.fetch(params, :items_per_page) do + {:ok, count} -> + {:ok, Enum.take(filtered, count)} + + :error -> + {:ok, filtered} + end + end + + def view("/admin/content/paragraphs/25", params) do + redirect("/paragraphs/custom-html/projects-index", params, 301) + end + + def view("/paragraphs/custom-html/projects-index", _) do + {:ok, paragraph_response()} + end + + def view("/redirected-url", params) do + redirect("/different-url", params, 302) + end + + def view("/invalid", _) do + {:error, :invalid_response} + end + + def view("/timeout", _) do + {:error, :timeout} + end + + def view(_, _) do + {:error, :not_found} + end + + @impl true + def preview(node_id, revision_id) + def preview(3518, vid), do: {:ok, do_preview(Enum.at(news_repo(), 1), vid)} + def preview(5, vid), do: {:ok, do_preview(Enum.at(events_response(), 1), vid)} + def preview(3480, vid), do: {:ok, do_preview(Enum.at(project_repo(), 1), vid)} + def preview(3174, vid), do: {:ok, do_preview(Enum.at(project_update_repo(), 1), vid)} + def preview(6, vid), do: {:ok, do_preview(basic_page_response(), vid)} + + defp filter_by(map, key, value) do + Enum.filter(map, &match?(%{^key => [%{"value" => ^value}]}, &1)) + end + + defp parse_json(filename) do + file_path = [Path.dirname(__ENV__.file), "../../../priv/cms/", filename] + + file_path + |> Path.join() + |> File.read!() + |> Parser.parse!() + end + + # Generates multiple revisions on the fly for a single fixture + @spec do_preview(map, integer | String.t() | nil) :: [map] + defp do_preview(%{"title" => [%{"value" => title}]} = response, vid) do + vid = Helpers.int_or_string_to_int(vid) + + revisions = + for v <- [113, 112, 111] do + %{response | "vid" => [%{"value" => v}], "title" => [%{"value" => "#{title} #{v}"}]} + end + + cms_revision_filter(revisions, vid) + end + + # Performs the CMS-side revision ID filtering + @spec cms_revision_filter([map], integer | nil) :: [map] + defp cms_revision_filter(revisions, vid) + + # When no vid is specified, all results are returned + defp cms_revision_filter(revisions, nil) do + revisions + end + + # CMS will filter results to single matching result with vid + defp cms_revision_filter(revisions, vid) do + Enum.filter(revisions, &match?(%{"vid" => [%{"value" => ^vid}]}, &1)) + end + + def redirect(path, params, code) when params == %{}, do: {:error, {:redirect, code, [to: path]}} + + def redirect(path, params, code), + do: redirect(path <> "?" <> URI.encode_query(params), %{}, code) + + defp filter_teasers(teasers, %{type: [type], type_op: "not in"}) do + Enum.reject(teasers, &filter_teaser?(&1, type)) + end + + defp filter_teasers(teasers, %{type: [type]}) do + Enum.filter(teasers, &filter_teaser?(&1, type)) + end + + defp filter_teasers(teasers, %{}) do + teasers + end + + defp filter_teaser?(%{"type" => type}, type_atom), do: Atom.to_string(type_atom) === type +end diff --git a/lib/cms/search_results/file.ex b/lib/cms/search_results/file.ex index 252dcd0704..732920beba 100644 --- a/lib/cms/search_results/file.ex +++ b/lib/cms/search_results/file.ex @@ -1,29 +1,24 @@ defmodule CMS.SearchResult.File do @moduledoc false - defstruct title: "", - url: "", - mimetype: "" + defstruct mimetype: "", title: "", url: "" - alias CMS.Config + @type t :: %__MODULE__{mimetype: String.t(), title: String.t(), url: String.t()} - @type t :: %__MODULE__{ - title: String.t(), - url: String.t(), - mimetype: String.t() - } + @static_path "/sites/default/files" @spec build(map) :: t def build(result) do %__MODULE__{ + mimetype: result["ss_filemime"], title: result["ts_filename"], - url: link(result["ss_uri"]), - mimetype: result["ss_filemime"] + url: link(result["ss_uri"]) } end @spec link(String.t()) :: String.t() defp link(path) do - path = String.replace(path, "public:/", Config.static_path()) + path = String.replace(path, "public:/", @static_path) + Util.site_path(:static_url, [path]) end end diff --git a/lib/dotcom_web/controllers/helpers.ex b/lib/dotcom_web/controllers/helpers.ex index 1a0da02d14..e39c3afc82 100644 --- a/lib/dotcom_web/controllers/helpers.ex +++ b/lib/dotcom_web/controllers/helpers.ex @@ -123,7 +123,7 @@ defmodule DotcomWeb.ControllerHelpers do """ @spec forward_static_file(Conn.t(), String.t()) :: Conn.t() def forward_static_file(conn, url) do - case @req.get(url) do + case client() |> @req.get(url) do {:ok, %{status: 200, body: body, headers: headers}} -> conn |> add_headers_if_valid(headers) @@ -134,6 +134,15 @@ defmodule DotcomWeb.ControllerHelpers do end end + defp client(headers \\ []) do + config = Application.get_env(:dotcom, :cms_api) + + @req.new( + base_url: config[:base_url], + headers: config[:headers] ++ headers + ) + end + @spec check_cms_or_404(Conn.t()) :: Conn.t() def check_cms_or_404(conn) do conn diff --git a/test/cms/api/http_client_test.exs b/test/cms/api/http_client_test.exs deleted file mode 100644 index 68e132c32e..0000000000 --- a/test/cms/api/http_client_test.exs +++ /dev/null @@ -1,201 +0,0 @@ -defmodule CMS.API.HTTPClientTest do - use ExUnit.Case - import Mock - import CMS.API.HTTPClient - alias CMS.ExternalRequest - - describe "preview/2" do - test "uses alternate path with timeout options" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - preview(6, 0) - - assert called( - ExternalRequest.process( - :get, - "/cms/revisions/6", - "", - params: [_format: "json", vid: 0], - timeout: 10_000, - recv_timeout: 10_000 - ) - ) - end - end - end - - describe "view/2" do - test "makes a get request with format: json params" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view("/path", []) - assert called(ExternalRequest.process(:get, "/path", "", params: [{"_format", "json"}])) - end - end - - test "accepts additional params" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view("/path", foo: "bar") - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}, {"foo", "bar"}] - ) - ) - end - end - - test "accepts integers as param values" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view("/path", foo: 1) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}, {"foo", "1"}] - ) - ) - end - end - - test "accepts atoms as param values" do - with_mock ExternalRequest, process: fn _, _, _, _ -> {:ok, []} end do - view("/path", foo: :bar) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}, {"foo", "bar"}] - ) - ) - end - end - - test "illegal param values are dropped" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view("/path", %{"foo" => ["bar", "baz"]}) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}] - ) - ) - - view("/path", %{"foo" => [bar: "baz"]}) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}] - ) - ) - - view("/path", %{"foo" => %{"bar" => "baz"}}) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [{"_format", "json"}] - ) - ) - end - end - - test "certain nested params are allowed, ordered, and keys are replaced by key[nested_key]" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view( - "/path", - %{ - "foo" => [ - bad_sub_key: "foobar", - min: :should_be_string, - max: "string" - ] - } - ) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [ - {"_format", "json"}, - {"foo[min]", "should_be_string"}, - {"foo[max]", "string"} - ] - ) - ) - end - end - - test "CMS-style multiple argument params are converted to key[] for each argument" do - with_mock ExternalRequest, process: fn _method, _path, _body, _params -> {:ok, []} end do - view( - "/path", - %{ - "type" => [ - :project, - :project_update, - :event - ] - } - ) - - assert called( - ExternalRequest.process( - :get, - "/path", - "", - params: [ - {"_format", "json"}, - {"type[]", "project"}, - {"type[]", "project_update"}, - {"type[]", "event"} - ] - ) - ) - end - end - - test "nested and non-nested key values in request work when being redirected by CMS" do - with_mock ExternalRequest, - process: fn _method, _path, _body, _params -> {:ok, []} end do - view( - "/redirect", - %{ - "location" => %{"latitude" => "1234", "longitude" => "5678"}, - "foo" => %{"bad_sub_key" => "bar"}, - "fiz" => "baz" - } - ) - - assert called( - ExternalRequest.process( - :get, - "/redirect", - "", - params: [ - {"_format", "json"}, - {"fiz", "baz"}, - {"location[latitude]", "1234"}, - {"location[longitude]", "5678"} - ] - ) - ) - end - end - end -end diff --git a/test/cms/api/time_request_test.exs b/test/cms/api/time_request_test.exs deleted file mode 100644 index 4be60dc76b..0000000000 --- a/test/cms/api/time_request_test.exs +++ /dev/null @@ -1,81 +0,0 @@ -defmodule CMS.API.TimeRequestTest do - use ExUnit.Case, async: false - - import ExUnit.CaptureLog - import Mox - - alias CMS.API.TimeRequest - - setup :set_mox_global - setup :verify_on_exit! - - defp setup_log_level do - old_level = Logger.level() - - on_exit(fn -> - Logger.configure(level: old_level) - end) - - Logger.configure(level: :info) - end - - @url Faker.Internet.url() - - describe "time_request/5" do - @tag :capture_log - test "returns an HTTP response" do - setup_log_level() - - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 200, body: "ok"}} - end) - - response = TimeRequest.time_request(:get, @url, "", [], params: [param: "value"]) - assert {:ok, %{status_code: 200, body: "ok"}} = response - end - - test "logs a successful request" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 200, body: "ok"}} - end) - - params = [params: [param: "value", _format: "json"]] - - log = - capture_log(fn -> - setup_log_level() - TimeRequest.time_request(:get, @url, "", [], params) - end) - - assert log =~ "status=200" - assert log =~ "url=#{@url}" - assert log =~ params_without__format(params) - assert log =~ ~r(duration=\d) - end - - test "logs a failed request" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:error, %HTTPoison.Error{reason: :econnrefused}} - end) - - log = - capture_log(fn -> - setup_log_level() - TimeRequest.time_request(:get, @url) - end) - - assert log =~ "status=error" - assert log =~ "error=" - assert log =~ "url=#{@url}" - assert log =~ params_without__format([]) - assert log =~ ~r(duration=\d) - end - end - - defp params_without__format(params) do - params - |> Keyword.delete(:_format) - |> Keyword.put(:hackney, pool: Application.get_env(:dotcom, :cms_http_pool)) - |> (fn params -> "options=#{inspect(params)}" end).() - end -end diff --git a/test/cms/lib/blurb_test.exs b/test/cms/blurb_test.exs similarity index 100% rename from test/cms/lib/blurb_test.exs rename to test/cms/blurb_test.exs diff --git a/test/cms/lib/breadcrumbs_test.exs b/test/cms/breadcrumbs_test.exs similarity index 100% rename from test/cms/lib/breadcrumbs_test.exs rename to test/cms/breadcrumbs_test.exs diff --git a/test/cms/lib/config_test.exs b/test/cms/lib/config_test.exs deleted file mode 100644 index 1381f145b2..0000000000 --- a/test/cms/lib/config_test.exs +++ /dev/null @@ -1,53 +0,0 @@ -defmodule CMS.ConfigTest do - use ExUnit.Case - alias CMS.Config - - describe "url/1" do - test "returns a full URL for the given path" do - assert Config.url("my-path") == "http://cms.test/my-path" - end - - test "prevents duplicates forward slashes when the root and path have a forward slash" do - set_drupal_root(%{cms_root: "http://cms.test/"}, fn -> - assert Config.url("/my-path") == "http://cms.test/my-path" - end) - end - - test "removes duplicate leading forward slashes from the path" do - assert Config.url("//my-path") == "http://cms.test/my-path" - end - - test "supports the root url being assessed at runtime" do - env_var_name = "DRUPAL_ROOT" - - set_drupal_root(%{cms_root: {:system, env_var_name}}, fn -> - System.put_env(env_var_name, "http://cms.test") - - assert Config.url("my-path") == "http://cms.test/my-path" - - System.delete_env("env_var_name") - end) - end - - test "supports the root url being set at build time" do - set_drupal_root(%{cms_root: "http://cms.test"}, fn -> - assert Config.url("my-path") == "http://cms.test/my-path" - end) - end - - test "raises when the root url is not configured" do - set_drupal_root(%{cms_root: nil}, fn -> - assert_raise RuntimeError, "Drupal root is not configured", fn -> - Config.url("will-raise") - end - end) - end - end - - defp set_drupal_root(value, fun) do - original_config = Application.get_env(:dotcom, :drupal) - Application.put_env(:dotcom, :drupal, value) - fun.() - Application.put_env(:dotcom, :drupal, original_config) - end -end diff --git a/test/cms/lib/external_request_test.exs b/test/cms/lib/external_request_test.exs deleted file mode 100644 index 4a377824f9..0000000000 --- a/test/cms/lib/external_request_test.exs +++ /dev/null @@ -1,184 +0,0 @@ -defmodule CMS.ExternaRequestTest do - use ExUnit.Case - - import CMS.ExternalRequest - import ExUnit.CaptureLog - import Mox - - setup :set_mox_global - setup :verify_on_exit! - - @headers [{"content-type", "application/json"}] - - describe "process/4" do - test "issues a request with the provided information" do - expected = [param: Faker.Pizza.style()] - - expect(HTTPoison.Mock, :request, fn method, _, _, _, opts -> - assert method == :get - [_, {:params, param}] = opts - assert param == expected - - {:ok, %HTTPoison.Response{status_code: 200, headers: @headers, body: "[]"}} - end) - - assert process(:get, "/get", "", params: expected) == {:ok, []} - end - - test "handles a request with a body" do - expected = Faker.Pizza.style() - - expect(HTTPoison.Mock, :request, fn method, _, body, _, _ -> - assert method == :post - assert body == expected - - {:ok, %HTTPoison.Response{status_code: 201, headers: @headers, body: "[]"}} - end) - - process(:post, "/post", expected) - end - - test "sets headers for GET requests" do - expect(HTTPoison.Mock, :request, fn method, _, _, headers, _ -> - assert method == :get - assert headers == ["Content-Type": "application/json"] - - {:ok, %HTTPoison.Response{status_code: 200, headers: @headers, body: "[]"}} - end) - - process(:get, "/get") - end - - test "sets auth headers for non-GET requests" do - expect(HTTPoison.Mock, :request, fn method, _, _, headers, _ -> - assert method != :get - assert headers == ["Content-Type": "application/json", Authorization: "Basic Og=="] - - {:ok, %HTTPoison.Response{status_code: 201, headers: @headers, body: "[]"}} - end) - - process(:post, "/post", "body") - end - - test "forbidden is :not_found" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 401, headers: @headers}} - end) - - assert process(:get, "/page") == {:error, :not_found} - end - - test "unauthorized is :not_found" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 403, headers: @headers}} - end) - - assert process(:get, "/page") == {:error, :not_found} - end - - test "not found is :not_found" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 404, headers: @headers}} - end) - - assert process(:get, "/page") == {:error, :not_found} - end - - test "Logs a warning and returns {:error, _} if the request returns an exception" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:error, %HTTPoison.Error{reason: :timeout}} - end) - - log = - capture_log(fn -> - assert process(:get, "/page") == {:error, :timeout} - end) - - assert log =~ "request timed out" - end - - test "Logs a warning and returns {:error, _} if the request returns an unhandled error" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 500, headers: @headers}} - end) - - log = - capture_log(fn -> - assert process(:get, "/page") == {:error, :invalid_response} - end) - - assert log =~ "Bad response" - end - - test "Logs a warning and returns {:error, _} if the JSON cannot be parsed" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:ok, %HTTPoison.Response{status_code: 200, headers: @headers, body: "not json"}} - end) - - log = - capture_log(fn -> - assert process(:get, "/page") == {:error, :invalid_response} - end) - - assert log =~ "Error parsing json" - end - - test "returns {:error, {:redirect, status, path}} when CMS issues a native redirect and removes _format=json" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - headers = [{"location", "/redirect"}, {"content-type", "application/json"}] - {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} - end) - - assert {:error, {:redirect, 302, url}} = process(:get, "/path?_format=json") - assert url == [to: "/redirect"] - end - - test "returns {:error, :timeout} when CMS times out" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - {:error, %HTTPoison.Error{reason: :timeout}} - end) - - assert process(:get, "/path?_format=json", "", recv_timeout: 100) == {:error, :timeout} - end - - test "path retains query params and removes _format=json when CMS issues a native redirect" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - headers = [{"location", "/redirect?foo=bar"}, {"content-type", "application/json"}] - {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} - end) - - assert {:error, {:redirect, 302, url}} = process(:get, "/path?_format=json&foo=bar") - assert url == [to: "/redirect?foo=bar"] - end - - test "path retains fragment identifiers when CMS issues a native redirect" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - headers = [{"location", "/redirect#fragment"}, {"content-type", "application/json"}] - {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} - end) - - assert {:error, {:redirect, 302, url}} = process(:get, "/path#fragment") - assert url == [to: "/redirect#fragment"] - end - - test "Drupal content redirects returned as absolute paths are counted as internal and not external" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - headers = [{"location", "/redirect"}, {"content-type", "application/json"}] - {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} - end) - - assert {:error, {:redirect, 302, url}} = process(:get, "/path") - assert url == [to: "/redirect"] - end - - test "External redirects result in an absolute destination for Phoenix to follow" do - expect(HTTPoison.Mock, :request, fn _, _, _, _, _ -> - headers = [{"location", "https://www.google.com/"}, {"content-type", "application/json"}] - {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} - end) - - assert {:error, {:redirect, 302, url}} = process(:get, "/path") - assert url == [external: "https://www.google.com/"] - end - end -end diff --git a/test/cms/repo_test.exs b/test/cms/repo_test.exs index 22e55712bb..b162af32cc 100644 --- a/test/cms/repo_test.exs +++ b/test/cms/repo_test.exs @@ -1,37 +1,11 @@ defmodule CMS.RepoTest do use ExUnit.Case, async: false - require Dotcom.Assertions + import Mox - import ExUnit.CaptureLog, only: [capture_log: 1] - import Phoenix.HTML, only: [safe_to_string: 1] - import Mock + alias CMS.Repo - # Misc - alias CMS.{ - API.Static, - Repo - } - - # Page Content - alias CMS.Page.{ - Basic, - Event, - Landing, - NewsEntry, - Project, - ProjectUpdate, - Redirect - } - - # Other Content Types - alias CMS.Partial.{ - Banner, - Paragraph, - RoutePdf, - Teaser, - WhatsHappeningItem - } + setup :set_mox_global setup do cache = Application.get_env(:dotcom, :cache) @@ -40,33 +14,7 @@ defmodule CMS.RepoTest do %{cache: cache} end - describe "news_entry_by/1" do - test "returns the news entry for the given id" do - assert %NewsEntry{id: 3519} = Repo.news_entry_by(id: 3519) - end - - test "returns :not_found given no record is found" do - assert :not_found == Repo.news_entry_by(id: 999) - end - - test "gracefully handles more than one API result" do - two_news = - Enum.map([1, 2], fn n -> - CMS.Factory.news_entry_factory(n, title: "News #{n}") - end) - - mock_view = fn - "/cms/news", _ -> {:ok, two_news} - end - - with_mocks [ - {Static, [], view: mock_view}, - {NewsEntry, [], [from_api: fn n -> n end]} - ] do - assert %NewsEntry{} = Repo.news_entry_by([]) - end - end - end + setup :verify_on_exit! describe "generate/3" do test "generates the correct key for /*" do @@ -113,532 +61,4 @@ defmodule CMS.RepoTest do "cms.repo" <> String.replace(path, "/", "|") <> "?biz=bang&data[latitude]=123" end end - - describe "get_page/1" do - test "caches views", %{cache: cache} do - path = "/news/2018/news-entry" - params = %{} - key = "cms.repo" <> String.replace(path, "/", "|") - - assert cache.get(key) == nil - - Repo.get_page(path, params) - - assert cache.get(key) != nil - end - - test "sets the ttl to < :infinity", %{cache: cache} do - path = "/news/2018/news-entry" - params = %{} - key = "/cms" <> path - - Repo.get_page(path, params) - - assert cache.ttl(key) != :infinity - end - - test "does not cache previews", %{cache: cache} do - path = "/news/2018/news-entry" - params = %{"preview" => "", "vid" => "112", "nid" => "6"} - key = "/cms" <> path - - assert cache.get(key) == nil - - Repo.get_page(path, params) - - assert cache.get(key) == nil - end - - test "given the path for a Basic page" do - result = Repo.get_page("/basic_page_with_sidebar") - assert %Basic{} = result - end - - test "returns a NewsEntry" do - assert %NewsEntry{} = Repo.get_page("/news/2018/news-entry") - end - - test "returns an Event" do - assert %Event{} = Repo.get_page("/events/date/title") - end - - test "returns a Project" do - assert %Project{} = Repo.get_page("/projects/project-name") - end - - test "returns a ProjectUpdate" do - assert %ProjectUpdate{} = Repo.get_page("/projects/project-name/update/project-progress") - end - - test "given the path for a Basic page with tracking params" do - result = Repo.get_page("/basic_page_with_sidebar", %{"from" => "search"}) - assert %Basic{} = result - end - - test "given the path for a Landing page" do - result = Repo.get_page("/landing_page") - assert %Landing{} = result - end - - test "given the path for a Redirect page" do - result = Repo.get_page("/redirect_node") - assert %Redirect{} = result - end - - test "returns {:error, :not_found} when the path does not match an existing page" do - assert Repo.get_page("/does/not/exist") == {:error, :not_found} - end - - test "returns {:error, :invalid_response} when the CMS returns a server error" do - assert Repo.get_page("/cms/route-pdfs/error") == {:error, :invalid_response} - end - - test "returns {:error, :invalid_response} when JSON is invalid" do - assert Repo.get_page("/invalid") == {:error, :invalid_response} - end - - test "given special preview query params, return certain revision of node" do - result = - Repo.get_page("/basic_page_no_sidebar", %{"preview" => "", "vid" => "112", "nid" => "6"}) - - assert %Basic{} = result - assert result.title == "Arts on the T 112" - end - - test "deprecated use of 'latest' value for revision parameter still returns newest revision" do - result = - Repo.get_page("/basic_page_no_sidebar", %{ - "preview" => "", - "vid" => "latest", - "nid" => "6" - }) - - assert %Basic{} = result - assert result.title == "Arts on the T 113" - end - end - - describe "get_page_with_encoded_id/2" do - test "encodes the id param into the request" do - assert Repo.get_page("/redirect_node_with_query", %{"id" => "5"}) == {:error, :not_found} - - assert %Redirect{} = - Repo.get_page_with_encoded_id("/redirect_node_with_query", %{"id" => "5"}) - end - end - - describe "events/1" do - test "returns list of Event" do - assert [ - %Event{ - id: id, - body: body - } - | _ - ] = Repo.events() - - assert id == 3268 - - assert safe_to_string(body) =~ - "(FMCB) closely monitors the T’s finances, management, and operations.

" - end - end - - describe "event_by/1" do - test "returns the event for the given id" do - assert %Event{id: 3268} = Repo.event_by(id: 3268) - end - - test "returns :not_found given no record is found" do - assert :not_found == Repo.event_by(id: 999) - end - end - - describe "whats_happening" do - test "returns a list of WhatsHappeningItem" do - assert [ - %WhatsHappeningItem{ - blurb: blurb - } - | _ - ] = Repo.whats_happening() - - assert blurb =~ - "Visiting Boston? Find your way around with our new Visitor's Guide to the T." - end - end - - describe "banner" do - test "returns a Banner" do - assert %Banner{ - blurb: blurb - } = Repo.banner() - - assert blurb == "Headline goes here" - end - end - - describe "search" do - test "with results" do - {:ok, result} = Repo.search("mbta", 0, []) - assert result.count == 2083 - end - - test "without results" do - {:ok, result} = Repo.search("empty", 0, []) - assert result.count == 0 - end - end - - describe "get_route_pdfs/1" do - test "returns list of RoutePdfs" do - assert [%RoutePdf{}, _, _] = Repo.get_route_pdfs("87") - end - - test "returns empty list if there's an error" do - log = - capture_log(fn -> - assert [] = Repo.get_route_pdfs("error") - end) - - assert log =~ "Error getting pdfs" - end - - test "returns empty list if there's no pdfs for the route id" do - assert [] = Repo.get_route_pdfs("doesntexist") - end - end - - describe "get_schedule_pdfs/1" do - assert [%RoutePdf{} | _] = Repo.get_schedule_pdfs("87") - end - - describe "teasers/1" do - test "returns only teasers for a project type" do - types = - [type: [:project]] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - assert types == [:project] - end - - test "returns teasers for a project and project update type" do - types = - [type: [:project, :project_update]] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - assert types == [:project, :project_update] - end - - test "returns all teasers for a type that are sticky" do - teasers = - [type: [:project], sticky: 1] - |> Repo.teasers() - - assert [%Teaser{}, %Teaser{}, %Teaser{}] = teasers - end - - test "returns all teasers for a route" do - types = - [route_id: "Red", sidebar: 1] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - Dotcom.Assertions.assert_equal_lists(types, [:event, :news_entry, :project]) - end - - test "returns all teasers for a topic" do - types = - [topic: "Guides", sidebar: 1] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - Dotcom.Assertions.assert_equal_lists(types, [:event, :news_entry, :project]) - end - - test "returns all teasers for a mode" do - types = - [mode: "subway", sidebar: 1] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - Dotcom.Assertions.assert_equal_lists(types, [:event, :news_entry, :project]) - end - - test "returns all teasers for a mode and topic combined" do - types = - [mode: "subway", topic: "Guides", sidebar: 1] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - Dotcom.Assertions.assert_equal_lists(types, [:event, :news_entry, :project]) - end - - test "returns all teasers for a route_id and topic combined" do - types = - [route_id: "Red", topic: "Guides", sidebar: 1] - |> Repo.teasers() - |> MapSet.new(& &1.type) - |> MapSet.to_list() - - Dotcom.Assertions.assert_equal_lists(types, [:event, :news_entry, :project]) - end - - test "converts generic arguments into path parts for API request" do - mock_view = fn - "/cms/teasers/Guides/Red", %{sticky: 0} -> {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(args: ["Guides", "Red"], sticky: 0) - - assert_called(Static.view("/cms/teasers/Guides/Red", %{sticky: 0})) - end - end - - test "takes a :type option" do - teasers = Repo.teasers(route_id: "Red", type: [:project], sidebar: 1) - assert Enum.all?(teasers, &(&1.type == :project)) - end - - test "takes a :type_op option" do - all_teasers = Repo.teasers(route_id: "Red", sidebar: 1) - assert Enum.any?(all_teasers, &(&1.type == :project)) - - filtered = Repo.teasers(route_id: "Red", type: [:project], type_op: "not in", sidebar: 1) - refute Enum.empty?(filtered) - refute Enum.any?(filtered, &(&1.type == :project)) - end - - test "takes an :items_per_page option" do - all_teasers = Repo.teasers(route_id: "Red", sidebar: 1) - assert Enum.count(all_teasers) > 1 - assert [%Teaser{}] = Repo.teasers(route_id: "Red", items_per_page: 1) - end - - test "takes a :related_to option" do - mock_view = fn - "/cms/teasers", %{related_to: 123} -> {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(related_to: 123) - - assert_called(Static.view("/cms/teasers", %{related_to: 123})) - end - end - - test "takes an :except option" do - mock_view = fn - "/cms/teasers", %{except: 123} -> {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(except: 123) - - assert_called(Static.view("/cms/teasers", %{except: 123})) - end - end - - test "takes an :only option" do - mock_view = fn - "/cms/teasers", %{only: 123} -> {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(only: 123) - - assert_called(Static.view("/cms/teasers", %{only: 123})) - end - end - - test "takes a :date and :date_op option" do - mock_view = fn - "/cms/teasers", %{date: "2018-01-01", date_op: ">="} -> {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(date: "2018-01-01", date_op: ">=") - - assert_called(Static.view("/cms/teasers", %{date: "2018-01-01", date_op: ">="})) - end - end - - test "accepts and passes through given :sort_order and :sort_by options" do - mock_view = fn - "/cms/teasers", %{type: [:page], sort_by: "changed", sort_order: :ASC} -> - {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(type: [:page], sort_by: "changed", sort_order: :ASC) - - assert_called( - Static.view("/cms/teasers", %{type: [:page], sort_by: "changed", sort_order: :ASC}) - ) - end - end - - test "sets correct :sort_by and :sort_order options for project_update and news_entry requests" do - mock_view = fn - "/cms/teasers", - %{type: [:project_update], sort_by: "field_posted_on_value", sort_order: :DESC} -> - {:ok, []} - - "/cms/teasers", - %{type: [:news_entry], sort_by: "field_posted_on_value", sort_order: :ASC} -> - {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(type: [:project_update]) - Repo.teasers(type: [:news_entry], sort_order: :ASC) - - assert_called( - Static.view("/cms/teasers", %{ - type: [:project_update], - sort_by: "field_posted_on_value", - sort_order: :DESC - }) - ) - - assert_called( - Static.view("/cms/teasers", %{ - type: [:news_entry], - sort_by: "field_posted_on_value", - sort_order: :ASC - }) - ) - end - end - - test "sets correct :sort_by and :sort_order options for project requests" do - mock_view = fn - "/cms/teasers", - %{type: [:project], sort_by: "field_updated_on_value", sort_order: :DESC} -> - {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(type: [:project]) - - assert_called( - Static.view("/cms/teasers", %{ - type: [:project], - sort_by: "field_updated_on_value", - sort_order: :DESC - }) - ) - end - end - - test "sets correct :sort_by and :sort_order options for event requests" do - mock_view = fn - "/cms/teasers", %{type: [:event], sort_by: "field_start_time_value", sort_order: :DESC} -> - {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(type: [:event]) - - assert_called( - Static.view("/cms/teasers", %{ - type: [:event], - sort_by: "field_start_time_value", - sort_order: :DESC - }) - ) - end - end - - test "drops :sort_by and :sort_order options when either option is missing" do - mock_view = fn - "/cms/teasers", %{type: [:page]} -> - {:ok, []} - end - - with_mock Static, view: mock_view do - Repo.teasers(type: [:page], sort_by: :ASC) - - assert_called(Static.view("/cms/teasers", %{type: [:page]})) - end - end - - test "returns an empty list and logs a warning if there is an error" do - log = - capture_log(fn -> - assert Repo.teasers(route_id: "NotFound", sidebar: 1) == [] - end) - - assert log =~ "error=:not_found" - end - end - - describe "get_paragraph/1" do - test "returns a single paragraph item" do - paragraph = Repo.get_paragraph("/paragraphs/custom-html/projects-index") - assert %Paragraph.CustomHTML{} = paragraph - end - - test "returns a single paragraph item which has a newer alias" do - paragraph = Repo.get_paragraph("/admin/content/paragraphs/25") - assert %Paragraph.CustomHTML{} = paragraph - end - end - - describe "events_for_year/1" do - test "calls /cms/teasers with desired opts for date range" do - opts = fn year -> - %{ - date: [min: "#{year}-01-01", max: "#{year + 1}-01-01"], - date_op: "between", - items_per_page: 50, - offset: 0, - sort_by: "field_start_time_value", - sort_order: :ASC, - type: [:event] - } - end - - year = 2018 - mock_2018_opts = opts.(year) - - with_mock Static, view: fn "/cms/teasers", ^mock_2018_opts -> {:ok, []} end do - Repo.events_for_year(year) - - assert_called(Static.view("/cms/teasers", opts.(year))) - end - end - end - - describe "next_n_event_teasers/2" do - test "calls for upcoming specified number of teasers" do - num = 3 - - with_mocks [ - {Static, [], [view: fn "/cms/teasers", _ -> {:ok, []} end]} - ] do - _ = Repo.next_n_event_teasers(~D[1999-01-01], num) - - assert_called( - Static.view("/cms/teasers", %{ - date: [value: "1999-01-01"], - date_op: ">=", - items_per_page: num, - sort_by: "field_start_time_value", - sort_order: :ASC, - type: [:event] - }) - ) - end - end - end end