diff --git a/internal/context/context.go b/internal/context/context.go index 0d1e5cd..e5b80d5 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -38,6 +38,8 @@ type NFContext struct { Fortunes []string FortuneMutex sync.RWMutex + AttendanceData []string + TimeZoneData map[string]string } @@ -85,6 +87,7 @@ func InitNfContext() { "Henry": "Henderson", "Martha": "Marriott", } + nfContext.AttendanceData = []string{} nfContext.MessageRecord = []string{} diff --git a/internal/sbi/api_attendance.go b/internal/sbi/api_attendance.go new file mode 100644 index 0000000..6e0e440 --- /dev/null +++ b/internal/sbi/api_attendance.go @@ -0,0 +1,51 @@ +package sbi + +import ( + "io" + "net/http" + + "github.com/Alonza0314/nf-example/internal/logger" + "github.com/gin-gonic/gin" +) + +func (s *Server) getAttendanceRoute() []Route { + return []Route{ + { + Name: "Get Attendance", + Method: http.MethodGet, + Pattern: "/", + APIFunc: s.GetAttendance, + }, + // curl -X GET http://127.0.0.163:8000/attendance/ -w "\n" + { + Name: "Post Attendance", + Method: http.MethodPost, + Pattern: "/", + APIFunc: s.PostAttendance, + }, + // curl -X POST http://127.0.0.163:8000/attendance/ -d 'John' -w "\n" + } +} + +func (s *Server) GetAttendance(c *gin.Context) { + logger.SBILog.Infof("In HTTPGetAttandence") + + s.Processor().ReturnAttendance(c) +} + +func (s *Server) PostAttendance(c *gin.Context) { + logger.SBILog.Infof("In HTTPPostAttendance") + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read body"}) + return + } + + targetName := string(body) + if targetName == "" { + c.String(http.StatusBadRequest, "error: no name provided") + return + } + s.Processor().PostAttendance(c, targetName) +} diff --git a/internal/sbi/api_attendance_test.go b/internal/sbi/api_attendance_test.go new file mode 100644 index 0000000..38ec385 --- /dev/null +++ b/internal/sbi/api_attendance_test.go @@ -0,0 +1,49 @@ +package sbi_test + +import ( + "net/http" + "net/http/httptest" + "strings" + "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 Test_Attendance(t *testing.T) { + 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() + server := sbi.NewServer(nfApp, "") + + t.Run("No attendance name provided", func(t *testing.T) { + const EXPECTED_STATUS = http.StatusBadRequest + const EXPECTED_BODY = "error: no name provided" + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + var err error + ginCtx.Request, err = http.NewRequest("POST", "/attendance", strings.NewReader("")) + if err != nil { + t.Errorf("Failed to create request: %s", err) + return + } + + server.PostAttendance(ginCtx) + if httpRecorder.Code != EXPECTED_STATUS { + t.Errorf("Expected status code %d, got %d", EXPECTED_STATUS, httpRecorder.Code) + } + if httpRecorder.Body.String() != EXPECTED_BODY { + t.Errorf("Expected body %s, got %s", EXPECTED_BODY, httpRecorder.Body.String()) + } + }) +} diff --git a/internal/sbi/processor/attendance.go b/internal/sbi/processor/attendance.go new file mode 100644 index 0000000..b15a45f --- /dev/null +++ b/internal/sbi/processor/attendance.go @@ -0,0 +1,38 @@ +package processor + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func (p *Processor) ReturnAttendance(c *gin.Context) { + con := p.Context() + + if len(con.AttendanceData) == 0 { + c.String(http.StatusOK, "No attendance recorded") + return + } else { + names := "" + for _, name := range con.AttendanceData { + names += name + ", " + } + c.String(http.StatusOK, "Attendance: "+names[:len(names)-2]) + return + } +} + +func (p *Processor) PostAttendance(c *gin.Context, targetName string) { + con := p.Context() + + for n := range con.AttendanceData { + if con.AttendanceData[n] == targetName { + c.String(http.StatusConflict, "Attendance already recorded: "+targetName) + return + } + } + + con.AttendanceData = append(con.AttendanceData, targetName) + + c.String(http.StatusOK, "Attendance recorded: "+targetName) +} diff --git a/internal/sbi/processor/attendance_test.go b/internal/sbi/processor/attendance_test.go new file mode 100644 index 0000000..f564e73 --- /dev/null +++ b/internal/sbi/processor/attendance_test.go @@ -0,0 +1,112 @@ +package processor_test + +import ( + "net/http/httptest" + "testing" + + nf_context "github.com/Alonza0314/nf-example/internal/context" + "github.com/Alonza0314/nf-example/internal/sbi/processor" + "github.com/gin-gonic/gin" + gomock "go.uber.org/mock/gomock" +) + +func Test_ReturnAttendance(t *testing.T) { + gin.SetMode(gin.TestMode) + + mockCtrl := gomock.NewController(t) + processorNf := processor.NewMockProcessorNf(mockCtrl) + processor, err := processor.NewProcessor(processorNf) + if err != nil { + t.Errorf("Failed to create processor: %s", err) + return + } + t.Run("No Attendance Recorded", func(t *testing.T) { + const EXPECTED_STATUS = 200 + const EXPECTED_BODY = "No attendance recorded" + processorNf.EXPECT().Context().Return(&nf_context.NFContext{ + AttendanceData: []string{}, + }) + + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + processor.ReturnAttendance(ginCtx) + + if httpRecorder.Code != EXPECTED_STATUS { + t.Errorf("Expected status code %d, got %d", EXPECTED_STATUS, httpRecorder.Code) + } + + if httpRecorder.Body.String() != EXPECTED_BODY { + t.Errorf("Expected body %s, got %s", EXPECTED_BODY, httpRecorder.Body.String()) + } + }) + + t.Run("Some Attendance Recorded", func(t *testing.T) { + const EXPECTED_STATUS = 200 + const EXPECTED_BODY = "Attendance: Alice, Bob, Charlie" + processorNf.EXPECT().Context().Return(&nf_context.NFContext{ + AttendanceData: []string{"Alice", "Bob", "Charlie"}, + }) + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + processor.ReturnAttendance(ginCtx) + + if httpRecorder.Code != EXPECTED_STATUS { + t.Errorf("Expected status code %d, got %d", EXPECTED_STATUS, httpRecorder.Code) + } + + if httpRecorder.Body.String() != EXPECTED_BODY { + t.Errorf("Expected body %s, got %s", EXPECTED_BODY, httpRecorder.Body.String()) + } + }) +} + +func Test_PostAttandence(t *testing.T) { + gin.SetMode(gin.TestMode) + mockCtrl := gomock.NewController(t) + processorNf := processor.NewMockProcessorNf(mockCtrl) + processor, err := processor.NewProcessor(processorNf) + if err != nil { + t.Errorf("Failed to create processor: %s", err) + return + } + + t.Run("Post New Attendance", func(t *testing.T) { + const INPUT_NAME = "David" + const EXPECTED_STATUS = 200 + const EXPECTED_BODY = "Attendance recorded: " + INPUT_NAME + processorNf.EXPECT().Context().Return(&nf_context.NFContext{ + AttendanceData: []string{"Alice", "Bob", "Charlie"}, + }) + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + processor.PostAttendance(ginCtx, INPUT_NAME) + + if httpRecorder.Code != EXPECTED_STATUS { + t.Errorf("Expected status code %d, got %d", EXPECTED_STATUS, httpRecorder.Code) + } + + if httpRecorder.Body.String() != EXPECTED_BODY { + t.Errorf("Expected body %s, got %s", EXPECTED_BODY, httpRecorder.Body.String()) + } + }) + + t.Run("Post Duplicate Attendance", func(t *testing.T) { + const INPUT_NAME = "Alice" + const EXPECTED_STATUS = 409 + const EXPECTED_BODY = "Attendance already recorded: " + INPUT_NAME + processorNf.EXPECT().Context().Return(&nf_context.NFContext{ + AttendanceData: []string{"Alice", "Bob", "Charlie"}, + }) + httpRecorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(httpRecorder) + processor.PostAttendance(ginCtx, INPUT_NAME) + + if httpRecorder.Code != EXPECTED_STATUS { + t.Errorf("Expected status code %d, got %d", EXPECTED_STATUS, httpRecorder.Code) + } + + if httpRecorder.Body.String() != EXPECTED_BODY { + t.Errorf("Expected body %s, got %s", EXPECTED_BODY, httpRecorder.Body.String()) + } + }) +} diff --git a/internal/sbi/router.go b/internal/sbi/router.go index 50f444f..598557c 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()) + attendanceGroup := router.Group("/attendance") + applyRoutes(attendanceGroup, s.getAttendanceRoute()) taskGroup := router.Group("/task") applyRoutes(taskGroup, s.getTaskRoute())