Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions app/lib/linear_cli/cli/commands/issues/filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,19 @@ defmodule LinearCli.CLI.Commands.Issues.Filter do
end

defp resolve_project_match(projects, search, :strict_non_interactive) do
case Projects.project_for_strict(projects, search) do
nil ->
{:error,
{:smells_bad, "JSON output requires an exact project match for #{inspect(search)}"}}
case Projects.project_scores(projects, search) do
[] ->
{:error, {:smells_bad, "No project found matching #{search}"}}

project ->
{:ok, project.id}
_possibles ->
case Projects.project_for_strict(projects, search) do
nil ->
{:error,
{:smells_bad, "JSON output requires an exact project match for #{inspect(search)}"}}

project ->
{:ok, project.id}
end
end
end

Expand Down
46 changes: 28 additions & 18 deletions app/lib/linear_cli/cli/commands/issues/move.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ defmodule LinearCli.CLI.Commands.Issues.Move do
end
end

defp resolve_move_project(_issues, %{project: nil, output: "json"}) do
{:error, {:smells_bad, "JSON output requires --project for issue move"}}
end

defp resolve_move_project(issues, options) do
with {:ok, tid} <- resolve_move_team_id(options.team || Profiles.default_team(), issues),
{:ok, projects} <- Linear.projects_by_team(tid, %{search: options.project}) do
Expand All @@ -60,19 +64,23 @@ defmodule LinearCli.CLI.Commands.Issues.Move do
Projects.project_for(projects, options.project)
end

project_result(project, options.project, options.output)
project_result(project, projects, options.project, options.output)
end
end

defp project_result(nil, search, "json"),
do:
defp project_result(nil, projects, search, "json") do
if Projects.project_scores(projects, search) == [] do
{:error, {:smells_bad, "No project found matching #{search}"}}
else
{:error,
{:smells_bad, "JSON output requires an exact project match for #{inspect(search)}"}}
end
end

defp project_result(nil, search, _output),
defp project_result(nil, _projects, search, _output),
do: {:error, {:smells_bad, "No project found matching #{inspect(search)}"}}

defp project_result(project, _search, _output), do: {:ok, project}
defp project_result(project, _projects, _search, _output), do: {:ok, project}

defp resolve_move_team_id(nil, issues), do: {:ok, hd(issues).team.id}

Expand Down Expand Up @@ -218,23 +226,25 @@ defmodule LinearCli.CLI.Commands.Issues.Move do
defp resolve_bulk_project(value, team_fn, output) do
team = team_fn.()

with {:ok, projects} <- Linear.projects_by_team(team.id, %{search: value}),
project when not is_nil(project) <- project_for_bulk(projects, value, output) do
{:ok, project}
else
nil ->
if output == "json" do
{:error,
{:smells_bad, "JSON output requires an exact project match for #{inspect(value)}"}}
else
{:error, {:smells_bad, "No project found matching #{value}"}}
end
with {:ok, projects} <- Linear.projects_by_team(team.id, %{search: value}) do
case project_for_bulk(projects, value, output) do
nil -> bulk_project_error(projects, value, output)
project -> {:ok, project}
end
end
end

{:error, reason} ->
{:error, reason}
defp bulk_project_error(projects, value, "json") do
if Projects.project_scores(projects, value) == [] do
{:error, {:smells_bad, "No project found matching #{value}"}}
else
{:error, {:smells_bad, "JSON output requires an exact project match for #{inspect(value)}"}}
end
end

defp bulk_project_error(_projects, value, _output),
do: {:error, {:smells_bad, "No project found matching #{value}"}}

defp project_for_bulk(projects, value, "json"),
do: Projects.project_for_strict(projects, value)

Expand Down
9 changes: 2 additions & 7 deletions app/lib/linear_cli/cli/commands/issues/mutations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,8 @@ defmodule LinearCli.CLI.Commands.Issues.Mutations do
:ok
end

defp cancel_unassign(options) do
if Map.get(options, :output, "text") == "json" do
Display.show([], %{output: "json"})
else
Prompt.warn("Unassign cancelled")
end

defp cancel_unassign(_options) do
Prompt.warn("Unassign cancelled")
:ok
end

Expand Down
189 changes: 189 additions & 0 deletions app/test/linear_cli/cli/commands/issues/move_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,99 @@ defmodule LinearCli.CLI.Commands.Issues.MoveTest do
assert stdout == ""
end

