diff --git a/lib/google_crawler/errors.ex b/lib/google_crawler/errors.ex
new file mode 100644
index 0000000..0521f3a
--- /dev/null
+++ b/lib/google_crawler/errors.ex
@@ -0,0 +1,3 @@
+defmodule GoogleCrawler.Errors.FileNotSupportedError do
+ defexception message: "File is not supported"
+end
diff --git a/lib/google_crawler/search.ex b/lib/google_crawler/search.ex
new file mode 100644
index 0000000..db39dac
--- /dev/null
+++ b/lib/google_crawler/search.ex
@@ -0,0 +1,73 @@
+defmodule GoogleCrawler.Search do
+ @moduledoc """
+ The Search context.
+ """
+
+ import Ecto.Query, warn: false
+ alias GoogleCrawler.Repo
+
+ alias GoogleCrawler.Search.Keyword
+ alias GoogleCrawler.Search.KeywordFile
+
+ @doc """
+ Returns the list of keywords.
+
+ ## Examples
+
+ iex> list_keywords()
+ [%Keyword{}, ...]
+
+ """
+ def list_keywords do
+ Repo.all(Keyword)
+ end
+
+ @doc """
+ Gets a single keyword.
+
+ Raises `Ecto.NoResultsError` if the Keyword does not exist.
+
+ ## Examples
+
+ iex> get_keyword(123)
+ %Keyword{}
+
+ iex> get_keyword(456)
+ nil
+
+ """
+ def get_keyword(id), do: Repo.get(Keyword, id)
+
+ @doc """
+ Creates a keyword.
+
+ ## Examples
+
+ iex> create_keyword(%{field: value})
+ {:ok, %Keyword{}}
+
+ iex> create_keyword(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_keyword(attrs \\ %{}) do
+ %Keyword{}
+ |> Keyword.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Parses the keyword from the given file.
+ Returns the stream for each line in the csv file as [line_result].
+ Raise an exception if the file mime type is not supported or the file parsing is failed.
+
+ ### Examples
+
+ iex > parse_keywords_from_file!("var/folder/abcdef", "text/csv") |> Enum.to_list
+ [ok: ["hotels"], ok: ["restaurants"]]
+
+ """
+ def parse_keywords_from_file!(file_path, mime_type) do
+ KeywordFile.parse!(file_path, mime_type)
+ end
+end
diff --git a/lib/google_crawler/search/keyword.ex b/lib/google_crawler/search/keyword.ex
new file mode 100644
index 0000000..50656c7
--- /dev/null
+++ b/lib/google_crawler/search/keyword.ex
@@ -0,0 +1,16 @@
+defmodule GoogleCrawler.Search.Keyword do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ schema "keywords" do
+ field :keyword, :string
+
+ timestamps()
+ end
+
+ def changeset(keyword, attrs \\ %{}) do
+ keyword
+ |> cast(attrs, [:keyword])
+ |> validate_required([:keyword])
+ end
+end
diff --git a/lib/google_crawler/search/keyword_file.ex b/lib/google_crawler/search/keyword_file.ex
new file mode 100644
index 0000000..fe3e60a
--- /dev/null
+++ b/lib/google_crawler/search/keyword_file.ex
@@ -0,0 +1,38 @@
+defmodule GoogleCrawler.Search.KeywordFile do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ embedded_schema do
+ field :file, :map
+ end
+
+ @accept_file_ext ~w(.csv)
+
+ def changeset(keyword_file, attrs \\ %{}) do
+ keyword_file
+ |> cast(attrs, [:file])
+ |> validate_required([:file])
+ |> validate_file_ext()
+ end
+
+ def parse!(file_path, "text/csv") do
+ file_path
+ |> File.stream!()
+ |> CSV.decode!()
+ end
+
+ def parse!(_file_path, _unexpected_mime_type) do
+ raise GoogleCrawler.Errors.FileNotSupportedError,
+ message: "File with this extension is not supported"
+ end
+
+ defp validate_file_ext(changeset) do
+ validate_change(changeset, :file, fn :file, file ->
+ if Enum.member?(@accept_file_ext, Path.extname(file.filename)) do
+ []
+ else
+ [file: "is not supported"]
+ end
+ end)
+ end
+end
diff --git a/lib/google_crawler_web/controllers/dashboard_controller.ex b/lib/google_crawler_web/controllers/dashboard_controller.ex
new file mode 100644
index 0000000..e341dbb
--- /dev/null
+++ b/lib/google_crawler_web/controllers/dashboard_controller.ex
@@ -0,0 +1,11 @@
+defmodule GoogleCrawlerWeb.DashboardController do
+ use GoogleCrawlerWeb, :controller
+
+ alias GoogleCrawler.Search.KeywordFile
+
+ def index(conn, _params) do
+ changeset = KeywordFile.changeset(%KeywordFile{})
+
+ render(conn, "index.html", changeset: changeset)
+ end
+end
diff --git a/lib/google_crawler_web/controllers/page_controller.ex b/lib/google_crawler_web/controllers/page_controller.ex
deleted file mode 100644
index 92121e0..0000000
--- a/lib/google_crawler_web/controllers/page_controller.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule GoogleCrawlerWeb.PageController do
- use GoogleCrawlerWeb, :controller
-
- def index(conn, _params) do
- render(conn, "index.html")
- end
-end
diff --git a/lib/google_crawler_web/controllers/registration_controller.ex b/lib/google_crawler_web/controllers/registration_controller.ex
index 435cb0e..e7f8d2e 100644
--- a/lib/google_crawler_web/controllers/registration_controller.ex
+++ b/lib/google_crawler_web/controllers/registration_controller.ex
@@ -16,7 +16,7 @@ defmodule GoogleCrawlerWeb.RegistrationController do
conn
|> put_flash(:info, gettext("You have signed up successfully!"))
# TODO: Change to login path
- |> redirect(to: Routes.page_path(conn, :index))
+ |> redirect(to: Routes.dashboard_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
diff --git a/lib/google_crawler_web/controllers/session_controller.ex b/lib/google_crawler_web/controllers/session_controller.ex
index dde4e80..896e290 100644
--- a/lib/google_crawler_web/controllers/session_controller.ex
+++ b/lib/google_crawler_web/controllers/session_controller.ex
@@ -16,7 +16,7 @@ defmodule GoogleCrawlerWeb.SessionController do
conn
|> put_flash(:info, gettext("Welcome back"))
|> put_session(:current_user_id, user.id)
- |> redirect(to: Routes.page_path(conn, :index))
+ |> redirect(to: Routes.dashboard_path(conn, :index))
{:error, _reason} ->
conn
diff --git a/lib/google_crawler_web/controllers/upload_controller.ex b/lib/google_crawler_web/controllers/upload_controller.ex
new file mode 100644
index 0000000..02172d8
--- /dev/null
+++ b/lib/google_crawler_web/controllers/upload_controller.ex
@@ -0,0 +1,23 @@
+defmodule GoogleCrawlerWeb.UploadController do
+ use GoogleCrawlerWeb, :controller
+ import Ecto.Changeset, only: [get_change: 3]
+
+ alias GoogleCrawler.Search
+ alias GoogleCrawler.Search.KeywordFile
+
+ def create(conn, %{"keyword_file" => keyword_file}) do
+ changeset = KeywordFile.changeset(%KeywordFile{}, keyword_file)
+
+ if changeset.valid? do
+ file = get_change(changeset, :file, nil)
+ result = Search.parse_keywords_from_file!(file.path, file.content_type)
+
+ # TODO: Save these keywords and triggers the task to google search for each keyword
+ text(conn, result |> Enum.map(fn keyword -> List.first(keyword) end) |> Enum.join(", "))
+ else
+ conn
+ |> put_flash(:error, gettext("Invalid file, please select again."))
+ |> redirect(to: Routes.dashboard_path(conn, :index))
+ end
+ end
+end
diff --git a/lib/google_crawler_web/plugs/skip_after_auth.ex b/lib/google_crawler_web/plugs/skip_after_auth.ex
index a88f1ea..739ea8f 100644
--- a/lib/google_crawler_web/plugs/skip_after_auth.ex
+++ b/lib/google_crawler_web/plugs/skip_after_auth.ex
@@ -17,7 +17,7 @@ defmodule GoogleCrawlerWeb.Plugs.SkipAfterAuth do
if conn.assigns.user_signed_in? do
conn
|> put_flash(:info, gettext("You are already signed in."))
- |> redirect(to: Routes.page_path(conn, :index))
+ |> redirect(to: Routes.dashboard_path(conn, :index))
|> halt()
else
conn
diff --git a/lib/google_crawler_web/router.ex b/lib/google_crawler_web/router.ex
index 8d13902..7145612 100644
--- a/lib/google_crawler_web/router.ex
+++ b/lib/google_crawler_web/router.ex
@@ -27,9 +27,9 @@ defmodule GoogleCrawlerWeb.Router do
pipe_through [:browser, GoogleCrawlerWeb.Plugs.EnsureAuth]
resources "/sessions", SessionController, only: [:delete]
+ resources "/upload", UploadController, only: [:create]
- # TODO: Cleanup this default route
- get "/", PageController, :index
+ get "/", DashboardController, :index
end
# Other scopes may use custom stacks.
diff --git a/lib/google_crawler_web/templates/dashboard/index.html.eex b/lib/google_crawler_web/templates/dashboard/index.html.eex
new file mode 100644
index 0000000..a3bf664
--- /dev/null
+++ b/lib/google_crawler_web/templates/dashboard/index.html.eex
@@ -0,0 +1,6 @@
+<%= render GoogleCrawlerWeb.KeywordView, "_form.html", assigns %>
+
+
+ <%= gettext("Keywords") %>
+ <%= gettext("You don't have any keywords.") %>
+
diff --git a/lib/google_crawler_web/templates/keyword/_form.html.eex b/lib/google_crawler_web/templates/keyword/_form.html.eex
new file mode 100644
index 0000000..d0f3471
--- /dev/null
+++ b/lib/google_crawler_web/templates/keyword/_form.html.eex
@@ -0,0 +1,11 @@
+
+ <%= gettext("Upload your keyword file (.csv)") %>
+ <%= gettext("📝 Please put one keyword per line") %>
+ <%= form_for @changeset, Routes.upload_path(@conn, :create), [multipart: true], fn f -> %>
+ <%= label f, :file %>
+ <%= file_input f, :file, required: true %>
+ <%= error_tag f, :file %>
+
+ <%= submit gettext("Upload") %>
+ <% end %>
+
diff --git a/lib/google_crawler_web/templates/page/index.html.eex b/lib/google_crawler_web/templates/page/index.html.eex
deleted file mode 100644
index 8cbd9d8..0000000
--- a/lib/google_crawler_web/templates/page/index.html.eex
+++ /dev/null
@@ -1,35 +0,0 @@
-
- <%= gettext "Welcome to %{name}!", name: "Phoenix" %>
- A productive web framework that
does not compromise speed or maintainability.
-
-
-
-
- Resources
-
-
-
- Help
-
-
-
diff --git a/lib/google_crawler_web/views/dashboard_view.ex b/lib/google_crawler_web/views/dashboard_view.ex
new file mode 100644
index 0000000..0b485a2
--- /dev/null
+++ b/lib/google_crawler_web/views/dashboard_view.ex
@@ -0,0 +1,3 @@
+defmodule GoogleCrawlerWeb.DashboardView do
+ use GoogleCrawlerWeb, :view
+end
diff --git a/lib/google_crawler_web/views/keyword_view.ex b/lib/google_crawler_web/views/keyword_view.ex
new file mode 100644
index 0000000..95f6c5e
--- /dev/null
+++ b/lib/google_crawler_web/views/keyword_view.ex
@@ -0,0 +1,3 @@
+defmodule GoogleCrawlerWeb.KeywordView do
+ use GoogleCrawlerWeb, :view
+end
diff --git a/lib/google_crawler_web/views/page_view.ex b/lib/google_crawler_web/views/page_view.ex
deleted file mode 100644
index 765f665..0000000
--- a/lib/google_crawler_web/views/page_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule GoogleCrawlerWeb.PageView do
- use GoogleCrawlerWeb, :view
-end
diff --git a/mix.exs b/mix.exs
index 736f017..5bb4cfb 100644
--- a/mix.exs
+++ b/mix.exs
@@ -44,7 +44,8 @@ defmodule GoogleCrawler.MixProject do
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:bcrypt_elixir, "~> 2.0"},
- {:faker_elixir_octopus, "~> 1.0.0", only: [:dev, :test]}
+ {:faker_elixir_octopus, "~> 1.0.0", only: [:dev, :test]},
+ {:csv, "~> 2.3"}
]
end
diff --git a/mix.lock b/mix.lock
index 1f2e5fe..6489f6e 100644
--- a/mix.lock
+++ b/mix.lock
@@ -4,6 +4,7 @@
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
+ "csv": {:hex, :csv, "2.3.1", "9ce11eff5a74a07baf3787b2b19dd798724d29a9c3a492a41df39f6af686da0e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "86626e1c89a4ad9a96d0d9c638f9e88c2346b89b4ba1611988594ebe72b5d5ee"},
"db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"ecto": {:hex, :ecto, "3.3.4", "95b05c82ae91361475e5491c9f3ac47632f940b3f92ae3988ac1aad04989c5bb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "9b96cbb83a94713731461ea48521b178b0e3863d310a39a3948c807266eebd69"},
@@ -15,6 +16,7 @@
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
"jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
+ "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
"phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "856cc1a032fa53822737413cf51aa60e750525d7ece7d1c0576d90d7c0f05c24"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
"phoenix_html": {:hex, :phoenix_html, "2.14.1", "7dabafadedb552db142aacbd1f11de1c0bbaa247f90c449ca549d5e30bbc66b4", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "536d5200ad37fecfe55b3241d90b7a8c3a2ca60cd012fc065f776324fa9ab0a9"},
diff --git a/priv/repo/migrations/20200329085134_create_keywords.exs b/priv/repo/migrations/20200329085134_create_keywords.exs
new file mode 100644
index 0000000..f86763a
--- /dev/null
+++ b/priv/repo/migrations/20200329085134_create_keywords.exs
@@ -0,0 +1,11 @@
+defmodule GoogleCrawler.Repo.Migrations.CreateKeywords do
+ use Ecto.Migration
+
+ def change do
+ create table(:keywords) do
+ add :keyword, :string
+
+ timestamps()
+ end
+ end
+end
diff --git a/test/factories/keyword_factory.ex b/test/factories/keyword_factory.ex
new file mode 100644
index 0000000..597c333
--- /dev/null
+++ b/test/factories/keyword_factory.ex
@@ -0,0 +1,21 @@
+defmodule GoogleCrawler.KeywordFactory do
+ alias GoogleCrawler.Search
+
+ def default_attrs do
+ %{
+ keyword: FakerElixir.Lorem.word()
+ }
+ end
+
+ def build_attrs(attrs \\ %{}) do
+ Enum.into(attrs, default_attrs())
+ end
+
+ def create(attrs \\ %{}) do
+ keyword_attrs = build_attrs(attrs)
+
+ {:ok, keyword} = Search.create_keyword(keyword_attrs)
+
+ keyword
+ end
+end
diff --git a/test/factories/keyword_file_factory.ex b/test/factories/keyword_file_factory.ex
new file mode 100644
index 0000000..6000058
--- /dev/null
+++ b/test/factories/keyword_file_factory.ex
@@ -0,0 +1,13 @@
+defmodule GoogleCrawler.KeywordFileFactory do
+ import GoogleCrawler.FixtureHelper
+
+ def default_attrs do
+ %{
+ file: upload_file_fixture("keyword_files/invalid_keyword.csv")
+ }
+ end
+
+ def build_attrs(attrs \\ %{}) do
+ Enum.into(attrs, default_attrs())
+ end
+end
diff --git a/test/fixtures/keyword_files/invalid_keyword.csv b/test/fixtures/keyword_files/invalid_keyword.csv
new file mode 100644
index 0000000..b9f1173
--- /dev/null
+++ b/test/fixtures/keyword_files/invalid_keyword.csv
@@ -0,0 +1,2 @@
+elixir, ruby
+javascript
diff --git a/test/fixtures/keyword_files/unsupported_keyword.txt b/test/fixtures/keyword_files/unsupported_keyword.txt
new file mode 100644
index 0000000..e7b0d58
--- /dev/null
+++ b/test/fixtures/keyword_files/unsupported_keyword.txt
@@ -0,0 +1 @@
+elixir ruby javascript
diff --git a/test/fixtures/keyword_files/valid_keyword.csv b/test/fixtures/keyword_files/valid_keyword.csv
new file mode 100644
index 0000000..cefeaf5
--- /dev/null
+++ b/test/fixtures/keyword_files/valid_keyword.csv
@@ -0,0 +1,3 @@
+elixir
+ruby
+javascript
diff --git a/test/google_crawler/accounts_test.exs b/test/google_crawler/accounts_test.exs
index 566a67c..f7dbaf6 100644
--- a/test/google_crawler/accounts_test.exs
+++ b/test/google_crawler/accounts_test.exs
@@ -2,11 +2,10 @@ defmodule GoogleCrawler.AccountsTest do
use GoogleCrawler.DataCase
alias GoogleCrawler.Accounts
+ alias GoogleCrawler.Accounts.User
alias GoogleCrawler.UserFactory
describe "users" do
- alias GoogleCrawler.Accounts.User
-
test "get_user/1 returns the user with given id" do
user = UserFactory.create()
diff --git a/test/google_crawler/search/keyword_file_test.exs b/test/google_crawler/search/keyword_file_test.exs
new file mode 100644
index 0000000..0daf16d
--- /dev/null
+++ b/test/google_crawler/search/keyword_file_test.exs
@@ -0,0 +1,30 @@
+defmodule GoogleCrawler.KeywordFileTest do
+ use GoogleCrawler.DataCase
+
+ import GoogleCrawler.FixtureHelper
+
+ alias GoogleCrawler.KeywordFileFactory
+ alias GoogleCrawler.Search.KeywordFile
+
+ describe "changeset" do
+ test "file is required" do
+ attrs = KeywordFileFactory.build_attrs(%{file: nil})
+ changeset = KeywordFile.changeset(%KeywordFile{}, attrs)
+
+ refute changeset.valid?
+ assert %{file: ["can't be blank"]} = errors_on(changeset)
+ end
+
+ test "file is in the allowed extensions" do
+ attrs =
+ KeywordFileFactory.build_attrs(%{
+ file: upload_file_fixture("keyword_files/unsupported_keyword.txt")
+ })
+
+ changeset = KeywordFile.changeset(%KeywordFile{}, attrs)
+
+ refute changeset.valid?
+ assert %{file: ["is not supported"]} = errors_on(changeset)
+ end
+ end
+end
diff --git a/test/google_crawler/search_test.exs b/test/google_crawler/search_test.exs
new file mode 100644
index 0000000..ceb3df5
--- /dev/null
+++ b/test/google_crawler/search_test.exs
@@ -0,0 +1,55 @@
+defmodule GoogleCrawler.SearchTest do
+ use GoogleCrawler.DataCase
+
+ alias GoogleCrawler.Search
+ alias GoogleCrawler.Search.Keyword
+ alias GoogleCrawler.KeywordFactory
+
+ describe "keywords" do
+ test "list_keywords/0 returns all keywords" do
+ keyword = KeywordFactory.create()
+
+ assert Search.list_keywords() |> Enum.map(&Map.get(&1, :keyword)) == [keyword.keyword]
+ end
+
+ test "get_keyword/1 returns the keyword with given id" do
+ keyword = KeywordFactory.create()
+
+ assert Search.get_keyword(keyword.id).keyword == keyword.keyword
+ end
+
+ test "create_keyword/1 with valid data creates a keyword" do
+ keyword_attrs = KeywordFactory.build_attrs(%{keyword: "elixir"})
+
+ assert {:ok, %Keyword{} = keyword} = Search.create_keyword(keyword_attrs)
+ assert keyword.keyword == "elixir"
+ end
+
+ test "create_keyword/1 with invalid data returns error changeset" do
+ keyword_attrs = KeywordFactory.build_attrs(%{keyword: ""})
+
+ assert {:error, %Ecto.Changeset{}} = Search.create_keyword(keyword_attrs)
+ end
+ end
+
+ describe "keyword file" do
+ test "parse_keywords_from_file!/2 returns the stream" do
+ csv_stream =
+ Search.parse_keywords_from_file!(
+ "test/fixtures/keyword_files/valid_keyword.csv",
+ "text/csv"
+ )
+
+ assert Enum.to_list(csv_stream) == [["elixir"], ["ruby"], ["javascript"]]
+ end
+
+ test "parse_keywords_from_file!/2 raises an exception when the file type is not supported" do
+ assert_raise GoogleCrawler.Errors.FileNotSupportedError, fn ->
+ Search.parse_keywords_from_file!(
+ "test/fixtures/keyword_files/unsupported_keyword.txt",
+ "text/plain"
+ )
+ end
+ end
+ end
+end
diff --git a/test/google_crawler_web/controllers/dashboard_controller_test.exs b/test/google_crawler_web/controllers/dashboard_controller_test.exs
new file mode 100644
index 0000000..f03e68f
--- /dev/null
+++ b/test/google_crawler_web/controllers/dashboard_controller_test.exs
@@ -0,0 +1,23 @@
+defmodule GoogleCrawlerWeb.DashboardControllerTest do
+ use GoogleCrawlerWeb.ConnCase
+
+ alias GoogleCrawler.UserFactory
+
+ test "GET / renders the index template with upload keyword form", %{conn: conn} do
+ user = UserFactory.create()
+
+ conn =
+ build_authenticated_conn(user)
+ |> get(Routes.dashboard_path(conn, :index))
+
+ assert html_response(conn, 200) =~ "Upload your keyword file"
+ end
+
+ test "GET / redirects to the login page if the user has not logged in", %{conn: conn} do
+ conn =
+ conn
+ |> get(Routes.dashboard_path(conn, :index))
+
+ assert redirected_to(conn) == Routes.session_path(conn, :new)
+ end
+end
diff --git a/test/google_crawler_web/controllers/page_controller_test.exs b/test/google_crawler_web/controllers/page_controller_test.exs
deleted file mode 100644
index e6161e9..0000000
--- a/test/google_crawler_web/controllers/page_controller_test.exs
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule GoogleCrawlerWeb.PageControllerTest do
- use GoogleCrawlerWeb.ConnCase
-
- alias GoogleCrawler.UserFactory
-
- test "GET / renders the index template", %{conn: conn} do
- user = UserFactory.create()
-
- conn =
- conn
- |> init_test_session(%{})
- |> put_session(:current_user_id, user.id)
- |> get(Routes.page_path(conn, :index))
-
- assert html_response(conn, 200) =~ "Welcome to Phoenix!"
- end
-
- test "GET / redirects to the login page if the user has not logged in", %{conn: conn} do
- conn =
- conn
- |> init_test_session(%{})
- |> get(Routes.page_path(conn, :index))
-
- assert redirected_to(conn) == Routes.session_path(conn, :new)
- end
-end
diff --git a/test/google_crawler_web/controllers/registration_controller_test.exs b/test/google_crawler_web/controllers/registration_controller_test.exs
index 5d4238c..8de7cbb 100644
--- a/test/google_crawler_web/controllers/registration_controller_test.exs
+++ b/test/google_crawler_web/controllers/registration_controller_test.exs
@@ -9,34 +9,30 @@ defmodule GoogleCrawlerWeb.RegistrationControllerTest do
assert html_response(conn, 200) =~ "Create Account"
end
- test "new/2 redirects to the pages controller if the user has already logged in", %{conn: conn} do
+ test "new/2 redirects to the user dashboard if the user has already logged in", %{conn: conn} do
user = UserFactory.create()
conn =
- conn
- |> init_test_session(%{})
- |> put_session(:current_user_id, user.id)
+ build_authenticated_conn(user)
|> get(Routes.registration_path(conn, :new))
- assert redirected_to(conn) == Routes.page_path(conn, :index)
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
end
- describe "create/2" do
- test "redirects to page index when the data is valid", %{conn: conn} do
- user_attrs = UserFactory.build_attrs()
+ test "create/2 redirects to page index when the data is valid", %{conn: conn} do
+ user_attrs = UserFactory.build_attrs()
- conn = post(conn, Routes.registration_path(conn, :create, user: user_attrs))
+ conn = post(conn, Routes.registration_path(conn, :create, user: user_attrs))
- assert redirected_to(conn) == Routes.page_path(conn, :index)
- assert get_flash(conn, :info) == "You have signed up successfully!"
- end
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
+ assert get_flash(conn, :info) == "You have signed up successfully!"
+ end
- test "renders the error when the data is invalid", %{conn: conn} do
- user_attrs = UserFactory.build_attrs(%{email: nil, username: nil, password: nil})
+ test "create/2 renders the error when the data is invalid", %{conn: conn} do
+ user_attrs = UserFactory.build_attrs(%{email: nil, username: nil, password: nil})
- conn = post(conn, Routes.registration_path(conn, :create, user: user_attrs))
+ conn = post(conn, Routes.registration_path(conn, :create, user: user_attrs))
- assert html_response(conn, 200) =~ "Create Account"
- end
+ assert html_response(conn, 200) =~ "Create Account"
end
end
diff --git a/test/google_crawler_web/controllers/session_controller_test.exs b/test/google_crawler_web/controllers/session_controller_test.exs
index 6b42a82..c0a6958 100644
--- a/test/google_crawler_web/controllers/session_controller_test.exs
+++ b/test/google_crawler_web/controllers/session_controller_test.exs
@@ -9,52 +9,46 @@ defmodule GoogleCrawlerWeb.SessionControllerTest do
assert html_response(conn, 200) =~ "Sign in"
end
- test "new/2 redirects to the page controller if the user has already logged in", %{conn: conn} do
+ test "new/2 redirects to the user dashboard if the user has already logged in", %{conn: conn} do
user = UserFactory.create()
conn =
- conn
- |> init_test_session(%{})
- |> put_session(:current_user_id, user.id)
+ build_authenticated_conn(user)
|> get(Routes.session_path(conn, :new))
- assert redirected_to(conn) == Routes.page_path(conn, :index)
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
end
- describe "create/2" do
- test "redirects to page controller when user credentials are valid", %{conn: conn} do
- user = UserFactory.create()
+ test "create/2 redirects to user dashboard when user credentials are valid", %{conn: conn} do
+ user = UserFactory.create()
- conn =
- post(conn, Routes.session_path(conn, :create),
- user: %{email: user.email, password: user.password}
- )
+ conn =
+ post(conn, Routes.session_path(conn, :create),
+ user: %{email: user.email, password: user.password}
+ )
- assert redirected_to(conn) == Routes.page_path(conn, :index)
- assert get_flash(conn, :info) == "Welcome back"
- assert get_session(conn, :current_user_id) == user.id
- end
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
+ assert get_flash(conn, :info) == "Welcome back"
+ assert get_session(conn, :current_user_id) == user.id
+ end
- test "renders the error when user credentials are invalid", %{conn: conn} do
- user = UserFactory.create()
+ test "create/2 renders the error when user credentials are invalid", %{conn: conn} do
+ user = UserFactory.create()
- conn =
- post(conn, Routes.session_path(conn, :create),
- user: %{email: user.email, password: "invalid_password"}
- )
+ conn =
+ post(conn, Routes.session_path(conn, :create),
+ user: %{email: user.email, password: "invalid_password"}
+ )
- assert redirected_to(conn) == Routes.session_path(conn, :new)
- assert get_flash(conn, :error) == "The email or password is incorrect, please try again"
- end
+ assert redirected_to(conn) == Routes.session_path(conn, :new)
+ assert get_flash(conn, :error) == "The email or password is incorrect, please try again"
end
- test "delete/2 clears the user session and redirects to the log in page", %{conn: conn} do
+ test "delete/2 clears the user session and redirects to the login page", %{conn: conn} do
user = UserFactory.create()
conn =
- conn
- |> init_test_session(%{})
- |> put_session(:current_user_id, user.id)
+ build_authenticated_conn(user)
|> delete(Routes.session_path(conn, :delete, user))
assert get_session(conn, :current_user_id) == nil
diff --git a/test/google_crawler_web/controllers/upload_controller_test.exs b/test/google_crawler_web/controllers/upload_controller_test.exs
new file mode 100644
index 0000000..b95f375
--- /dev/null
+++ b/test/google_crawler_web/controllers/upload_controller_test.exs
@@ -0,0 +1,42 @@
+defmodule GoogleCrawlerWeb.UploadControllerTest do
+ use GoogleCrawlerWeb.ConnCase
+
+ import GoogleCrawler.FixtureHelper
+
+ alias GoogleCrawler.UserFactory
+
+ test "create/2 renders csv content as text if the keyword file is valid", %{conn: conn} do
+ user = UserFactory.create()
+ upload_file = upload_file_fixture("keyword_files/valid_keyword.csv")
+
+ conn =
+ build_authenticated_conn(user)
+ |> post(Routes.upload_path(conn, :create), %{keyword_file: %{file: upload_file}})
+
+ assert text_response(conn, 200) == "elixir, ruby, javascript"
+ end
+
+ test "create/2 raises error if the file is failed to parse" do
+ user = UserFactory.create()
+ upload_file = upload_file_fixture("keyword_files/invalid_keyword.csv")
+
+ conn = build_authenticated_conn(user)
+
+ assert_raise CSV.RowLengthError, fn ->
+ post(conn, Routes.upload_path(conn, :create), %{keyword_file: %{file: upload_file}})
+ end
+ end
+
+ test "create/2 redirects to the user dashboard with an error message if the file is not supported",
+ %{conn: conn} do
+ user = UserFactory.create()
+ upload_file = upload_file_fixture("keyword_files/unsupported_keyword.txt")
+
+ conn =
+ build_authenticated_conn(user)
+ |> post(Routes.upload_path(conn, :create), %{keyword_file: %{file: upload_file}})
+
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
+ assert get_flash(conn, :error) == "Invalid file, please select again."
+ end
+end
diff --git a/test/google_crawler_web/plugs/ensure_auth_test.exs b/test/google_crawler_web/plugs/ensure_auth_test.exs
index cc5049d..dc55631 100644
--- a/test/google_crawler_web/plugs/ensure_auth_test.exs
+++ b/test/google_crawler_web/plugs/ensure_auth_test.exs
@@ -8,9 +8,7 @@ defmodule GoogleCrawlerWeb.EnsureAuthTest do
user = UserFactory.create()
conn =
- build_conn()
- |> init_test_session(current_user_id: user.id)
- |> assign(:user_signed_in?, true)
+ build_authenticated_conn(user)
|> call(%{})
refute conn.halted
diff --git a/test/google_crawler_web/plugs/set_current_user_test.exs b/test/google_crawler_web/plugs/set_current_user_test.exs
index 35819c5..1886814 100644
--- a/test/google_crawler_web/plugs/set_current_user_test.exs
+++ b/test/google_crawler_web/plugs/set_current_user_test.exs
@@ -8,8 +8,7 @@ defmodule GoogleCrawlerWeb.SetCurrentUserTest do
user = UserFactory.create()
conn =
- build_conn()
- |> init_test_session(current_user_id: user.id)
+ build_authenticated_conn(user)
|> call(%{})
assert conn.assigns.current_user.id == user.id
diff --git a/test/google_crawler_web/plugs/skip_after_auth_test.exs b/test/google_crawler_web/plugs/skip_after_auth_test.exs
index 46590bf..459db66 100644
--- a/test/google_crawler_web/plugs/skip_after_auth_test.exs
+++ b/test/google_crawler_web/plugs/skip_after_auth_test.exs
@@ -4,18 +4,16 @@ defmodule GoogleCrawlerWeb.SkipAfterAuthTest do
alias GoogleCrawler.UserFactory
- test "redirects to the index page if the user has already logged in" do
+ test "redirects to the user dashboard if the user has already logged in" do
user = UserFactory.create()
conn =
- build_conn()
- |> init_test_session(current_user_id: user.id)
+ build_authenticated_conn(user)
|> fetch_flash
- |> assign(:user_signed_in?, true)
|> call(%{})
assert conn.halted
- assert redirected_to(conn) == Routes.page_path(conn, :index)
+ assert redirected_to(conn) == Routes.dashboard_path(conn, :index)
assert get_flash(conn, :info) == "You are already signed in."
end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 05e29ce..af89bd3 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -26,6 +26,12 @@ defmodule GoogleCrawlerWeb.ConnCase do
# The default endpoint for testing
@endpoint GoogleCrawlerWeb.Endpoint
+
+ def build_authenticated_conn(user) do
+ build_conn()
+ |> Plug.Test.init_test_session(current_user_id: user.id)
+ |> assign(:user_signed_in?, true)
+ end
end
end
diff --git a/test/support/fixture_helper.ex b/test/support/fixture_helper.ex
new file mode 100644
index 0000000..310bc94
--- /dev/null
+++ b/test/support/fixture_helper.ex
@@ -0,0 +1,13 @@
+defmodule GoogleCrawler.FixtureHelper do
+ @fixture_path "test/fixtures"
+
+ def upload_file_fixture(path) do
+ path = Path.join([@fixture_path, path])
+
+ %Plug.Upload{
+ content_type: MIME.from_path(path),
+ filename: Path.basename(path),
+ path: path
+ }
+ end
+end