CI / Frontend - type-check & test (push) Canceled after 0s
직접 만든 연습곡 다섯 파일을 공개 보관함에 등록했다. 별빛 계단은 입문과 쉬움 두 단계로 제공한다. MIDI 파서 검사와 백엔드 단위 테스트 5개를 통과했다. 운영 백업은 /home/syskiller/piano/backups/bach-to-basics/20261009-beginner-midi 에 있다. 백엔드만 갱신했고 비회원 목록과 다운로드를 확인했다. 실제 USB MIDI 장치 검사는 남아 있다. 사전 훅은 로컬 pnpm 의존성 불일치로 실행되지 않아 관련 검사를 직접 수행했다.
83 lines
3.2 KiB
Python
83 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
class LibraryDatabaseTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
os.environ["LIBRARY_DATA_DIR"] = self.temp.name
|
|
os.environ.pop("BOOTSTRAP_ADMIN_USERNAME", None)
|
|
os.environ.pop("BOOTSTRAP_ADMIN_PASSWORD_FILE", None)
|
|
import library_db
|
|
|
|
self.db = importlib.reload(library_db)
|
|
self.db.initialize_database()
|
|
|
|
def tearDown(self) -> None:
|
|
self.temp.cleanup()
|
|
|
|
def test_password_hash_is_salted_and_verifiable(self) -> None:
|
|
first = self.db.hash_password("safe-password-123")
|
|
second = self.db.hash_password("safe-password-123")
|
|
self.assertNotEqual(first, second)
|
|
self.assertTrue(self.db.verify_password("safe-password-123", first))
|
|
self.assertFalse(self.db.verify_password("wrong-password", first))
|
|
|
|
def test_session_round_trip(self) -> None:
|
|
with self.db.connect() as connection:
|
|
cursor = connection.execute(
|
|
"""
|
|
INSERT INTO users (username, display_name, password_hash, created_at)
|
|
VALUES ('tester', '테스터', ?, ?)
|
|
""",
|
|
(self.db.hash_password("safe-password-123"), self.db.utc_now()),
|
|
)
|
|
user_id = cursor.lastrowid
|
|
token, csrf = self.db.create_session(user_id)
|
|
user = self.db.get_user_for_session(token)
|
|
self.assertIsNotNone(user)
|
|
self.assertEqual(user["username"], "tester")
|
|
self.assertEqual(user["csrf_token"], csrf)
|
|
self.db.delete_session(token)
|
|
self.assertIsNone(self.db.get_user_for_session(token))
|
|
|
|
def test_database_directory_is_private_project_data(self) -> None:
|
|
self.assertTrue(Path(self.temp.name, "library.sqlite3").is_file())
|
|
self.assertTrue(Path(self.temp.name, "midi").is_dir())
|
|
|
|
def test_graded_samples_are_two_hand_piano_solos(self) -> None:
|
|
for sample in self.db.SAMPLES:
|
|
self.assertEqual(sample["arrangement"], "piano_solo")
|
|
self.assertIn("양손", sample["tags"])
|
|
self.assertNotIn("오른손", sample["tags"])
|
|
self.assertNotIn("쉬운 반주", sample["tags"])
|
|
|
|
def test_self_composed_beginner_midis_are_seeded_as_public(self) -> None:
|
|
names = {
|
|
"starlight-steps-beginner.mid",
|
|
"starlight-steps-easy.mid",
|
|
"evening-waltz-beginner.mid",
|
|
"sunrise-arpeggio-easy.mid",
|
|
"black-key-pentatonic-beginner.mid",
|
|
}
|
|
with self.db.connect() as connection:
|
|
rows = connection.execute(
|
|
"SELECT seed_key, visibility, min_tier, owner_id FROM midi_items WHERE seed_key IS NOT NULL"
|
|
).fetchall()
|
|
matching = {row["seed_key"]: row for row in rows if row["seed_key"] in names}
|
|
self.assertEqual(set(matching), names)
|
|
for name, row in matching.items():
|
|
self.assertEqual(row["visibility"], "public")
|
|
self.assertEqual(row["min_tier"], 0)
|
|
self.assertIsNone(row["owner_id"])
|
|
self.assertTrue(Path(self.temp.name, "midi", name).is_file())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|