From 8544bb96783f18aac6fd8ec163f9fb08b74e511a Mon Sep 17 00:00:00 2001 From: Ng Warren Date: Sun, 19 Oct 2025 23:25:01 +0800 Subject: [PATCH 1/4] feat: add onepiece api routes --- internal/sbi/api_onepiece.go | 43 ++++++++++++++++++++++++++++++++++++ internal/sbi/router.go | 3 +++ 2 files changed, 46 insertions(+) create mode 100644 internal/sbi/api_onepiece.go 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/router.go b/internal/sbi/router.go index b62139f..a95ce00 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -45,6 +45,9 @@ func newRouter(s *Server) *gin.Engine { spyFamilyGroup := router.Group("/spyfamily") applyRoutes(spyFamilyGroup, s.getSpyFamilyRoute()) + onePieceGroup := router.Group("/onepiece") + applyRoutes(onePieceGroup, s.getOnePieceRoute()) + return router } From b62d309e5846b46b0df03f9a6397396656dba5af Mon Sep 17 00:00:00 2001 From: Ng Warren Date: Sun, 19 Oct 2025 23:25:33 +0800 Subject: [PATCH 2/4] docs: update README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) 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 From 7b15b5023d21938062854761c53ce9b953ca02b6 Mon Sep 17 00:00:00 2001 From: Ng Warren Date: Mon, 20 Oct 2025 23:43:05 +0800 Subject: [PATCH 3/4] feat: add unit test --- internal/sbi/processor/onepiece_test.go | 111 ++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 internal/sbi/processor/onepiece_test.go diff --git a/internal/sbi/processor/onepiece_test.go b/internal/sbi/processor/onepiece_test.go new file mode 100644 index 0000000..865ca99 --- /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) + } + }) +} From ece7a9566e008724e9276b1ab434d3047d6cf652 Mon Sep 17 00:00:00 2001 From: Ng Warren Date: Tue, 21 Oct 2025 14:42:25 +0800 Subject: [PATCH 4/4] fix: lint error --- internal/sbi/processor/onepiece_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/sbi/processor/onepiece_test.go b/internal/sbi/processor/onepiece_test.go index 865ca99..53fe79b 100644 --- a/internal/sbi/processor/onepiece_test.go +++ b/internal/sbi/processor/onepiece_test.go @@ -43,7 +43,7 @@ func Test_HTTPOnePieceGreeting(t *testing.T) { } var actual string - if err := json.Unmarshal(recorder.Body.Bytes(), &actual); err != nil { + if err = json.Unmarshal(recorder.Body.Bytes(), &actual); err != nil { t.Fatalf("decode response: %v", err) } const expected = "Hello Straw Hat Pirates!" @@ -73,7 +73,7 @@ func Test_HTTPOnePieceRecruit(t *testing.T) { } var actual string - if err := json.Unmarshal(recorder.Body.Bytes(), &actual); err != nil { + 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!" @@ -100,7 +100,7 @@ func Test_HTTPOnePieceRecruit(t *testing.T) { } var payload map[string]string - if err := json.Unmarshal(recorder.Body.Bytes(), &payload); err != nil { + if err = json.Unmarshal(recorder.Body.Bytes(), &payload); err != nil { t.Fatalf("decode error response: %v", err) } const expected = "name is required"