- Unit tests use an in-memory fake database (internal/testutil/fakedb), temp folders and httptest; `go test ./...` needs no Docker or env vars - Integration tests (build tag "integration") start PostgreSQL with testcontainers: migrations step by step, SQL dumps (skipped if missing) and a full sync against a real database - Removed tests that only checked copies of the logic or had empty bodies, the broken root integration_test.go, compose.test.yaml and .env.test with hardcoded paths - testMusic MP3s replaced with tiny placeholders (sync never decodes audio), 194 MB -> 168 KB
121 lines
3.3 KiB
Go
121 lines
3.3 KiB
Go
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"), "<html>frontend</html>")
|
|
|
|
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
|
|
}
|