test(create_note): integration test for create note
This commit is contained in:
parent
884e9cd207
commit
d9cc1c6ec7
5 changed files with 134 additions and 56 deletions
|
|
@ -19,8 +19,8 @@ func NewNoteRepository(tx *sql.Tx) NoteRepository {
|
|||
|
||||
func (r *noteRepository) Upsert(ctx context.Context, title string, path string, slug string) error {
|
||||
query := `
|
||||
INSERT INTO notes (title, path, slug)
|
||||
VALUES ($1, $2, $3)
|
||||
INSERT INTO notes (title, path, slug, created_at, updated_at)
|
||||
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
||||
ON CONFLICT (slug) DO UPDATE
|
||||
SET title = EXCLUDED.title,
|
||||
path = EXCLUDED.path;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type tagRepository struct {
|
|||
}
|
||||
|
||||
func NewTagRepository(tx *sql.Tx) TagRepository {
|
||||
return &tagRepository{Transaction: tx}
|
||||
return &tagRepository{Transaction: tx}
|
||||
}
|
||||
|
||||
func (r *tagRepository) Upsert(ctx context.Context, names []string) error {
|
||||
|
|
@ -31,18 +31,10 @@ func (r *tagRepository) Upsert(ctx context.Context, names []string) error {
|
|||
args = append(args, name)
|
||||
}
|
||||
|
||||
query := "WITH input(name) AS (VALUES " +
|
||||
strings.Join(placeholders, ",") +
|
||||
") INSERT INTO tags(name) " +
|
||||
"SELECT name FROM input " +
|
||||
"ON CONFLICT(name) DO NOTHING;"
|
||||
query := "INSERT OR IGNORE INTO tags(name) VALUES " + strings.Join(placeholders, ",")
|
||||
|
||||
_, err := r.Transaction.ExecContext(ctx, query, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err := r.Transaction.ExecContext(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *tagRepository) UpsertNoteTags(noteID int64, tagIDs []int64) error {
|
||||
|
|
@ -65,7 +57,7 @@ func (r *tagRepository) UpsertNoteTags(noteID int64, tagIDs []int64) error {
|
|||
"ON CONFLICT(note_id, tag_id) DO NOTHING" +
|
||||
"SELECT note_id, tag_id FROM input;"
|
||||
|
||||
_, err := r.Transaction.ExecContext(context.Background(), query, args)
|
||||
_, err := r.Transaction.ExecContext(context.Background(), query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import (
|
|||
)
|
||||
|
||||
type UnitOfWork struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
Transaction *sql.Tx
|
||||
FileStore *store.FileStore
|
||||
FileStore *store.FileStore
|
||||
}
|
||||
|
||||
func NewUnitOfWork(db *sql.DB) *UnitOfWork {
|
||||
return &UnitOfWork{db: db}
|
||||
return &UnitOfWork{db: db, FileStore: store.NewFileStore()}
|
||||
}
|
||||
|
||||
func (u *UnitOfWork) Begin() (tx *sql.Tx, err error) {
|
||||
|
|
@ -26,10 +26,18 @@ func (u *UnitOfWork) Begin() (tx *sql.Tx, err error) {
|
|||
}
|
||||
|
||||
func (u *UnitOfWork) Commit() error {
|
||||
if u.Transaction == nil {
|
||||
return nil
|
||||
if err := u.FileStore.Flush(); err != nil {
|
||||
u.Transaction.Rollback()
|
||||
u.Transaction = nil
|
||||
return err
|
||||
}
|
||||
return u.Transaction.Commit()
|
||||
if err := u.Transaction.Commit(); err != nil {
|
||||
u.FileStore.Rollback()
|
||||
u.Transaction = nil
|
||||
return err
|
||||
}
|
||||
u.Transaction = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UnitOfWork) Rollback() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "os"
|
|||
|
||||
type FileStore struct {
|
||||
staged []stagedFile
|
||||
commited []string
|
||||
committed []string
|
||||
}
|
||||
|
||||
type stagedFile struct {
|
||||
|
|
@ -12,6 +12,10 @@ type stagedFile struct {
|
|||
data []byte
|
||||
}
|
||||
|
||||
func NewFileStore() *FileStore {
|
||||
return &FileStore{}
|
||||
}
|
||||
|
||||
func (fs *FileStore) Stage(path string, data []byte) {
|
||||
fs.staged = append(fs.staged, stagedFile{path: path, data: data})
|
||||
}
|
||||
|
|
@ -24,19 +28,19 @@ func (fs *FileStore) Flush() error {
|
|||
if err := os.WriteFile(file.path, file.data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
fs.commited = append(fs.commited, file.path)
|
||||
fs.committed = append(fs.committed, file.path)
|
||||
}
|
||||
fs.staged = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FileStore) Rollback() error {
|
||||
for _, path := range fs.commited {
|
||||
for _, path := range fs.committed {
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fs.commited = nil
|
||||
fs.committed = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,42 +2,116 @@ package integration_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/handlers"
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateNoteHandlerOnSucess(t *testing.T) {
|
||||
// Arrange
|
||||
func newCreateNoteHandler() *handlers.CreateNoteHandler {
|
||||
uow := repositories.NewUnitOfWork(Fixture.DB)
|
||||
|
||||
handler := handlers.NewCreateNoteHandler(uow)
|
||||
|
||||
requestParams := struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}{
|
||||
Title: "Test Note",
|
||||
Content: "This is a test note.",
|
||||
}
|
||||
|
||||
requestParamsBytes, err := json.Marshal(requestParams)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal request params: %v", err)
|
||||
}
|
||||
|
||||
request := CreateTestRequest("createNote", 1, requestParamsBytes)
|
||||
|
||||
// Act
|
||||
response, err := handler.Handle(Fixture.TestContext, request.Params)
|
||||
|
||||
// Assert
|
||||
if err != nil {
|
||||
t.Fatalf("handler returned an error: %v", err)
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
t.Fatalf("expected response to be nil, got: %v", response)
|
||||
}
|
||||
return handlers.NewCreateNoteHandler(uow)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_NoTemplate_CreatesNoteFileAndReturnsPath(t *testing.T) {
|
||||
handler := newCreateNoteHandler()
|
||||
notePath := filepath.Join(t.TempDir(), "my-note.md")
|
||||
|
||||
params, _ := json.Marshal(map[string]any{
|
||||
"title": "My Note",
|
||||
"path": notePath,
|
||||
})
|
||||
|
||||
result, err := handler.Handle(Fixture.TestContext, params)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, notePath, result)
|
||||
|
||||
var count int
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "my-note").Scan(&count))
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
_, statErr := os.Stat(notePath)
|
||||
assert.NoError(t, statErr)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t *testing.T) {
|
||||
handler := newCreateNoteHandler()
|
||||
dir := t.TempDir()
|
||||
|
||||
templatePath := filepath.Join(dir, "template.md")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte("---\ntitle: Template\ntags: [go, testing]\n---\n"), 0o644))
|
||||
|
||||
notePath := filepath.Join(dir, "templated-note.md")
|
||||
params, _ := json.Marshal(map[string]any{
|
||||
"title": "Templated Note",
|
||||
"path": notePath,
|
||||
"templatePath": templatePath,
|
||||
})
|
||||
|
||||
result, err := handler.Handle(Fixture.TestContext, params)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, notePath, result)
|
||||
|
||||
var noteCount int
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "templated-note").Scan(¬eCount))
|
||||
assert.Equal(t, 1, noteCount)
|
||||
|
||||
rows, err := Fixture.DB.Query(`SELECT name FROM tags WHERE name IN ('go', 'testing')`)
|
||||
require.NoError(t, err)
|
||||
defer rows.Close()
|
||||
|
||||
var tags []string
|
||||
for rows.Next() {
|
||||
var name string
|
||||
require.NoError(t, rows.Scan(&name))
|
||||
tags = append(tags, name)
|
||||
}
|
||||
assert.Len(t, tags, 2)
|
||||
|
||||
_, statErr := os.Stat(notePath)
|
||||
assert.NoError(t, statErr)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
path1 := filepath.Join(dir, "dup-note.md")
|
||||
params1, _ := json.Marshal(map[string]any{"title": "Dup Note", "path": path1})
|
||||
_, err := newCreateNoteHandler().Handle(Fixture.TestContext, params1)
|
||||
require.NoError(t, err)
|
||||
|
||||
path2 := filepath.Join(dir, "dup-note-moved.md")
|
||||
params2, _ := json.Marshal(map[string]any{"title": "Dup Note", "path": path2})
|
||||
_, err = newCreateNoteHandler().Handle(Fixture.TestContext, params2)
|
||||
require.NoError(t, err)
|
||||
|
||||
var count int
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "dup-note").Scan(&count))
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
var path string
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT path FROM notes WHERE slug = ?`, "dup-note").Scan(&path))
|
||||
assert.Equal(t, path2, path)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_InvalidJSON_ReturnsError(t *testing.T) {
|
||||
_, err := newCreateNoteHandler().Handle(Fixture.TestContext, json.RawMessage(`{invalid json}`))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_NonExistentTemplatePath_ReturnsError(t *testing.T) {
|
||||
params, _ := json.Marshal(map[string]any{
|
||||
"title": "Ghost Note",
|
||||
"path": filepath.Join(t.TempDir(), "ghost.md"),
|
||||
"templatePath": "/non/existent/template.md",
|
||||
})
|
||||
|
||||
_, err := newCreateNoteHandler().Handle(Fixture.TestContext, params)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue