From b4ec7e92f35b0c33a16c9f5d2c410be5c5c17223 Mon Sep 17 00:00:00 2001 From: name <83965216+Jasonkuuo@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:25:26 +0800 Subject: [PATCH 1/4] feat: Init commit --- internal/sbi/processor/attendance.go | 38 ++++++++ internal/sbi/processor/attendance_test.go | 113 ++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 internal/sbi/processor/attendance.go create mode 100644 internal/sbi/processor/attendance_test.go 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..fe91d24 --- /dev/null +++ b/internal/sbi/processor/attendance_test.go @@ -0,0 +1,113 @@ +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()) + } + }) +} From 98e246db3388a18a5e36125ef34d44683ebdedf1 Mon Sep 17 00:00:00 2001 From: name <83965216+Jasonkuuo@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:26:35 +0800 Subject: [PATCH 2/4] feat: Init commit --- internal/context/context.go | 2 ++ internal/sbi/api_attendance.go | 52 +++++++++++++++++++++++++++++ internal/sbi/api_attendance_test.go | 49 +++++++++++++++++++++++++++ internal/sbi/router.go | 3 ++ 4 files changed, 106 insertions(+) create mode 100644 internal/sbi/api_attendance.go create mode 100644 internal/sbi/api_attendance_test.go diff --git a/internal/context/context.go b/internal/context/context.go index 2bfe8b2..6504b68 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -18,6 +18,7 @@ type NFContext struct { SBIPort int SpyFamilyData map[string]string + AttendanceData []string } var nfContext = NFContext{} @@ -57,6 +58,7 @@ func InitNfContext() { "Henry": "Henderson", "Martha": "Marriott", } + nfContext.AttendanceData = []string{} } func GetSelf() *NFContext { diff --git a/internal/sbi/api_attendance.go b/internal/sbi/api_attendance.go new file mode 100644 index 0000000..2349469 --- /dev/null +++ b/internal/sbi/api_attendance.go @@ -0,0 +1,52 @@ +package sbi + +import ( + "net/http" + + "io" + + "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/router.go b/internal/sbi/router.go index b62139f..31755b5 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()) + attendanceGroup := router.Group("/attendance") + applyRoutes(attendanceGroup, s.getAttendanceRoute()) + return router } From 23468b3f95321a490ac9a154e36056e6e778196d Mon Sep 17 00:00:00 2001 From: name <83965216+Jasonkuuo@users.noreply.github.com> Date: Sat, 11 Oct 2025 15:12:29 +0800 Subject: [PATCH 3/4] fix: lint errors --- internal/context/context.go | 3 ++- internal/sbi/api_attendance.go | 3 +-- internal/sbi/processor/attendance_test.go | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/context/context.go b/internal/context/context.go index b3fbe5f..4e834f5 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -24,7 +24,6 @@ type NFContext struct { SBIPort int SpyFamilyData map[string]string - AttendanceData []string MessageRecord []string MessageMu sync.Mutex @@ -38,6 +37,8 @@ type NFContext struct { Fortunes []string FortuneMutex sync.RWMutex + + AttendanceData []string } type Message struct { diff --git a/internal/sbi/api_attendance.go b/internal/sbi/api_attendance.go index 2349469..6e0e440 100644 --- a/internal/sbi/api_attendance.go +++ b/internal/sbi/api_attendance.go @@ -1,9 +1,8 @@ package sbi import ( - "net/http" - "io" + "net/http" "github.com/Alonza0314/nf-example/internal/logger" "github.com/gin-gonic/gin" diff --git a/internal/sbi/processor/attendance_test.go b/internal/sbi/processor/attendance_test.go index fe91d24..f564e73 100644 --- a/internal/sbi/processor/attendance_test.go +++ b/internal/sbi/processor/attendance_test.go @@ -65,7 +65,6 @@ func Test_PostAttandence(t *testing.T) { 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 From 3875abc90561a298bc2df1e7886d154a763f8e08 Mon Sep 17 00:00:00 2001 From: name <83965216+Jasonkuuo@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:10:14 +0800 Subject: [PATCH 4/4] fix: lint error --- internal/context/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/context/context.go b/internal/context/context.go index 72f37b3..e5b80d5 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -39,7 +39,7 @@ type NFContext struct { FortuneMutex sync.RWMutex AttendanceData []string - + TimeZoneData map[string]string }