diff --git a/internal/context/context.go b/internal/context/context.go
index 3b98168..4649273 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -18,6 +18,7 @@ type NFContext struct {
SBIPort int
SpyFamilyData map[string]string
+ NoteData map[string]string
}
var nfContext = NFContext{}
@@ -57,6 +58,9 @@ func InitNfContext() {
"Henry": "Henderson",
"Martha": "Marriott",
}
+ nfContext.NoteData = map[string]string{
+ "User_Guide": "/
/ Show the content of the note with title .\n",
+ }
}
func GetSelf() *NFContext {
diff --git a/internal/sbi/api_notebook.go b/internal/sbi/api_notebook.go
new file mode 100644
index 0000000..c0c5491
--- /dev/null
+++ b/internal/sbi/api_notebook.go
@@ -0,0 +1,110 @@
+package sbi
+
+import (
+ "net/http"
+
+ "github.com/andy89923/nf-example/internal/logger"
+ "github.com/gin-gonic/gin"
+)
+
+func (s *Server) getNotebookRoute() []Route {
+ return []Route{
+ {
+ Name: "Show Note",
+ Method: http.MethodGet,
+ Pattern: "/:Title",
+ APIFunc: s.HTTPShowNote,
+ // Use
+ // curl -X GET http://127.0.0.163:8000/notebook/User_Guide -w "\n"
+ // "Title: User_Guide"
+ // " // Show the content of the note with title ."
+ },
+ {
+ Name: "Create Note",
+ Method: http.MethodPut,
+ Pattern: "/:Title/:Content",
+ APIFunc: s.HTTPCreateNote,
+ // Use
+ // curl -X PUT http://127.0.0.163:8000/notebook/New_Title/New_content -w "\n"
+ // "Title: New_Title"
+ // "Content:"
+ // " New_content"
+ },
+ {
+ Name: "Update Note",
+ Method: http.MethodPost,
+ Pattern: "/:Title/:Content",
+ APIFunc: s.HTTPUpdateNote,
+ // Use
+ // curl -X POST http://127.0.0.163:8000/notebook/New_Title/New_content -w "\n"
+ // "Title: New_Title"
+ // "Content:"
+ // " New_content"
+ },
+ {
+ Name: "Note Append with whitespace at front.",
+ Method: http.MethodPost,
+ Pattern: "/:Title/append/:Content_append",
+ APIFunc: s.HTTPNoteWhitespaceAppend,
+ // Use
+ // curl -X POST http://127.0.0.163:8000/notebook/New_Title/append/new_content_to_be_append. -w "\n"
+ // "Title: New_Title"
+ // "Content:"
+ // " New_content new_content_to_be_append."
+ },
+ }
+}
+
+func (s *Server) HTTPShowNote(c *gin.Context) {
+ logger.SBILog.Infof("In HTTPShowNote")
+
+ targetName := c.Param("Title")
+ if targetName == "" {
+ c.String(http.StatusBadRequest, "No name provided")
+ return
+ }
+
+ s.Processor().FindNote(c, targetName)
+}
+
+func (s *Server) HTTPUpdateNote(c *gin.Context) {
+ logger.SBILog.Infof("In HTTPUpdateNote")
+
+ targetName := c.Param("Title")
+ if targetName == "" {
+ c.String(http.StatusBadRequest, "No name provided")
+ return
+ }
+
+ newContent := c.Param("Content")
+
+ s.Processor().UpdateNote(c, targetName, newContent)
+}
+
+func (s *Server) HTTPCreateNote(c *gin.Context) {
+ logger.SBILog.Infof("In HTTPCreateNote")
+
+ targetName := c.Param("Title")
+ if targetName == "" {
+ c.String(http.StatusBadRequest, "No name provided")
+ return
+ }
+
+ newContent := c.Param("Content")
+
+ s.Processor().CreateNote(c, targetName, newContent)
+}
+
+func (s *Server) HTTPNoteWhitespaceAppend(c *gin.Context) {
+ logger.SBILog.Infof("In HTTPNoteAppend")
+
+ targetName := c.Param("Title")
+ if targetName == "" {
+ c.String(http.StatusBadRequest, "No name provided")
+ return
+ }
+
+ newContent := c.Param("Content_append")
+
+ s.Processor().NoteWhitespaceAppend(c, targetName, newContent)
+}
diff --git a/internal/sbi/api_notebook_test.go b/internal/sbi/api_notebook_test.go
new file mode 100644
index 0000000..ec53531
--- /dev/null
+++ b/internal/sbi/api_notebook_test.go
@@ -0,0 +1,102 @@
+package sbi_test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/andy89923/nf-example/internal/sbi"
+ "github.com/andy89923/nf-example/pkg/factory"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/mock/gomock"
+)
+
+func Test_getNotebookRoutes(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 name provided", func(t *testing.T) {
+ const EXPECTED_STATUS = http.StatusBadRequest
+ const EXPECTED_BODY = "No name provided"
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+
+ var err error
+ ginCtx.Request, err = http.NewRequest("GET", "/notebook/", nil)
+ if err != nil {
+ t.Errorf("Failed to create request: %s", err)
+ return
+ }
+
+ server.HTTPShowNote(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("No name provided", func(t *testing.T) {
+ const EXPECTED_STATUS = http.StatusBadRequest
+ const EXPECTED_BODY = "No name provided"
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+
+ var err error
+ ginCtx.Request, err = http.NewRequest("POST", "/notebook/", nil)
+ if err != nil {
+ t.Errorf("Failed to create request: %s", err)
+ return
+ }
+
+ server.HTTPUpdateNote(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("No name provided", func(t *testing.T) {
+ const EXPECTED_STATUS = http.StatusBadRequest
+ const EXPECTED_BODY = "No name provided"
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+
+ var err error
+ ginCtx.Request, err = http.NewRequest("POST", "/notebook//append/", nil)
+ if err != nil {
+ t.Errorf("Failed to create request: %s", err)
+ return
+ }
+
+ server.HTTPUpdateNote(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/notebook.go b/internal/sbi/processor/notebook.go
new file mode 100644
index 0000000..8c8e34f
--- /dev/null
+++ b/internal/sbi/processor/notebook.go
@@ -0,0 +1,58 @@
+package processor
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+func (p *Processor) FindNote(c *gin.Context, targetName string) {
+ if content, ok := p.Context().NoteData[targetName]; ok {
+ c.String(http.StatusOK, fmt.Sprintf("Title: %s\nContent:\n\t%s\n", targetName, content))
+ return
+ }
+ c.String(http.StatusNotFound, fmt.Sprintf("[%s] not found in Notebook\n", targetName))
+}
+
+func (p *Processor) UpdateNote(c *gin.Context, targetName string, newContent string) {
+ p.Context().NoteData[targetName] = newContent
+
+ if content, ok := p.Context().NoteData[targetName]; ok {
+ c.String(http.StatusOK, fmt.Sprintf("Title: %s\nContent:\n\t%s\n", targetName, content))
+ return
+ }
+ c.String(http.StatusNotFound, fmt.Sprintf("[%s] not found in Notebook\n", targetName))
+}
+
+func (p *Processor) CreateNote(c *gin.Context, targetName string, newContent string) {
+ if _, ok := p.Context().NoteData[targetName]; !ok {
+ p.Context().NoteData[targetName] = newContent
+
+ content := p.Context().NoteData[targetName]
+ c.String(http.StatusOK, fmt.Sprintf("Title: %s\nContent:\n\t%s\n", targetName, content))
+ return
+ } else if ok {
+ c.String(http.StatusForbidden, fmt.Sprintf(
+ "[%s] already exist. Please use POST to modify the content.\n",
+ targetName),
+ )
+ }
+}
+
+func (p *Processor) NoteWhitespaceAppend(c *gin.Context, targetName string, newContent string) {
+ if _, ok := p.Context().NoteData[targetName]; ok {
+ var sb strings.Builder
+ sb.WriteString(p.Context().NoteData[targetName])
+ sb.WriteString(" ")
+ sb.WriteString(newContent)
+
+ p.Context().NoteData[targetName] = sb.String()
+
+ content := p.Context().NoteData[targetName]
+ c.String(http.StatusOK, fmt.Sprintf("Title: %s\nContent:\n\t%s\n", targetName, content))
+ return
+ }
+ c.String(http.StatusNotFound, fmt.Sprintf("[%s] not found in Notebook\n", targetName))
+}
diff --git a/internal/sbi/processor/notebook_test.go b/internal/sbi/processor/notebook_test.go
new file mode 100644
index 0000000..a9e9996
--- /dev/null
+++ b/internal/sbi/processor/notebook_test.go
@@ -0,0 +1,125 @@
+package processor_test
+
+import (
+ "net/http/httptest"
+ "testing"
+
+ nf_context "github.com/andy89923/nf-example/internal/context"
+ "github.com/andy89923/nf-example/internal/sbi/processor"
+ "github.com/gin-gonic/gin"
+ gomock "go.uber.org/mock/gomock"
+)
+
+func Test_Note(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("Find and Show Note", func(t *testing.T) {
+ const INPUT_NAME = "User_Guide"
+ const EXPECTED_STATUS = 200
+ const EXPECTED_BODY = "Title: " + INPUT_NAME +
+ "\nContent:\n\t" +
+ "// Show the content of the note with title .\n\n"
+
+ processorNf.EXPECT().Context().Return(&nf_context.NFContext{
+ NoteData: map[string]string{
+ "User_Guide": "// Show the content of the note with title .\n",
+ },
+ })
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+ processor.FindNote(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("Find Note That Does Not Exist", func(t *testing.T) {
+ const INPUT_NAME = "Andy"
+ const EXPECTED_STATUS = 404
+ const EXPECTED_BODY = "[" + INPUT_NAME + "] not found in Notebook\n"
+
+ processorNf.EXPECT().Context().Return(&nf_context.NFContext{
+ NoteData: map[string]string{
+ "User_Guide": "// Show the content of the note with title .\n",
+ },
+ })
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+ processor.FindNote(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("Update Note", func(t *testing.T) {
+ const INPUT_NAME = "new_note_title"
+ const INPUT_CONTENT = "Content"
+ const EXPECTED_STATUS = 200
+ const EXPECTED_BODY = "Title: " + INPUT_NAME +
+ "\nContent:\n\t" + INPUT_CONTENT + "\n"
+
+ processorNf.EXPECT().Context().Return(&nf_context.NFContext{
+ NoteData: map[string]string{
+ "User_Guide": "// Show the content of the note with title .\n",
+ },
+ }).AnyTimes()
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+ processor.UpdateNote(ginCtx, INPUT_NAME, INPUT_CONTENT)
+
+ 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("Append with Whitespace Prefix on Note", func(t *testing.T) {
+ const INPUT_NAME = "new_note_title"
+ const INPUT_CONTENT = "can't_contain_whitespace."
+ const EXPECTED_STATUS = 200
+ const EXPECTED_BODY = "Title: " + INPUT_NAME +
+ "\nContent:\n\tContent " + INPUT_CONTENT + "\n"
+
+ processorNf.EXPECT().Context().Return(&nf_context.NFContext{
+ NoteData: map[string]string{
+ "User_Guide": "// Show the content of the note with title .\n",
+ },
+ }).AnyTimes()
+
+ httpRecorder := httptest.NewRecorder()
+ ginCtx, _ := gin.CreateTestContext(httpRecorder)
+ processor.NoteWhitespaceAppend(ginCtx, INPUT_NAME, INPUT_CONTENT)
+
+ 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 1d16794..3da7a84 100644
--- a/internal/sbi/router.go
+++ b/internal/sbi/router.go
@@ -63,6 +63,9 @@ func newRouter(s *Server) *gin.Engine {
helloGroup := router.Group("/hello")
applyRoutes(helloGroup, s.getHelloRoute())
+ notebookGroup := router.Group("/notebook")
+ applyRoutes(notebookGroup, s.getNotebookRoute())
+
return router
}