package server import ( "context" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "sync/atomic" "testing" "music-server/internal/config" "music-server/internal/db/repository" "music-server/internal/logging" "music-server/internal/testutil/fakedb" "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { logging.Init("fatal", false) os.Exit(m.Run()) } type fakeDatabase struct { closed atomic.Bool onClose func() } func (f *fakeDatabase) Health(context.Context) map[string]string { return map[string]string{"status": "up"} } func (f *fakeDatabase) Close() { if f.onClose != nil { f.onClose() } f.closed.Store(true) } // testEnv is a Server backed by an in-memory database and temporary folders. type testEnv struct { t *testing.T cfg config.Config store *fakedb.Store db *fakeDatabase server *Server handler http.Handler } func writeFile(t *testing.T, path, content string) { t.Helper() require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) require.NoError(t, os.WriteFile(path, []byte(content), 0o644)) } func newTestEnv(t *testing.T, configure ...func(*config.Config)) *testEnv { t.Helper() base := t.TempDir() cfg := config.Config{ Port: 0, MusicPath: filepath.Join(base, "music"), CharactersPath: filepath.Join(base, "characters"), SoundCheckPath: filepath.Join(base, "songs"), FrontendPath: filepath.Join(base, "frontend"), DisableSessionAuth: true, ReleasesURL: "http://127.0.0.1:1", } for _, c := range configure { c(&cfg) } writeFile(t, filepath.Join(cfg.CharactersPath, "Adol.png"), "png") writeFile(t, filepath.Join(cfg.CharactersPath, "Aloy.jpg"), "jpg") writeFile(t, filepath.Join(base, "secret.png"), "secret") writeFile(t, filepath.Join(cfg.SoundCheckPath, "check.mp3"), "sound check") writeFile(t, filepath.Join(cfg.FrontendPath, "index.html"), "frontend") store := fakedb.New() database := &fakeDatabase{} s, err := New(cfg, store, database) require.NoError(t, err) t.Cleanup(func() { s.sync.Wait(); s.characters.Close() }) return &testEnv{t: t, cfg: cfg, store: store, db: database, server: s, handler: s.httpServer.Handler} } // addSong creates a song file and its database rows. func (e *testEnv) addSong(soundtrack, song, content string) repository.Song { e.t.Helper() dir := e.cfg.MusicPath + "/" + soundtrack + "/" fileName := song + ".mp3" writeFile(e.t, dir+fileName, content) st, ok := e.store.Soundtrack(soundtrack) if !ok { st = e.store.AddSoundtrack(repository.Soundtrack{SoundtrackName: soundtrack, Path: dir}) } return e.store.AddTestSong(repository.Song{SoundtrackID: st.ID, SongName: song, Path: dir + fileName, FileName: &fileName}) } func (e *testEnv) do(method, path string, headers ...string) *httptest.ResponseRecorder { return e.doBody(method, path, "", headers...) } func (e *testEnv) doBody(method, path, body string, headers ...string) *httptest.ResponseRecorder { var reader io.Reader if body != "" { reader = strings.NewReader(body) } req := httptest.NewRequest(method, path, reader) if body != "" { req.Header.Set("Content-Type", "application/json") } for i := 0; i+1 < len(headers); i += 2 { req.Header.Set(headers[i], headers[i+1]) } rec := httptest.NewRecorder() e.handler.ServeHTTP(rec, req) return rec }