all repos

onasty @ 4137dee2e4c044b00f9640746a52fc6bc37e6762

a one-time notes service
2 files changed, 37 insertions(+), 14 deletions(-)
feat(api): validate user provided slug (#164)

* feat(api): user should not be able to pass " " within the note's slug

* test(models): validate slug
Author: Olexandr Smirnov ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-07-09 20:26:21 +0300
Parent: d631546
M internal/models/note.go

@@ -2,6 +2,7 @@ package models

import ( "errors" + "strings" "time" "github.com/gofrs/uuid/v5"

@@ -10,6 +11,7 @@

var ( ErrNoteContentIsEmpty = errors.New("note: content is empty") ErrNoteSlugIsAlreadyInUse = errors.New("note: slug is already in use") + ErrNoteSlugIsInvalid = errors.New("note: slug is invalid") ErrNoteExpired = errors.New("note: expired") ErrNoteNotFound = errors.New("note: not found") )

@@ -28,6 +30,10 @@

func (n Note) Validate() error { if n.Content == "" { return ErrNoteContentIsEmpty + } + + if n.Slug == "" || strings.Contains(n.Slug, " ") { + return ErrNoteSlugIsInvalid } if n.IsExpired() {
M internal/models/note_test.go

@@ -7,26 +7,28 @@

assert "github.com/stretchr/testify/require" ) +//nolint:exhaustruct func TestNote_Validate(t *testing.T) { // NOTE: there no need to test if note is expired since it tested in IsExpired test - t.Run("should pass the validation only if content provided", func(t *testing.T) { - n := Note{Content: "the content"} //nolint:exhaustruct + t.Run("should pass the validation if only slug and content are provided", func(t *testing.T) { + n := Note{Content: "the content", Slug: "s"} assert.NoError(t, n.Validate()) }) t.Run("should pass validation with content and correct expiration time", func(t *testing.T) { - n := Note{ //nolint:exhaustruct + n := Note{ Content: "content", + Slug: "s", ExpiresAt: time.Now().Add(time.Minute), } assert.NoError(t, n.Validate()) }) t.Run("should fail if content is missing", func(t *testing.T) { - n := Note{Content: ""} //nolint:exhaustruct + n := Note{Content: ""} assert.EqualError(t, n.Validate(), ErrNoteContentIsEmpty.Error()) }) t.Run("should fail if content is missing and other fields are set", func(t *testing.T) { - n := Note{ //nolint:exhaustruct + n := Note{ Slug: "some-slug", Password: "some-password", BurnBeforeExpiration: false,

@@ -34,43 +36,57 @@ }

assert.EqualError(t, n.Validate(), ErrNoteContentIsEmpty.Error()) }) t.Run("should fail if expiration time is in the past", func(t *testing.T) { - n := Note{Content: "content", ExpiresAt: time.Now().Add(-time.Hour)} //nolint:exhaustruct + n := Note{ + Content: "content", + Slug: "s", + ExpiresAt: time.Now().Add(-time.Hour), + } assert.EqualError(t, n.Validate(), ErrNoteExpired.Error()) }) + t.Run("should fail if slug is empty", func(t *testing.T) { + n := Note{Content: "the content", Slug: ""} + assert.EqualError(t, n.Validate(), ErrNoteSlugIsInvalid.Error()) + }) + t.Run("should fail if slug is empty", func(t *testing.T) { + n := Note{Content: "the content", Slug: " "} + assert.EqualError(t, n.Validate(), ErrNoteSlugIsInvalid.Error()) + }) } +//nolint:exhaustruct func TestNote_IsExpired(t *testing.T) { t.Run("should be expired", func(t *testing.T) { - note := Note{ExpiresAt: time.Now().Add(-time.Hour)} //nolint:exhaustruct + note := Note{ExpiresAt: time.Now().Add(-time.Hour)} assert.True(t, note.IsExpired()) }) t.Run("should be not expired", func(t *testing.T) { - note := Note{ExpiresAt: time.Now().Add(time.Hour)} //nolint:exhaustruct + note := Note{ExpiresAt: time.Now().Add(time.Hour)} assert.False(t, note.IsExpired()) }) t.Run("should be not expired when [ExpiredAt] is zero", func(t *testing.T) { - note := Note{ExpiresAt: time.Time{}} //nolint:exhaustruct + note := Note{ExpiresAt: time.Time{}} assert.False(t, note.IsExpired()) }) } +//nolint:exhaustruct func TestNote_ShouldBeBurnt(t *testing.T) { t.Run("should be burnt", func(t *testing.T) { - note := Note{ //nolint:exhaustruct + note := Note{ BurnBeforeExpiration: true, ExpiresAt: time.Now().Add(time.Hour), } assert.True(t, note.ShouldBeBurnt()) }) t.Run("should not be burnt", func(t *testing.T) { - note := Note{ //nolint:exhaustruct + note := Note{ BurnBeforeExpiration: true, ExpiresAt: time.Time{}, } assert.False(t, note.ShouldBeBurnt()) }) t.Run("could not be burnt when expiration and shouldBurn set to false", func(t *testing.T) { - note := Note{ //nolint:exhaustruct + note := Note{ BurnBeforeExpiration: false, ExpiresAt: time.Time{}, }

@@ -78,13 +94,14 @@ assert.False(t, note.ShouldBeBurnt())

}) } +//nolint:exhaustruct func TestNote_IsRead(t *testing.T) { t.Run("should be unread", func(t *testing.T) { - n := Note{ReadAt: time.Time{}} //nolint:exhaustruct + n := Note{ReadAt: time.Time{}} assert.False(t, n.IsRead()) }) t.Run("should be read", func(t *testing.T) { - n := Note{ReadAt: time.Now()} //nolint:exhaustruct + n := Note{ReadAt: time.Now()} assert.True(t, n.IsRead()) }) }