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
58 changes: 58 additions & 0 deletions cmd/notes/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"fmt"
"log"
"time"

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

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

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

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

fmt.Println(header)
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")
}
31 changes: 31 additions & 0 deletions internal/model/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package model

import "time"

// TimeFields содержит общие временные метки для сущностей
type TimeFields struct {
createdAt time.Time
updatedAt time.Time
}

// GetCreatedAt возвращает время создания
func (t *TimeFields) GetCreatedAt() time.Time {
return t.createdAt
}

// GetUpdatedAt возвращает время обновления
func (t *TimeFields) GetUpdatedAt() time.Time {
return t.updatedAt
}

// updateTimestamp обновляет время изменения
func (t *TimeFields) updateTimestamp() {
t.updatedAt = time.Now()
}

// initializeTimestamps инициализирует временные метки
func (t *TimeFields) initializeTimestamps() {
now := time.Now()
t.createdAt = now
t.updatedAt = now
}
40 changes: 40 additions & 0 deletions internal/model/note.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package model

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

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

// GetTitle возвращает заголовок заметки
func (n *Note) GetTitle() string {
return n.title
}

// GetContent возвращает содержимое заметки
func (n *Note) GetContent() string {
return n.content
}

// SetTitle устанавливает новый заголовок и обновляет временную метку
func (n *Note) SetTitle(newTitle string) {
n.title = newTitle
n.updateTimestamp()
}

// SetContent устанавливает новое содержимое и обновляет временную метку
func (n *Note) SetContent(newContent string) {
n.content = newContent
n.updateTimestamp()
}
231 changes: 231 additions & 0 deletions internal/model/note_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package model

import (
"testing"
"time"
)

func TestNewNote(t *testing.T) {
title := "Test Title"
content := "Test Content"

note := NewNote(title, content)

if note == nil {
t.Fatal("NewNote returned nil")
}

// Проверяем установку полей
if note.GetTitle() != title {
t.Errorf("Expected title %q, got %q", title, note.GetTitle())
}

if note.GetContent() != content {
t.Errorf("Expected content %q, got %q", content, note.GetContent())
}

// Проверяем инициализацию временных меток
if note.GetCreatedAt().IsZero() {
t.Error("CreatedAt should be initialized")
}

if note.GetUpdatedAt().IsZero() {
t.Error("UpdatedAt should be initialized")
}

// Проверяем, что created и updated равны при создании
if !note.GetCreatedAt().Equal(note.GetUpdatedAt()) {
t.Error("CreatedAt and UpdatedAt should be equal for new note")
}

// Проверяем, что временные метки близки к текущему времени
now := time.Now()
createdAt := note.GetCreatedAt()

if createdAt.After(now) {
t.Error("CreatedAt should not be in the future")
}

// Допускаем небольшую погрешность во времени выполнения
if now.Sub(createdAt) > time.Second {
t.Error("CreatedAt should be close to current time")
}
}

func TestNoteSetters(t *testing.T) {
note := NewNote("Initial Title", "Initial Content")
initialCreatedAt := note.GetCreatedAt()
initialUpdatedAt := note.GetUpdatedAt()

// Даем небольшое время для обеспечения разных временных меток
time.Sleep(10 * time.Millisecond)

// Тестируем SetTitle
newTitle := "Updated Title"
note.SetTitle(newTitle)

if note.GetTitle() != newTitle {
t.Errorf("Expected title %q after SetTitle, got %q", newTitle, note.GetTitle())
}

// Проверяем, что updatedAt изменился
if note.GetUpdatedAt().Equal(initialUpdatedAt) {
t.Error("UpdatedAt should change after SetTitle")
}

// Проверяем, что createdAt не изменился
if !note.GetCreatedAt().Equal(initialCreatedAt) {
t.Error("CreatedAt should not change after SetTitle")
}

// Проверяем, что updatedAt стал позже
if note.GetUpdatedAt().Before(initialUpdatedAt) {
t.Error("UpdatedAt should be after previous UpdatedAt")
}

// Сохраняем updatedAt после первого изменения
updatedAfterTitle := note.GetUpdatedAt()
time.Sleep(10 * time.Millisecond)

// Тестируем SetContent
newContent := "Updated Content"
note.SetContent(newContent)

if note.GetContent() != newContent {
t.Errorf("Expected content %q after SetContent, got %q", newContent, note.GetContent())
}

// Проверяем, что updatedAt снова изменился
if note.GetUpdatedAt().Equal(updatedAfterTitle) {
t.Error("UpdatedAt should change after SetContent")
}

// Проверяем, что createdAt все еще не изменился
if !note.GetCreatedAt().Equal(initialCreatedAt) {
t.Error("CreatedAt should not change after SetContent")
}
}

