Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PATCH /genres/:genreID`の実装 #1023

Merged
merged 12 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 50 additions & 13 deletions src/handler/v2/game_genre.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
)

type GameGenre struct {
gameGenreService service.GameGenre
game service.GameV2
session *Session
gameGenreUnimplemented //実装し終わったら消す
gameGenreService service.GameGenre
game service.GameV2
session *Session
}

func NewGameGenre(gameGenreService service.GameGenre, game service.GameV2, session *Session) *GameGenre {
Expand All @@ -27,15 +26,6 @@
}
}

// gameGenreUnimplemented
// メソッドとして実装予定だが、未実装のもの
// TODO: 実装
type gameGenreUnimplemented interface {
// ジャンル情報の変更
// (PATCH /genres/{gameGenreID})
PatchGameGenre(ctx echo.Context, gameGenreID openapi.GameGenreIDInPath) error
}

// ジャンルの削除
// (DELETE /genres/{gameGenreID})
func (gameGenre *GameGenre) DeleteGameGenre(c echo.Context, gameGenreID openapi.GameGenreIDInPath) error {
Expand Down Expand Up @@ -178,3 +168,50 @@

return c.JSON(http.StatusOK, res)
}

// ジャンル情報の変更
// (PATCH /genres/{gameGenreID})
func (gameGenre *GameGenre) PatchGameGenre(c echo.Context, gameGenreID openapi.GameGenreIDInPath) error {
var reqBody openapi.PatchGameGenreJSONRequestBody
if err := c.Bind(&reqBody); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
}

gameGenreName := values.NewGameGenreName(reqBody.Genre)
if err := gameGenreName.Validate(); err != nil {
if errors.Is(err, values.ErrGameGenreNameEmpty) {
return echo.NewHTTPError(http.StatusBadRequest, "genre name must not be empty")
}
if errors.Is(err, values.ErrGameGenreNameTooLong) {
return echo.NewHTTPError(http.StatusBadRequest, "genre name is too long")
}
log.Printf("failed to validate genre name: %v\n", err)
return echo.NewHTTPError(http.StatusInternalServerError, "failed to validate genre name")

Check warning on line 189 in src/handler/v2/game_genre.go

View check run for this annotation

Codecov / codecov/patch

src/handler/v2/game_genre.go#L188-L189

Added lines #L188 - L189 were not covered by tests
}

ctx := c.Request().Context()

gameGenreInfo, err := gameGenre.gameGenreService.UpdateGameGenre(ctx, values.GameGenreIDFromUUID(gameGenreID), gameGenreName)
if errors.Is(err, service.ErrNoGameGenre) {
return echo.NewHTTPError(http.StatusNotFound, "game genre not found")
}
if errors.Is(err, service.ErrDuplicateGameGenreName) {
return echo.NewHTTPError(http.StatusBadRequest, "duplicate genre name")
}
if errors.Is(err, service.ErrNoGameGenreUpdated) {
return echo.NewHTTPError(http.StatusBadRequest, "no game genre updated")
}
if err != nil {
log.Printf("error: failed to update game genre: %v\n", err)
return echo.NewHTTPError(http.StatusInternalServerError, "failed to update game genre")
}

res := openapi.GameGenre{
Id: uuid.UUID(gameGenreInfo.GetID()),
Genre: string(gameGenreInfo.GetName()),
Num: gameGenreInfo.Num,
CreatedAt: gameGenreInfo.GetCreatedAt(),
}

return c.JSON(http.StatusOK, res)
}
207 changes: 207 additions & 0 deletions src/handler/v2/game_genre_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
mockConfig "github.com/traPtitech/trap-collection-server/src/config/mock"
"github.com/traPtitech/trap-collection-server/src/domain"
"github.com/traPtitech/trap-collection-server/src/domain/values"
Expand Down Expand Up @@ -601,3 +602,209 @@ func TestPutGameGenres(t *testing.T) {
})
}
}

func TestPatchGameGenre(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockGameGenreService := mock.NewMockGameGenre(ctrl)
mockGameService := mock.NewMockGameV2(ctrl)

mockConf := mockConfig.NewMockHandler(ctrl)
mockConf.
EXPECT().
SessionKey().
Return("key", nil)
mockConf.
EXPECT().
SessionSecret().
Return("secret", nil)
sess, err := common.NewSession(mockConf)
if err != nil {
t.Fatalf("failed to create session: %v", err)
return
}
session, err := NewSession(sess)
if err != nil {
t.Fatalf("failed to create session: %v", err)
return
}

gameGenreHandler := NewGameGenre(mockGameGenreService, mockGameService, session)

gameGenreID := uuid.New()

testCases := map[string]struct {
gameGenreID openapi.GameGenreIDInPath
req openapi.PatchGameGenreJSONRequestBody
invalidRequestBody bool
executeUpdateGame bool
UpdateGameGenreErr error
gameGenre *service.GameGenreInfo
resBody openapi.GameGenre
statusCode int
isErr bool
expectedErr error
}{
"特に問題ないのでエラー無し": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{
Genre: "new genre",
},
executeUpdateGame: true,
gameGenre: &service.GameGenreInfo{
GameGenre: *domain.NewGameGenre(values.GameGenreID(gameGenreID), values.NewGameGenreName("new genre"), time.Now()),
Num: 1,
},
resBody: openapi.GameGenre{
Id: uuid.UUID(gameGenreID),
Genre: "new genre",
CreatedAt: time.Now(),
Num: 1,
},
statusCode: http.StatusOK,
},
"リクエストボディがおかしいので400": {
gameGenreID: gameGenreID,
invalidRequestBody: true,
isErr: true,
statusCode: http.StatusBadRequest,
},
"ジャンル名が空なので400": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{
Genre: "",
},
isErr: true,
statusCode: http.StatusBadRequest,
},
"ジャンル名が長すぎるので400": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{
Genre: strings.Repeat("a", 100),
},
isErr: true,
statusCode: http.StatusBadRequest,
},
"UpdateGameGenreがErrNoGameGenreなので404": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{Genre: "new genre"},
executeUpdateGame: true,
UpdateGameGenreErr: service.ErrNoGameGenre,
isErr: true,
statusCode: http.StatusNotFound,
},
"UpdateGameGenreがErrDuplicateGameGenreNameなので400": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{Genre: "new genre"},
executeUpdateGame: true,
UpdateGameGenreErr: service.ErrDuplicateGameGenreName,
isErr: true,
statusCode: http.StatusBadRequest,
},
"UpdateGameGenreがErrNoGameGenreUpdatedなので400": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{Genre: "new genre"},
executeUpdateGame: true,
UpdateGameGenreErr: service.ErrNoGameGenreUpdated,
isErr: true,
statusCode: http.StatusBadRequest,
},
"UpdateGameGenreがエラーなので500": {
gameGenreID: gameGenreID,
req: openapi.PatchGameGenreJSONRequestBody{Genre: "new genre"},
executeUpdateGame: true,
UpdateGameGenreErr: errors.New("test error"),
isErr: true,
statusCode: http.StatusInternalServerError,
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
reqBody := new(bytes.Buffer)
if !testCase.invalidRequestBody {
err := json.NewEncoder(reqBody).Encode(testCase.req)
if err != nil {
t.Fatalf("failed to encode request body: %v", err)
}
} else {
_, err := strings.NewReader("invalid request body").WriteTo(reqBody)
if err != nil {
t.Fatalf("failed to write to request body: %v", err)
}
}

e := echo.New()
req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v2/genres/%s", testCase.gameGenreID), reqBody)
req.Header.Set(echo.HeaderContentType, "application/json")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

sess, err := session.New(req)
if err != nil {
t.Fatal(err)
}

authSession := domain.NewOIDCSession(values.NewOIDCAccessToken("token"), time.Now().Add(time.Hour))

sess.Values[accessTokenSessionKey] = string(authSession.GetAccessToken())
sess.Values[expiresAtSessionKey] = authSession.GetExpiresAt()

err = sess.Save(req, rec)
if err != nil {
t.Fatalf("failed to save session: %v", err)
}

setCookieHeader(c)

sess, err = session.Get(req)
if err != nil {
t.Fatal(err)
}

c.Set("session", sess)

if testCase.executeUpdateGame {
mockGameGenreService.
EXPECT().
UpdateGameGenre(gomock.Any(), values.GameGenreIDFromUUID(testCase.gameGenreID), values.NewGameGenreName(testCase.req.Genre)).
Return(testCase.gameGenre, testCase.UpdateGameGenreErr)
}

err = gameGenreHandler.PatchGameGenre(c, testCase.gameGenreID)

if testCase.isErr {
if testCase.statusCode != 0 {
var httpErr *echo.HTTPError
if errors.As(err, &httpErr) {
assert.Equal(t, testCase.statusCode, httpErr.Code)
} else {
t.Errorf("err must be http error, but not http error: %v", err)
}
} else {
if testCase.expectedErr != nil {
assert.ErrorIs(t, err, testCase.expectedErr)
} else {
assert.Error(t, err)
}
}
}

if testCase.isErr {
return
}

var res openapi.GameGenre
err = json.NewDecoder(rec.Body).Decode(&res)
require.NoError(t, err)

assert.Equal(t, testCase.resBody.Id, res.Id)
assert.Equal(t, testCase.resBody.Genre, res.Genre)
assert.Equal(t, testCase.resBody.Num, res.Num)
assert.WithinDuration(t, testCase.resBody.CreatedAt, res.CreatedAt, time.Second)
})
}
}
12 changes: 12 additions & 0 deletions src/repository/game_genre.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ type GameGenre interface {
// ゲームジャンルが存在しない場合は、ErrIncludeInvalidArgsを返す。
// ゲームジャンルを追加するのではなく、置き換える。
RegisterGenresToGame(ctx context.Context, gameID values.GameID, gameGenres []values.GameGenreID) error
// UpdateGameGenre
// ゲームジャンルの情報を更新する。
// ゲームジャンルが存在しない場合、または変更が起きなかった場合は、ErrNoRecordUpdatedを返す。
// ゲームジャンルの名前が重複する場合は、ErrDuplicatedUniqueKeyを返す。
UpdateGameGenre(ctx context.Context, gameGenre *domain.GameGenre) error
// GetGameGenre
// ゲームジャンルのIDからゲームジャンルを取得する。
// ゲームジャンルが存在しない場合は、ErrRecordNotFoundを返す。
GetGameGenre(ctx context.Context, gameGenreID values.GameGenreID) (*domain.GameGenre, error)
// GetGamesByGenreID
// ゲームジャンルのIDからそのジャンルに含まれるゲームを取得する。
GetGamesByGenreID(ctx context.Context, gameGenreID values.GameGenreID) ([]*domain.Game, error)
}

type GameGenreInfo struct {
Expand Down
Loading
Loading