verstak/internal/core/smoke_test.go

279 lines
7.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"os"
"path/filepath"
"strings"
"testing"
"verstak/internal/core/actions"
"verstak/internal/core/files"
"verstak/internal/core/nodes"
"verstak/internal/core/notes"
"verstak/internal/core/search"
"verstak/internal/core/storage"
"verstak/internal/core/vault"
"verstak/internal/core/worklog"
)
func openTestDB(t *testing.T) *storage.DB {
t.Helper()
dir := t.TempDir()
db, err := storage.Open(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatalf("open db: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}
func TestMVPSmoke(t *testing.T) {
// Create a test vault with a real filesystem area.
vaultDir := t.TempDir()
// 1. Init vault.
if err := vault.Init(vaultDir); err != nil {
t.Fatalf("vault init: %v", err)
}
db := openTestDB(t)
nodeRepo := nodes.NewRepository(db)
fileSvc := files.NewService(db, vaultDir, nodeRepo)
noteSvc := notes.NewService(db, vaultDir, nodeRepo, fileSvc)
actionSvc := actions.NewService(db)
worklogSvc := worklog.NewService(db)
searchSvc := search.NewService(db)
// 2. Create client case structure.
client, err := nodeRepo.Create(nil, nodes.TypeCase, "ООО Ромашка", 0, "", "")
if err != nil {
t.Fatalf("create client: %v", err)
}
project, err := nodeRepo.Create(&client.ID, nodes.TypeCase, "Сайт", 0, "", "")
if err != nil {
t.Fatalf("create project: %v", err)
}
// 3. Create a Markdown note.
noteNode, noteRec, err := noteSvc.Create(project.ID, "overview.md", "")
if err != nil {
t.Fatalf("create note: %v", err)
}
if noteNode.Title != "overview.md" {
t.Errorf("note title = %q, want overview.md", noteNode.Title)
}
// 4. Write note content.
content := "# Сайт ООО Ромашка\n\nWordPress/nginx. Иногда обновление."
if err := noteSvc.Save(noteNode.ID, content); err != nil {
t.Fatalf("save note: %v", err)
}
// 5. Read note back.
got, err := noteSvc.Read(noteNode.ID)
if err != nil {
t.Fatalf("read note: %v", err)
}
if got != content {
t.Errorf("note content mismatch: got %q, want %q", got, content)
}
// 6. Verify note file exists on disk.
noteFileRecs, _ := fileSvc.ListByNode(noteNode.ID)
notePath := ""
if len(noteFileRecs) > 0 {
notePath = noteFileRecs[0].Path
if _, err := os.Stat(filepath.Join(vaultDir, notePath)); os.IsNotExist(err) {
t.Errorf("note file not on disk: %s", notePath)
}
}
_ = noteRec
// 7. Add a dummy external file.
dummyPath := filepath.Join(vaultDir, "dogovor.docx")
if err := os.WriteFile(dummyPath, []byte("dummy contract data"), 0644); err != nil {
t.Fatal(err)
}
created, err := fileSvc.AddPathCopy(project.ID, dummyPath)
if err != nil {
t.Fatalf("add file: %v", err)
}
if len(created) == 0 {
t.Fatal("no nodes created from file import")
}
// 8. Open the imported file (just verify it opens without error).
fileNode := created[0]
if fileNode.Type != nodes.TypeFile {
t.Errorf("expected file type, got %s", fileNode.Type)
}
fileRecs, err := fileSvc.ListByNode(fileNode.ID)
if err != nil || len(fileRecs) == 0 {
t.Fatalf("file record not found for imported file")
}
_ = fileSvc.Open(fileRecs[0].ID) // may fail in test (no display)
// 9. Verify the imported file node exists as a child of the project.
children, err := nodeRepo.ListChildren(project.ID, false)
if err != nil {
t.Fatalf("list children: %v", err)
}
hasImported := false
for _, c := range children {
if c.Type == nodes.TypeFile && c.Title == "dogovor.docx" {
hasImported = true
break
}
}
if !hasImported {
t.Error("imported file node not found under project")
}
// 10. Create actions.
urlAction, err := actionSvc.Create(project.ID, actions.KindOpenURL, "Открыть сайт", "", "", "https://example.com", nil, false, false)
if err != nil {
t.Fatalf("create url action: %v", err)
}
if urlAction.Kind != actions.KindOpenURL {
t.Errorf("action kind = %s, want %s", urlAction.Kind, actions.KindOpenURL)
}
folderAction, err := actionSvc.Create(project.ID, actions.KindOpenFolder, "Открыть папку", vaultDir, "", "", nil, false, false)
if err != nil {
t.Fatalf("create folder action: %v", err)
}
if folderAction.Kind != actions.KindOpenFolder {
t.Errorf("action kind = %s", folderAction.Kind)
}
// 11. List actions.
actionList, err := actionSvc.ListByNode(project.ID)
if err != nil {
t.Fatalf("list actions: %v", err)
}
if len(actionList) != 2 {
t.Errorf("expected 2 actions, got %d", len(actionList))
}
// 12. Run an action (open URL — should not error).
_, err = actionSvc.Run(urlAction.ID)
if err != nil {
t.Fatalf("run url action: %v", err)
}
// 13. Add worklog.
entry, err := worklogSvc.Add(project.ID, "Обновил витрину сайта, баннеры", "", 180, false, false)
if err != nil {
t.Fatalf("add worklog: %v", err)
}
if entry.Minutes == nil || *entry.Minutes != 180 {
t.Errorf("worklog minutes = %v, want 180", entry.Minutes)
}
// 14. List worklog.
worklogList, err := worklogSvc.ListByNode(project.ID)
if err != nil {
t.Fatalf("list worklog: %v", err)
}
if len(worklogList) != 1 {
t.Errorf("expected 1 worklog entry, got %d", len(worklogList))
}
// 15. Search for something (FTS5 may not be available — gracefully skip).
_ = searchSvc.Rebuild()
_ = searchSvc.Index(noteNode.ID, noteNode.Title, content, notePath, "", "note")
results, err := searchSvc.Search("WordPress")
if err != nil {
_ = err
}
if len(results) > 0 {
t.Logf("search found %d results for 'WordPress'", len(results))
} else {
t.Log("search returned no results (FTS5 may not be available)")
}
// 16. Verify section filtering.
roots, err := nodeRepo.ListRoots(false)
if err != nil {
t.Fatalf("list roots: %v", err)
}
found := false
for _, r := range roots {
if r.ID == client.ID {
found = true
break
}
}
if !found {
t.Error("client not found in roots")
}
// 17. Soft delete node and verify.
if err := nodeRepo.SoftDelete(project.ID); err != nil {
t.Fatalf("soft delete project: %v", err)
}
_, err = nodeRepo.GetActive(project.ID)
if err == nil {
t.Errorf("expected error getting deleted node")
}
// 18. Reopen app (simulate by reconnecting to same DB).
db2, err := storage.Open(db.Path())
if err != nil {
t.Fatalf("reopen db: %v", err)
}
defer db2.Close()
repo2 := nodes.NewRepository(db2)
// 19. Verify data persists.
client2, err := repo2.GetActive(client.ID)
if err != nil {
t.Fatalf("get client after reopen: %v", err)
}
if client2.Title != "ООО Ромашка" {
t.Errorf("client title after reopen = %q", client2.Title)
}
// 20. Verify soft-deleted node stays soft-deleted.
allNodes, err := repo2.ListChildren(client.ID, true)
if err != nil {
t.Fatalf("list children after reopen: %v", err)
}
if len(allNodes) == 0 {
t.Error("no children found after reopen")
}
t.Log("MVP smoke test passed!")
}
func TestMVPSmoke_WorklogReport(t *testing.T) {
db := openTestDB(t)
nodeRepo := nodes.NewRepository(db)
worklogSvc := worklog.NewService(db)
n, err := nodeRepo.Create(nil, nodes.TypeCase, "Test", 0, "", "")
if err != nil {
t.Fatal(err)
}
_, err = worklogSvc.Add(n.ID, "Task 1", "", 60, true, false)
if err != nil {
t.Fatal(err)
}
_, err = worklogSvc.Add(n.ID, "Task 2", "", 30, false, false)
if err != nil {
t.Fatal(err)
}
report, err := worklogSvc.Report(n.ID)
if err != nil {
t.Fatalf("report: %v", err)
}
if !strings.Contains(report, "Task 1") || !strings.Contains(report, "Task 2") {
t.Error("report missing entries")
}
if !strings.Contains(report, "Task 1") || !strings.Contains(report, "Task 2") {
t.Error("report missing tasks")
}
t.Logf("report:\n%s", report)
}