func TestNoteGetters(t *testing.T) {
title := "Getter Test Title"
content := "Getter Test Content"

note := NewNote(title, content)

// Тестируем геттеры
if got := note.GetTitle(); got != title {
t.Errorf("GetTitle() = %q, want %q", got, title)
}

if got := note.GetContent(); got != content {
t.Errorf("GetContent() = %q, want %q", got, content)
}

// Проверяем, что геттеры временных меток возвращают не-zero значения
if note.GetCreatedAt().IsZero() {
t.Error("GetCreatedAt() returned zero time")
}

if note.GetUpdatedAt().IsZero() {
t.Error("GetUpdatedAt() returned zero time")
}
}

func TestTimeFieldsMethods(t *testing.T) {
tf := &TimeFields{}

// До инициализации временные метки должны быть zero
if !tf.GetCreatedAt().IsZero() {
t.Error("CreatedAt should be zero before initialization")
}

if !tf.GetUpdatedAt().IsZero() {
t.Error("UpdatedAt should be zero before initialization")
}

// Инициализируем временные метки
tf.initializeTimestamps()

// После инициализации не должны быть zero
if tf.GetCreatedAt().IsZero() {
t.Error("CreatedAt should not be zero after initialization")
}

if tf.GetUpdatedAt().IsZero() {
t.Error("UpdatedAt should not be zero after initialization")
}

// Сохраняем текущие значения
initialCreatedAt := tf.GetCreatedAt()
initialUpdatedAt := tf.GetUpdatedAt()

time.Sleep(10 * time.Millisecond)

// Обновляем временную метку
tf.updateTimestamp()

// Проверяем, что updatedAt изменился, а createdAt остался прежним
if tf.GetCreatedAt() != initialCreatedAt {
t.Error("CreatedAt should not change after updateTimestamp")
}

if tf.GetUpdatedAt().Equal(initialUpdatedAt) {
t.Error("UpdatedAt should change after updateTimestamp")
}

if tf.GetUpdatedAt().Before(initialUpdatedAt) {
t.Error("UpdatedAt should be after previous value")
}
}

func TestNoteEdgeCases(t *testing.T) {
// Тестируем пустые значения
emptyNote := NewNote("", "")
if emptyNote.GetTitle() != "" {
t.Error("Should handle empty title")
}
if emptyNote.GetContent() != "" {
t.Error("Should handle empty content")
}

// Тестируем установку пустых значений
note := NewNote("Title", "Content")
note.SetTitle("")
if note.GetTitle() != "" {
t.Error("SetTitle should handle empty string")
}

note.SetContent("")
if note.GetContent() != "" {
t.Error("SetContent should handle empty string")
}
}

func TestMultipleNotes(t *testing.T) {
// Создаем несколько заметок и проверяем их независимость
note1 := NewNote("Note 1", "Content 1")
note2 := NewNote("Note 2", "Content 2")

// Проверяем, что у них разные временные метки
// (могут быть равны если созданы в одну наносекунду, но это маловероятно)
if note1.GetCreatedAt().Equal(note2.GetCreatedAt()) {
t.Log("Note: Both notes have same CreatedAt (very close creation time)")
}

// Проверяем независимость данных
if note1.GetTitle() == note2.GetTitle() {
t.Error("Notes should have different titles")
}

if note1.GetContent() == note2.GetContent() {
t.Error("Notes should have different content")
}

// Изменяем одну заметку и проверяем, что другая не изменилась
note1OriginalTitle := note1.GetTitle()
note2.SetTitle("Modified Note 2")

if note1.GetTitle() != note1OriginalTitle {
t.Error("Modifying one note should not affect another")
}
}
Loading