diff --git a/internal/sbi/api_foodpicker.go b/internal/sbi/api_foodpicker.go new file mode 100644 index 0000000..39c5cd5 --- /dev/null +++ b/internal/sbi/api_foodpicker.go @@ -0,0 +1,60 @@ +package sbi + +import ( + "math/rand" + "net/http" + "sync" + + "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() + + randomFood := foodList[rand.Intn(len(foodList))] + c.JSON(http.StatusOK, gin.H{"lunch/dinner pick": randomFood}) + }, + // curl -X GET http://127.0.0.163: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.163: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..be0a79c --- /dev/null +++ b/internal/sbi/api_foodpicker_test.go @@ -0,0 +1,121 @@ +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, 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 { + 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 693e589..5ee955b 100644 --- a/internal/sbi/router.go +++ b/internal/sbi/router.go @@ -48,6 +48,9 @@ func newRouter(s *Server) *gin.Engine { spyFamilyGroup := router.Group("/spyfamily") applyRoutes(spyFamilyGroup, s.getSpyFamilyRoute()) + foodPickerGroup := router.Group("/foodpicker") // lab6 + applyRoutes(foodPickerGroup, s.getFoodPickerRoutes()) + taskGroup := router.Group("/task") applyRoutes(taskGroup, s.getTaskRoute())