From d04855cef6ad1e73e6cf313e7829adb727a12dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=93=B2=E6=9A=90?= Date: Mon, 29 Sep 2025 17:17:11 +0800 Subject: [PATCH 1/5] test: add api_foodpicker test --- Makefile | 6 +- config/nfcfg.yaml | 2 +- internal/sbi/api_foodpicker.go | 60 ++++++++++++++ internal/sbi/api_foodpicker_test.go | 118 ++++++++++++++++++++++++++++ internal/sbi/router.go | 3 + 5 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 internal/sbi/api_foodpicker.go create mode 100644 internal/sbi/api_foodpicker_test.go diff --git a/Makefile b/Makefile index 96d2a93..7121631 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ GO_BIN_PATH = bin -VERSION = $(shell git describe --tags) +VERSION = $(shell git describe --tags 2>/dev/null || echo "v0.0.0") BUILD_TIME = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") COMMIT_HASH = $(shell git submodule status | grep $(GO_SRC_PATH)/$(@F) | awk '{print $$(1)}' | cut -c1-8) -COMMIT_TIME = $(shell git log --pretty="@%at" -1 | xargs date -u +"%Y-%m-%dT%H:%M:%SZ" -d) +COMMIT_TIME = $(shell git log -1 --format=%ct | xargs -I{} date -u -r {} +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "") LDFLAGS = -X github.com/free5gc/util/version.VERSION=$(VERSION) \ -X github.com/free5gc/util/version.BUILD_TIME=$(BUILD_TIME) \ -X github.com/free5gc/util/version.COMMIT_HASH=$(COMMIT_HASH) \ -X github.com/free5gc/util/version.COMMIT_TIME=$(COMMIT_TIME) NF = nf -NF_GO_FILES = $(shell find -name "*.go" ! -name "*_test.go") +NF_GO_FILES = $(shell find . -name "*.go" ! -name "*_test.go") debug: GCFLAGS += -N -l diff --git a/config/nfcfg.yaml b/config/nfcfg.yaml index 45001e3..39e1236 100644 --- a/config/nfcfg.yaml +++ b/config/nfcfg.yaml @@ -6,7 +6,7 @@ configuration: nfName: NF # the name of this NF sbi: # Service-based interface information scheme: http # the protocol for sbi (http or https) - bindingIPv4: 127.0.0.163 # IP used to bind the service + bindingIPv4: 127.0.0.1 # IP used to bind the service port: 8000 # Port used to bind the service tls: # the local path of TLS key pem: cert/nf.pem # NF TLS Certificate diff --git a/internal/sbi/api_foodpicker.go b/internal/sbi/api_foodpicker.go new file mode 100644 index 0000000..3ac2080 --- /dev/null +++ b/internal/sbi/api_foodpicker.go @@ -0,0 +1,60 @@ +package sbi + +import ( + "math/rand" + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +var ( + foodList = []string{"Beef Noodle", "Burger", "Bento", "McDonalds", "Luway"} + mu sync.Mutex // To ensure thread-safe operations on the food list +) + +func (s *Server) getFoodPickerRoutes() []Route { + return []Route{ + { + Name: "FoodPicker GET", + Method: http.MethodGet, + Pattern: "", + APIFunc: func(c *gin.Context) { + mu.Lock() + defer mu.Unlock() + + rand.Seed(time.Now().UnixNano()) + randomFood := foodList[rand.Intn(len(foodList))] + c.JSON(http.StatusOK, gin.H{"lunch/dinner pick": randomFood}) + }, + // curl -X GET http://127.0.0.1:8000/foodpicker -w "\n" + }, + { + Name: "FoodPicker POST", + Method: http.MethodPost, + Pattern: "", + APIFunc: func(c *gin.Context) { + var newFood struct { + Name string `json:"name"` + } + if err := c.ShouldBindJSON(&newFood); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + mu.Lock() + defer mu.Unlock() + + foodList = append(foodList, newFood.Name) + c.JSON(http.StatusOK, gin.H{"message": "Food added successfully", "foodList": foodList}) + }, + // curl -X POST http://127.0.0.1:8000/foodpicker -H "Content-Type: application/json" -d '{"name":"McDonalds"}' -w "\n" + }, + } +} + +// foodpicker.go +func (s *Server) GetFoodPickerRoutes() []Route { + return s.getFoodPickerRoutes() +} diff --git a/internal/sbi/api_foodpicker_test.go b/internal/sbi/api_foodpicker_test.go new file mode 100644 index 0000000..97496b0 --- /dev/null +++ b/internal/sbi/api_foodpicker_test.go @@ -0,0 +1,118 @@ +package sbi_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 setupFoodPickerServer(t *testing.T) *sbi.Server { + t.Helper() + gin.SetMode(gin.TestMode) + + mockCtrl := gomock.NewController(t) + nfApp := sbi.NewMocknfApp(mockCtrl) + nfApp.EXPECT().Config().Return(&factory.Config{ + Configuration: &factory.Configuration{ + Sbi: &factory.Sbi{ + Port: 8000, + }, + }, + }).AnyTimes() + + return sbi.NewServer(nfApp, "") +} + +func Test_FoodPickerGET(t *testing.T) { + server := setupFoodPickerServer(t) + + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + + req, err := http.NewRequest(http.MethodGet, "/foodpicker", nil) + if err != nil { + t.Fatalf("Failed to create GET request: %v", err) + } + ginCtx.Request = req + + // Call the GET handler directly + for _, route := range server.GetFoodPickerRoutes() { + if route.Method == http.MethodGet { + route.APIFunc(ginCtx) + } + } + + if httpRecorder.Code != http.StatusOK { + t.Errorf("Expected status 200 OK, got %d", httpRecorder.Code) + } + + var body map[string]string + if err := json.Unmarshal(httpRecorder.Body.Bytes(), &body); err != nil { + t.Errorf("Failed to parse response JSON: %v", err) + } + + if _, ok := body["lunch/dinner pick"]; !ok { + t.Errorf("Expected key 'lunch/dinner pick' in response body") + } +} + +func Test_FoodPickerPOST(t *testing.T) { + server := setupFoodPickerServer(t) + + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + + payload := map[string]string{"name": "Sushi"} + jsonData, _ := json.Marshal(payload) + + req, err := http.NewRequest(http.MethodPost, "/foodpicker", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatalf("Failed to create POST request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + ginCtx.Request = req + + // Call the POST handler directly + for _, route := range server.GetFoodPickerRoutes() { + if route.Method == http.MethodPost { + route.APIFunc(ginCtx) + } + } + + if httpRecorder.Code != http.StatusOK { + t.Errorf("Expected status 200 OK, got %d", httpRecorder.Code) + } + + var resp map[string]interface{} + if err := json.Unmarshal(httpRecorder.Body.Bytes(), &resp); err != nil { + t.Errorf("Failed to parse response JSON: %v", err) + } + + if resp["message"] != "Food added successfully" { + t.Errorf("Expected success message, got %v", resp["message"]) + } + + // Optional: Check if "Sushi" is present in returned list + list, ok := resp["foodList"].([]interface{}) + if !ok { + t.Errorf("Expected foodList array in response") + } else { + found := false + for _, f := range list { + if f == "Sushi" { + found = true + break + } + } + if !found { + t.Errorf("Expected 'Sushi' to be in foodList") + } + } +} diff --git a/internal/sbi/router.go b/internal/sbi/router.go index b62139f..2e9fa76 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()) + foodPickerGroup := router.Group("/foodpicker") + applyRoutes(foodPickerGroup, s.getFoodPickerRoutes()) + return router } From 61c189044533971b01caf536bd85b7162dcea1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=93=B2=E6=9A=90?= Date: Thu, 2 Oct 2025 23:47:54 +0800 Subject: [PATCH 2/5] fix: revise test file while keeping Makefile and config intact --- Makefile | 6 +++--- config/nfcfg.yaml | 2 +- internal/sbi/api_foodpicker.go | 8 ++++---- internal/sbi/api_foodpicker_test.go | 9 ++++++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 7121631..96d2a93 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ GO_BIN_PATH = bin -VERSION = $(shell git describe --tags 2>/dev/null || echo "v0.0.0") +VERSION = $(shell git describe --tags) BUILD_TIME = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") COMMIT_HASH = $(shell git submodule status | grep $(GO_SRC_PATH)/$(@F) | awk '{print $$(1)}' | cut -c1-8) -COMMIT_TIME = $(shell git log -1 --format=%ct | xargs -I{} date -u -r {} +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "") +COMMIT_TIME = $(shell git log --pretty="@%at" -1 | xargs date -u +"%Y-%m-%dT%H:%M:%SZ" -d) LDFLAGS = -X github.com/free5gc/util/version.VERSION=$(VERSION) \ -X github.com/free5gc/util/version.BUILD_TIME=$(BUILD_TIME) \ -X github.com/free5gc/util/version.COMMIT_HASH=$(COMMIT_HASH) \ -X github.com/free5gc/util/version.COMMIT_TIME=$(COMMIT_TIME) NF = nf -NF_GO_FILES = $(shell find . -name "*.go" ! -name "*_test.go") +NF_GO_FILES = $(shell find -name "*.go" ! -name "*_test.go") debug: GCFLAGS += -N -l diff --git a/config/nfcfg.yaml b/config/nfcfg.yaml index 39e1236..45001e3 100644 --- a/config/nfcfg.yaml +++ b/config/nfcfg.yaml @@ -6,7 +6,7 @@ configuration: nfName: NF # the name of this NF sbi: # Service-based interface information scheme: http # the protocol for sbi (http or https) - bindingIPv4: 127.0.0.1 # IP used to bind the service + bindingIPv4: 127.0.0.163 # IP used to bind the service port: 8000 # Port used to bind the service tls: # the local path of TLS key pem: cert/nf.pem # NF TLS Certificate diff --git a/internal/sbi/api_foodpicker.go b/internal/sbi/api_foodpicker.go index 3ac2080..39c5cd5 100644 --- a/internal/sbi/api_foodpicker.go +++ b/internal/sbi/api_foodpicker.go @@ -4,7 +4,6 @@ import ( "math/rand" "net/http" "sync" - "time" "github.com/gin-gonic/gin" ) @@ -24,11 +23,10 @@ func (s *Server) getFoodPickerRoutes() []Route { mu.Lock() defer mu.Unlock() - rand.Seed(time.Now().UnixNano()) randomFood := foodList[rand.Intn(len(foodList))] c.JSON(http.StatusOK, gin.H{"lunch/dinner pick": randomFood}) }, - // curl -X GET http://127.0.0.1:8000/foodpicker -w "\n" + // curl -X GET http://127.0.0.163:8000/foodpicker -w "\n" }, { Name: "FoodPicker POST", @@ -49,7 +47,9 @@ func (s *Server) getFoodPickerRoutes() []Route { foodList = append(foodList, newFood.Name) c.JSON(http.StatusOK, gin.H{"message": "Food added successfully", "foodList": foodList}) }, - // curl -X POST http://127.0.0.1:8000/foodpicker -H "Content-Type: application/json" -d '{"name":"McDonalds"}' -w "\n" + // curl -X POST http://127.0.0.163:8000/foodpicker \ + // -H "Content-Type: application/json" \ + // -d '{"name":"McDonalds"}' -w "\n" }, } } diff --git a/internal/sbi/api_foodpicker_test.go b/internal/sbi/api_foodpicker_test.go index 97496b0..be0a79c 100644 --- a/internal/sbi/api_foodpicker_test.go +++ b/internal/sbi/api_foodpicker_test.go @@ -54,7 +54,7 @@ func Test_FoodPickerGET(t *testing.T) { } var body map[string]string - if err := json.Unmarshal(httpRecorder.Body.Bytes(), &body); err != nil { + if err = json.Unmarshal(httpRecorder.Body.Bytes(), &body); err != nil { t.Errorf("Failed to parse response JSON: %v", err) } @@ -70,7 +70,10 @@ func Test_FoodPickerPOST(t *testing.T) { ginCtx, _ := gin.CreateTestContext(httpRecorder) payload := map[string]string{"name": "Sushi"} - jsonData, _ := json.Marshal(payload) + jsonData, err := json.Marshal(payload) + if err != nil { + t.Fatalf("Failed to marshal payload: %v", err) + } req, err := http.NewRequest(http.MethodPost, "/foodpicker", bytes.NewBuffer(jsonData)) if err != nil { @@ -91,7 +94,7 @@ func Test_FoodPickerPOST(t *testing.T) { } var resp map[string]interface{} - if err := json.Unmarshal(httpRecorder.Body.Bytes(), &resp); err != nil { + if err = json.Unmarshal(httpRecorder.Body.Bytes(), &resp); err != nil { t.Errorf("Failed to parse response JSON: %v", err) } From 8a6102ae0336cc6faad44a93ca902164bc2fed84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=93=B2=E6=9A=90?= Date: Sat, 4 Oct 2025 01:57:32 +0800 Subject: [PATCH 3/5] fix: router.go import grouping fixed --- internal/sbi/router.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/sbi/router.go b/internal/sbi/router.go index b4bd121..74d3cbd 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -4,12 +4,12 @@ import ( "fmt" "net/http" - "github.com/Alonza0314/nf-example/internal/logger" - "github.com/Alonza0314/nf-example/pkg/app" - "github.com/gin-gonic/gin" - "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" + "github.com/gin-gonic/gin" + + "github.com/Alonza0314/nf-example/internal/logger" + "github.com/Alonza0314/nf-example/pkg/app" ) type Route struct { @@ -48,9 +48,9 @@ func newRouter(s *Server) *gin.Engine { spyFamilyGroup := router.Group("/spyfamily") applyRoutes(spyFamilyGroup, s.getSpyFamilyRoute()) - foodPickerGroup := router.Group("/foodpicker") + foodPickerGroup := router.Group("/foodpicker") // lab6 applyRoutes(foodPickerGroup, s.getFoodPickerRoutes()) - + taskGroup := router.Group("/task") applyRoutes(taskGroup, s.getTaskRoute()) From ec9eccf428fafb4c7dd5fd46fc6bec08eee4399b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=93=B2=E6=9A=90?= Date: Sat, 4 Oct 2025 02:23:23 +0800 Subject: [PATCH 4/5] fix: router.go: imports sorted alphabetically --- internal/sbi/router.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/sbi/router.go b/internal/sbi/router.go index 74d3cbd..e927e33 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -4,12 +4,11 @@ import ( "fmt" "net/http" + "github.com/Alonza0314/nf-example/internal/logger" + "github.com/Alonza0314/nf-example/pkg/app" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" "github.com/gin-gonic/gin" - - "github.com/Alonza0314/nf-example/internal/logger" - "github.com/Alonza0314/nf-example/pkg/app" ) type Route struct { From e22ed9916b4b987fee6987291c19787bc29641c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=93=B2=E6=9A=90?= Date: Sat, 4 Oct 2025 09:38:19 +0800 Subject: [PATCH 5/5] fix: router.go: gci auto format --- internal/sbi/router.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/sbi/router.go b/internal/sbi/router.go index e927e33..5ee955b 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -6,9 +6,10 @@ import ( "github.com/Alonza0314/nf-example/internal/logger" "github.com/Alonza0314/nf-example/pkg/app" + "github.com/gin-gonic/gin" + "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" - "github.com/gin-gonic/gin" ) type Route struct {