Initial commit: WYSIWYG Markdown Editor - Go backend + React/TypeScript frontend with Tailwind CSS
Backend: - Cobra CLI with --data-dir, --port, --host flags - Gin HTTP server with REST API for markdown CRUD operations - File storage on disk (.md files only) - Comprehensive logrus logging - Backend tests with CRUD round-trip verification Frontend: - React 18 + TypeScript + Tailwind CSS - Markdown editor with live GFM preview (react-markdown + remark-gfm) - File management UI (list, create, open, save, delete) - Theme switcher with Dark/Light/System modes - Responsive design - Frontend tests with vitest Testing: - All backend tests pass (go test ./...) - All frontend tests pass (npm test)
This commit is contained in:
228
internal/api/api_test.go
Normal file
228
internal/api/api_test.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"markdown-editor/internal/logger"
|
||||
"markdown-editor/internal/storage"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestAPI_ListFiles(t *testing.T) {
|
||||
logger.SetLevel("error")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
store, err := storage.NewStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := NewAPI(store)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
api.RegisterRoutes(router)
|
||||
|
||||
// Create a test file
|
||||
testFile := filepath.Join(tempDir, "test.md")
|
||||
err = os.WriteFile(testFile, []byte("# Test Content"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/api/files", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp gin.H
|
||||
err = json.NewDecoder(w.Body).Decode(&resp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp["files"].([]interface{})) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(resp["files"].([]interface{})))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_CreateFile(t *testing.T) {
|
||||
logger.SetLevel("error")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
store, err := storage.NewStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := NewAPI(store)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
api.RegisterRoutes(router)
|
||||
|
||||
body, _ := json.Marshal(File{
|
||||
Name: "test.md",
|
||||
Content: "# Test Content",
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/api/files", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusCreated, w.Code)
|
||||
}
|
||||
|
||||
// Verify file was created
|
||||
if _, err := os.Stat(filepath.Join(tempDir, "test.md")); os.IsNotExist(err) {
|
||||
t.Error("File was not created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_GetFile(t *testing.T) {
|
||||
logger.SetLevel("error")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
store, err := storage.NewStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create test file
|
||||
err = store.CreateFile("test.md", "# Test Content")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := NewAPI(store)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
api.RegisterRoutes(router)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/api/files/test.md", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var resp File
|
||||
err = json.NewDecoder(w.Body).Decode(&resp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.Content != "# Test Content" {
|
||||
t.Errorf("Expected '# Test Content', got '%s'", resp.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_UpdateFile(t *testing.T) {
|
||||
logger.SetLevel("error")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
store, err := storage.NewStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create test file
|
||||
err = store.CreateFile("test.md", "# Original Content")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := NewAPI(store)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
api.RegisterRoutes(router)
|
||||
|
||||
body, _ := json.Marshal(File{
|
||||
Name: "test.md",
|
||||
Content: "# Updated Content",
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("PUT", "/api/files/test.md", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
// Verify file was updated
|
||||
content, _ := os.ReadFile(filepath.Join(tempDir, "test.md"))
|
||||
if string(content) != "# Updated Content" {
|
||||
t.Errorf("Expected '# Updated Content', got '%s'", string(content))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_DeleteFile(t *testing.T) {
|
||||
logger.SetLevel("error")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
store, err := storage.NewStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create test file
|
||||
err = store.CreateFile("test.md", "# Test Content")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := NewAPI(store)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
api.RegisterRoutes(router)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/api/files/test.md", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusNoContent, w.Code)
|
||||
}
|
||||
|
||||
// Verify file was deleted
|
||||
if _, err := os.Stat(filepath.Join(tempDir, "test.md")); !os.IsNotExist(err) {
|
||||
t.Error("File was not deleted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user