Skip to content

Commit 844a007

Browse files
committed
Add PATCH endpoint for user tasks
1 parent 4733eab commit 844a007

File tree

8 files changed

+135
-7
lines changed

8 files changed

+135
-7
lines changed

test/controllers/user_task_controller_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule CodeCorps.UserTaskControllerTest do
33

44
use CodeCorps.ApiCase, resource_name: :user_task
55

6+
alias CodeCorps.{Repo, UserTask}
7+
68
describe "index" do
79
test "lists all entries on index", %{conn: conn} do
810
[user_task_1, user_task_2] = insert_pair(:user_task)
@@ -75,6 +77,32 @@ defmodule CodeCorps.UserTaskControllerTest do
7577
end
7678
end
7779

80+
describe "update" do
81+
@tag :authenticated
82+
test "updates chosen resource", %{conn: conn, current_user: current_user} do
83+
task = insert(:task, user: current_user)
84+
user_task = insert(:user_task, task: task)
85+
new_user = insert(:user)
86+
87+
assert conn |> request_update(user_task, %{user_id: new_user.id}) |> response(200)
88+
89+
updated_task = Repo.get(UserTask, user_task.id)
90+
assert updated_task.user_id == new_user.id
91+
end
92+
93+
test "renders 401 when unauthenticated", %{conn: conn} do
94+
user_task = insert(:user_task)
95+
new_user = insert(:user)
96+
97+
assert conn |> request_update(user_task, %{user_id: new_user.id}) |> json_response(401)
98+
end
99+
100+
@tag :authenticated
101+
test "renders 404 when id is nonexistent", %{conn: conn} do
102+
assert conn |> request_update(:not_found) |> json_response(404)
103+
end
104+
end
105+
78106
describe "delete" do
79107
@tag :authenticated
80108
test "deletes chosen resource", %{conn: conn, current_user: current_user} do

test/models/user_task_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,25 @@ defmodule CodeCorps.UserTaskTest do
4040
assert_error_message(response_changeset, :user, "has already been taken")
4141
end
4242
end
43+
44+
describe "update_changeset/2" do
45+
@required_attrs ~w(user_id)
46+
47+
test "requires #{@required_attrs}" do
48+
user_task = insert(:user_task)
49+
50+
changeset = UserTask.update_changeset(user_task, %{user_id: nil})
51+
52+
assert_validation_triggered(changeset, :user_id, :required)
53+
end
54+
55+
test "ensures associated User record exists" do
56+
user_task = insert(:user_task)
57+
58+
changeset = UserTask.update_changeset(user_task, %{user_id: -1})
59+
60+
{:error, response_changeset} = Repo.update(changeset)
61+
assert_error_message(response_changeset, :user, "does not exist")
62+
end
63+
end
4364
end

test/policies/user_task_policy_test.exs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule CodeCorps.UserTaskPolicyTest do
33

44
use CodeCorps.PolicyCase
55

6-
import CodeCorps.UserTaskPolicy, only: [create?: 2, delete?: 2]
6+
import CodeCorps.UserTaskPolicy, only: [create?: 2, update?: 2, delete?: 2]
77
import CodeCorps.UserTask, only: [create_changeset: 2]
88

99
alias CodeCorps.UserTask
@@ -79,6 +79,56 @@ defmodule CodeCorps.UserTaskPolicyTest do
7979
end
8080
end
8181

82+
describe "update?" do
83+
test "returns false when user is not member of organization" do
84+
{user, task} = generate_data_for("non-member")
85+
86+
user_task = insert(:user_task, task: task)
87+
88+
refute update?(user, user_task)
89+
end
90+
91+
test "returns false when user is pending member of organization" do
92+
{user, task} = generate_data_for("pending")
93+
94+
user_task = insert(:user_task, task: task)
95+
96+
refute update?(user, user_task)
97+
end
98+
99+
test "returns true when user is contributor of organization" do
100+
{user, task} = generate_data_for("contributor")
101+
102+
user_task = insert(:user_task, task: task)
103+
104+
assert update?(user, user_task)
105+
end
106+
107+
test "returns true when user is admin of organization" do
108+
{user, task} = generate_data_for("admin")
109+
110+
user_task = insert(:user_task, task: task)
111+
112+
assert update?(user, user_task)
113+
end
114+
115+
test "returns true when user is owner of organization" do
116+
{user, task} = generate_data_for("owner")
117+
118+
user_task = insert(:user_task, task: task)
119+
120+
assert update?(user, user_task)
121+
end
122+
123+
test "returns true when user is author of task" do
124+
{user, task} = generate_data_for("author")
125+
126+
user_task = insert(:user_task, task: task)
127+
128+
assert update?(user, user_task)
129+
end
130+
end
131+
82132
describe "delete?" do
83133
test "returns false when user is not member of organization" do
84134
{user, task} = generate_data_for("non-member")

