Skip to content

Commit 1c55a26

Browse files
begedinjoshsmith
authored andcommitted
Add ProjectUserController actions and tests
1 parent b66ab3b commit 1c55a26

File tree

11 files changed

+207
-14
lines changed

11 files changed

+207
-14
lines changed

lib/code_corps/analytics/segment_event_name_builder.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ defmodule CodeCorps.Analytics.SegmentEventNameBuilder do
2020
defp get_event_name(:update, %CodeCorps.OrganizationMembership{}) do
2121
"Approved Organization Membership"
2222
end
23+
defp get_event_name(:create, %CodeCorps.ProjectUser{}) do
24+
"Requested Project Membership"
25+
end
26+
defp get_event_name(:update, %CodeCorps.ProjectUser{}) do
27+
"Approved Project Membership"
28+
end
2329
defp get_event_name(:payment_succeeded, %CodeCorps.StripeInvoice{}) do
2430
"Processed Subscription Payment"
2531
end

lib/code_corps/analytics/segment_tracking_support.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ defmodule CodeCorps.Analytics.SegmentTrackingSupport do
1313
def includes?(:update, %CodeCorps.DonationGoal{}), do: true
1414
def includes?(:create, %CodeCorps.OrganizationMembership{}), do: true
1515
def includes?(:update, %CodeCorps.OrganizationMembership{}), do: true
16+
def includes?(:create, %CodeCorps.ProjectUser{}), do: true
17+
def includes?(:update, %CodeCorps.ProjectUser{}), do: true
1618
def includes?(:create, %CodeCorps.StripeConnectAccount{}), do: true
1719
def includes?(:create, %CodeCorps.StripeConnectCharge{}), do: true
1820
def includes?(:create, %CodeCorps.StripeConnectPlan{}), do: true

lib/code_corps/analytics/segment_traits_builder.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ defmodule CodeCorps.Analytics.SegmentTraitsBuilder do
3838
}
3939
end
4040

41+
defp traits(record = %CodeCorps.ProjectUser{}) do
42+
record = record |> CodeCorps.Repo.preload(:project)
43+
%{
44+
project: record.project.title,
45+
project_id: record.project_id
46+
}
47+
end
48+
4149
defp traits(charge = %CodeCorps.StripeConnectCharge{}) do
4250
# NOTE: this only works for some currencies
4351
revenue = charge.amount / 100
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
defmodule CodeCorps.ProjectUserControllerTest do
2+
use CodeCorps.ApiCase, resource_name: :project_user
3+
4+
@attrs %{role: "contributor"}
5+
6+
describe "index" do
7+
test "lists all resources", %{conn: conn} do
8+
[record_1, record_2] = insert_pair(:project_user)
9+
10+
conn
11+
|> request_index
12+
|> json_response(200)
13+
|> assert_ids_from_response([record_1.id, record_2.id])
14+
end
15+
16+
test "filters resources by record id", %{conn: conn} do
17+
[record_1, record_2 | _] = insert_list(3, :project_user)
18+
19+
path = "project-users/?filter[id]=#{record_1.id},#{record_2.id}"
20+
21+
conn
22+
|> get(path)
23+
|> json_response(200)
24+
|> assert_ids_from_response([record_1.id, record_2.id])
25+
end
26+
end
27+
28+
describe "show" do
29+
test "shows chosen resource", %{conn: conn} do
30+
record = insert(:project_user)
31+
conn
32+
|> request_show(record)
33+
|> json_response(200)
34+
|> Map.get("data")
35+
|> assert_result_id(record.id)
36+
end
37+
38+
test "renders 404 when id is nonexistent", %{conn: conn} do
39+
assert conn |> request_show(:not_found) |> json_response(404)
40+
end
41+
end
42+
43+
describe "create" do
44+
@tag :authenticated
45+
test "creates and renders resource when data is valid", %{conn: conn, current_user: user} do
46+
project = insert(:project)
47+
attrs = @attrs |> Map.merge(%{project: project, user: user})
48+
49+
assert conn |> request_create(attrs) |> json_response(201)
50+
51+
user_id = user.id
52+
tracking_properties = %{
53+
project: project.title,
54+
project_id: project.id
55+
}
56+
57+
assert_received {:track, ^user_id, "Requested Project Membership", ^tracking_properties}
58+
end
59+
60+
@tag :authenticated
61+
test "does not create resource and renders 422 when data is invalid", %{conn: conn, current_user: user} do
62+
# only way to trigger a validation error is to provide a non-existant organization
63+
# anything else will fail on authorization level
64+
project = build(:project)
65+
attrs = @attrs |> Map.merge(%{project: project, user: user})
66+
assert conn |> request_create(attrs) |> json_response(422)
67+
end
68+
69+
test "does not create resource and renders 401 when not authenticated", %{conn: conn} do
70+
assert conn |> request_create |> json_response(401)
71+
end
72+
73+
@tag :authenticated
74+
test "does not create resource and renders 403 when not authorized", %{conn: conn} do
75+
assert conn |> request_create |> json_response(403)
76+
end
77+
end
78+
79+
describe "update" do
80+
@tag :authenticated
81+
test "updates and renders resource when data is valid", %{conn: conn, current_user: current_user} do
82+
project = insert(:project)
83+
record = insert(:project_user, project: project, role: "pending")
84+
insert(:project_user, project: project, user: current_user, role: "owner")
85+
86+
assert conn |> request_update(record, @attrs) |> json_response(200)
87+
88+
user_id = current_user.id
89+
tracking_properties = %{
90+
project: project.title,
91+
project_id: project.id
92+
}
93+
94+
assert_received {:track, ^user_id, "Approved Project Membership", ^tracking_properties}
95+
end
96+
97+
test "doesn't update and renders 401 when unauthenticated", %{conn: conn} do
98+
assert conn |> request_update |> json_response(401)
99+
end
100+
101+
@tag :authenticated
102+
test "doesn't update and renders 403 when not authorized", %{conn: conn} do
103+
assert conn |> request_update |> json_response(403)
104+
end
105+
106+
@tag :authenticated
107+
test "renders 404 when id is nonexistent on update", %{conn: conn} do
108+
assert conn |> request_update(:not_found) |> json_response(404)
109+
end
110+
end
111+
112+
describe "delete" do
113+
@tag :authenticated
114+
test "deletes resource", %{conn: conn, current_user: current_user} do
115+
project = insert(:project)
116+
record = insert(:project_user, project: project)
117+
insert(:project_user, project: project, user: current_user, role: "owner")
118+
119+
assert conn |> request_delete(record) |> response(204)
120+
end
121+
122+
test "renders 401 when unauthenticated", %{conn: conn} do
123+
assert conn |> request_delete |> json_response(401)
124+
end
125+
126+
@tag :authenticated
127+
test "renders 403 when not authorized", %{conn: conn} do
128+
assert conn |> request_delete |> json_response(403)
129+
end
130+
131+
@tag :authenticated
132+
test "renders 404 when id is nonexistent on delete", %{conn: conn} do
133+
assert conn |> request_delete(:not_found) |> json_response(404)
134+
end
135+
end
136+
end

