Skip to content

Commit 304c202

Browse files
bougymanclaude
andauthored
CRY-58: fix(linear): normalize malformed responses in User.Read.ByTeam (#147)
## Summary Fixes a defensive-coding gap in `User.Read.ByTeam` where malformed or partially-populated API responses could leak a bare map out of the `else` clause instead of returning a proper `{:ok, []}` or `{:error, _}` tuple. - Replace the two-step `with` match (extract team, then match members) with a single deep pattern match, consistent with `Label.Read.ByTeam`, `WorkflowState.Read.ByTeam`, and `Project.Read.ByTeam`. - The `else` catch-all `{:ok, _} -> {:ok, []}` now correctly handles null team, missing `members` key, and missing `nodes` key as graceful empty results. - Add 5 new tests covering: valid response, null team, missing members key, missing nodes key, and API error propagation. Closes CRY-58. ## Test plan - [x] `mix test test/linear_cli/linear/user_test.exs` — all 6 tests pass - [x] `mix compile --warnings-as-errors` — clean - [x] `mix test` — 260/267 pass; 7 failures are pre-existing `GitTest` environment failures unrelated to this change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 39dfd80 commit 304c202

2 files changed

Lines changed: 54 additions & 2 deletions

File tree

‎app/lib/linear_cli/linear/user.ex‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ defmodule LinearCli.Linear.User.Read.ByTeam do
7474
document =
7575
"query($id: String!) { team(id: $id) { members(first: 50) { nodes { #{User.base_fields()} } } } }"
7676

77-
with {:ok, %{"team" => team}} when is_map(team) <- Api.call(document, %{"id" => team_id}),
78-
%{"members" => %{"nodes" => nodes}} <- team do
77+
with {:ok, %{"team" => %{"members" => %{"nodes" => nodes}}}} <-
78+
Api.call(document, %{"id" => team_id}) do
7979
{:ok, Enum.map(nodes, &User.from_map/1)}
8080
else
8181
{:ok, _} -> {:ok, []}

‎app/test/linear_cli/linear/user_test.exs‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,58 @@ defmodule LinearCli.Linear.UserTest do
33

44
alias LinearCli.Linear
55

6+
test "team_members/1 returns a list of users for a valid team response" do
7+
Req.Test.stub(LinearCli.Api, fn conn ->
8+
Req.Test.json(conn, %{
9+
"data" => %{
10+
"team" => %{
11+
"members" => %{
12+
"nodes" => [
13+
%{"id" => "u1", "name" => "Alice", "email" => "alice@example.com"},
14+
%{"id" => "u2", "name" => "Bob", "email" => "bob@example.com"}
15+
]
16+
}
17+
}
18+
}
19+
})
20+
end)
21+
22+
assert {:ok, [%Linear.User{id: "u1", name: "Alice"}, %Linear.User{id: "u2", name: "Bob"}]} =
23+
Linear.team_members("t1")
24+
end
25+
26+
test "team_members/1 returns an empty list when team is null" do
27+
Req.Test.stub(LinearCli.Api, fn conn ->
28+
Req.Test.json(conn, %{"data" => %{"team" => nil}})
29+
end)
30+
31+
assert {:ok, []} = Linear.team_members("nonexistent")
32+
end
33+
34+
test "team_members/1 returns an empty list when members key is absent" do
35+
Req.Test.stub(LinearCli.Api, fn conn ->
36+
Req.Test.json(conn, %{"data" => %{"team" => %{}}})
37+
end)
38+
39+
assert {:ok, []} = Linear.team_members("t1")
40+
end
41+
42+
test "team_members/1 returns an empty list when nodes key is absent" do
43+
Req.Test.stub(LinearCli.Api, fn conn ->
44+
Req.Test.json(conn, %{"data" => %{"team" => %{"members" => %{}}}})
45+
end)
46+
47+
assert {:ok, []} = Linear.team_members("t1")
48+
end
49+
50+
test "team_members/1 propagates API errors" do
51+
Req.Test.stub(LinearCli.Api, fn conn ->
52+
Req.Test.json(conn, %{"errors" => [%{"message" => "Unauthorized"}]})
53+
end)
54+
55+
assert {:error, %Ash.Error.Unknown{}} = Linear.team_members("t1")
56+
end
57+
658
test "me/0 decodes the viewer, including nested teams" do
759
Req.Test.stub(LinearCli.Api, fn conn ->
860
Req.Test.json(conn, %{

0 commit comments

Comments
 (0)