30a40b2bca
- Backend: plugin_dirs (list) thay vst_dir/soundfont_dir rieng — luu plugin_dirs.json, backward compat gop field cu; scan walk tung dir phan loai .vst3/.dll/.so -> vst_found, .sf2/.sf3 -> soundfonts (kem dir goc) - SoundFontAutoScanner: system_sf_dirs (list) — scan tat ca thu muc user khai bao - Frontend: PluginManagerModal — nut Add Directory (Tauri dialog / prompt fallback), moi dir co nut x xoa, nut Scan -> hien danh sach rieng re VST Instruments + SoundFonts (kem duong dan thu muc) - Tests: +2 (save/get/scan phan loai, xoa dir)
148 lines
6.2 KiB
Python
148 lines
6.2 KiB
Python
import os
|
|
import json
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from app.main import app
|
|
from app.core.vst_engine import HAS_PEDALBOARD, HAS_PYFLUIDSYNTH
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def get_admin_token():
|
|
resp = client.post("/api/v1/auth/login", json={"username": "admin", "password": "admin123"})
|
|
if resp.status_code == 200:
|
|
token = resp.json()["access_token"]
|
|
# Admin is seeded with must_change_password=1; the app blocks music
|
|
# processing until the first password change. Complete that flow here
|
|
# (keeping the same password) so feature tests run unblocked.
|
|
user = resp.json()["user"]
|
|
if user.get("must_change_password"):
|
|
r = client.post("/api/v1/auth/change-password", headers={"Authorization": f"Bearer {token}"},
|
|
json={"old_password": "admin123", "new_password": "admin123"})
|
|
if r.status_code == 200:
|
|
token = r.json()["access_token"]
|
|
return token
|
|
return None
|
|
|
|
|
|
class TestPluginAPI:
|
|
def test_list_plugins_requires_auth(self):
|
|
resp = client.get("/api/v1/plugins/available")
|
|
assert resp.status_code in (401, 403)
|
|
|
|
def test_list_plugins_authenticated(self):
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
resp = client.get("/api/v1/plugins/available", headers={"Authorization": f"Bearer {token}"})
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "vst_instruments" in data
|
|
assert "soundfonts" in data
|
|
|
|
def test_list_default_soundfonts(self):
|
|
resp = client.get("/api/v1/plugins/default-soundfonts")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list)
|
|
|
|
def test_render_project_invalid_json(self):
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
resp = client.post("/api/v1/plugins/render", headers={"Authorization": f"Bearer {token}"}, json={"project_json": {}})
|
|
# Should fail because project is empty, but API should return 500 or error
|
|
assert resp.status_code in (400, 422, 500)
|
|
|
|
def test_upload_soundfont_requires_auth(self):
|
|
resp = client.post("/api/v1/plugins/upload-soundfont")
|
|
assert resp.status_code in (401, 403, 422)
|
|
|
|
def test_upload_soundfont_invalid_magic(self):
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
# Upload a file with invalid magic bytes
|
|
fake_content = b'XXXX\x00\x00\x00\x00YYYY' * 100
|
|
resp = client.post(
|
|
"/api/v1/plugins/upload-soundfont",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
files={"file": ("fake.sf2", fake_content, "application/octet-stream")}
|
|
)
|
|
assert resp.status_code == 400
|
|
assert "Invalid SoundFont" in resp.json().get("detail", "")
|
|
|
|
def test_upload_soundfont_valid_magic(self):
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
# Upload a file with valid RIFF + sfbk magic
|
|
valid_content = b'RIFF\x00\x00\x00\x00sfbk' + b'\x00' * 200
|
|
resp = client.post(
|
|
"/api/v1/plugins/upload-soundfont",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
files={"file": ("test.sf2", valid_content, "application/octet-stream")}
|
|
)
|
|
# Should succeed (200) unless auth/permission issues
|
|
if resp.status_code == 200:
|
|
data = resp.json()
|
|
assert "id" in data
|
|
assert "size_bytes" in data
|
|
assert data["size_bytes"] == len(valid_content)
|
|
elif resp.status_code == 403:
|
|
pytest.skip("Permission denied for admin user")
|
|
|
|
def test_plugin_dirs_save_list(self):
|
|
"""plugin_dirs (list) — save/get/effective + scan phân loại riêng rẽ."""
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
h = {"Authorization": f"Bearer {token}"}
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as td:
|
|
# 2 dir giả: 1 chứa VST, 1 chứa SoundFont
|
|
vst_dir = os.path.join(td, "vsts")
|
|
sf_dir = os.path.join(td, "sfs")
|
|
os.makedirs(vst_dir)
|
|
os.makedirs(sf_dir)
|
|
open(os.path.join(vst_dir, "Synth1.vst3"), "w").write("x")
|
|
open(os.path.join(sf_dir, "piano.sf2"), "w").write("x")
|
|
# Save list
|
|
r = client.post("/api/v1/plugins/dirs", headers=h,
|
|
json={"plugin_dirs": [vst_dir, sf_dir]})
|
|
assert r.status_code == 200
|
|
assert r.json()["plugin_dirs"] == [vst_dir, sf_dir]
|
|
# Get lại
|
|
g = client.get("/api/v1/plugins/dirs", headers=h)
|
|
assert g.json()["plugin_dirs"] == [vst_dir, sf_dir]
|
|
# Scan → phân loại riêng rẽ
|
|
s = client.post("/api/v1/plugins/scan", headers=h)
|
|
assert s.status_code == 200
|
|
data = s.json()
|
|
assert any(v["name"] == "Synth1" for v in data["vst_found"])
|
|
assert any(x["name"] == "piano" for x in data["soundfonts"])
|
|
assert data["vst_count"] == 1
|
|
assert data["soundfont_count"] == 1
|
|
# Mỗi entry có dir gốc
|
|
assert data["vst_found"][0]["dir"] == vst_dir
|
|
assert data["soundfonts"][0]["dir"] == sf_dir
|
|
|
|
def test_plugin_dirs_remove(self):
|
|
"""Xóa 1 dir khỏi list → save lại → không còn trong effective."""
|
|
token = get_admin_token()
|
|
if not token:
|
|
pytest.skip("Cannot get admin token")
|
|
h = {"Authorization": f"Bearer {token}"}
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as td:
|
|
d1 = os.path.join(td, "d1")
|
|
d2 = os.path.join(td, "d2")
|
|
os.makedirs(d1)
|
|
os.makedirs(d2)
|
|
client.post("/api/v1/plugins/dirs", headers=h, json={"plugin_dirs": [d1, d2]})
|
|
client.post("/api/v1/plugins/dirs", headers=h, json={"plugin_dirs": [d1]})
|
|
g = client.get("/api/v1/plugins/dirs", headers=h)
|
|
assert g.json()["plugin_dirs"] == [d1]
|