본문으로 건너뛰기
cccskills
무료GitHub에 공개

testing

Write or fix a test in the Lyrion Dashboard's suite. Use this whenever a task adds a test, changes one, or asks why the suite fails — anything touching `tests/`. Covers the standalone-per-file scaffolding, the env-before-import rule, where to patch (the route/service the code under test imports), the cache-and-clock testing pattern, and how to run one test vs the whole suite. Reach for it any time you touch `tests/` so new tests match the existing shape instead of re-inventing setup.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md4.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Testing conventions

Tests use unittest + unittest.mock.patch (no pytest, no fixtures library). They run in CI via python -m unittest discover from the repo root (.github/workflows/python-ci.yml), and must never touch a real Lyrion server or a real SQLite DB.

Run them

python -m unittest discover                       # whole suite (what CI runs)
python -m unittest tests.test_now_playing_route   # one file
python -m unittest tests.test_now_playing_route.NowPlayingKnownTest          # one case
python -m unittest tests.test_now_playing_route.NowPlayingKnownTest.test_no_known_includes_lyrics  # one method

The standalone-file rule

Each tests/test_*.py stands entirely on its own — no shared conftest, no base class. That means the env scaffolding is duplicated in every file on purpose, marked # pylint: disable=duplicate-code. Don't try to factor it out; the duplication is the design (a test file can be read and run in isolation).

Env before import

config.py reads env vars at import time, so the config vars must be set before anything imports app. Every test file opens the same way:

import os, tempfile, unittest
from unittest.mock import patch

os.environ.setdefault("LYRION_HOST", "http://localhost:9000")
os.environ.setdefault("DB_DIR", tempfile.mkdtemp())
os.environ.setdefault("DB_PERSIST_DIR", tempfile.mkdtemp())

# pylint: disable=wrong-import-position
from app import create_app

Build a client per test with create_app().test_client() in setUp.

Patch where the code imports the name, not where it's defined

Routes do from services.database import get_track_lyrics, so the name to patch is routes.nowplaying.get_track_lyrics — the route module's binding — not services.database.get_track_lyrics. Patching the definition site misses the already-imported reference the route holds.

@patch("routes.nowplaying.get_track_lyrics", return_value="la la la")
@patch("routes.nowplaying.get_active_now_playing", return_value=dict(NOW))
class NowPlayingKnownTest(unittest.TestCase):
    ...
  • Assert on status, JSON body, and how the service was called (mock.assert_called_once_with(42), mock.assert_called_with(selected_id=None)).
  • For a service test, patch one layer down instead — e.g. test_lyrion_request.py patches the HTTP session, test_get_track_lyrics.py builds a temp SQLite DB.

What to cover, by layer

  • Routes (test_*_route.py) — the request/response contract: input validation and clamping (test_files_route.py, test_cover_route.py), the ?known= / ?player= behaviours (test_now_playing_route.py), security headers (test_security_headers.py). Patch the services the route imports.
  • Services — the logic itself: the lyrics cache and TTLs (test_lyrics_cache.py), verification (test_lyrics_verify.py), the rate limiter/cooldown (test_ratelimit.py), the now-playing snapshot cache.

Asserting on logs

tests/__init__.py pins LOG_LEVEL=CRITICAL so the suite's output stays readable. A diagnostic log line worth guarding is asserted with assertLogs, which raises the level of the logger it names, so it works despite that pin:

with self.assertLogs("services.lyrics", level="INFO") as captured:
    L.fetch_lyrics("1", "A", "T")
self.assertTrue(any("-> lrclib" in line for line in captured.output))

Match a stable fragment of the message, not the whole formatted line (tests/test_logging.py).

Testing caches and the clock

Several services cache behind a lock and age values by wall-clock time (get_active_now_playing, the lyrics cache, RateLimiter/Cooldown). To test time-dependent behaviour deterministically, patch time.time at the service module (@patch("services.lyrics.time.time", ...)) and step it, rather than calling time.sleep. Reset any module-level cache/state between tests so one test's snapshot doesn't leak into the next (see test_now_playing_cache.py, test_stats_cache.py).

Checklist for a new test

  1. Name it tests/test_<thing>.py; copy the env-before-import header verbatim.
  2. create_app().test_client() in setUp.
  3. Patch the service/HTTP/DB boundary at the module that imports it; never reach a real Lyrion or DB.
  4. Assert on the observable contract and on how the boundary was called.
  5. For time-based logic, patch time.time and reset module state between tests.
  6. Run python -m unittest discover and pylint ... tests — both gate CI.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

add-route

무료

Add or modify a Flask endpoint in the Lyrion Dashboard. Use this whenever a task adds a new URL/route, a JSON API endpoint, or a page — anything that registers a handler in `routes/`. Covers the repo's route → service → test layering, input validation, caching headers, security headers, i18n wiring, and the exact test pattern, so a new endpoint matches how `routes/` already works instead of reinventing the structure.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

android

무료

Work on the Android app — the thin WebView wrapper in `android/`. Use this whenever a task touches the Kotlin sources, the Gradle build, the WebView shell, the native settings screen, LMS server auto-discovery, the JS↔native bridge, app strings/i18n, or the Android CI/lint. Covers the "shell only, UI lives on the web" principle, the debug/release signing split, the static versioning F-Droid needs, and the min/target SDKs, so changes fit the wrapper.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

covers

무료

Work on album artwork — reading an image's dimensions, and embedding a folder's cover file into the tags of its tracks. Use this whenever a task touches `services/artwork.py`, the cover half of `services/tags.py`, or `scripts/embed_covers.py` / `embed_covers_cron.sh`. Covers why Lyrion shows the embedded artwork and never the folder file, how covers are compared, why images are never re-encoded, and what embedding costs in rewritten files.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

database

무료

Read data from Lyrion's SQLite databases — library stats, stored lyrics, album covers, recent plays. Use this whenever a task queries `library.db` or `persist.db`, adds or changes a statistic, or touches `services/database.py`. Covers the read-only connection model (RO open + `ATTACH persist.db`), the key tables, the `play_counts` view over the Alternative Play Count plugin, the single-flight stats cache, and why the app must never write to these DBs. Reach for it before writing any SQL against the Lyrion databases.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

frontend

무료

Work on the dashboard's frontend — the vanilla JS/CSS page with no framework or bundler. Use this whenever a task touches `static/nowplaying.js`, `static/style.css`, the Jinja templates (`templates/nowplaying.html`, `_icons.html`), the accent-color tinting, the karaoke lyrics sync, the now-playing poll, or the empty-state mosaic / recent-plays pile. Covers how the page is wired, how it reads i18n and data from the server, the ESLint gate, and the Android bridge, so changes match the existing patterns.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

i18n

무료

Add, change, or translate user-facing text in the Lyrion Dashboard, which ships bilingual FR/EN. Use this whenever a task introduces a new visible string (a label, tooltip, aria-label, empty state, stat name), changes wording, or touches the README or a `docs/` page — so every string lands in both languages and the EN/FR documentation pairs stay in sync. Covers `i18n.py`, how templates and JS consume translations, and the parity rule.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

werdeil/lyrion-dashboard32026년 10월 19일 업데이트

werdeil의 스킬 모두 보기

이 스킬의 문제 신고