test "JSON ID move names the missing --project option" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end

Req.Test.stub(LinearCli.Api, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
%{"query" => query} = Jason.decode!(body)

cond do
String.contains?(query, "issue(id: $id)") ->
Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}})

String.contains?(query, "issueUpdate") ->
raise "a missing --project must stop before mutation"

true ->
raise "missing --project must not resolve a target project"
end
end)

stdout =
capture_io(fn ->
stderr =
capture_stderr(fn stderr ->
LinearCli.CLI.main(
["issue", "move", "--output", "json", "CRY-1"],
halt,
stderr: stderr
)
end)

send(test_pid, {:stderr, stderr})
end)

assert_received {:halted, 22}
assert_received {:stderr, stderr}
assert stderr =~ "JSON output requires --project for issue move"
assert stdout == ""
end

test "JSON ID move reports when no project matches" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end

Req.Test.stub(LinearCli.Api, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
%{"query" => query} = Jason.decode!(body)

cond do
String.contains?(query, "issue(id: $id)") ->
Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}})

String.contains?(query, "projects(first: 100") ->
Req.Test.json(conn, move_team_projects([]))

String.contains?(query, "issueUpdate") ->
raise "an unknown project must stop before mutation"

true ->
raise "no stub matched query: #{query}"
end
end)

stdout =
capture_io(fn ->
stderr =
capture_stderr(fn stderr ->
LinearCli.CLI.main(
[
"issue",
"move",
"--project",
"Missing Project",
"--dry-run",
"--output",
"json",
"CRY-1"
],
halt,
stderr: stderr
)
end)

send(test_pid, {:stderr, stderr})
end)

assert_received {:halted, 22}
assert_received {:stderr, stderr}
assert stderr =~ "No project found matching Missing Project"
refute stderr =~ "exact project match"
assert stdout == ""
end

test "with no issue ids, exits 22 (smells bad)" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end
Expand Down Expand Up @@ -807,6 +900,46 @@ defmodule LinearCli.CLI.Commands.Issues.MoveTest do
assert stdout == ""
end

test "JSON bulk move requires --team or an active profile" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end

Req.Test.stub(LinearCli.Api, fn _conn ->
send(test_pid, :api_called)
raise "the bulk team guard must run before any API call"
end)

stdout =
capture_io(fn ->
stderr =
capture_stderr(fn stderr ->
LinearCli.CLI.main(
[
"issue",
"move",
"--from",
"Source Project",
"--to",
"Target Project",
"--dry-run",
"--output",
"json"
],
halt,
stderr: stderr
)
end)

send(test_pid, {:stderr, stderr})
end)

assert_received {:halted, 22}
assert_received {:stderr, stderr}
refute_received :api_called
assert stderr =~ "JSON output requires --team or an active profile for bulk issue move"
assert stdout == ""
end

test "--from/--to JSON dry-run rejects a partial project before listing issues" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end
Expand Down Expand Up @@ -868,6 +1001,62 @@ defmodule LinearCli.CLI.Commands.Issues.MoveTest do
assert stdout == ""
end

test "--from/--to JSON dry-run reports when no project matches" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end

Req.Test.stub(LinearCli.Api, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
%{"query" => query} = Jason.decode!(body)

cond do
String.contains?(query, "$teamId") ->
Req.Test.json(conn, team_projects([]))

String.contains?(query, "team(id: $id)") ->
Req.Test.json(conn, %{"data" => %{"team" => team_map()}})

String.contains?(query, "issues(filter:") ->
raise "an unknown project must stop before listing issues"

true ->
raise "no stub matched query: #{query}"
end
end)

stdout =
capture_io(fn ->
stderr =
capture_stderr(fn stderr ->
LinearCli.CLI.main(
[
"issue",
"move",
"--from",
"Missing Project",
"--to",
"Target Project",
"--team",
"ENG",
"--dry-run",
"--output",
"json"
],
halt,
stderr: stderr
)
end)

send(test_pid, {:stderr, stderr})
end)

assert_received {:halted, 22}
assert_received {:stderr, stderr}
assert stderr =~ "No project found matching Missing Project"
refute stderr =~ "exact project match"
assert stdout == ""
end

test "--from/--to --output json returns an empty array when no issues match" do
pairs =
Enum.map(bulk_stub_pairs(), fn
Expand Down
Loading
Loading