diff --git a/README.md b/README.md index 2e93c12..f52ab78 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,14 @@ make > curl -X GET http://127.0.0.163:8000/spyfamily/character/Loid "Character: Loid Forger" + +> curl -X GET http://127.0.0.163:8000/onepiece/ +"Hello Straw Hat Pirates!" + +> curl -X POST http://127.0.0.163:8000/onepiece/crew \ + -H "Content-Type: application/json" \ + -d '{"name":"Jinbe"}' +"Jinbe has joined the Straw Hat crew!" ``` ## Go Test diff --git a/internal/sbi/api_onepiece.go b/internal/sbi/api_onepiece.go new file mode 100644 index 0000000..e6670ce --- /dev/null +++ b/internal/sbi/api_onepiece.go @@ -0,0 +1,43 @@ +package sbi + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +func (s *Server) getOnePieceRoute() []Route { + return []Route{ + { + Name: "Hello Straw Hats", + Method: http.MethodGet, + Pattern: "/", + APIFunc: s.HTTPOnePieceGreeting, + }, + { + Name: "Recruit Straw Hat", + Method: http.MethodPost, + Pattern: "/crew", + APIFunc: s.HTTPOnePieceRecruit, + }, + } +} + +func (s *Server) HTTPOnePieceGreeting(c *gin.Context) { + c.JSON(http.StatusOK, "Hello Straw Hat Pirates!") +} + +func (s *Server) HTTPOnePieceRecruit(c *gin.Context) { + var request struct { + Name string `json:"name" binding:"required"` + } + + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + return + } + + message := fmt.Sprintf("%s has joined the Straw Hat crew!", request.Name) + c.JSON(http.StatusCreated, message) +} diff --git a/internal/sbi/processor/onepiece_test.go b/internal/sbi/processor/onepiece_test.go new file mode 100644 index 0000000..53fe79b --- /dev/null +++ b/internal/sbi/processor/onepiece_test.go @@ -0,0 +1,111 @@ +package processor_test + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Alonza0314/nf-example/internal/sbi" + "github.com/Alonza0314/nf-example/pkg/factory" + "github.com/gin-gonic/gin" + "go.uber.org/mock/gomock" +) + +func newTestServer(t *testing.T) *sbi.Server { + t.Helper() + gin.SetMode(gin.TestMode) + ctrl := gomock.NewController(t) + nfApp := sbi.NewMocknfApp(ctrl) + nfApp.EXPECT().Config().Return(&factory.Config{ + Configuration: &factory.Configuration{ + Sbi: &factory.Sbi{Port: 8000}, + }, + }).AnyTimes() + return sbi.NewServer(nfApp, "") +} + +func Test_HTTPOnePieceGreeting(t *testing.T) { + server := newTestServer(t) + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + req, err := http.NewRequest(http.MethodGet, "/onepiece", nil) + if err != nil { + t.Fatalf("create request: %v", err) + } + ctx.Request = req + + server.HTTPOnePieceGreeting(ctx) + + if recorder.Code != http.StatusOK { + t.Fatalf("unexpected status: got %d want %d", recorder.Code, http.StatusOK) + } + + var actual string + if err = json.Unmarshal(recorder.Body.Bytes(), &actual); err != nil { + t.Fatalf("decode response: %v", err) + } + const expected = "Hello Straw Hat Pirates!" + if actual != expected { + t.Fatalf("unexpected body: got %q want %q", actual, expected) + } +} + +func Test_HTTPOnePieceRecruit(t *testing.T) { + server := newTestServer(t) + + t.Run("Success", func(t *testing.T) { + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + body := bytes.NewBufferString(`{"name":"Jinbe"}`) + req, err := http.NewRequest(http.MethodPost, "/onepiece/crew", body) + if err != nil { + t.Fatalf("create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + ctx.Request = req + + server.HTTPOnePieceRecruit(ctx) + + if recorder.Code != http.StatusCreated { + t.Fatalf("unexpected status: got %d want %d", recorder.Code, http.StatusCreated) + } + + var actual string + if err = json.Unmarshal(recorder.Body.Bytes(), &actual); err != nil { + t.Fatalf("decode response: %v", err) + } + const expected = "Jinbe has joined the Straw Hat crew!" + if actual != expected { + t.Fatalf("unexpected body: got %q want %q", actual, expected) + } + }) + + t.Run("MissingName", func(t *testing.T) { + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + body := bytes.NewBufferString(`{}`) + req, err := http.NewRequest(http.MethodPost, "/onepiece/crew", body) + if err != nil { + t.Fatalf("create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + ctx.Request = req + + server.HTTPOnePieceRecruit(ctx) + + if recorder.Code != http.StatusBadRequest { + t.Fatalf("unexpected status: got %d want %d", recorder.Code, http.StatusBadRequest) + } + + var payload map[string]string + if err = json.Unmarshal(recorder.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode error response: %v", err) + } + const expected = "name is required" + if payload["error"] != expected { + t.Fatalf("unexpected error message: got %q want %q", payload["error"], expected) + } + }) +} diff --git a/internal/sbi/router.go b/internal/sbi/router.go index 598557c..49ee563 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -49,6 +49,8 @@ func newRouter(s *Server) *gin.Engine { spyFamilyGroup := router.Group("/spyfamily") applyRoutes(spyFamilyGroup, s.getSpyFamilyRoute()) + onePieceGroup := router.Group("/onepiece") + applyRoutes(onePieceGroup, s.getOnePieceRoute()) attendanceGroup := router.Group("/attendance") applyRoutes(attendanceGroup, s.getAttendanceRoute()) taskGroup := router.Group("/task")