-
Notifications
You must be signed in to change notification settings - Fork 0
[codex] Implement stub handlers and split handler files #22
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
|
|
||
| api "github.com/fun-dotto/admin-bff-api/generated" | ||
| "github.com/fun-dotto/admin-bff-api/generated/external/academic_api" | ||
| "github.com/fun-dotto/admin-bff-api/internal/middleware" | ||
| ) | ||
|
|
||
| // CourseRegistrationsV1List 履修情報を取得する | ||
| func (h *Handler) CourseRegistrationsV1List(c *gin.Context, params api.CourseRegistrationsV1ListParams) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.CourseRegistrationsV1ListWithResponse(c.Request.Context(), &academic_api.CourseRegistrationsV1ListParams{ | ||
| UserId: params.UserId, | ||
| Year: params.Year, | ||
| Semesters: convertSlice[api.DottoFoundationV1CourseSemester, academic_api.DottoFoundationV1CourseSemester](params.Semesters), | ||
| }) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON200 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, gin.H{ | ||
| "registrations": response.JSON200.CourseRegistrations, | ||
| }) | ||
| } | ||
|
|
||
| // CourseRegistrationsV1Create 履修情報を作成する | ||
| func (h *Handler) CourseRegistrationsV1Create(c *gin.Context) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| var req academic_api.CourseRegistrationRequest | ||
| if err := c.ShouldBindJSON(&req); err != nil { | ||
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.CourseRegistrationsV1CreateWithResponse(c.Request.Context(), req) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON201 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusCreated, gin.H{ | ||
| "registration": response.JSON201.CourseRegistration, | ||
| }) | ||
| } | ||
|
|
||
| // CourseRegistrationsV1Delete 履修情報を削除する | ||
| func (h *Handler) CourseRegistrationsV1Delete(c *gin.Context, id string) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.CourseRegistrationsV1DeleteWithResponse(c.Request.Context(), id) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.StatusCode() != http.StatusNoContent { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.Status(http.StatusNoContent) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/fun-dotto/admin-bff-api/generated/external/academic_api" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| func TestCourseRegistrationsV1Create_ProxiesAcademicAPI(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
|
|
||
| var gotMethod string | ||
| var gotPath string | ||
| var gotBody academic_api.CourseRegistrationRequest | ||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| gotMethod = r.Method | ||
| gotPath = r.URL.Path | ||
|
|
||
| if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | ||
| t.Fatalf("decode request body: %v", err) | ||
| } | ||
|
|
||
| w.Header().Set("Content-Type", "application/json") | ||
| w.WriteHeader(http.StatusCreated) | ||
| _, _ = w.Write([]byte(`{"courseRegistration":{"id":"reg-1","userId":"user-1","subject":{"id":"subject-1","name":"Algorithms","faculties":[]}}}`)) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| h := newTestHandler(t, server.URL) | ||
| rec := httptest.NewRecorder() | ||
| c, _ := gin.CreateTestContext(rec) | ||
| c.Request = httptest.NewRequest( | ||
| http.MethodPost, | ||
| "/v1/courseRegistrations", | ||
| bytes.NewBufferString(`{"subjectId":"subject-1","userId":"user-1"}`), | ||
| ) | ||
| c.Request.Header.Set("Content-Type", "application/json") | ||
| setAdminClaim(c) | ||
|
|
||
| h.CourseRegistrationsV1Create(c) | ||
|
|
||
| if rec.Code != http.StatusCreated { | ||
| t.Fatalf("status = %d, want %d", rec.Code, http.StatusCreated) | ||
| } | ||
| if gotMethod != http.MethodPost || gotPath != "/v1/courseRegistrations" { | ||
| t.Fatalf("method/path = %s %s", gotMethod, gotPath) | ||
| } | ||
| if gotBody.SubjectId != "subject-1" || gotBody.UserId != "user-1" { | ||
| t.Fatalf("unexpected upstream request body: %+v", gotBody) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| "time" | ||
|
|
||
| api "github.com/fun-dotto/admin-bff-api/generated" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| func TestPersonalCalendarItemsV1List_ProxiesAcademicAPI(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
|
|
||
| wantDate := time.Date(2026, 3, 26, 9, 0, 0, 0, time.UTC) | ||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| query := r.URL.Query() | ||
| if got := query.Get("userId"); got != "user-1" { | ||
| t.Fatalf("userId = %q, want %q", got, "user-1") | ||
| } | ||
| if got := query.Get("dates"); got == "" { | ||
| t.Fatal("dates query parameter is empty") | ||
| } | ||
|
Comment on lines
+18
to
+25
|
||
|
|
||
| w.Header().Set("Content-Type", "application/json") | ||
| _, _ = w.Write([]byte(`{"personalCalendarItems":[{"date":"2026-03-26T09:00:00Z","slot":{"dayOfWeek":"Thursday","period":"first"},"timetableItem":{"id":"item-1","subject":{"id":"subject-1","name":"Algorithms"},"slot":{"dayOfWeek":"Thursday","period":"first"},"rooms":[]}}]}`)) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| h := newTestHandler(t, server.URL) | ||
| rec := httptest.NewRecorder() | ||
| c, _ := gin.CreateTestContext(rec) | ||
| c.Request = httptest.NewRequest(http.MethodGet, "/v1/personalCalendarItems", nil) | ||
| setAdminClaim(c) | ||
|
|
||
| h.PersonalCalendarItemsV1List(c, api.PersonalCalendarItemsV1ListParams{ | ||
| UserId: "user-1", | ||
| Dates: []time.Time{wantDate}, | ||
| }) | ||
|
|
||
| if rec.Code != http.StatusOK { | ||
| t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) | ||
| } | ||
|
|
||
| var body struct { | ||
| PersonalCalendarItems []struct { | ||
| Date string `json:"date"` | ||
| } `json:"personalCalendarItems"` | ||
| } | ||
| if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { | ||
| t.Fatalf("unmarshal response: %v", err) | ||
| } | ||
| if len(body.PersonalCalendarItems) != 1 || body.PersonalCalendarItems[0].Date != "2026-03-26T09:00:00Z" { | ||
| t.Fatalf("unexpected response body: %s", rec.Body.String()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
|
|
||
| api "github.com/fun-dotto/admin-bff-api/generated" | ||
| "github.com/fun-dotto/admin-bff-api/generated/external/academic_api" | ||
| "github.com/fun-dotto/admin-bff-api/internal/middleware" | ||
| ) | ||
|
|
||
| // RoomsV1List 教室一覧を取得する | ||
| func (h *Handler) RoomsV1List(c *gin.Context, params api.RoomsV1ListParams) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.RoomsV1ListWithResponse(c.Request.Context(), &academic_api.RoomsV1ListParams{ | ||
| Floor: convertSlicePtr[api.DottoFoundationV1Floor, academic_api.DottoFoundationV1Floor](params.Floor), | ||
| }) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON200 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, response.JSON200) | ||
| } | ||
|
Comment on lines
+13
to
+33
|
||
|
|
||
| // RoomsV1Create 教室を作成する | ||
| func (h *Handler) RoomsV1Create(c *gin.Context) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| var req academic_api.RoomRequest | ||
| if err := c.ShouldBindJSON(&req); err != nil { | ||
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.RoomsV1CreateWithResponse(c.Request.Context(), req) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON201 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusCreated, response.JSON201) | ||
| } | ||
|
|
||
| // RoomsV1Detail 教室を詳細取得する | ||
| func (h *Handler) RoomsV1Detail(c *gin.Context, id string) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.RoomsV1DetailWithResponse(c.Request.Context(), id) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON200 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, response.JSON200) | ||
| } | ||
|
|
||
| // RoomsV1Update 教室を更新する | ||
| func (h *Handler) RoomsV1Update(c *gin.Context, id string) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| var req academic_api.RoomRequest | ||
| if err := c.ShouldBindJSON(&req); err != nil { | ||
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.RoomsV1UpdateWithResponse(c.Request.Context(), id, req) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON200 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, response.JSON200) | ||
| } | ||
|
|
||
| // RoomsV1Delete 教室を削除する | ||
| func (h *Handler) RoomsV1Delete(c *gin.Context, id string) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.RoomsV1DeleteWithResponse(c.Request.Context(), id) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.StatusCode() != http.StatusNoContent { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.Status(http.StatusNoContent) | ||
| } | ||
|
|
||
| // ReservationsV1List 教室の予約一覧を取得する | ||
| func (h *Handler) ReservationsV1List(c *gin.Context, id string, params api.ReservationsV1ListParams) { | ||
| if !middleware.RequireAnyClaim(c, "admin", "developer") { | ||
| return | ||
| } | ||
|
|
||
| response, err := h.academicClient.ReservationsV1ListWithResponse(c.Request.Context(), id, &academic_api.ReservationsV1ListParams{ | ||
| From: params.From, | ||
| Until: params.Until, | ||
| }) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if response.JSON200 == nil { | ||
| c.JSON(response.StatusCode(), gin.H{"error": "unexpected response from upstream"}) | ||
| return | ||
| } | ||
|
|
||
| c.JSON(http.StatusOK, response.JSON200) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The httptest server handler calls t.Fatalf from its own goroutine (net/http serves requests concurrently). In Go tests, Fatal/FailNow should be called only from the test goroutine; this can lead to confusing behavior and makes the test harder to reason about. Capture assertion failures via a channel (or store them and assert after the handler call) instead of calling t.Fatalf inside the server handler.