web/controllers/user_task_controller.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule CodeCorps.UserTaskController do
88

99
plug :load_resource, model: UserTask, only: [:show], preload: [:task, :user]
1010
plug :load_and_authorize_changeset, model: UserTask, only: [:create]
11-
plug :load_and_authorize_resource, model: UserTask, only: [:delete]
11+
plug :load_and_authorize_resource, model: UserTask, only: [:update, :delete]
1212
plug JaResource
1313

1414
@spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Plug.Conn.t
@@ -20,4 +20,9 @@ defmodule CodeCorps.UserTaskController do
2020
def handle_create(_conn, attributes) do
2121
%UserTask{} |> UserTask.create_changeset(attributes)
2222
end
23+
24+
@spec handle_update(Plug.Conn.t, UserTask.t, map) :: Ecto.Changeset.t
25+
def handle_update(_conn, user_task, attributes) do
26+
user_task |> UserTask.update_changeset(attributes)
27+
end
2328
end

web/models/abilities.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ defmodule Canary.Abilities do
131131
def can?(%User{} = user, :delete, %UserSkill{} = user_skill), do: UserSkillPolicy.delete?(user, user_skill)
132132

133133
def can?(%User{} = user, :create, %Changeset{data: %UserTask{}} = changeset), do: UserTaskPolicy.create?(user, changeset)
134+
def can?(%User{} = user, :update, %UserTask{} = user_task), do: UserTaskPolicy.update?(user, user_task)
134135
def can?(%User{} = user, :delete, %UserTask{} = user_task), do: UserTaskPolicy.delete?(user, user_task)
135136
end
136137
end

web/models/user_task.ex

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,33 @@ defmodule CodeCorps.UserTask do
1414
timestamps()
1515
end
1616

17-
@permitted_attrs [:user_id, :task_id]
18-
@required_attrs @permitted_attrs
17+
@permitted_create_attrs [:user_id, :task_id]
18+
@required_create_attrs @permitted_create_attrs
1919

2020
@doc """
2121
Builds a changeset used to insert a record into the database
2222
"""
2323
@spec create_changeset(CodeCorps.UserTask.t, map) :: Ecto.Changeset.t
2424
def create_changeset(struct, params \\ %{}) do
2525
struct
26-
|> cast(params, @permitted_attrs)
27-
|> validate_required(@required_attrs)
26+
|> cast(params, @permitted_create_attrs)
27+
|> validate_required(@required_create_attrs)
2828
|> assoc_constraint(:task)
2929
|> assoc_constraint(:user)
3030
|> unique_constraint(:user, name: :user_tasks_user_id_task_id_index)
3131
end
32+
33+
@permitted_update_attrs [:user_id]
34+
@required_update_attrs @permitted_update_attrs
35+
36+
@doc """
37+
Builds a changeset used to update an existing record in the database
38+
"""
39+
@spec update_changeset(CodeCorps.UserTask.t, map) :: Ecto.Changeset.t
40+
def update_changeset(struct, params \\ %{}) do
41+
struct
42+
|> cast(params, @permitted_update_attrs)
43+
|> validate_required(@required_update_attrs)
44+
|> assoc_constraint(:user)
45+
end
3246
end

web/policies/user_task_policy.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ defmodule CodeCorps.UserTaskPolicy do
2525
end
2626
end
2727

28+
@spec update?(User.t, UserTask.t) :: boolean
29+
def update?(%User{} = user, %UserTask{} = user_task) do
30+
cond do
31+
user_task |> get_task |> get_project |> get_membership(user) |> get_role |> contributor_or_higher? -> true
32+
user_task |> get_task |> task_authored_by?(user) -> true
33+
true -> false
34+
end
35+
end
36+
2837
@spec delete?(User.t, UserTask.t) :: boolean
2938
def delete?(%User{} = user, %UserTask{} = user_task) do
3039
cond do

web/router.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ defmodule CodeCorps.Router do
8383
resources "/user-categories", UserCategoryController, only: [:create, :delete]
8484
resources "/user-roles", UserRoleController, only: [:create, :delete]
8585
resources "/user-skills", UserSkillController, only: [:create, :delete]
86-
resources "/user-tasks", UserTaskController, only: [:create, :delete]
86+
resources "/user-tasks", UserTaskController, only: [:create, :update, :delete]
8787
end
8888

8989
scope "/", CodeCorps, host: "api." do

0 commit comments

Comments
 (0)