package server import ( "encoding/json" "net/http" "net/url" "strings" "testing" "music-server/internal/backend" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func mp3Body(content string) []byte { return append([]byte("ID3"), content...) } func (e *testEnv) startUpload(name string) backend.Upload { e.t.Helper() rec := e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":"`+name+`"}`) require.Equal(e.t, http.StatusCreated, rec.Code, rec.Body.String()) var upload backend.Upload require.NoError(e.t, json.Unmarshal(rec.Body.Bytes(), &upload)) return upload } func uploadPath(upload backend.Upload) string { return "/api/v1/soundtracks/uploads/" + upload.ID.String() } func TestUploadSoundtrackEndpoints(t *testing.T) { e := newTestEnv(t) upload := e.startUpload("Chrono Trigger") assert.Equal(t, "Chrono Trigger", upload.Name) for _, song := range []string{"01 Wind Scene.mp3", "02 Corridors of Time.mp3"} { rec := e.put(uploadPath(upload)+"/files/"+url.PathEscape(song), "audio/mpeg", mp3Body(song)) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) } rec := e.do(http.MethodGet, uploadPath(upload)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), `"name":"02 Corridors of Time.mp3"`) rec = e.do(http.MethodPost, uploadPath(upload)+"/finish") require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var soundtrack backend.SoundtrackWithSongList require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &soundtrack)) assert.Equal(t, "Chrono Trigger", soundtrack.Name) assert.Len(t, soundtrack.Songs, 2) // It is in the library, the soundtrack lists and the search right away. assert.Contains(t, e.do(http.MethodGet, "/api/v1/soundtracks").Body.String(), `"name":"Chrono Trigger"`) assert.Contains(t, e.do(http.MethodGet, "/api/v1/music/all/order").Body.String(), "Chrono Trigger") assert.Equal(t, http.StatusNotFound, e.do(http.MethodGet, uploadPath(upload)).Code) } func TestUploadEndpointErrors(t *testing.T) { e := newTestEnv(t) e.addSong("Kingdom Hearts", "Dearly Beloved", "x") assert.Equal(t, http.StatusConflict, e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":"kingdom hearts"}`).Code) assert.Equal(t, http.StatusBadRequest, e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":"a/b"}`).Code) assert.Equal(t, http.StatusUnprocessableEntity, e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":""}`).Code) upload := e.startUpload("Game") assert.Equal(t, http.StatusBadRequest, e.put(uploadPath(upload)+"/files/song.wav", "audio/wav", mp3Body("x")).Code) assert.Equal(t, http.StatusBadRequest, e.put(uploadPath(upload)+"/files/song.mp3", "audio/mpeg", []byte("text")).Code) assert.Equal(t, http.StatusRequestEntityTooLarge, e.put(uploadPath(upload)+"/files/big.mp3", "audio/mpeg", mp3Body(strings.Repeat("x", 1<<20))).Code) assert.Equal(t, http.StatusBadRequest, e.put(uploadPath(upload)+"/files/Disc%201%2F01.mp3", "audio/mpeg", mp3Body("x")).Code, "an encoded / in the file name") rec := e.put(uploadPath(upload)+"/files/Disc-1/01.mp3", "audio/mpeg", mp3Body("x")) assert.Equal(t, http.StatusBadRequest, rec.Code, "a / in the file name") assert.Contains(t, rec.Body.String(), "plain file names only") assert.Equal(t, http.StatusBadRequest, e.do(http.MethodPost, uploadPath(upload)+"/finish").Code, "no songs yet") missing := "/api/v1/soundtracks/uploads/00000000-0000-0000-0000-000000000000" assert.Equal(t, http.StatusNotFound, e.do(http.MethodGet, missing).Code) assert.Equal(t, http.StatusNotFound, e.put(missing+"/files/a.mp3", "audio/mpeg", mp3Body("x")).Code) assert.Equal(t, http.StatusNotFound, e.do(http.MethodPost, missing+"/finish").Code) assert.Equal(t, http.StatusNotFound, e.do(http.MethodDelete, missing).Code) assert.Equal(t, http.StatusNoContent, e.do(http.MethodDelete, uploadPath(upload)).Code) assert.Equal(t, http.StatusNotFound, e.do(http.MethodGet, uploadPath(upload)).Code) } func TestUploadEndpointsLockedDuringSync(t *testing.T) { e := newTestEnv(t) upload := e.startUpload("Game") require.Equal(t, http.StatusOK, e.put(uploadPath(upload)+"/files/01.mp3", "audio/mpeg", mp3Body("x")).Code) release := e.blockSync() require.Equal(t, http.StatusOK, e.do(http.MethodPost, "/api/v1/sync").Code) assert.Equal(t, http.StatusLocked, e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":"Other"}`).Code) assert.Equal(t, http.StatusLocked, e.do(http.MethodPost, uploadPath(upload)+"/finish").Code) release() assert.Equal(t, http.StatusOK, e.do(http.MethodPost, uploadPath(upload)+"/finish").Code) } func TestUploadEndpointsRequireToken(t *testing.T) { e := newTestEnv(t, withAuth("secret")) assert.Equal(t, http.StatusUnauthorized, e.doBody(http.MethodPost, "/api/v1/soundtracks/uploads", `{"name":"Game"}`).Code) }