Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 18 additions & 26 deletions cmd/notes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,44 @@ import (
"time"

"github.com/rd2w/go-notes/internal/model"
"github.com/rd2w/go-notes/internal/repository"
"github.com/rd2w/go-notes/internal/service"
)

func main() {
note := model.NewNote("Первая заметка", "Это содержимое моей первой заметки")
displayNoteInfo("Исходная заметка:", note)
updateNote(note, "Обновленный заголовок", "Обновленное содержимое")
displayNoteInfo("После обновления:", note)
repo := repository.NewRepository()
svc := service.NewService(repo)

log.Println("Запуск генерации тестовых данных...")
svc.StartDataGeneration(1 * time.Second)

fmt.Printf("\n=== РЕЗУЛЬТАТЫ ===\n")
fmt.Printf("Всего заметок создано: %d\n", repo.GetNotesCount())

notes := repo.GetAllNotes()
for i, note := range notes {
displayNoteInfo(i, note)
}

log.Println("Приложение \"Заметки\" успешно завершило выполнение программы!")
}

// displayNoteInfo отображает информацию о заметке в форматированном виде
func displayNoteInfo(header string, note *model.Note) {
func displayNoteInfo(count int, note *model.Note) {
if note == nil {
fmt.Println("Ошибка: заметка не существует")
return
}

fmt.Println(header)
fmt.Printf("\nЗаметка %d:\n", count+1)
fmt.Printf(" ID: %s\n", note.GetID())
fmt.Printf(" Заголовок: %s\n", note.GetTitle())
fmt.Printf(" Содержимое: %s\n", note.GetContent())
fmt.Printf(" Создана: %s\n", formatTime(note.GetCreatedAt()))
fmt.Printf(" Обновлена: %s\n", formatTime(note.GetUpdatedAt()))
fmt.Println()
}

// updateNote обновляет заголовок и содержимое заметки
func updateNote(note *model.Note, newTitle, newContent string) {
if note == nil {
fmt.Println("Ошибка: нельзя обновить несуществующую заметку!")
return
}

fmt.Println("Обновление заметки...")

if newTitle != "" {
note.SetTitle(newTitle)
}

if newContent != "" {
note.SetContent(newContent)
}

fmt.Printf("✅ Заметка успешно обновлена\n\n")
}

// formatTime форматирует время в едином стиле
func formatTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
Expand Down
27 changes: 27 additions & 0 deletions internal/model/note.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
package model

import (
"crypto/rand"
"encoding/base64"
)

// Note представляет сущность заметки
type Note struct {
TimeFields
id string
title string
content string
}

// NewNote создает новую заметку с инициализацией временных меток
func NewNote(title, content string) *Note {
note := &Note{
id: generateID(),
title: title,
content: content,
}
note.initializeTimestamps()
return note
}

// GetID возвращает идентификатор заметки (реализация интерфейса Entity)
func (n *Note) GetID() string {
return n.id
}

// GetType возвращает тип сущности (реализация интерфейса Entity)
func (n *Note) GetType() string {
return "note"
}

// GetTitle возвращает заголовок заметки
func (n *Note) GetTitle() string {
return n.title
Expand All @@ -38,3 +55,13 @@ func (n *Note) SetContent(newContent string) {
n.content = newContent
n.updateTimestamp()
}

// generateID генерирует уникальный идентификатор
func generateID() string {
b := make([]byte, 8)
_, err := rand.Read(b)
if err != nil {
panic("не удалось сгенерировать ID")
}
return base64.URLEncoding.EncodeToString(b)
}
141 changes: 141 additions & 0 deletions internal/model/note_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package model

import (
"encoding/base64"
"fmt"
"testing"
"time"
)
Expand Down Expand Up @@ -229,3 +231,142 @@ func TestMultipleNotes(t *testing.T) {
t.Error("Modifying one note should not affect another")
}
}

func TestNoteIDGeneration(t *testing.T) {
// Создаем несколько заметок и проверяем что у них разные ID
note1 := NewNote("Title 1", "Content 1")
note2 := NewNote("Title 2", "Content 2")
note3 := NewNote("Title 3", "Content 3")

// Проверяем что ID не пустые
if note1.GetID() == "" {
t.Error("Note ID should not be empty")
}

if note2.GetID() == "" {
t.Error("Note ID should not be empty")
}

if note3.GetID() == "" {
t.Error("Note ID should not be empty")
}

// Проверяем что все ID уникальны
ids := make(map[string]bool)
ids[note1.GetID()] = true
ids[note2.GetID()] = true
ids[note3.GetID()] = true

if len(ids) != 3 {
t.Error("All note IDs should be unique")
}

// Проверяем формат ID (base64 URL encoding)
id := note1.GetID()
_, err := base64.URLEncoding.DecodeString(id)
if err != nil {
t.Errorf("Note ID should be valid base64 URL encoding: %v", err)
}
}

func TestNoteEntityInterface(t *testing.T) {
note := NewNote("Test Note", "Test Content")

// Проверяем метод GetID
id := note.GetID()
if id == "" {
t.Error("GetID should return non-empty string")
}

// Проверяем метод GetType
entityType := note.GetType()
if entityType != "note" {
t.Errorf("GetType should return 'note', got %q", entityType)
}

// Проверяем что методы возвращают консистентные данные
if note.GetID() != id {
t.Error("GetID should return consistent value")
}

if note.GetType() != entityType {
t.Error("GetType should return consistent value")
}
}

func TestNoteImmutabilityOfID(t *testing.T) {
note := NewNote("Original Title", "Original Content")
originalID := note.GetID()

// Изменяем другие поля
note.SetTitle("New Title")
note.SetContent("New Content")

// ID должен остаться неизменным
if note.GetID() != originalID {
t.Error("Note ID should be immutable after creation")
}

// Создаем новую заметку и проверяем что ID другой
newNote := NewNote("Another Title", "Another Content")
if newNote.GetID() == originalID {
t.Error("Different notes should have different IDs")
}
}

func TestNoteIDLengthAndFormat(t *testing.T) {
note := NewNote("Test", "Content")
id := note.GetID()

// Проверяем длину ID (base64 от 8 байт = 11 символов без padding или 12 с padding)
if len(id) != 11 && len(id) != 12 {
t.Errorf("Expected ID length 11 or 12, got %d for ID %q", len(id), id)
}

// Проверяем что ID состоит из валидных base64 URL символов
isValidBase64URLChar := func(char rune) bool {
return (char >= 'A' && char <= 'Z') ||
(char >= 'a' && char <= 'z') ||
(char >= '0' && char <= '9') ||
char == '-' || char == '_' || char == '='
}

for _, char := range id {
if !isValidBase64URLChar(char) {
t.Errorf("ID contains invalid base64 URL character: %c", char)
}
}
}

func TestMultipleNoteCreationConsistency(t *testing.T) {
// Создаем несколько заметок и проверяем целостность данных
notes := make([]*Note, 10)
for i := 0; i < 10; i++ {
notes[i] = NewNote(
fmt.Sprintf("Note %d", i),
fmt.Sprintf("Content %d", i),
)
}

for i, note := range notes {
// Проверяем что все поля установлены корректно
expectedTitle := fmt.Sprintf("Note %d", i)
expectedContent := fmt.Sprintf("Content %d", i)

if note.GetTitle() != expectedTitle {
t.Errorf("Note %d: expected title %q, got %q", i, expectedTitle, note.GetTitle())
}

if note.GetContent() != expectedContent {
t.Errorf("Note %d: expected content %q, got %q", i, expectedContent, note.GetContent())
}

if note.GetID() == "" {
t.Errorf("Note %d: ID should not be empty", i)
}

if note.GetType() != "note" {
t.Errorf("Note %d: type should be 'note', got %q", i, note.GetType())
}
}
}
50 changes: 50 additions & 0 deletions internal/repository/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package repository

import (
"fmt"
"log"

"github.com/rd2w/go-notes/internal/model"
)

// Entity интерфейс, который должны реализовывать все сущности
type Entity interface {
GetID() string
GetType() string
}

// Repository управляет хранением различных сущностей
type Repository struct {
notes []*model.Note
// В будущем нужно добавить другие слайсы для других сущностей
}

// NewRepository создает новый экземпляр репозитория
func NewRepository() *Repository {
return &Repository{
notes: make([]*model.Note, 0),
}
}

// Save принимает интерфейс Entity и сохраняет в соответствующий слайс
func (r *Repository) Save(entity Entity) error {
// Проверяем тип сущности и сохраняем в соответствующий слайс
switch entity := entity.(type) {
case *model.Note:
r.notes = append(r.notes, entity)
log.Printf("Заметка сохранена: ID=%s, Title=%s", entity.GetID(), entity.GetTitle())
default:
return fmt.Errorf("неподдерживаемый тип сущности: %T", entity)
}
return nil
}

// GetAllNotes возвращает все сохраненные заметки
func (r *Repository) GetAllNotes() []*model.Note {
return r.notes
}

// GetNotesCount возвращает количество сохраненных заметок
func (r *Repository) GetNotesCount() int {
return len(r.notes)
}
Loading
Loading