Skip to content

Commit 0458995

Browse files
authored
endpoint unit test (#53)
* endpoint unit test * test coverage * badge * revert
1 parent c80c267 commit 0458995

File tree

14 files changed

+784
-67
lines changed

14 files changed

+784
-67
lines changed

.github/workflows/backend.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ jobs:
2828
working-directory: MyMusicBoxApi
2929
run: go mod tidy
3030

31-
- name: Test
31+
- name: Run tests with coverage
3232
working-directory: MyMusicBoxApi
33-
run: go test -v ./...
33+
run: go test -v -coverprofile=coverage.out ./...
34+
35+
- name: Show coverage summary
36+
working-directory: MyMusicBoxApi
37+
run: go tool cover -func=coverage.out
38+
39+
- name: Upload coverage artifact
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: coverage-report
43+
path: MyMusicBoxApi/coverage.out

MyMusicBoxApi/database/playlistsongtable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type PlaylistsongTable struct {
1818
BaseTable
1919
}
2020

21-
func NewPlaylistsongTableInstance() *PlaylistsongTable {
21+
func NewPlaylistsongTableInstance() IPlaylistsongTable {
2222
return &PlaylistsongTable{
2323
BaseTable: NewBaseTableInstance(),
2424
}

MyMusicBoxApi/database/playlisttable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type PlaylistTable struct {
1717
BaseTable
1818
}
1919

20-
func NewPlaylistTableInstance() *PlaylistTable {
20+
func NewPlaylistTableInstance() IPlaylistTable {
2121
return &PlaylistTable{
2222
BaseTable: NewBaseTableInstance(),
2323
}

MyMusicBoxApi/database/songtable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type SongTable struct {
1616
BaseTable
1717
}
1818

19-
func NewSongTableInstance() *SongTable {
19+
func NewSongTableInstance() ISongTable {
2020
return &SongTable{
2121
BaseTable: NewBaseTableInstance(),
2222
}

MyMusicBoxApi/http/playlist.go

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import (
1010
"github.com/gin-gonic/gin"
1111
)
1212

13-
func FetchPlaylists(ctx *gin.Context) {
13+
type PlaylistHandler struct {
14+
PlaylistTable database.IPlaylistTable
15+
}
16+
17+
func (handler *PlaylistHandler) FetchPlaylists(ctx *gin.Context) {
1418
lastKnowPlaylistIdQuery := ctx.Query("lastKnowPlaylistId")
1519

1620
lastKnowPlaylistId := 0
@@ -19,9 +23,7 @@ func FetchPlaylists(ctx *gin.Context) {
1923
lastKnowPlaylistId, _ = strconv.Atoi(lastKnowPlaylistIdQuery)
2024
}
2125

22-
playlistTable := database.NewPlaylistTableInstance()
23-
24-
playlists, err := playlistTable.FetchPlaylists(ctx.Request.Context(), lastKnowPlaylistId)
26+
playlists, err := handler.PlaylistTable.FetchPlaylists(ctx.Request.Context(), lastKnowPlaylistId)
2527
if err != nil {
2628
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
2729
return
@@ -30,7 +32,7 @@ func FetchPlaylists(ctx *gin.Context) {
3032
ctx.JSON(http.StatusOK, models.OkResponse(playlists, fmt.Sprintf("Found %d playlist", len(playlists))))
3133
}
3234

33-
func InsertPlaylist(ctx *gin.Context) {
35+
func (hanlder *PlaylistHandler) InsertPlaylist(ctx *gin.Context) {
3436
var playlist models.Playlist
3537

3638
err := ctx.ShouldBindBodyWithJSON(&playlist)
@@ -40,9 +42,7 @@ func InsertPlaylist(ctx *gin.Context) {
4042
return
4143
}
4244

43-
playlistTable := database.NewPlaylistTableInstance()
44-
45-
playlistId, err := playlistTable.InsertPlaylist(playlist)
45+
playlistId, err := hanlder.PlaylistTable.InsertPlaylist(playlist)
4646

4747
if err != nil {
4848
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
@@ -52,24 +52,17 @@ func InsertPlaylist(ctx *gin.Context) {
5252
ctx.JSON(http.StatusOK, models.OkResponse(gin.H{"playlistId": playlistId}, "Created new playlist"))
5353
}
5454

55-
func DeletePlaylist(ctx *gin.Context) {
55+
func (handler *PlaylistHandler) DeletePlaylist(ctx *gin.Context) {
5656
playlistIdParameter := ctx.Param("playlistId")
5757

58-
if playlistIdParameter == "" {
59-
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse("playlistId is empty"))
60-
return
61-
}
62-
6358
id, err := strconv.Atoi(playlistIdParameter)
6459

6560
if err != nil {
6661
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
6762
return
6863
}
6964

70-
playlistTable := database.NewPlaylistTableInstance()
71-
72-
err = playlistTable.DeletePlaylist(id)
65+
err = handler.PlaylistTable.DeletePlaylist(id)
7366

7467
if err != nil {
7568
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package http
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"musicboxapi/database"
9+
"musicboxapi/models"
10+
"net/http"
11+
"net/http/httptest"
12+
"testing"
13+
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
type mockPlaylistTable struct {
18+
database.IPlaylistTable
19+
fetchPlaylists func(ctx context.Context, lastKnowPlaylistId int) (playlists []models.Playlist, error error)
20+
insertPlaylist func(playlist models.Playlist) (lastInsertedId int, error error)
21+
deletePlaylist func(playlistId int) (error error)
22+
}
23+
24+
func (m *mockPlaylistTable) FetchPlaylists(ctx context.Context, lastKnowPlaylistId int) (playlists []models.Playlist, error error) {
25+
return m.fetchPlaylists(ctx, lastKnowPlaylistId)
26+
}
27+
func (m *mockPlaylistTable) InsertPlaylist(playlist models.Playlist) (lastInsertedId int, error error) {
28+
return m.insertPlaylist(playlist)
29+
}
30+
func (m *mockPlaylistTable) DeletePlaylist(playlistId int) (error error) {
31+
return m.deletePlaylist(playlistId)
32+
}
33+
34+
func TestFetchPlaylists(t *testing.T) {
35+
// Arrange
36+
route := "/api/v1/playlist"
37+
router := SetupTestRouter()
38+
39+
mockTable := &mockPlaylistTable{
40+
fetchPlaylists: func(ctx context.Context, lastKnowPlaylistId int) ([]models.Playlist, error) {
41+
return []models.Playlist{
42+
{Id: 1, Name: "Playlist_1", Description: "Best ever", ThumbnailPath: "path/image1.png", IsPublic: false},
43+
{Id: 2, Name: "Playlist_2", Description: "Second best", ThumbnailPath: "path/image2.png", IsPublic: true},
44+
}, nil
45+
},
46+
}
47+
48+
playlistHandler := PlaylistHandler{
49+
PlaylistTable: mockTable,
50+
}
51+
52+
router.GET(route, playlistHandler.FetchPlaylists)
53+
54+
recorder := httptest.NewRecorder()
55+
56+
req, _ := http.NewRequest("GET", route, nil)
57+
58+
// Act
59+
router.ServeHTTP(recorder, req)
60+
61+
// Assert
62+
assert.Equal(t, http.StatusOK, recorder.Code)
63+
64+
var rawResult map[string]any
65+
66+
err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)
67+
68+
assert.Equal(t, nil, err)
69+
70+
dataBytes, err := json.Marshal(rawResult["Data"])
71+
72+
var playlists []models.Playlist
73+
err = json.Unmarshal(dataBytes, &playlists)
74+
assert.NoError(t, err)
75+
76+
assert.Equal(t, 2, len(playlists))
77+
assert.Equal(t, "Playlist_1", playlists[0].Name)
78+
assert.Equal(t, "Playlist_2", playlists[1].Name)
79+
}
80+
81+
func TestFetchPlaylistsLastKnowPlaylistId(t *testing.T) {
82+
// Arrange
83+
_lastKnowPlaylistId := 4
84+
route := "/api/v1/playlist"
85+
router := SetupTestRouter()
86+
87+
mockTable := &mockPlaylistTable{
88+
fetchPlaylists: func(ctx context.Context, lastKnowPlaylistId int) ([]models.Playlist, error) {
89+
assert.Equal(t, _lastKnowPlaylistId, lastKnowPlaylistId)
90+
return []models.Playlist{
91+
{Id: 4, Name: "Playlist_4", Description: "Second best", ThumbnailPath: "path/image4.png", IsPublic: true},
92+
}, nil
93+
},
94+
}
95+
96+
playlistHandler := PlaylistHandler{
97+
PlaylistTable: mockTable,
98+
}
99+
100+
router.GET(route, playlistHandler.FetchPlaylists)
101+
102+
recorder := httptest.NewRecorder()
103+
104+
queryRoute := fmt.Sprintf("%s?lastKnowPlaylistId=%d", route, _lastKnowPlaylistId)
105+
106+
req, _ := http.NewRequest("GET", queryRoute, nil)
107+
108+
// Act
109+
router.ServeHTTP(recorder, req)
110+
111+
// Assert
112+
assert.Equal(t, http.StatusOK, recorder.Code)
113+
114+
var rawResult map[string]any
115+
116+
err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)
117+
118+
assert.Equal(t, nil, err)
119+
120+
dataBytes, err := json.Marshal(rawResult["Data"])
121+
122+
var playlists []models.Playlist
123+
err = json.Unmarshal(dataBytes, &playlists)
124+
assert.NoError(t, err)
125+
126+
assert.Equal(t, 1, len(playlists))
127+
assert.Equal(t, "Playlist_4", playlists[0].Name)
128+
}
129+
130+
func TestInsertPlaylist(t *testing.T) {
131+
// Arrange
132+
route := "/api/v1/playlist"
133+
router := SetupTestRouter()
134+
135+
mockTable := &mockPlaylistTable{
136+
insertPlaylist: func(playlist models.Playlist) (lastInsertedId int, error error) {
137+
return 1, nil
138+
},
139+
}
140+
141+
playlistHandler := PlaylistHandler{
142+
PlaylistTable: mockTable,
143+
}
144+
145+
router.POST(route, playlistHandler.InsertPlaylist)
146+
147+
recorder := httptest.NewRecorder()
148+
149+
body := models.Playlist{
150+
Description: "Cool playlist",
151+
}
152+
153+
bodyBytes, _ := json.Marshal(body)
154+
155+
req, _ := http.NewRequest("POST", route, bytes.NewBuffer(bodyBytes))
156+
157+
// Act
158+
router.ServeHTTP(recorder, req)
159+
160+
// Assert
161+
assert.Equal(t, http.StatusOK, recorder.Code)
162+
163+
var rawResult map[string]any
164+
165+
err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)
166+
167+
assert.Equal(t, nil, err)
168+
169+
dataBytes, err := json.Marshal(rawResult["Data"])
170+
171+
err = json.Unmarshal(dataBytes, &rawResult)
172+
173+
assert.Equal(t, 1, int(rawResult["playlistId"].(float64)))
174+
}
175+
176+
func TestInsertPlaylistJsonError(t *testing.T) {
177+
// Arrange
178+
route := "/api/v1/playlist"
179+
router := SetupTestRouter()
180+
181+
mockTable := &mockPlaylistTable{
182+
insertPlaylist: func(playlist models.Playlist) (lastInsertedId int, error error) {
183+
return 1, nil
184+
},
185+
}
186+
187+
playlistHandler := PlaylistHandler{
188+
PlaylistTable: mockTable,
189+
}
190+
191+
router.POST(route, playlistHandler.InsertPlaylist)
192+
193+
recorder := httptest.NewRecorder()
194+
195+
// Wrong type, will throw an error
196+
bodyBytes, _ := json.Marshal("")
197+
198+
req, _ := http.NewRequest("POST", route, bytes.NewBuffer(bodyBytes))
199+
200+
// Act
201+
router.ServeHTTP(recorder, req)
202+
203+
// Assert
204+
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
205+
}
206+
207+
func TestDeletePlaylistPlaylistIdError(t *testing.T) {
208+
// Arrange
209+
route := "/playlist/:playlistId"
210+
router := SetupTestRouter()
211+
212+
mockTable := &mockPlaylistTable{
213+
deletePlaylist: func(playlistId int) (error error) {
214+
return nil
215+
},
216+
}
217+
218+
playlistHandler := PlaylistHandler{
219+
PlaylistTable: mockTable,
220+
}
221+
222+
router.DELETE(route, playlistHandler.DeletePlaylist)
223+
224+
recorder := httptest.NewRecorder()
225+
226+
// Unable to parse to int, will throw error
227+
_route := "/playlist/sdfsd"
228+
229+
req, _ := http.NewRequest("DELETE", _route, nil)
230+
231+
// Act
232+
router.ServeHTTP(recorder, req)
233+
234+
// Assert
235+
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
236+
}
237+
238+
func TestDeletePlaylistPlaylistId(t *testing.T) {
239+
// Arrange
240+
route := "/playlist/:playlistId"
241+
router := SetupTestRouter()
242+
243+
mockTable := &mockPlaylistTable{
244+
deletePlaylist: func(playlistId int) (error error) {
245+
return nil
246+
},
247+
}
248+
249+
playlistHandler := PlaylistHandler{
250+
PlaylistTable: mockTable,
251+
}
252+
253+
router.DELETE(route, playlistHandler.DeletePlaylist)
254+
255+
recorder := httptest.NewRecorder()
256+
257+
// Unable to parse to int, will throw error
258+
_route := "/playlist/1"
259+
260+
req, _ := http.NewRequest("DELETE", _route, nil)
261+
262+
// Act
263+
router.ServeHTTP(recorder, req)
264+
265+
// Assert
266+
assert.Equal(t, http.StatusOK, recorder.Code)
267+
}

0 commit comments

Comments
 (0)