4 files changed,
138 insertions(+),
23 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-08-27 17:52:04 +0300
Parent:
c24198a
M
e2e/apiv1_notes_test.go
··· 3 3 import ( 4 4 "net/http" 5 5 "net/http/httptest" 6 + "testing" 7 + "testing/synctest" 6 8 "time" 7 9 8 10 "github.com/gofrs/uuid/v5" ··· 39 41 name: "content only", 40 42 inp: apiv1NoteCreateRequest{Content: e.uuid()}, //nolint:exhaustruct 41 43 assert: func(r *httptest.ResponseRecorder, _ apiv1NoteCreateRequest) { 42 - e.Equal(r.Code, http.StatusCreated) 44 + e.Equal(http.StatusCreated, r.Code) 43 45 44 46 var body apiv1NoteCreateResponse 45 47 e.readBodyAndUnjsonify(r.Body, &body) ··· 58 60 Content: e.uuid(), 59 61 }, 60 62 assert: func(r *httptest.ResponseRecorder, inp apiv1NoteCreateRequest) { 61 - e.Equal(r.Code, http.StatusCreated) 63 + e.Equal(http.StatusCreated, r.Code) 62 64 63 65 var body apiv1NoteCreateResponse 64 66 e.readBodyAndUnjsonify(r.Body, &body) ··· 94 96 Content: e.uuid(), 95 97 }, 96 98 assert: func(r *httptest.ResponseRecorder, _ apiv1NoteCreateRequest) { 97 - e.Equal(r.Code, http.StatusBadRequest) 99 + e.Equal(http.StatusBadRequest, r.Code) 98 100 99 101 var body errorResponse 100 102 e.readBodyAndUnjsonify(r.Body, &body) ··· 109 111 Content: e.uuid(), 110 112 }, 111 113 assert: func(r *httptest.ResponseRecorder, _ apiv1NoteCreateRequest) { 112 - e.Equal(r.Code, http.StatusBadRequest) 114 + e.Equal(http.StatusBadRequest, r.Code) 113 115 114 116 var body errorResponse 115 117 e.readBodyAndUnjsonify(r.Body, &body) ··· 124 126 Content: e.uuid(), 125 127 }, 126 128 assert: func(r *httptest.ResponseRecorder, inp apiv1NoteCreateRequest) { 127 - e.Equal(r.Code, http.StatusCreated) 129 + e.Equal(http.StatusCreated, r.Code) 128 130 129 131 var body apiv1NoteCreateResponse 130 132 e.readBodyAndUnjsonify(r.Body, &body) ··· 165 167 ExpiresAt: time.Now().Add(time.Hour), 166 168 }, 167 169 assert: func(r *httptest.ResponseRecorder, inp apiv1NoteCreateRequest) { 168 - e.Equal(r.Code, http.StatusCreated) 170 + e.Equal(http.StatusCreated, r.Code) 169 171 170 172 var body apiv1NoteCreateResponse 171 173 e.readBodyAndUnjsonify(r.Body, &body) ··· 194 196 } 195 197 196 198 func (e *AppTestSuite) TestNoteV1_Get() { 199 + // create note 197 200 content := e.uuid() 198 201 httpResp := e.httpRequest( 199 202 http.MethodPost, ··· 207 210 var bodyCreated apiv1NoteCreateResponse 208 211 e.readBodyAndUnjsonify(httpResp.Body, &bodyCreated) 209 212 210 - httpResp = e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 211 - e.Equal(httpResp.Code, http.StatusOK) 213 + // read note 214 + httpResp2 := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 215 + e.Equal(http.StatusOK, httpResp2.Code) 212 216 213 217 var body apiv1NoteGetResponse 214 - e.readBodyAndUnjsonify(httpResp.Body, &body) 218 + e.readBodyAndUnjsonify(httpResp2.Body, &body) 215 219 216 220 e.Equal(content, body.Content) 217 221 218 222 dbNote := e.getNoteBySlug(bodyCreated.Slug) 219 - e.Equal(dbNote.Content, "") 220 - e.Equal(dbNote.ReadAt.IsZero(), false) 223 + e.Empty(dbNote.Content) 224 + e.False(dbNote.ReadAt.IsZero()) 225 +} 226 + 227 +func (e *AppTestSuite) TestNoteV1_Get_alreadyRead() { 228 + // create note 229 + content := e.uuid() 230 + httpRespCreated := e.httpRequest( 231 + http.MethodPost, 232 + "/api/v1/note", 233 + e.jsonify(apiv1NoteCreateRequest{Content: content}), //nolint:exhaustruct 234 + ) 235 + e.Equal(http.StatusCreated, httpRespCreated.Code) 236 + 237 + var bodyCreated apiv1NoteCreateResponse 238 + e.readBodyAndUnjsonify(httpRespCreated.Body, &bodyCreated) 239 + 240 + // read note 241 + httpRespRead := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 242 + e.Equal(httpRespRead.Code, http.StatusOK) 243 + 244 + var bodyRead apiv1NoteGetResponse 245 + e.readBodyAndUnjsonify(httpRespRead.Body, &bodyRead) 246 + 247 + e.Equal(content, bodyRead.Content) 248 + 249 + dbNote := e.getNoteBySlug(bodyCreated.Slug) 250 + e.Empty(dbNote.Content) 251 + e.False(dbNote.ReadAt.IsZero()) 252 + 253 + // read note once again 254 + httpRespRead2 := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 255 + e.Equal(http.StatusNotFound, httpRespRead2.Code) 256 + 257 + var bodyRead2 apiv1NoteGetResponse 258 + e.readBodyAndUnjsonify(httpRespRead2.Body, &bodyRead2) 259 + 260 + dbNote2 := e.getNoteBySlug(bodyCreated.Slug) 261 + e.Empty(dbNote2.Content) 262 + 263 + e.Empty(bodyRead2.Content) 264 + e.Equal(dbNote2.ReadAt.Unix(), bodyRead2.ReadAt.Unix()) 265 + e.Equal(dbNote2.CreatedAt.Unix(), bodyRead2.CreatedAt.Unix()) 266 + e.Equal(dbNote2.ExpiresAt.Unix(), bodyRead2.ExpiresAt.Unix()) 267 +} 268 + 269 +func (e *AppTestSuite) TestNoteV1_Get_ShouldNotBurnBeforeExpiration() { 270 + // create note 271 + content := e.uuid() 272 + httpRespCreated := e.httpRequest( 273 + http.MethodPost, 274 + "/api/v1/note", 275 + e.jsonify(apiv1NoteCreateRequest{ //nolint:exhaustruct 276 + Content: content, 277 + ExpiresAt: time.Now().Add(time.Hour), 278 + BurnBeforeExpiration: true, 279 + }), 280 + ) 281 + e.Equal(http.StatusCreated, httpRespCreated.Code) 282 + 283 + var bodyCreated apiv1NoteCreateResponse 284 + e.readBodyAndUnjsonify(httpRespCreated.Body, &bodyCreated) 285 + 286 + // read note 287 + httpRespRead := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 288 + e.Equal(http.StatusOK, httpRespRead.Code) 289 + 290 + var bodyRead apiv1NoteGetResponse 291 + e.readBodyAndUnjsonify(httpRespRead.Body, &bodyRead) 292 + 293 + e.Equal(content, bodyRead.Content) 294 + 295 + dbNote := e.getNoteBySlug(bodyCreated.Slug) 296 + e.Equal(content, dbNote.Content) 297 + e.True(dbNote.ReadAt.IsZero()) 298 +} 299 + 300 +func (e *AppTestSuite) TestNoteV1_Get_ShouldBurnBeforeExpiration() { 301 + // synctest is used here to ensure proper synchronization and isolation of test execution 302 + // it still feels wrong to use synctest in e2e test, but it works nonetheless 303 + synctest.Test(e.T(), func(_ *testing.T) { 304 + // create note 305 + content := e.uuid() 306 + httpRespCreated := e.httpRequest( 307 + http.MethodPost, 308 + "/api/v1/note", 309 + e.jsonify(apiv1NoteCreateRequest{ //nolint:exhaustruct 310 + Content: content, 311 + ExpiresAt: time.Now().Add(time.Hour), 312 + BurnBeforeExpiration: true, 313 + }), 314 + ) 315 + e.Equal(http.StatusCreated, httpRespCreated.Code) 316 + 317 + var bodyCreated apiv1NoteCreateResponse 318 + e.readBodyAndUnjsonify(httpRespCreated.Body, &bodyCreated) 319 + 320 + time.Sleep(2 * time.Hour) 321 + 322 + // read note 323 + httpRespRead := e.httpRequest(http.MethodGet, "/api/v1/note/"+bodyCreated.Slug, nil) 324 + e.Equal(http.StatusGone, httpRespRead.Code) 325 + 326 + dbNote := e.getNoteBySlug(bodyCreated.Slug) 327 + e.Equal(content, dbNote.Content) 328 + e.True(dbNote.ReadAt.IsZero()) 329 + }) 221 330 } 222 331 223 332 type apiv1NoteGetWithPasswordRequest struct {
M
internal/jwtutil/jwtutil_test.go
··· 2 2 3 3 import ( 4 4 "testing" 5 + "testing/synctest" 5 6 "time" 6 7 7 8 "github.com/stretchr/testify/assert" ··· 46 47 } 47 48 48 49 func TestJWTUtil_Parse_expired(t *testing.T) { 49 - ttl := 100 * time.Millisecond 50 - jwt := NewJWTUtil("key", ttl) 51 - payload := Payload{UserID: "qwerty"} 50 + ttl := 24 * time.Hour 52 51 53 - token, err := jwt.AccessToken(payload) 54 - require.NoError(t, err) 55 - assert.NotEmpty(t, token) 52 + synctest.Test(t, func(t *testing.T) { 53 + jwt := NewJWTUtil("key", ttl) 54 + payload := Payload{UserID: "qwerty"} 56 55 57 - time.Sleep(ttl) 58 - _, err = jwt.Parse(token) 59 - require.Error(t, err) 56 + token, err := jwt.AccessToken(payload) 57 + require.NoError(t, err) 58 + assert.NotEmpty(t, token) 59 + 60 + time.Sleep(2 * ttl) 61 + 62 + _, err = jwt.Parse(token) 63 + require.EqualError(t, err, ErrTokenExpired.Error()) 64 + }) 60 65 }
M
internal/service/notesrv/notesrv.go
··· 143 143 144 144 // since not every note should be burn before expiration 145 145 // we return early if it's not 146 + // TODO: fix naming 146 147 if note.ShouldBeBurnt() { 147 148 return respNote, nil 148 149 } ··· 230 231 } 231 232 232 233 func (n *NoteSrv) getNote(ctx context.Context, inp GetNoteBySlugInput) (models.Note, error) { 233 - if r, err := n.cache.GetNote(ctx, inp.Slug); err == nil { 234 - return r, nil 234 + if note, err := n.cache.GetNote(ctx, inp.Slug); err == nil { 235 + return note, nil 235 236 } 236 237 237 238 note, err := n.getNoteFromDBasedOnInput(ctx, inp)