From 0d8c6b58db78d2146fbdb1b9164cde9d7c6ca000 Mon Sep 17 00:00:00 2001
From: Deluan <github@deluan.com>
Date: Wed, 9 Mar 2016 10:22:10 -0500
Subject: [PATCH] Grouped some API controllers by functionality

---
 api/{get_music_directory.go => browsing.go} |  52 +++++-
 api/browsing_test.go                        | 187 ++++++++++++++++++++
 api/get_indexes.go                          |  50 ------
 api/get_indexes_test.go                     |  99 -----------
 api/get_license.go                          |  13 --
 api/get_license_test.go                     |  23 ---
 api/get_music_directory_test.go             |  85 ---------
 api/get_music_folders.go                    |  29 ---
 api/get_music_folders_test.go               |  23 ---
 api/ping.go                                 |   7 -
 api/system.go                               |  15 ++
 api/{ping_test.go => system_test.go}        |  18 +-
 conf/router.go                              |  17 +-
 13 files changed, 278 insertions(+), 340 deletions(-)
 rename api/{get_music_directory.go => browsing.go} (50%)
 create mode 100644 api/browsing_test.go
 delete mode 100644 api/get_indexes.go
 delete mode 100644 api/get_indexes_test.go
 delete mode 100644 api/get_license.go
 delete mode 100644 api/get_license_test.go
 delete mode 100644 api/get_music_directory_test.go
 delete mode 100644 api/get_music_folders.go
 delete mode 100644 api/get_music_folders_test.go
 delete mode 100644 api/ping.go
 create mode 100644 api/system.go
 rename api/{ping_test.go => system_test.go} (65%)

