diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6192543..e8fdf30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,7 +200,7 @@ jobs: uses: josecfreittas/elixir-coverage-feedback-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - coverage_threshold: 50 + coverage_threshold: 70 services: pg: image: postgres:16 diff --git a/apps/web/lib/web/controllers/enrollments/enrollments_controller.ex b/apps/web/lib/web/controllers/enrollments/enrollments_controller.ex index 324e1dd..c1d0e29 100644 --- a/apps/web/lib/web/controllers/enrollments/enrollments_controller.ex +++ b/apps/web/lib/web/controllers/enrollments/enrollments_controller.ex @@ -6,11 +6,11 @@ defmodule Web.Enrollments.EnrollmentsController do import Web.Auth.AuthorizedPlug import Web.Plug.CheckRequest - alias GoEscuelaLms.Core.Schema.{Enrollment} + alias GoEscuelaLms.Core.Schema.{Course, Enrollment} - plug :permit_authorized when action in [:index, :create] + plug :organizer_authorized when action in [:create, :delete] + plug :load_course when action in [:create] plug :load_user when action in [:create] - plug :load_course when action in [:create, :index] plug :load_enrollment when action in [:delete] @create_params %{ @@ -18,9 +18,19 @@ defmodule Web.Enrollments.EnrollmentsController do user_id: [type: :string, required: true] } + def index(conn, %{"courses_id" => course_id}) do + with course <- Course.find(course_id), + false <- is_nil(course) do + render(conn, :index, %{enrollments: course.enrollments}) + else + _ -> + Web.FallbackController.call(conn, {:error, "course is invalid"}) |> halt() + end + end + def index(conn, _params) do - course = conn.assigns.course - render(conn, :index, %{enrollments: course.enrollments}) + user = conn.assigns.account + render(conn, :index, %{enrollments: user.enrollments}) end def create(conn, params) do diff --git a/apps/web/lib/web/controllers/enrollments/enrollments_json.ex b/apps/web/lib/web/controllers/enrollments/enrollments_json.ex index f7d3d32..3b3d249 100644 --- a/apps/web/lib/web/controllers/enrollments/enrollments_json.ex +++ b/apps/web/lib/web/controllers/enrollments/enrollments_json.ex @@ -22,8 +22,9 @@ defmodule Web.Enrollments.EnrollmentsJSON do defp data(%Enrollment{} = enrollment) do %{ id: enrollment.uuid, - enrollment_id: enrollment.uuid, - inserted_at: enrollment.inserted_at + course_id: enrollment.course_id, + user_id: enrollment.user_id, + inserted_at: enrollment.inserted_at |> to_string() } end end diff --git a/apps/web/lib/web/controllers/home/page_controller.ex b/apps/web/lib/web/controllers/home/page_controller.ex index c725915..1307d95 100644 --- a/apps/web/lib/web/controllers/home/page_controller.ex +++ b/apps/web/lib/web/controllers/home/page_controller.ex @@ -4,6 +4,6 @@ defmodule Web.Home.PageController do action_fallback Web.FallbackController def show(conn, _params) do - render(conn, :show, %{}) + render(conn, :show, %{status: :ok}) end end diff --git a/apps/web/lib/web/controllers/home/page_json.ex b/apps/web/lib/web/controllers/home/page_json.ex index 374a72c..20075f8 100644 --- a/apps/web/lib/web/controllers/home/page_json.ex +++ b/apps/web/lib/web/controllers/home/page_json.ex @@ -2,7 +2,7 @@ defmodule Web.Home.PageJSON do @doc """ Renders a single user. """ - def show(_params) do - %{status: :ok} + def show(%{status: status}) do + %{status: status} end end diff --git a/apps/web/lib/web/controllers/onboarding/institution_info_controlller.ex b/apps/web/lib/web/controllers/onboarding/institution_info_controlller.ex index 18b818a..1482a59 100644 --- a/apps/web/lib/web/controllers/onboarding/institution_info_controlller.ex +++ b/apps/web/lib/web/controllers/onboarding/institution_info_controlller.ex @@ -14,8 +14,4 @@ defmodule Web.Onboarding.InstitutionInfoController do render(conn, :show, %{institution_info: institution_info}) end - - def update(conn, _params) do - render(conn, :update, %{}) - end end diff --git a/apps/web/lib/web/controllers/onboarding/institution_info_json.ex b/apps/web/lib/web/controllers/onboarding/institution_info_json.ex index 6400707..57310e8 100644 --- a/apps/web/lib/web/controllers/onboarding/institution_info_json.ex +++ b/apps/web/lib/web/controllers/onboarding/institution_info_json.ex @@ -4,14 +4,6 @@ defmodule Web.Onboarding.InstitutionInfoJSON do """ def show(%{institution_info: institution_info}) do - %{data: %{name: institution_info.name, created_at: institution_info.inserted_at}} - end - - def create(%{}) do - %{data: %{}} - end - - def update(%{}) do - %{data: %{}} + %{data: %{name: institution_info.name}} end end diff --git a/apps/web/lib/web/controllers/onboarding/organizer_controller.ex b/apps/web/lib/web/controllers/onboarding/organizer_controller.ex deleted file mode 100644 index ab1325e..0000000 --- a/apps/web/lib/web/controllers/onboarding/organizer_controller.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Web.Onboarding.OrganizerController do - use Web, :controller - - action_fallback Web.FallbackController - - def show(conn, _params) do - render(conn, :index, %{}) - end - - def create(conn, _params) do - render(conn, :update, %{}) - end - - def update(conn, _params) do - render(conn, :update, %{}) - end -end diff --git a/apps/web/lib/web/controllers/onboarding/organizer_json.ex b/apps/web/lib/web/controllers/onboarding/organizer_json.ex deleted file mode 100644 index 9cf01c4..0000000 --- a/apps/web/lib/web/controllers/onboarding/organizer_json.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Web.Onboarding.OrganizerJSON do - @doc """ - Renders institution info - """ - - def show(%{}) do - %{data: %{}} - end - - def create(%{}) do - %{data: %{}} - end - - def update(%{}) do - %{data: %{}} - end -end diff --git a/apps/web/lib/web/router.ex b/apps/web/lib/web/router.ex index eea1866..f235d30 100644 --- a/apps/web/lib/web/router.ex +++ b/apps/web/lib/web/router.ex @@ -21,8 +21,6 @@ defmodule Web.Router do scope "/api", Web do pipe_through [:api, :auth] - - post "/onboarding/organizer", Onboarding.OrganizerController, :create get "/onboarding/institution_info", Onboarding.InstitutionInfoController, :show resources "/users", Users.UsersController, only: [:create, :update, :index, :delete] do @@ -37,7 +35,7 @@ defmodule Web.Router do resources "/enrollments", Enrollments.EnrollmentsController, only: [:index] end - resources "/enrollments", Enrollments.EnrollmentsController, only: [:create, :delete] + resources "/enrollments", Enrollments.EnrollmentsController, only: [:index, :create, :delete] get "/profile", Users.ProfileController, :show put "/profile", Users.ProfileController, :update diff --git a/apps/web/mix.exs b/apps/web/mix.exs index ce1a54a..ca15316 100644 --- a/apps/web/mix.exs +++ b/apps/web/mix.exs @@ -16,7 +16,7 @@ defmodule Web.MixProject do deps: deps(), test_coverage: [ summary: [ - threshold: 50 + threshold: 70 ] ] ] diff --git a/apps/web/test/web/controllers/enrollments_controller_test.exs b/apps/web/test/web/controllers/enrollments_controller_test.exs new file mode 100644 index 0000000..1a2d64c --- /dev/null +++ b/apps/web/test/web/controllers/enrollments_controller_test.exs @@ -0,0 +1,192 @@ +defmodule Web.EnrollmentsControllerTest do + use ExUnit.Case + use Core.DataCase + use Web.ConnCase + + import Web.Auth.Guardian + import Core.Factory + + alias GoEscuelaLms.Core.Schema.Enrollment + + setup do + course = insert!(:course) + user = insert!(:user, role: :instructor) + organizer = insert!(:user, role: :organizer) + + {:ok, user: user, organizer: organizer, course: course} + end + + describe "index/2" do + test "list enrollments by course", %{user: user, organizer: organizer, course: course} do + user1 = insert!(:user, role: :instructor) + user2 = insert!(:user, role: :student) + + enrollment1 = insert!(:enrollment, course_id: course.uuid, user_id: user1.uuid) + enrollment2 = insert!(:enrollment, course_id: course.uuid, user_id: user2.uuid) + + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, user.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> get(~p"/api/courses/#{course.uuid}/enrollments") + + response = json_response(conn, 200)["data"] + + assert response == [ + %{ + "id" => enrollment1.uuid, + "course_id" => enrollment1.course_id, + "user_id" => enrollment1.user_id, + "inserted_at" => enrollment1.inserted_at |> to_string() + }, + %{ + "id" => enrollment2.uuid, + "course_id" => enrollment2.course_id, + "user_id" => enrollment2.user_id, + "inserted_at" => enrollment2.inserted_at |> to_string() + } + ] + end + + test "list enrollments by current user", %{user: user} do + course1 = insert!(:course) + course2 = insert!(:course) + + enrollment1 = insert!(:enrollment, course_id: course1.uuid, user_id: user.uuid) + enrollment2 = insert!(:enrollment, course_id: course2.uuid, user_id: user.uuid) + + {:ok, token, _} = encode_and_sign(user, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, user.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> get(~p"/api/enrollments") + + response = json_response(conn, 200)["data"] + + assert response == [ + %{ + "id" => enrollment1.uuid, + "course_id" => enrollment1.course_id, + "user_id" => enrollment1.user_id, + "inserted_at" => enrollment1.inserted_at |> to_string() + }, + %{ + "id" => enrollment2.uuid, + "course_id" => enrollment2.course_id, + "user_id" => enrollment2.user_id, + "inserted_at" => enrollment2.inserted_at |> to_string() + } + ] + end + end + + describe "create/2" do + test "unauthorized", %{user: user} do + {:ok, token, _} = encode_and_sign(user, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, user.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> post(~p"/api/enrollments", %{}) + + assert json_response(conn, 403)["errors"] == %{"detail" => "Forbidden resource"} + end + + test "with invalid course", %{organizer: organizer} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> post(~p"/api/enrollments", %{course_id: Faker.UUID.v4()}) + + assert json_response(conn, 422)["errors"] == %{"detail" => "course is invalid"} + end + + test "with invalid user", %{organizer: organizer, course: course} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> post(~p"/api/enrollments", %{course_id: course.uuid, user_id: Faker.UUID.v4()}) + + assert json_response(conn, 422)["errors"] == %{"detail" => "user is invalid"} + end + + test "valid create", %{user: user, organizer: organizer, course: course} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> post(~p"/api/enrollments", %{course_id: course.uuid, user_id: user.uuid}) + + response = json_response(conn, 200)["data"] + assert response["course_id"] == course.uuid + assert response["user_id"] == user.uuid + assert is_nil(response["id"]) == false + assert is_nil(response["inserted_at"]) == false + end + end + + describe "delete/2" do + test "unauthorized", %{user: user, course: course} do + enrollment = insert!(:enrollment, course_id: course.uuid, user_id: user.uuid) + + {:ok, token, _} = encode_and_sign(user, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, user.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> delete(~p"/api/enrollments/#{enrollment.uuid}") + + assert json_response(conn, 403)["errors"] == %{"detail" => "Forbidden resource"} + end + + test "with invalid enrollment", %{organizer: organizer} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> delete(~p"/api/enrollments/#{Faker.UUID.v4()}") + + assert json_response(conn, 422)["errors"] == %{"detail" => "enrollment is invalid"} + end + + test "valid delete", %{organizer: organizer, course: course, user: user} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + enrollment = insert!(:enrollment, course_id: course.uuid, user_id: user.uuid) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> delete(~p"/api/enrollments/#{enrollment.uuid}") + + assert json_response(conn, 200) + assert Enrollment.find(enrollment.uuid) == nil + end + end +end diff --git a/apps/web/test/web/controllers/institution_info_controller_test.exs b/apps/web/test/web/controllers/institution_info_controller_test.exs new file mode 100644 index 0000000..dcf0c5e --- /dev/null +++ b/apps/web/test/web/controllers/institution_info_controller_test.exs @@ -0,0 +1,32 @@ +defmodule Web.InstitutionInfoControllerTest do + use ExUnit.Case + use Core.DataCase + use Web.ConnCase + + import Web.Auth.Guardian + import Core.Factory + + setup do + institution = insert!(:institution_info) + organizer = insert!(:user, role: :organizer) + + {:ok, institution: institution, organizer: organizer} + end + + describe "show/2" do + test "display institution info", %{organizer: organizer, institution: institution} do + {:ok, token, _} = encode_and_sign(organizer, %{}, token_type: :access) + + conn = + session_conn() + |> put_session(:user_id, organizer.uuid) + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer " <> token) + |> get(~p"/api/onboarding/institution_info") + + response = json_response(conn, 200)["data"] + + assert response == %{"name" => institution.name} + end + end +end diff --git a/apps/web/test/web/controllers/page_controller_test.exs b/apps/web/test/web/controllers/page_controller_test.exs new file mode 100644 index 0000000..478426f --- /dev/null +++ b/apps/web/test/web/controllers/page_controller_test.exs @@ -0,0 +1,12 @@ +defmodule Web.PageControllerTest do + use ExUnit.Case + use Core.DataCase + use Web.ConnCase + + describe "show/2" do + test "return ok", %{conn: conn} do + conn = conn |> put_req_header("accept", "application/json") |> get(~p"/api") + assert json_response(conn, 200) + end + end +end