test/lib/code_corps/analytics/segment_event_name_builder_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ defmodule CodeCorps.Analytics.SegmentEventNameBuilderTest do
1818
assert SegmentEventNameBuilder.build(:update, build(:organization_membership)) == "Approved Organization Membership"
1919
end
2020

21+
test "with project_user" do
22+
assert SegmentEventNameBuilder.build(:create, build(:project_user)) == "Requested Project Membership"
23+
assert SegmentEventNameBuilder.build(:update, build(:project_user)) == "Approved Project Membership"
24+
end
25+
2126
test "with task" do
2227
assert SegmentEventNameBuilder.build(:create, build(:task)) == "Created Task"
2328
assert SegmentEventNameBuilder.build(:update, build(:task)) == "Edited Task"

test/models/project_user_test.exs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,19 @@ defmodule CodeCorps.ProjectUserTest do
6767
end
6868
end
6969

70-
describe "create_pending_changeset/2" do
70+
describe "create_changeset/2" do
7171
@attributes ~w(project_id user_id role)
7272

7373
test "casts #{@attributes}, with role cast to 'pending'" do
7474
attrs = %{foo: "bar", project_id: 1, user_id: 2}
75-
changeset = ProjectUser.create_pending_changeset(%ProjectUser{}, attrs)
75+
changeset = ProjectUser.create_changeset(%ProjectUser{}, attrs)
7676
assert changeset.changes == %{project_id: 1, user_id: 2, role: "pending"}
7777
end
7878

7979
test "ensures user record exists" do
8080
project = insert(:project)
8181
attrs = %{project_id: project.id, user_id: -1}
82-
changeset = ProjectUser.create_pending_changeset(%ProjectUser{}, attrs)
82+
changeset = ProjectUser.create_changeset(%ProjectUser{}, attrs)
8383

8484
{:error, invalid_changeset} = changeset |> Repo.insert
8585
refute invalid_changeset.valid?
@@ -90,7 +90,7 @@ defmodule CodeCorps.ProjectUserTest do
9090
test "ensures project record exists" do
9191
user = insert(:user)
9292
attrs = %{project_id: -1, user_id: user.id}
93-
changeset = ProjectUser.create_pending_changeset(%ProjectUser{}, attrs)
93+
changeset = ProjectUser.create_changeset(%ProjectUser{}, attrs)
9494

9595
{:error, invalid_changeset} = changeset |> Repo.insert
9696
refute invalid_changeset.valid?

test/policies/project_user_policy_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ defmodule CodeCorps.ProjectUserPolicyTest do
22
use CodeCorps.PolicyCase
33

44
import CodeCorps.ProjectUserPolicy, only: [create?: 2, update?: 2, delete?: 2]
5-
import CodeCorps.ProjectUser, only: [create_pending_changeset: 2, update_changeset: 2]
5+
import CodeCorps.ProjectUser, only: [create_changeset: 2, update_changeset: 2]
66

77
alias CodeCorps.ProjectUser
88