diff --git a/api/get_music_directory.go b/api/browsing.go
similarity index 50%
rename from api/get_music_directory.go
rename to api/browsing.go
index 4c55b9215..2fc268cbc 100644
--- a/api/get_music_directory.go
+++ b/api/browsing.go
@@ -1,6 +1,8 @@
 package api
 
 import (
+	"fmt"
+
 	"github.com/astaxie/beego"
 	"github.com/deluan/gosonic/api/responses"
 	"github.com/deluan/gosonic/engine"
@@ -8,16 +10,58 @@ import (
 	"github.com/karlkfi/inject"
 )
 
-type GetMusicDirectoryController struct {
+type BrowsingController struct {
 	BaseAPIController
 	browser engine.Browser
 }
 
-func (c *GetMusicDirectoryController) Prepare() {
+func (c *BrowsingController) Prepare() {
 	inject.ExtractAssignable(utils.Graph, &c.browser)
 }
 
-func (c *GetMusicDirectoryController) Get() {
+func (c *BrowsingController) GetMediaFolders() {
+	mediaFolderList, _ := c.browser.MediaFolders()
+	folders := make([]responses.MusicFolder, len(*mediaFolderList))
+	for i, f := range *mediaFolderList {
+		folders[i].Id = f.Id
+		folders[i].Name = f.Name
+	}
+	response := c.NewEmpty()
+	response.MusicFolders = &responses.MusicFolders{Folders: folders}
+	c.SendResponse(response)
+}
+
+// TODO: Shortcuts amd validate musicFolder parameter
+func (c *BrowsingController) GetIndexes() {
+	ifModifiedSince := c.ParamTime("ifModifiedSince")
+
+	indexes, lastModified, err := c.browser.Indexes(ifModifiedSince)
+	if err != nil {
+		beego.Error("Error retrieving Indexes:", err)
+		c.SendError(responses.ERROR_GENERIC, "Internal Error")
+	}
+
+	res := responses.Indexes{
+		IgnoredArticles: beego.AppConfig.String("ignoredArticles"),
+		LastModified:    fmt.Sprint(utils.ToMillis(lastModified)),
+	}
+
+	res.Index = make([]responses.Index, len(*indexes))
+	for i, idx := range *indexes {
+		res.Index[i].Name = idx.Id
+		res.Index[i].Artists = make([]responses.Artist, len(idx.Artists))
+		for j, a := range idx.Artists {
+			res.Index[i].Artists[j].Id = a.ArtistId
+			res.Index[i].Artists[j].Name = a.Artist
+		}
+	}
+
+	response := c.NewEmpty()
+	response.Indexes = &res
+	c.SendResponse(response)
+}
+
+func (c *BrowsingController) GetDirectory() {
 	id := c.RequiredParamString("id", "id parameter required")
 
 	response := c.NewEmpty()
@@ -37,7 +81,7 @@ func (c *GetMusicDirectoryController) Get() {
 	c.SendResponse(response)
 }
 
-func (c *GetMusicDirectoryController) buildDirectory(d *engine.DirectoryInfo) *responses.Directory {
+func (c *BrowsingController) buildDirectory(d *engine.DirectoryInfo) *responses.Directory {
 	dir := &responses.Directory{Id: d.Id, Name: d.Name}
 
 	dir.Child = make([]responses.Child, len(d.Children))
diff --git a/api/browsing_test.go b/api/browsing_test.go
new file mode 100644
index 000000000..e719462a4
--- /dev/null
+++ b/api/browsing_test.go
@@ -0,0 +1,187 @@
+package api_test
+
+import (
+	"testing"
+
+	"github.com/deluan/gosonic/api/responses"
+	"github.com/deluan/gosonic/consts"
+	"github.com/deluan/gosonic/domain"
+	"github.com/deluan/gosonic/engine"
+	. "github.com/deluan/gosonic/tests"
+	"github.com/deluan/gosonic/tests/mocks"
+	"github.com/deluan/gosonic/utils"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestGetMusicFolders(t *testing.T) {
+	Init(t, false)
+
+	_, w := Get(AddParams("/rest/getMusicFolders.view"), "TestGetMusicFolders")
+
+	Convey("Subject: GetMusicFolders Endpoint", t, func() {
+		Convey("Status code should be 200", func() {
+			So(w.Code, ShouldEqual, 200)
+		})
+		Convey("The response should include the default folder", func() {
+			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, `{"musicFolder":[{"id":"0","name":"iTunes Library"}]}`)
+		})
+	})
+}
+
+const (
+	emptyResponse = `{"indexes":{"ignoredArticles":"The El La Los Las Le Les Os As O A","lastModified":"1"}`
+)
+
+func TestGetIndexes(t *testing.T) {
+	Init(t, false)
+
+	mockRepo := mocks.CreateMockArtistIndexRepo()
+	utils.DefineSingleton(new(domain.ArtistIndexRepository), func() domain.ArtistIndexRepository {
+		return mockRepo
+	})
+	propRepo := mocks.CreateMockPropertyRepo()
+	utils.DefineSingleton(new(engine.PropertyRepository), func() engine.PropertyRepository {
+		return propRepo
+	})
+
+	mockRepo.SetData("[]", 0)
+	mockRepo.SetError(false)
+	propRepo.Put(consts.LastScan, "1")
+	propRepo.SetError(false)
+
+	Convey("Subject: GetIndexes Endpoint", t, func() {
+		Convey("Return fail on Index Table error", func() {
+			mockRepo.SetError(true)
+			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=0"), "TestGetIndexes")
+
+			So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
+		})
+		Convey("Return fail on Property Table error", func() {
+			propRepo.SetError(true)
+			_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
+
+			So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
+		})
+		Convey("When the index is empty", func() {
+			_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
+
+			Convey("Status code should be 200", func() {
+				So(w.Code, ShouldEqual, 200)
+			})
+			Convey("Then it should return an empty collection", func() {
+				So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
+			})
+		})
+		Convey("When the index is not empty", func() {
+			mockRepo.SetData(`[{"Id": "A","Artists": [
+				{"ArtistId": "21", "Artist": "Afrolicious"}
+			]}]`, 2)
+
+			SkipConvey("Then it should return the the items in the response", func() {
+				_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
+
+				So(w.Body.String(), ShouldContainSubstring,
+					`<index name="A"><artist id="21" name="Afrolicious"></artist></index>`)
+			})
+		})
+		Convey("And it should return empty if 'ifModifiedSince' is more recent than the index", func() {
+			mockRepo.SetData(`[{"Id": "A","Artists": [
+				{"ArtistId": "21", "Artist": "Afrolicious"}
+			]}]`, 2)
+			propRepo.Put(consts.LastScan, "1")
+
+			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=2"), "TestGetIndexes")
+
+			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
+		})
+		Convey("And it should return empty if 'ifModifiedSince' is the asme as tie index last update", func() {
+			mockRepo.SetData(`[{"Id": "A","Artists": [
+				{"ArtistId": "21", "Artist": "Afrolicious"}
+			]}]`, 2)
+			propRepo.Put(consts.LastScan, "1")
+
+			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=1"), "TestGetIndexes")
+
+			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
+		})
+		Reset(func() {
+			mockRepo.SetData("[]", 0)
+			mockRepo.SetError(false)
+			propRepo.Put(consts.LastScan, "1")
+			propRepo.SetError(false)
+		})
+	})
+}
+
+func TestGetMusicDirectory(t *testing.T) {
+	Init(t, false)
+
+	mockArtistRepo := mocks.CreateMockArtistRepo()
+	utils.DefineSingleton(new(domain.ArtistRepository), func() domain.ArtistRepository {
+		return mockArtistRepo
+	})
+	mockAlbumRepo := mocks.CreateMockAlbumRepo()
+	utils.DefineSingleton(new(domain.AlbumRepository), func() domain.AlbumRepository {
+		return mockAlbumRepo
+	})
+	mockMediaFileRepo := mocks.CreateMockMediaFileRepo()
+	utils.DefineSingleton(new(domain.MediaFileRepository), func() domain.MediaFileRepository {
+		return mockMediaFileRepo
+	})
+
+	Convey("Subject: GetMusicDirectory Endpoint", t, func() {
+		Convey("Should fail if missing Id parameter", func() {
+			_, w := Get(AddParams("/rest/getMusicDirectory.view"), "TestGetMusicDirectory")
+
+			So(w.Body, ShouldReceiveError, responses.ERROR_MISSING_PARAMETER)
+		})
+		Convey("Id is for an artist", func() {
+			Convey("Return fail on Artist Table error", func() {
+				mockArtistRepo.SetData(`[{"Id":"1","Name":"The Charlatans"}]`, 1)
+				mockArtistRepo.SetError(true)
+				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
+
+				So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
+			})
+		})
+		Convey("When id is not found", func() {
+			mockArtistRepo.SetData(`[{"Id":"1","Name":"The Charlatans"}]`, 1)
+			_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=NOT_FOUND"), "TestGetMusicDirectory")
+
+			So(w.Body, ShouldReceiveError, responses.ERROR_DATA_NOT_FOUND)
+		})
+		Convey("When id matches an artist", func() {
+			mockArtistRepo.SetData(`[{"Id":"1","Name":"The KLF"}]`, 1)
+
+			Convey("Without albums", func() {
+				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
+
+				So(w.Body, ShouldContainJSON, `"id":"1","name":"The KLF"`)
+			})
+			Convey("With albums", func() {
+				mockAlbumRepo.SetData(`[{"Id":"A","Name":"Tardis","ArtistId":"1"}]`, 1)
+				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
+
+				So(w.Body, ShouldContainJSON, `"child":[{"album":"Tardis","id":"A","isDir":true,"parent":"1","title":"Tardis"}]`)
+			})
+		})
+		Convey("When id matches an album with tracks", func() {
+			mockArtistRepo.SetData(`[{"Id":"2","Name":"Céu"}]`, 1)
+			mockAlbumRepo.SetData(`[{"Id":"A","Name":"Vagarosa","ArtistId":"2"}]`, 1)
+			mockMediaFileRepo.SetData(`[{"Id":"3","Title":"Cangote","AlbumId":"A"}]`, 1)
+			_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=A"), "TestGetMusicDirectory")
+
+			So(w.Body, ShouldContainJSON, `"child":[{"id":"3","isDir":false,"parent":"A","title":"Cangote"}]`)
+		})
+		Reset(func() {
+			mockArtistRepo.SetData("[]", 0)
+			mockArtistRepo.SetError(false)
+
+			mockAlbumRepo.SetData("[]", 0)
+			mockAlbumRepo.SetError(false)
+
+			mockMediaFileRepo.SetData("[]", 0)
+			mockMediaFileRepo.SetError(false)
+		})
+	})
+}
diff --git a/api/get_indexes.go b/api/get_indexes.go
deleted file mode 100644
index be13d323c..000000000
--- a/api/get_indexes.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package api
-
-import (
-	"fmt"
-
-	"github.com/astaxie/beego"
-	"github.com/deluan/gosonic/api/responses"
-	"github.com/deluan/gosonic/engine"
-	"github.com/deluan/gosonic/utils"
-	"github.com/karlkfi/inject"
-)
-
-type GetIndexesController struct {
-	BaseAPIController
-	browser engine.Browser
-}
-
-func (c *GetIndexesController) Prepare() {
-	inject.ExtractAssignable(utils.Graph, &c.browser)
-}
-
-// TODO: Shortcuts amd validate musicFolder parameter
-func (c *GetIndexesController) Get() {
-	ifModifiedSince := c.ParamTime("ifModifiedSince")
-
-	indexes, lastModified, err := c.browser.Indexes(ifModifiedSince)
-	if err != nil {
-		beego.Error("Error retrieving Indexes:", err)
-		c.SendError(responses.ERROR_GENERIC, "Internal Error")
-	}
-
-	res := responses.Indexes{
-		IgnoredArticles: beego.AppConfig.String("ignoredArticles"),
-		LastModified:    fmt.Sprint(utils.ToMillis(lastModified)),
-	}
-
-	res.Index = make([]responses.Index, len(*indexes))
-	for i, idx := range *indexes {
-		res.Index[i].Name = idx.Id
-		res.Index[i].Artists = make([]responses.Artist, len(idx.Artists))
-		for j, a := range idx.Artists {
-			res.Index[i].Artists[j].Id = a.ArtistId
-			res.Index[i].Artists[j].Name = a.Artist
-		}
-	}
-
-	response := c.NewEmpty()
-	response.Indexes = &res
-	c.SendResponse(response)
-}
diff --git a/api/get_indexes_test.go b/api/get_indexes_test.go
deleted file mode 100644
index 56aab2d87..000000000
--- a/api/get_indexes_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package api_test
-
-import (
-	"testing"
-
-	"github.com/deluan/gosonic/api/responses"
-	"github.com/deluan/gosonic/consts"
-	"github.com/deluan/gosonic/domain"
-	"github.com/deluan/gosonic/engine"
-	. "github.com/deluan/gosonic/tests"
-	"github.com/deluan/gosonic/tests/mocks"
-	"github.com/deluan/gosonic/utils"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-const (
-	emptyResponse = `{"indexes":{"ignoredArticles":"The El La Los Las Le Les Os As O A","lastModified":"1"}`
-)
-
-func TestGetIndexes(t *testing.T) {
-	Init(t, false)
-
-	mockRepo := mocks.CreateMockArtistIndexRepo()
-	utils.DefineSingleton(new(domain.ArtistIndexRepository), func() domain.ArtistIndexRepository {
-		return mockRepo
-	})
-	propRepo := mocks.CreateMockPropertyRepo()
-	utils.DefineSingleton(new(engine.PropertyRepository), func() engine.PropertyRepository {
-		return propRepo
-	})
-
-	mockRepo.SetData("[]", 0)
-	mockRepo.SetError(false)
-	propRepo.Put(consts.LastScan, "1")
-	propRepo.SetError(false)
-
-	Convey("Subject: GetIndexes Endpoint", t, func() {
-		Convey("Return fail on Index Table error", func() {
-			mockRepo.SetError(true)
-			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=0"), "TestGetIndexes")
-
-			So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
-		})
-		Convey("Return fail on Property Table error", func() {
-			propRepo.SetError(true)
-			_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
-
-			So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
-		})
-		Convey("When the index is empty", func() {
-			_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
-
-			Convey("Status code should be 200", func() {
-				So(w.Code, ShouldEqual, 200)
-			})
-			Convey("Then it should return an empty collection", func() {
-				So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
-			})
-		})
-		Convey("When the index is not empty", func() {
-			mockRepo.SetData(`[{"Id": "A","Artists": [
-				{"ArtistId": "21", "Artist": "Afrolicious"}
-			]}]`, 2)
-
-			SkipConvey("Then it should return the the items in the response", func() {
-				_, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes")
-
-				So(w.Body.String(), ShouldContainSubstring,
-					`<index name="A"><artist id="21" name="Afrolicious"></artist></index>`)
-			})
-		})
-		Convey("And it should return empty if 'ifModifiedSince' is more recent than the index", func() {
-			mockRepo.SetData(`[{"Id": "A","Artists": [
-				{"ArtistId": "21", "Artist": "Afrolicious"}
-			]}]`, 2)
-			propRepo.Put(consts.LastScan, "1")
-
-			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=2"), "TestGetIndexes")
-
-			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
-		})
-		Convey("And it should return empty if 'ifModifiedSince' is the asme as tie index last update", func() {
-			mockRepo.SetData(`[{"Id": "A","Artists": [
-				{"ArtistId": "21", "Artist": "Afrolicious"}
-			]}]`, 2)
-			propRepo.Put(consts.LastScan, "1")
-
-			_, w := Get(AddParams("/rest/getIndexes.view", "ifModifiedSince=1"), "TestGetIndexes")
-
-			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, emptyResponse)
-		})
-		Reset(func() {
-			mockRepo.SetData("[]", 0)
-			mockRepo.SetError(false)
-			propRepo.Put(consts.LastScan, "1")
-			propRepo.SetError(false)
-		})
-	})
-}
diff --git a/api/get_license.go b/api/get_license.go
deleted file mode 100644
index ae33aeb5b..000000000
--- a/api/get_license.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package api
-
-import (
-	"github.com/deluan/gosonic/api/responses"
-)
-
-type GetLicenseController struct{ BaseAPIController }
-
-func (c *GetLicenseController) Get() {
-	response := c.NewEmpty()
-	response.License = &responses.License{Valid: true}
-	c.SendResponse(response)
-}
diff --git a/api/get_license_test.go b/api/get_license_test.go
deleted file mode 100644
index 4598d7591..000000000
--- a/api/get_license_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package api_test
-
-import (
-	. "github.com/deluan/gosonic/tests"
-	. "github.com/smartystreets/goconvey/convey"
-	"testing"
-)
-
-func TestGetLicense(t *testing.T) {
-	Init(t, false)
-
-	_, w := Get(AddParams("/rest/getLicense.view"), "TestGetLicense")
-
-	Convey("Subject: GetLicense Endpoint", t, func() {
-		Convey("Status code should be 200", func() {
-			So(w.Code, ShouldEqual, 200)
-		})
-		Convey("The license should always be valid", func() {
-			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, `"license":{"valid":true}`)
-		})
-
-	})
-}
diff --git a/api/get_music_directory_test.go b/api/get_music_directory_test.go
deleted file mode 100644
index 2cd1d8070..000000000
--- a/api/get_music_directory_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package api_test
-
-import (
-	"testing"
-
-	"github.com/deluan/gosonic/api/responses"
-	"github.com/deluan/gosonic/domain"
-	. "github.com/deluan/gosonic/tests"
-	"github.com/deluan/gosonic/tests/mocks"
-	"github.com/deluan/gosonic/utils"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func TestGetMusicDirectory(t *testing.T) {
-	Init(t, false)
-
-	mockArtistRepo := mocks.CreateMockArtistRepo()
-	utils.DefineSingleton(new(domain.ArtistRepository), func() domain.ArtistRepository {
-		return mockArtistRepo
-	})
-	mockAlbumRepo := mocks.CreateMockAlbumRepo()
-	utils.DefineSingleton(new(domain.AlbumRepository), func() domain.AlbumRepository {
-		return mockAlbumRepo
-	})
-	mockMediaFileRepo := mocks.CreateMockMediaFileRepo()
-	utils.DefineSingleton(new(domain.MediaFileRepository), func() domain.MediaFileRepository {
-		return mockMediaFileRepo
-	})
-
-	Convey("Subject: GetMusicDirectory Endpoint", t, func() {
-		Convey("Should fail if missing Id parameter", func() {
-			_, w := Get(AddParams("/rest/getMusicDirectory.view"), "TestGetMusicDirectory")
-
-			So(w.Body, ShouldReceiveError, responses.ERROR_MISSING_PARAMETER)
-		})
-		Convey("Id is for an artist", func() {
-			Convey("Return fail on Artist Table error", func() {
-				mockArtistRepo.SetData(`[{"Id":"1","Name":"The Charlatans"}]`, 1)
-				mockArtistRepo.SetError(true)
-				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
-
-				So(w.Body, ShouldReceiveError, responses.ERROR_GENERIC)
-			})
-		})
-		Convey("When id is not found", func() {
-			mockArtistRepo.SetData(`[{"Id":"1","Name":"The Charlatans"}]`, 1)
-			_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=NOT_FOUND"), "TestGetMusicDirectory")
-
-			So(w.Body, ShouldReceiveError, responses.ERROR_DATA_NOT_FOUND)
-		})
-		Convey("When id matches an artist", func() {
-			mockArtistRepo.SetData(`[{"Id":"1","Name":"The KLF"}]`, 1)
-
-			Convey("Without albums", func() {
-				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
-
-				So(w.Body, ShouldContainJSON, `"id":"1","name":"The KLF"`)
-			})
-			Convey("With albums", func() {
-				mockAlbumRepo.SetData(`[{"Id":"A","Name":"Tardis","ArtistId":"1"}]`, 1)
-				_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory")
-
-				So(w.Body, ShouldContainJSON, `"child":[{"album":"Tardis","id":"A","isDir":true,"parent":"1","title":"Tardis"}]`)
-			})
-		})
-		Convey("When id matches an album with tracks", func() {
-			mockArtistRepo.SetData(`[{"Id":"2","Name":"Céu"}]`, 1)
-			mockAlbumRepo.SetData(`[{"Id":"A","Name":"Vagarosa","ArtistId":"2"}]`, 1)
-			mockMediaFileRepo.SetData(`[{"Id":"3","Title":"Cangote","AlbumId":"A"}]`, 1)
-			_, w := Get(AddParams("/rest/getMusicDirectory.view", "id=A"), "TestGetMusicDirectory")
-
-			So(w.Body, ShouldContainJSON, `"child":[{"id":"3","isDir":false,"parent":"A","title":"Cangote"}]`)
-		})
-		Reset(func() {
-			mockArtistRepo.SetData("[]", 0)
-			mockArtistRepo.SetError(false)
-
-			mockAlbumRepo.SetData("[]", 0)
-			mockAlbumRepo.SetError(false)
-
-			mockMediaFileRepo.SetData("[]", 0)
-			mockMediaFileRepo.SetError(false)
-		})
-	})
-}
diff --git a/api/get_music_folders.go b/api/get_music_folders.go
deleted file mode 100644
index cb65ec932..000000000
--- a/api/get_music_folders.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package api
-
-import (
-	"github.com/deluan/gosonic/api/responses"
-	"github.com/deluan/gosonic/engine"
-	"github.com/deluan/gosonic/utils"
-	"github.com/karlkfi/inject"
-)
-
-type GetMusicFoldersController struct {
-	BaseAPIController
-	browser engine.Browser
-}
-
-func (c *GetMusicFoldersController) Prepare() {
-	inject.ExtractAssignable(utils.Graph, &c.browser)
-}
-
-func (c *GetMusicFoldersController) Get() {
-	mediaFolderList, _ := c.browser.MediaFolders()
-	folders := make([]responses.MusicFolder, len(*mediaFolderList))
-	for i, f := range *mediaFolderList {
-		folders[i].Id = f.Id
-		folders[i].Name = f.Name
-	}
-	response := c.NewEmpty()
-	response.MusicFolders = &responses.MusicFolders{Folders: folders}
-	c.SendResponse(response)
-}
diff --git a/api/get_music_folders_test.go b/api/get_music_folders_test.go
deleted file mode 100644
index dd131ead1..000000000
--- a/api/get_music_folders_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package api_test
-
-import (
-	"testing"
-
-	. "github.com/deluan/gosonic/tests"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func TestGetMusicFolders(t *testing.T) {
-	Init(t, false)
-
-	_, w := Get(AddParams("/rest/getMusicFolders.view"), "TestGetMusicFolders")
-
-	Convey("Subject: GetMusicFolders Endpoint", t, func() {
-		Convey("Status code should be 200", func() {
-			So(w.Code, ShouldEqual, 200)
-		})
-		Convey("The response should include the default folder", func() {
-			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, `{"musicFolder":[{"id":"0","name":"iTunes Library"}]}`)
-		})
-	})
-}
diff --git a/api/ping.go b/api/ping.go
deleted file mode 100644
index 4de664274..000000000
--- a/api/ping.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package api
-
-type PingController struct{ BaseAPIController }
-
-func (c *PingController) Get() {
-	c.SendResponse(c.NewEmpty())
-}
diff --git a/api/system.go b/api/system.go
new file mode 100644
index 000000000..53a7377cb
--- /dev/null
+++ b/api/system.go
@@ -0,0 +1,15 @@
+package api
+
+import "github.com/deluan/gosonic/api/responses"
+
+type SystemController struct{ BaseAPIController }
+
+func (c *SystemController) Ping() {
+	c.SendResponse(c.NewEmpty())
+}
+
+func (c *SystemController) GetLicense() {
+	response := c.NewEmpty()
+	response.License = &responses.License{Valid: true}
+	c.SendResponse(response)
+}
diff --git a/api/ping_test.go b/api/system_test.go
similarity index 65%
rename from api/ping_test.go
rename to api/system_test.go
index 425c8ed6c..e12419b3a 100644
--- a/api/ping_test.go
+++ b/api/system_test.go
@@ -2,10 +2,11 @@ package api_test
 
 import (
 	"encoding/json"
+	"testing"
+
 	"github.com/deluan/gosonic/api/responses"
 	. "github.com/deluan/gosonic/tests"
 	. "github.com/smartystreets/goconvey/convey"
-	"testing"
 )
 
 func TestPing(t *testing.T) {
@@ -30,3 +31,18 @@ func TestPing(t *testing.T) {
 
 	})
 }
+func TestGetLicense(t *testing.T) {
+	Init(t, false)
+
+	_, w := Get(AddParams("/rest/getLicense.view"), "TestGetLicense")
+
+	Convey("Subject: GetLicense Endpoint", t, func() {
+		Convey("Status code should be 200", func() {
+			So(w.Code, ShouldEqual, 200)
+		})
+		Convey("The license should always be valid", func() {
+			So(UnindentJSON(w.Body.Bytes()), ShouldContainSubstring, `"license":{"valid":true}`)
+		})
+
+	})
+}
diff --git a/conf/router.go b/conf/router.go
index ad38c838f..0e56a7b82 100644
--- a/conf/router.go
+++ b/conf/router.go
@@ -16,17 +16,22 @@ func init() {
 
 func mapEndpoints() {
 	ns := beego.NewNamespace("/rest",
-		beego.NSRouter("/ping.view", &api.PingController{}, "*:Get"),
-		beego.NSRouter("/getLicense.view", &api.GetLicenseController{}, "*:Get"),
-		beego.NSRouter("/getMusicFolders.view", &api.GetMusicFoldersController{}, "*:Get"),
-		beego.NSRouter("/getIndexes.view", &api.GetIndexesController{}, "*:Get"),
-		beego.NSRouter("/getMusicDirectory.view", &api.GetMusicDirectoryController{}, "*:Get"),
+		beego.NSRouter("/ping.view", &api.SystemController{}, "*:Ping"),
+		beego.NSRouter("/getLicense.view", &api.SystemController{}, "*:GetLicense"),
+
+		beego.NSRouter("/getMusicFolders.view", &api.BrowsingController{}, "*:GetMediaFolders"),
+		beego.NSRouter("/getIndexes.view", &api.BrowsingController{}, "*:GetIndexes"),
+		beego.NSRouter("/getMusicDirectory.view", &api.BrowsingController{}, "*:GetDirectory"),
+
 		beego.NSRouter("/getCoverArt.view", &api.GetCoverArtController{}, "*:Get"),
 		beego.NSRouter("/stream.view", &api.StreamController{}, "*:Stream"),
 		beego.NSRouter("/download.view", &api.StreamController{}, "*:Download"),
-		beego.NSRouter("/getUser.view", &api.UsersController{}, "*:GetUser"),
+
 		beego.NSRouter("/getAlbumList.view", &api.GetAlbumListController{}, "*:Get"),
+
 		beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"),
+
+		beego.NSRouter("/getUser.view", &api.UsersController{}, "*:GetUser"),
 	)
 	beego.AddNamespace(ns)