# 동호회 관리 플랫폼 개발기 #4 - Domain Rules

현재 Hoing 에는 3가지 리소스가 있다.

1. User
    
2. Club
    
3. Member
    

이 중 Club 리소스에 대한 도메인 규칙을 정하고, 이를 테스트로 표현해본다.

### Club

1. Club 은 로그인 된 상태에서만 생성이 가능하다.
    
2. Club 을 생성한 생성자는 자동으로 Club 의 owner member 가 된다.
    
3. Club 의 owner 만 Club 정보를 수정할 수 있다.
    

### generator

각 리소스의 생성자를 정의한다.

별도로 인자가 없는 경우 리소스를 생성하는데 필요한 다른 관계들은 자동으로 생성하도록 처리한다.

```elixir
defmodule Hoing.Generator do
  @moduledoc "Data generation for tests"
  use Ash.Generator

  def member(opts \\ []) do
    user = opts[:user] || generate(user())
    club = opts[:club] || generate(club())

    seed_generator(
      %Hoing.Clubs.Member{
        user_id: user.id,
        club_id: club.id,
        role: :member
      },
      overrides: opts
    )
  end

  def club(opts \\ []) do
    actor = opts[:actor] || once(:default_user, fn -> generate(user()) end)

    changeset_generator(
      Hoing.Clubs.Club,
      :create,
      defaults: [
        name: sequence(:club_name, &"Club #{&1}", &((&1 || 0) + 1)),
        description: sequence(:club_description, &"Description of Club #{&1}")
      ],
      overrides: opts,
      actor: actor
    )
  end

  def user(opts \\ []) do
    seed_generator(
      %Hoing.Accounts.User{
        email: sequence(:user_email, &"user#{&1}@example.com")
      },
      overrides: opts
    )
  end
end
```

### club\_test.exs

위에서 정의한 generator 를 활용해서 테스트를 구현하자.

```elixir
defmodule Hoing.Clubs.ClubTest do
  use Hoing.DataCase, async: true

  alias Hoing.Clubs, warn: false

  describe "Hoing.Clubs.create_club!" do
    test "can not create club without actor" do
      assert_raise Ash.Error.Forbidden, fn ->
        Clubs.create_club!(%{name: "New Club"})
      end
    end

    test "can create club with actor" do
      actor = generate(user())

      club = Clubs.create_club!(%{name: "New Club"}, actor: actor)

      assert club.name == "New Club"
    end

    test "set actor as owner member of the created club" do
      actor = generate(user())

      club = Clubs.create_club!(%{name: "New Club"}, actor: actor) |> Ash.load!(:members)
      member = List.first(club.members)

      assert length(club.members) == 1
      assert member.user_id == actor.id
      assert member.club_id == club.id
      assert member.role == :owner
    end
  end

  describe "Hoing.Clubs.update_club!" do
    test "can not update club without actor" do
      club = generate(club())

      assert_raise Ash.Error.Forbidden, fn ->
        Clubs.update_club!(club, %{name: "Updated Club"})
      end
    end

    test "can update club if actor is owner of the club" do
      user = generate(user())
      club = generate(club(actor: user)) |> Ash.load!(:members)

      member =
        club.members
        |> Enum.find(fn member -> member.user_id == user.id && member.club_id == club.id end)

      updated_club =
        Clubs.update_club!(club, %{name: "Updated Club", description: "No more description."},
          actor: user,
          context: %{member: member}
        )

      assert updated_club.id == club.id
      assert updated_club.name == "Updated Club"
      assert updated_club.description == "No more description."
    end

    test "can not update club if actor is not owner of the club" do
      club = generate(club())

      manager_user = generate(user())
      manager_member = generate(member(user: manager_user, club: club, role: :manager))

      member_user = generate(user())
      member_member = generate(member(user: member_user, club: club, role: :member))

      assert_raise Ash.Error.Forbidden,
                   fn ->
                     Clubs.update_club!(
                       club,
                       %{name: "Updated Club", description: "No more description."},
                       actor: manager_user,
                       context: %{member: manager_member}
                     )
                   end

      assert_raise Ash.Error.Forbidden,
                   fn ->
                     Clubs.update_club!(
                       club,
                       %{name: "Updated Club", description: "No more description."},
                       actor: member_user,
                       context: %{member: member_member}
                     )
                   end
    end
  end
end
```

위 test 를 실행해보자.

```bash
mix test test/hoing/clubs/club_test.exs
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753023108684/98a62eb9-4ae2-4dc1-a9c6-fb2589df0876.png align="center")

test 가 정상작동하는 것을 확인할 수 있다.
