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
14 changes: 12 additions & 2 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ jobs:
working-directory: MyMusicBoxApi
run: go mod tidy

- name: Test
- name: Run tests with coverage
working-directory: MyMusicBoxApi
run: go test -v ./...
run: go test -v -coverprofile=coverage.out ./...

- name: Show coverage summary
working-directory: MyMusicBoxApi
run: go tool cover -func=coverage.out

- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: MyMusicBoxApi/coverage.out
2 changes: 1 addition & 1 deletion MyMusicBoxApi/database/playlistsongtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type PlaylistsongTable struct {
BaseTable
}

func NewPlaylistsongTableInstance() *PlaylistsongTable {
func NewPlaylistsongTableInstance() IPlaylistsongTable {
return &PlaylistsongTable{
BaseTable: NewBaseTableInstance(),
}
Expand Down
2 changes: 1 addition & 1 deletion MyMusicBoxApi/database/playlisttable.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PlaylistTable struct {
BaseTable
}

func NewPlaylistTableInstance() *PlaylistTable {
func NewPlaylistTableInstance() IPlaylistTable {
return &PlaylistTable{
BaseTable: NewBaseTableInstance(),
}
Expand Down
2 changes: 1 addition & 1 deletion MyMusicBoxApi/database/songtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type SongTable struct {
BaseTable
}

func NewSongTableInstance() *SongTable {
func NewSongTableInstance() ISongTable {
return &SongTable{
BaseTable: NewBaseTableInstance(),
}
Expand Down
27 changes: 10 additions & 17 deletions MyMusicBoxApi/http/playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import (
"github.com/gin-gonic/gin"
)

func FetchPlaylists(ctx *gin.Context) {
type PlaylistHandler struct {
PlaylistTable database.IPlaylistTable
}

func (handler *PlaylistHandler) FetchPlaylists(ctx *gin.Context) {
lastKnowPlaylistIdQuery := ctx.Query("lastKnowPlaylistId")

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

playlistTable := database.NewPlaylistTableInstance()

playlists, err := playlistTable.FetchPlaylists(ctx.Request.Context(), lastKnowPlaylistId)
playlists, err := handler.PlaylistTable.FetchPlaylists(ctx.Request.Context(), lastKnowPlaylistId)
if err != nil {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
Expand All @@ -30,7 +32,7 @@ func FetchPlaylists(ctx *gin.Context) {
ctx.JSON(http.StatusOK, models.OkResponse(playlists, fmt.Sprintf("Found %d playlist", len(playlists))))
}

func InsertPlaylist(ctx *gin.Context) {
func (hanlder *PlaylistHandler) InsertPlaylist(ctx *gin.Context) {
var playlist models.Playlist

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

playlistTable := database.NewPlaylistTableInstance()

playlistId, err := playlistTable.InsertPlaylist(playlist)
playlistId, err := hanlder.PlaylistTable.InsertPlaylist(playlist)

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

func DeletePlaylist(ctx *gin.Context) {
func (handler *PlaylistHandler) DeletePlaylist(ctx *gin.Context) {
playlistIdParameter := ctx.Param("playlistId")

if playlistIdParameter == "" {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse("playlistId is empty"))
return
}

id, err := strconv.Atoi(playlistIdParameter)

if err != nil {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}

playlistTable := database.NewPlaylistTableInstance()

err = playlistTable.DeletePlaylist(id)
err = handler.PlaylistTable.DeletePlaylist(id)

if err != nil {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
Expand Down
267 changes: 267 additions & 0 deletions MyMusicBoxApi/http/playlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package http

import (
"bytes"
"context"
"encoding/json"
"fmt"
"musicboxapi/database"
"musicboxapi/models"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

type mockPlaylistTable struct {
database.IPlaylistTable
fetchPlaylists func(ctx context.Context, lastKnowPlaylistId int) (playlists []models.Playlist, error error)
insertPlaylist func(playlist models.Playlist) (lastInsertedId int, error error)
deletePlaylist func(playlistId int) (error error)
}

func (m *mockPlaylistTable) FetchPlaylists(ctx context.Context, lastKnowPlaylistId int) (playlists []models.Playlist, error error) {
return m.fetchPlaylists(ctx, lastKnowPlaylistId)
}
func (m *mockPlaylistTable) InsertPlaylist(playlist models.Playlist) (lastInsertedId int, error error) {
return m.insertPlaylist(playlist)
}
func (m *mockPlaylistTable) DeletePlaylist(playlistId int) (error error) {
return m.deletePlaylist(playlistId)
}

func TestFetchPlaylists(t *testing.T) {
// Arrange
route := "/api/v1/playlist"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
fetchPlaylists: func(ctx context.Context, lastKnowPlaylistId int) ([]models.Playlist, error) {
return []models.Playlist{
{Id: 1, Name: "Playlist_1", Description: "Best ever", ThumbnailPath: "path/image1.png", IsPublic: false},
{Id: 2, Name: "Playlist_2", Description: "Second best", ThumbnailPath: "path/image2.png", IsPublic: true},
}, nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.GET(route, playlistHandler.FetchPlaylists)

recorder := httptest.NewRecorder()

req, _ := http.NewRequest("GET", route, nil)

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusOK, recorder.Code)

var rawResult map[string]any

err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)

assert.Equal(t, nil, err)

dataBytes, err := json.Marshal(rawResult["Data"])

var playlists []models.Playlist
err = json.Unmarshal(dataBytes, &playlists)
assert.NoError(t, err)

assert.Equal(t, 2, len(playlists))
assert.Equal(t, "Playlist_1", playlists[0].Name)
assert.Equal(t, "Playlist_2", playlists[1].Name)
}

func TestFetchPlaylistsLastKnowPlaylistId(t *testing.T) {
// Arrange
_lastKnowPlaylistId := 4
route := "/api/v1/playlist"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
fetchPlaylists: func(ctx context.Context, lastKnowPlaylistId int) ([]models.Playlist, error) {
assert.Equal(t, _lastKnowPlaylistId, lastKnowPlaylistId)
return []models.Playlist{
{Id: 4, Name: "Playlist_4", Description: "Second best", ThumbnailPath: "path/image4.png", IsPublic: true},
}, nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.GET(route, playlistHandler.FetchPlaylists)

recorder := httptest.NewRecorder()

queryRoute := fmt.Sprintf("%s?lastKnowPlaylistId=%d", route, _lastKnowPlaylistId)

req, _ := http.NewRequest("GET", queryRoute, nil)

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusOK, recorder.Code)

var rawResult map[string]any

err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)

assert.Equal(t, nil, err)

dataBytes, err := json.Marshal(rawResult["Data"])

var playlists []models.Playlist
err = json.Unmarshal(dataBytes, &playlists)
assert.NoError(t, err)

assert.Equal(t, 1, len(playlists))
assert.Equal(t, "Playlist_4", playlists[0].Name)
}

func TestInsertPlaylist(t *testing.T) {
// Arrange
route := "/api/v1/playlist"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
insertPlaylist: func(playlist models.Playlist) (lastInsertedId int, error error) {
return 1, nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.POST(route, playlistHandler.InsertPlaylist)

recorder := httptest.NewRecorder()

body := models.Playlist{
Description: "Cool playlist",
}

bodyBytes, _ := json.Marshal(body)

req, _ := http.NewRequest("POST", route, bytes.NewBuffer(bodyBytes))

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusOK, recorder.Code)

var rawResult map[string]any

err := json.Unmarshal(recorder.Body.Bytes(), &rawResult)

assert.Equal(t, nil, err)

dataBytes, err := json.Marshal(rawResult["Data"])

err = json.Unmarshal(dataBytes, &rawResult)

assert.Equal(t, 1, int(rawResult["playlistId"].(float64)))
}

func TestInsertPlaylistJsonError(t *testing.T) {
// Arrange
route := "/api/v1/playlist"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
insertPlaylist: func(playlist models.Playlist) (lastInsertedId int, error error) {
return 1, nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.POST(route, playlistHandler.InsertPlaylist)

recorder := httptest.NewRecorder()

// Wrong type, will throw an error
bodyBytes, _ := json.Marshal("")

req, _ := http.NewRequest("POST", route, bytes.NewBuffer(bodyBytes))

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
}

func TestDeletePlaylistPlaylistIdError(t *testing.T) {
// Arrange
route := "/playlist/:playlistId"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
deletePlaylist: func(playlistId int) (error error) {
return nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.DELETE(route, playlistHandler.DeletePlaylist)

recorder := httptest.NewRecorder()

// Unable to parse to int, will throw error
_route := "/playlist/sdfsd"

req, _ := http.NewRequest("DELETE", _route, nil)

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
}

func TestDeletePlaylistPlaylistId(t *testing.T) {
// Arrange
route := "/playlist/:playlistId"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
deletePlaylist: func(playlistId int) (error error) {
return nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.DELETE(route, playlistHandler.DeletePlaylist)

recorder := httptest.NewRecorder()

// Unable to parse to int, will throw error
_route := "/playlist/1"

req, _ := http.NewRequest("DELETE", _route, nil)

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusOK, recorder.Code)
}
Loading