all repos

onasty @ 4137dee

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
        2
         

      
        3
        3
         import (

      
        4
        4
         	"errors"

      
        
        5
        +	"strings"

      
        5
        6
         	"time"

      
        6
        7
         

      
        7
        8
         	"github.com/gofrs/uuid/v5"

      ···
        10
        11
         var (

      
        11
        12
         	ErrNoteContentIsEmpty     = errors.New("note: content is empty")

      
        12
        13
         	ErrNoteSlugIsAlreadyInUse = errors.New("note: slug is already in use")

      
        
        14
        +	ErrNoteSlugIsInvalid      = errors.New("note: slug is invalid")

      
        13
        15
         	ErrNoteExpired            = errors.New("note: expired")

      
        14
        16
         	ErrNoteNotFound           = errors.New("note: not found")

      
        15
        17
         )

      ···
        28
        30
         func (n Note) Validate() error {

      
        29
        31
         	if n.Content == "" {

      
        30
        32
         		return ErrNoteContentIsEmpty

      
        
        33
        +	}

      
        
        34
        +

      
        
        35
        +	if n.Slug == "" || strings.Contains(n.Slug, " ") {

      
        
        36
        +		return ErrNoteSlugIsInvalid

      
        31
        37
         	}

      
        32
        38
         

      
        33
        39
         	if n.IsExpired() {

      
M internal/models/note_test.go
···
        7
        7
         	assert "github.com/stretchr/testify/require"

      
        8
        8
         )

      
        9
        9
         

      
        
        10
        +//nolint:exhaustruct

      
        10
        11
         func TestNote_Validate(t *testing.T) {

      
        11
        12
         	// NOTE: there no need to test if note is expired since it tested in IsExpired test

      
        12
        13
         

      
        13
        
        -	t.Run("should pass the validation only if content provided", func(t *testing.T) {

      
        14
        
        -		n := Note{Content: "the content"} //nolint:exhaustruct

      
        
        14
        +	t.Run("should pass the validation if only slug and content are provided", func(t *testing.T) {

      
        
        15
        +		n := Note{Content: "the content", Slug: "s"}

      
        15
        16
         		assert.NoError(t, n.Validate())

      
        16
        17
         	})

      
        17
        18
         	t.Run("should pass validation with content and correct expiration time", func(t *testing.T) {

      
        18
        
        -		n := Note{ //nolint:exhaustruct

      
        
        19
        +		n := Note{

      
        19
        20
         			Content:   "content",

      
        
        21
        +			Slug:      "s",

      
        20
        22
         			ExpiresAt: time.Now().Add(time.Minute),

      
        21
        23
         		}

      
        22
        24
         		assert.NoError(t, n.Validate())

      
        23
        25
         	})

      
        24
        26
         	t.Run("should fail if content is missing", func(t *testing.T) {

      
        25
        
        -		n := Note{Content: ""} //nolint:exhaustruct

      
        
        27
        +		n := Note{Content: ""}

      
        26
        28
         		assert.EqualError(t, n.Validate(), ErrNoteContentIsEmpty.Error())

      
        27
        29
         	})

      
        28
        30
         	t.Run("should fail if content is missing and other fields are set", func(t *testing.T) {

      
        29
        
        -		n := Note{ //nolint:exhaustruct

      
        
        31
        +		n := Note{

      
        30
        32
         			Slug:                 "some-slug",

      
        31
        33
         			Password:             "some-password",

      
        32
        34
         			BurnBeforeExpiration: false,

      ···
        34
        36
         		assert.EqualError(t, n.Validate(), ErrNoteContentIsEmpty.Error())

      
        35
        37
         	})

      
        36
        38
         	t.Run("should fail if expiration time is in the past", func(t *testing.T) {

      
        37
        
        -		n := Note{Content: "content", ExpiresAt: time.Now().Add(-time.Hour)} //nolint:exhaustruct

      
        
        39
        +		n := Note{

      
        
        40
        +			Content:   "content",

      
        
        41
        +			Slug:      "s",

      
        
        42
        +			ExpiresAt: time.Now().Add(-time.Hour),

      
        
        43
        +		}

      
        38
        44
         		assert.EqualError(t, n.Validate(), ErrNoteExpired.Error())

      
        39
        45
         	})

      
        
        46
        +	t.Run("should fail if slug is empty", func(t *testing.T) {

      
        
        47
        +		n := Note{Content: "the content", Slug: ""}

      
        
        48
        +		assert.EqualError(t, n.Validate(), ErrNoteSlugIsInvalid.Error())

      
        
        49
        +	})

      
        
        50
        +	t.Run("should fail if slug is empty", func(t *testing.T) {

      
        
        51
        +		n := Note{Content: "the content", Slug: " "}

      
        
        52
        +		assert.EqualError(t, n.Validate(), ErrNoteSlugIsInvalid.Error())

      
        
        53
        +	})

      
        40
        54
         }

      
        41
        55
         

      
        
        56
        +//nolint:exhaustruct

      
        42
        57
         func TestNote_IsExpired(t *testing.T) {

      
        43
        58
         	t.Run("should be expired", func(t *testing.T) {

      
        44
        
        -		note := Note{ExpiresAt: time.Now().Add(-time.Hour)} //nolint:exhaustruct

      
        
        59
        +		note := Note{ExpiresAt: time.Now().Add(-time.Hour)}

      
        45
        60
         		assert.True(t, note.IsExpired())

      
        46
        61
         	})

      
        47
        62
         	t.Run("should be not expired", func(t *testing.T) {

      
        48
        
        -		note := Note{ExpiresAt: time.Now().Add(time.Hour)} //nolint:exhaustruct

      
        
        63
        +		note := Note{ExpiresAt: time.Now().Add(time.Hour)}

      
        49
        64
         		assert.False(t, note.IsExpired())

      
        50
        65
         	})

      
        51
        66
         	t.Run("should be not expired when [ExpiredAt] is zero", func(t *testing.T) {

      
        52
        
        -		note := Note{ExpiresAt: time.Time{}} //nolint:exhaustruct

      
        
        67
        +		note := Note{ExpiresAt: time.Time{}}

      
        53
        68
         		assert.False(t, note.IsExpired())

      
        54
        69
         	})

      
        55
        70
         }

      
        56
        71
         

      
        
        72
        +//nolint:exhaustruct

      
        57
        73
         func TestNote_ShouldBeBurnt(t *testing.T) {

      
        58
        74
         	t.Run("should be burnt", func(t *testing.T) {

      
        59
        
        -		note := Note{ //nolint:exhaustruct

      
        
        75
        +		note := Note{

      
        60
        76
         			BurnBeforeExpiration: true,

      
        61
        77
         			ExpiresAt:            time.Now().Add(time.Hour),

      
        62
        78
         		}

      
        63
        79
         		assert.True(t, note.ShouldBeBurnt())

      
        64
        80
         	})

      
        65
        81
         	t.Run("should not be burnt", func(t *testing.T) {

      
        66
        
        -		note := Note{ //nolint:exhaustruct

      
        
        82
        +		note := Note{

      
        67
        83
         			BurnBeforeExpiration: true,

      
        68
        84
         			ExpiresAt:            time.Time{},

      
        69
        85
         		}

      
        70
        86
         		assert.False(t, note.ShouldBeBurnt())

      
        71
        87
         	})

      
        72
        88
         	t.Run("could not be burnt when expiration and shouldBurn set to false", func(t *testing.T) {

      
        73
        
        -		note := Note{ //nolint:exhaustruct

      
        
        89
        +		note := Note{

      
        74
        90
         			BurnBeforeExpiration: false,

      
        75
        91
         			ExpiresAt:            time.Time{},

      
        76
        92
         		}

      ···
        78
        94
         	})

      
        79
        95
         }

      
        80
        96
         

      
        
        97
        +//nolint:exhaustruct

      
        81
        98
         func TestNote_IsRead(t *testing.T) {

      
        82
        99
         	t.Run("should be unread", func(t *testing.T) {

      
        83
        
        -		n := Note{ReadAt: time.Time{}} //nolint:exhaustruct

      
        
        100
        +		n := Note{ReadAt: time.Time{}}

      
        84
        101
         		assert.False(t, n.IsRead())

      
        85
        102
         	})

      
        86
        103
         	t.Run("should be read", func(t *testing.T) {

      
        87
        
        -		n := Note{ReadAt: time.Now()} //nolint:exhaustruct

      
        
        104
        +		n := Note{ReadAt: time.Now()}

      
        88
        105
         		assert.True(t, n.IsRead())

      
        89
        106
         	})

      
        90
        107
         }