99
describe "create?/2" do
1010
test "returns true when user is creating their own membership" do
1111
user = insert(:user)
12-
changeset = %ProjectUser{} |> create_pending_changeset(%{user_id: user.id})
12+
changeset = %ProjectUser{} |> create_changeset(%{user_id: user.id})
1313

1414
assert create?(user, changeset)
1515
end
1616

1717
test "returns false for normal user, creating someone else's membership" do
1818
user = insert(:user)
19-
changeset = %ProjectUser{} |> create_pending_changeset(%{user_id: "someone_else"})
19+
changeset = %ProjectUser{} |> create_changeset(%{user_id: "someone_else"})
2020

2121
refute create?(user, changeset)
2222
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule CodeCorps.ProjectUserController do
2+
use CodeCorps.Web, :controller
3+
use JaResource
4+
5+
import CodeCorps.Helpers.Query, only: [id_filter: 2]
6+
7+
alias CodeCorps.ProjectUser
8+
9+
@preloads [:project, :user]
10+
11+
plug :load_resource, model: ProjectUser, only: [:show], preload: @preloads
12+
plug :load_and_authorize_resource, model: ProjectUser, only: [:delete]
13+
plug :load_and_authorize_changeset, model: ProjectUser, only: [:create, :update], preload: @preloads
14+
plug JaResource
15+
16+
@spec filter(Plug.Conn.t, Ecto.Query.t, String.t, String.t) :: Ecto.Query.t
17+
def filter(_conn, query, "id", id_list) do
18+
query |> id_filter(id_list)
19+
end
20+
21+
@spec handle_create(Plug.Conn.t, map) :: Ecto.Changeset.t
22+
def handle_create(_conn, attributes) do
23+
%ProjectUser{} |> ProjectUser.create_changeset(attributes)
24+
end
25+
26+
@spec handle_update(Plug.Conn.t, ProjectUser.t, map) :: Ecto.Changeset.t
27+
def handle_update(_conn, model, attributes) do
28+
model |> ProjectUser.update_changeset(attributes)
29+
end
30+
end

web/models/abilities.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule Canary.Abilities do
88
alias CodeCorps.Project
99
alias CodeCorps.ProjectCategory
1010
alias CodeCorps.ProjectSkill
11+
alias CodeCorps.ProjectUser
1112
alias CodeCorps.Role
1213
alias CodeCorps.RoleSkill
1314
alias CodeCorps.Skill
@@ -33,6 +34,7 @@ defmodule Canary.Abilities do
3334
alias CodeCorps.ProjectPolicy
3435
alias CodeCorps.ProjectCategoryPolicy
3536
alias CodeCorps.ProjectSkillPolicy
37+
alias CodeCorps.ProjectUserPolicy
3638
alias CodeCorps.RolePolicy
3739
alias CodeCorps.RoleSkillPolicy
3840
alias CodeCorps.SkillPolicy
@@ -91,6 +93,10 @@ defmodule Canary.Abilities do
9193
def can?(%User{} = user, :create, %Changeset{data: %ProjectSkill{}} = changeset), do: ProjectSkillPolicy.create?(user, changeset)
9294
def can?(%User{} = user, :delete, %ProjectSkill{} = project_skill), do: ProjectSkillPolicy.delete?(user, project_skill)
9395

96+
def can?(%User{} = user, :create, %Changeset{data: %ProjectUser{}} = changeset), do: ProjectUserPolicy.create?(user, changeset)
97+
def can?(%User{} = user, :update, %Changeset{data: %ProjectUser{}} = changeset), do: ProjectUserPolicy.update?(user, changeset)
98+
def can?(%User{} = user, :delete, %ProjectUser{} = record), do: ProjectUserPolicy.delete?(user, record)
99+
94100
def can?(%User{} = user, :create, Role), do: RolePolicy.create?(user)
95101

96102
def can?(%User{} = user, :create, RoleSkill), do: RoleSkillPolicy.create?(user)

web/models/project_user.ex

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ defmodule CodeCorps.ProjectUser do
2020
@doc """
2121
Builds a changeset to create a pending membership
2222
"""
23-
def create_pending_changeset(struct, params \\ %{}) do
23+
def create_changeset(struct, params \\ %{}) do
2424
struct
25-
|> create_changeset(params)
25+
|> changeset(params)
2626
|> put_change(:role, "pending")
2727
end
2828

@@ -31,14 +31,12 @@ defmodule CodeCorps.ProjectUser do
3131
"""
3232
def create_owner_changeset(struct, params \\ %{}) do
3333
struct
34-
|> create_changeset(params)
34+
|> changeset(params)
3535
|> put_change(:role, "owner")
3636
end
3737

38-
@doc """
39-
Builds a changeset for inserting a new record into the database
40-
"""
41-
defp create_changeset(struct, params \\ %{}) do
38+
# Builds a base changeset for inserting a new record into the database
39+
defp changeset(struct, params) do
4240
struct
4341
|> cast(params, [:user_id, :project_id])
4442
|> validate_required([:user_id, :project_id])

0 commit comments

Comments
 (0)