diff --git a/lib/slax/commands/github_commands.ex b/lib/slax/commands/github_commands.ex index a7ea26e..f1c4321 100644 --- a/lib/slax/commands/github_commands.ex +++ b/lib/slax/commands/github_commands.ex @@ -10,11 +10,17 @@ defmodule Slax.Commands.GithubCommands do :slack_channel, :lintron, :board_checker, - :resuseable_stories + :reusable_stories ] alias Slax.{Github} + @doc """ + Accepts a results map and the potential name of a new github repo. + If the name is a valid github repo name, it will return a new result map + containing the `project_name` as well as a key indicating that the `project_name` + step is complete + """ def parse_project_name(results, text) do case Regex.run(~r/^[a-zA-Z0-9\-_]{3,21}$/, text) do [project_name] -> @@ -28,8 +34,13 @@ defmodule Slax.Commands.GithubCommands do end end + @doc """ + Pulls all issue templates from a story repo that are included in `story_paths`. + It then uses these templates to create issues in a newly created github repository, + If there is no github repository the function will exit early + """ def create_reusable_stories( - %{project_name: project_name} = results, + %{project_name: project_name, github_repo: _} = results, github_access_token, org_name, story_repo, @@ -53,7 +64,7 @@ defmodule Slax.Commands.GithubCommands do Enum.map(errors, fn {:error, path, message} -> "#{path}: #{message}" end) |> Enum.join("\n") - Map.update(results, :errors, %{}, fn x -> Map.put(x, :resuseable_stories, errors) end) + Map.update(results, :errors, %{}, fn x -> Map.put(x, :reusable_stories, errors) end) else results end @@ -62,7 +73,7 @@ defmodule Slax.Commands.GithubCommands do if length(issue_ids) > 0 do Map.put(results, :reusable_stories, true) |> Map.update(:success, %{}, fn x -> - Map.put(x, :resuseable_stories, "Reuseable Stories Created") + Map.put(x, :reusable_stories, "Reuseable Stories Created") end) else results @@ -71,10 +82,12 @@ defmodule Slax.Commands.GithubCommands do results {:error, message} -> - Map.update(results, :errors, %{}, fn x -> Map.put(x, :resuseable_stories, message) end) + Map.update(results, :errors, %{}, fn x -> Map.put(x, :reusable_stories, message) end) end end + def create_reusable_stories(results, _, _, _, _), do: results + defp process_tree(data, story_repo, story_paths, github_access_token) do Map.get(data, "tree", []) |> Enum.filter(fn x -> x["type"] == "blob" && String.ends_with?(x["path"], ".md") end) @@ -102,7 +115,7 @@ defmodule Slax.Commands.GithubCommands do blobs |> Enum.map(fn {:ok, path, content} -> with {:ok, issue} <- Base.decode64(content |> String.replace("\n", "")), - {:ok, front_matter, body} <- YamlFrontMatter.parse(issue) do + {:ok, {:ok, front_matter}, body} <- YamlFrontMatter.parse(issue) do {:ok, path, front_matter, body} else :error -> @@ -139,7 +152,7 @@ defmodule Slax.Commands.GithubCommands do end) |> Enum.split_with(fn {:ok, _, _} -> true - {:error, _} -> false + {:error, _, _} -> false end) end @@ -186,8 +199,8 @@ defmodule Slax.Commands.GithubCommands do :board_checker -> "Board Checker" - :resuseable_stories -> - "Reuseable Stories" + :reusable_stories -> + "Reusable Stories" _ -> "" diff --git a/test/slax/commands/github_commands_test.exs b/test/slax/commands/github_commands_test.exs new file mode 100644 index 0000000..0c30451 --- /dev/null +++ b/test/slax/commands/github_commands_test.exs @@ -0,0 +1,361 @@ +defmodule Slax.GithubCommands.Test do + use Slax.ModelCase, async: true + import Mox + @subject Slax.Commands.GithubCommands + + describe "format_results/1" do + setup :setup_no_errors + + def setup_no_errors(context) do + {:ok, + context + |> Map.put(:starting_result, %{ + project_name: true, + github_repo: true, + slack_channel: true, + errors: %{}, + success: %{ + project_name: "project_name", + github_repo: "org_name/project_name", + slack_channel: "slack_channel" + } + })} + end + + test "when there are no errors for any steps", %{starting_result: starting_result} do + result = @subject.format_results(starting_result) + + assert result == + "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel\nLintron: \nBoard Checker: \nReusable Stories: " + end + + test "when there are errors for certain steps" do + starting_result = %{ + project_name: true, + github_repo: true, + errors: %{ + slack_channel: "slack_channel error" + }, + success: %{ + project_name: "project_name", + github_repo: "org_name/project_name" + } + } + + result = @subject.format_results(starting_result) + + assert result == + "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel error\nLintron: \nBoard Checker: \nReusable Stories: " + end + end + + describe "parse_project_name/2" do + def setup_starting_result(context) do + {:ok, + context + |> Map.put(:starting_result, %{errors: %{}, success: %{}})} + end + + setup :setup_starting_result + + test "invalid characters", %{starting_result: starting_result} do + with result <- @subject.parse_project_name(starting_result, "hi&") do + assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"} + assert Map.get(result, :project_name) == nil + end + end + + test "too many characters", %{starting_result: starting_result} do + with result <- + @subject.parse_project_name( + starting_result, + "thisshouldbemorethantwentytwocharacters" + ) do + assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"} + assert Map.get(result, :project_name) == nil + end + end + + test "valid_input", %{starting_result: starting_result} do + with result <- @subject.parse_project_name(starting_result, "valid_repo_name") do + assert Map.get(result, :errors) == %{} + assert Map.get(result, :success) == %{project_name: "Project Name Parsed"} + assert Map.get(result, :project_name) == "valid_repo_name" + end + end + end + + def setup_starting_result_with_name(context) do + {:ok, + context + |> Map.put(:starting_result, %{errors: %{}, success: %{}}) + |> put_in([:starting_result, :success, :project_name], "Project Name Parsed") + |> put_in([:starting_result, :project_name], "valid_project_name") + |> put_in([:starting_result, :github_repo], "repo_url") + |> put_in([:starting_result, :success, :github_repo], "Github Repo Found or Created: ") + } + end + + def successful_tree_request(_, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: ~s<{ + "tree": [ + { + "path": "story_path1.md", + "sha": "test_sha", + "type": "blob" + }, + { + "path": "story_path3.md", + "sha": "test_sha", + "type": "blob" + } + + ] + }> + }} + end + + def successful_blob_request(_, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: ~s<{ + "content": "LS0tCnRpdGxlOiB0ZXN0X3RpdGxlCi0tLQp0ZXN0IGNvbnRlbnQK" + }> + }} + end + + def successful_issue_request(_, _, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: ~s<{ + "content": ["1"] + }> + }} + end + + describe "create_reusable_stories/5" do + setup [:setup_starting_result_with_name] + + test "when all requests are successful", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &successful_tree_request/3) + expect(Slax.HttpMock, :get, 2, &successful_blob_request/3) + expect(Slax.HttpMock, :post, 3, &successful_issue_request/4) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{} + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == true + assert result[:success] == %{ + project_name: "Project Name Parsed", + reusable_stories: "Reuseable Stories Created", + github_repo: "Github Repo Found or Created: " + } + end + + def failing_request(_, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 400, + body: ~s<{ + "message": "you done goofed" + }> + }} + end + + test "when the starting results dont have a github repo" do + starting_result = + %{ + project_name: "valid_project_name", + errors: %{}, + success: %{ + project_name: "Project Name Parsed" + } + } + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result == starting_result + end + + def failing_request(w, x, y, _), do: failing_request(w, x, y) + + test "when fetching the project tree fails", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &failing_request/3) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{reusable_stories: "you done goofed"} + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == nil + assert result[:success] == %{ + project_name: "Project Name Parsed", + github_repo: "Github Repo Found or Created: " + } + end + + test "when fetching a blob fails", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &successful_tree_request/3) + expect(Slax.HttpMock, :get, 2, &failing_request/3) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{ + reusable_stories: + "story_path1.md: you done goofed\nstory_path3.md: you done goofed" + } + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == nil + assert result[:success] == %{ + project_name: "Project Name Parsed", + github_repo: "Github Repo Found or Created: " + } + + end + + test "when posting issues fails", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &successful_tree_request/3) + expect(Slax.HttpMock, :get, 2, &successful_blob_request/3) + expect(Slax.HttpMock, :post, 3, &failing_request/4) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{ + reusable_stories: + "story_path1.md: you done goofed\nstory_path3.md: you done goofed" + } + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == nil + assert result[:success] == %{ + project_name: "Project Name Parsed", + github_repo: "Github Repo Found or Created: " + } + + end + + def invalid_blob_request(_, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: ~s<{ + "content": "invalidblob" + }> + }} + end + + test "when decoding a blob fails", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &successful_tree_request/3) + expect(Slax.HttpMock, :get, 2, &invalid_blob_request/3) + expect(Slax.HttpMock, :post, 3, &successful_issue_request/4) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{ + reusable_stories: + "story_path1.md: Unable to parse content\nstory_path3.md: Unable to parse content" + } + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == nil + assert result[:success] == %{ + project_name: "Project Name Parsed", + github_repo: "Github Repo Found or Created: " + } + end + + def invalid_frontmatter_request(_, _, _) do + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: ~s<{ + "content": "LS0KaW52YWxpZCBmcm9udG1hdHRlcgpfCm9vcHMK" + }> + }} + end + + test "when parsing issue frontmatter fails", %{starting_result: starting_result} do + expect(Slax.HttpMock, :get, 1, &successful_tree_request/3) + expect(Slax.HttpMock, :get, 2, &invalid_frontmatter_request/3) + expect(Slax.HttpMock, :post, 3, &successful_issue_request/4) + + result = + @subject.create_reusable_stories( + starting_result, + "access_token", + "test_org", + "story_repo", + path1: "story_path1", + path3: "story_path3" + ) + + assert result[:errors] == %{ + reusable_stories: + "story_path1.md: invalid_front_matter\nstory_path3.md: invalid_front_matter" + } + assert result[:project_name] == "valid_project_name" + assert result[:github_repo] == "repo_url" + assert result[:reusable_stories] == nil + assert result[:success] == %{ + project_name: "Project Name Parsed", + github_repo: "Github Repo Found or Created: " + } + end + end +end