summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-19 09:28:23 +0300
committerPaul Buetow <paul@buetow.org>2026-05-19 09:28:23 +0300
commita44ce87c9acd57314d1b147ca447f373cab0aae7 (patch)
treee51eb2948d9b3c611666b96935201f87da252ba3
parent60c8097e9fdb79d6b5ecea73d529803cbf297af0 (diff)
Commit license-cleared test media; retire ./testmedia in tests
Add player-server/testdata/media/ with ten public-domain media files: five LibriVox Aesop's-Fables mp3 chapters (audiobooks/), four NASA image-library jpgs (images/), and one NASA mp4 short (videos/). Total ~11 MB; each file's source URL and license is documented in testdata/LICENSES.md and README.md. Switch every default reference in tests, scenarios, docs, and CI from MEDIA_ROOT=./testmedia to MEDIA_ROOT=./testdata/media so a fresh clone can run mage E2E and the Playwright smoke suite without supplying any external media. The ./testmedia path remains gitignored — it's the local-only personal library directory. Verified: 12/12 Playwright e2e-web pass; 18/18 LLM e2e scenarios pass against the new fixture set. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-rw-r--r--.woodpecker/server.yml6
-rw-r--r--docs/ci-design.md6
-rw-r--r--docs/llm-e2e-design.md2
-rw-r--r--magefile.go2
-rw-r--r--player-server/test/e2e-llm/README.md6
-rw-r--r--player-server/test/e2e-llm/runner/index.ts2
-rw-r--r--player-server/test/e2e-llm/scenarios/S03-upload.md2
-rw-r--r--player-server/test/e2e-llm/scenarios/S06-set-browse-cover.md4
-rw-r--r--player-server/test/e2e-llm/scenarios/S08-media-streaming.md2
-rw-r--r--player-server/test/e2e-llm/scenarios/S14-admin-rescan.md2
-rw-r--r--player-server/test/e2e-llm/scenarios/S15-auth-boundaries.md4
-rw-r--r--player-server/test/e2e-llm/scenarios/S16-media-list-pagination.md4
-rw-r--r--player-server/test/e2e-web/README.md6
-rw-r--r--player-server/test/e2e-web/tests/helpers/server.ts2
-rw-r--r--player-server/test/e2e-web/tests/smoke.test.ts4
-rw-r--r--player-server/testdata/LICENSES.md57
-rw-r--r--player-server/testdata/README.md56
-rw-r--r--player-server/testdata/media/audiobooks/aesops-fables/10-chapter.mp3bin0 -> 1933824 bytes
-rw-r--r--player-server/testdata/media/audiobooks/aesops-fables/13-chapter.mp3bin0 -> 1815040 bytes
-rw-r--r--player-server/testdata/media/audiobooks/aesops-fables/16-chapter.mp3bin0 -> 1875968 bytes
-rw-r--r--player-server/testdata/media/audiobooks/aesops-fables/34-chapter.mp3bin0 -> 1825792 bytes
-rw-r--r--player-server/testdata/media/audiobooks/aesops-fables/37-chapter.mp3bin0 -> 1013760 bytes
-rw-r--r--player-server/testdata/media/images/.thumbnails/cygnus-loop-pia17172.jpgbin0 -> 4577 bytes
-rw-r--r--player-server/testdata/media/images/.thumbnails/earth-pia18033.jpgbin0 -> 30666 bytes
-rw-r--r--player-server/testdata/media/images/.thumbnails/jupiter-pia12235.jpgbin0 -> 13850 bytes
-rw-r--r--player-server/testdata/media/images/.thumbnails/mars-pia22231.jpgbin0 -> 4844 bytes
-rw-r--r--player-server/testdata/media/images/cygnus-loop-pia17172.jpgbin0 -> 10255 bytes
-rw-r--r--player-server/testdata/media/images/earth-pia18033.jpgbin0 -> 68407 bytes
-rw-r--r--player-server/testdata/media/images/jupiter-pia12235.jpgbin0 -> 33438 bytes
-rw-r--r--player-server/testdata/media/images/mars-pia22231.jpgbin0 -> 10607 bytes
-rw-r--r--player-server/testdata/media/videos/.thumbnails/nile-red-sea-nasa.jpgbin0 -> 2258 bytes
-rw-r--r--player-server/testdata/media/videos/nile-red-sea-nasa.mp4bin0 -> 2394337 bytes
32 files changed, 140 insertions, 27 deletions
diff --git a/.woodpecker/server.yml b/.woodpecker/server.yml
index 75798dd..474efb5 100644
--- a/.woodpecker/server.yml
+++ b/.woodpecker/server.yml
@@ -89,16 +89,16 @@ steps:
- CGO_ENABLED=0 go build -o player ./cmd/player
# ── 3. Start the server in the background with test media ─────────────
- # MEDIA_ROOT points at the in-repo testmedia directory so the server
+ # MEDIA_ROOT points at the in-repo testdata/media directory so the server
# serves real media files without any external setup.
# SECURE_COOKIES=false avoids HTTPS-only cookie restrictions in CI.
# DB_PATH uses a temp file so each run starts from a clean database.
- - MEDIA_ROOT=./testmedia SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-ci.db ./player &
+ - MEDIA_ROOT=./testdata/media SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-ci.db ./player &
# ── 4. Wait for the server to be ready ────────────────────────────────
# curl retries up to 10 times with a 1-second delay between attempts.
# This guards against Playwright launching before the server is ready
- # to accept connections (especially while it scans testmedia on startup).
+ # to accept connections (especially while it scans testdata/media on startup).
- curl --retry 10 --retry-delay 1 --fail http://localhost:8080/healthz
# ── 5. Install Playwright dependencies and run the smoke suite ─────────
diff --git a/docs/ci-design.md b/docs/ci-design.md
index 7fb8db2..54f2b6f 100644
--- a/docs/ci-design.md
+++ b/docs/ci-design.md
@@ -142,8 +142,8 @@ No running server process needed — integration tests start their own in-proces
Runs the Playwright smoke suite against a real running server with test media.
```
-# Start server with test database and testmedia
-MEDIA_ROOT=./testmedia SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-ci.db \
+# Start server with test database and testdata/media
+MEDIA_ROOT=./testdata/media SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-ci.db \
./player &
# Wait for server readiness (done inside the Playwright globalSetup hook)
@@ -429,7 +429,7 @@ bumping Flutter locally. This is a one-line change.
The Playwright suite is configured with `retries: 1` on CI and generous timeouts
(15 s navigation, 10 s action). Flakiness can still occur if the Go server takes
-longer than expected to start or if testmedia scanning is slow.
+longer than expected to start or if testdata/media scanning is slow.
**Mitigation:** The Playwright `globalSetup` hook already waits for the server to
respond before tests run (`waitForServer` helper in `tests/helpers/server.ts`). The
diff --git a/docs/llm-e2e-design.md b/docs/llm-e2e-design.md
index 52d5d37..236fe33 100644
--- a/docs/llm-e2e-design.md
+++ b/docs/llm-e2e-design.md
@@ -334,7 +334,7 @@ position.
**Goal:** Verify that a file uploaded via the admin API appears in the web UI
media grid.
-**Precondition:** Server running, admin account exists, testmedia fixture file
+**Precondition:** Server running, admin account exists, testdata/media fixture file
available.
**Steps (abbreviated):**
diff --git a/magefile.go b/magefile.go
index e3ce281..fc63a50 100644
--- a/magefile.go
+++ b/magefile.go
@@ -18,7 +18,7 @@ const runnerDir = "player-server/test/e2e-llm/runner"
// The Player server must already be running before invoking this target.
// Start it from player-server/ with:
//
-// MEDIA_ROOT=./testmedia SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-llm.db ./player
+// MEDIA_ROOT=./testdata/media SECURE_COOKIES=false DB_PATH=/tmp/player-e2e-llm.db ./player
//
// Override the target URL with PLAYER_URL (default: http://localhost:8080).
// Set ANTHROPIC_API_KEY to enable the Haiku screenshot oracle (Layer 5).
diff --git a/player-server/test/e2e-llm/README.md b/player-server/test/e2e-llm/README.md
index 842db98..50b1b7b 100644
--- a/player-server/test/e2e-llm/README.md
+++ b/player-server/test/e2e-llm/README.md
@@ -62,8 +62,8 @@ The harness defaults to `http://localhost:8080`. Override with `PLAYER_URL`.
Start the server from `player-server/` so it can resolve embedded static assets:
```sh
-# From player-server/ — use testmedia/ as the media library:
-MEDIA_ROOT=./testmedia \
+# From player-server/ — use testdata/media/ as the media library:
+MEDIA_ROOT=./testdata/media \
SECURE_COOKIES=false \
DB_PATH=/tmp/player-e2e-llm.db \
./player
@@ -73,7 +73,7 @@ Key server environment variables:
| Variable | Value | Reason |
|---|---|---|
-| `MEDIA_ROOT` | `./testmedia` | Provides pre-existing media for upload/verify scenarios |
+| `MEDIA_ROOT` | `./testdata/media` | Provides pre-existing media for upload/verify scenarios |
| `SECURE_COOKIES` | `false` | Allows session cookie over plain HTTP |
| `DB_PATH` | `/tmp/player-e2e-llm.db` | Isolates the test database from production |
diff --git a/player-server/test/e2e-llm/runner/index.ts b/player-server/test/e2e-llm/runner/index.ts
index 4ec03c0..58e5034 100644
--- a/player-server/test/e2e-llm/runner/index.ts
+++ b/player-server/test/e2e-llm/runner/index.ts
@@ -383,7 +383,7 @@ function precheckServer(): void {
);
console.error('[runner] Start it from player-server/ before running the e2e suite:');
console.error(
- '[runner] MEDIA_ROOT=./testmedia SECURE_COOKIES=false ' +
+ '[runner] MEDIA_ROOT=./testdata/media SECURE_COOKIES=false ' +
'DB_PATH=/tmp/player-e2e-llm.db ./player',
);
console.error('[runner] Override the target URL with PLAYER_URL.');
diff --git a/player-server/test/e2e-llm/scenarios/S03-upload.md b/player-server/test/e2e-llm/scenarios/S03-upload.md
index 3961e4a..0b30ead 100644
--- a/player-server/test/e2e-llm/scenarios/S03-upload.md
+++ b/player-server/test/e2e-llm/scenarios/S03-upload.md
@@ -34,7 +34,7 @@ YAML front-matter is used instead.
or any non-podcast set) as `set_id`.
4. Prepare a small test audio file to upload. Use the fixture file at
- `player-server/testmedia/podcast/` or create a minimal 1-second silent MP3
+ `player-server/testdata/media/podcast/` or create a minimal 1-second silent MP3
named `test-audio-e2e.mp3` using `ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 1 -q:a 9 -acodec libmp3lame /tmp/test-audio-e2e.mp3`
(requires ffmpeg on PATH). Confirm the file exists at `/tmp/test-audio-e2e.mp3`.
diff --git a/player-server/test/e2e-llm/scenarios/S06-set-browse-cover.md b/player-server/test/e2e-llm/scenarios/S06-set-browse-cover.md
index 9e54f2e..a8b9066 100644
--- a/player-server/test/e2e-llm/scenarios/S06-set-browse-cover.md
+++ b/player-server/test/e2e-llm/scenarios/S06-set-browse-cover.md
@@ -20,7 +20,7 @@ in step 5 is a benign payload — the server ignores it but accepts the
request. The endpoint returns HTTP 200 only when the set contains at least
one media file the server can derive a cover from; the chosen set in
step 3 must therefore be a non-empty set such as `musicvideos` or any
-seeded testmedia set.
+seeded testdata/media set.
---
@@ -37,7 +37,7 @@ seeded testmedia set.
3. List the available sets: call `GET /api/v1/sets` with the session cookie.
Confirm the response is HTTP 200 and the returned array contains at least
- one set. Prefer a non-podcast set seeded from `testmedia/` (for example
+ one set. Prefer a non-podcast set seeded from `testdata/media/` (for example
`musicvideos`). Save the `id` of the chosen set as `set_id`.
4. Browse the contents of the chosen set: call
diff --git a/player-server/test/e2e-llm/scenarios/S08-media-streaming.md b/player-server/test/e2e-llm/scenarios/S08-media-streaming.md
index 079d707..878106a 100644
--- a/player-server/test/e2e-llm/scenarios/S08-media-streaming.md
+++ b/player-server/test/e2e-llm/scenarios/S08-media-streaming.md
@@ -17,7 +17,7 @@ request, and the download endpoint. No actual browser playback is performed —
each step inspects HTTP status codes, response headers and JSON bodies.
The `MEDIA_ROOT` must point at a directory containing at least one media
-item (see the harness README — `./testmedia` is the default for the LLM e2e
+item (see the harness README — `./testdata/media` is the default for the LLM e2e
suite). If no media items exist the scenario will fail at step 2.
---
diff --git a/player-server/test/e2e-llm/scenarios/S14-admin-rescan.md b/player-server/test/e2e-llm/scenarios/S14-admin-rescan.md
index 4aeb7ae..961f8ca 100644
--- a/player-server/test/e2e-llm/scenarios/S14-admin-rescan.md
+++ b/player-server/test/e2e-llm/scenarios/S14-admin-rescan.md
@@ -33,7 +33,7 @@ skip: false
- `files_done` (integer, >= 0)
The fields `current_set` (string) and `last_error` (string) may be present
or omitted depending on state. Do not require a specific value for `running`
- — for a small `./testmedia` library the scan can finish before the first
+ — for a small `./testdata/media` library the scan can finish before the first
poll.
4. Poll scan progress a second time: call `GET /api/v1/admin/scan-progress`
diff --git a/player-server/test/e2e-llm/scenarios/S15-auth-boundaries.md b/player-server/test/e2e-llm/scenarios/S15-auth-boundaries.md
index 5f1f5e6..cac96b6 100644
--- a/player-server/test/e2e-llm/scenarios/S15-auth-boundaries.md
+++ b/player-server/test/e2e-llm/scenarios/S15-auth-boundaries.md
@@ -119,13 +119,13 @@ below lock that behaviour in.
(`tagService.RemoveTag` returns `ErrNotFound` for unknown tag names.)
17. Find a set whose media items have no generated thumbnail. If every
- media item in `testmedia/` happens to have one, skip the rest of this
+ media item in `testdata/media/` happens to have one, skip the rest of this
step and continue — the regression case is unreachable in this DB.
Otherwise, call `GET /api/v1/media/{id}/thumbnail` for one of those
items and confirm the response is HTTP 404 — not 500.
18. The empty-set cover regen path: `POST /api/v1/sets/{id}/cover` for a
- set with no eligible media files. If the seeded `testmedia/` library
+ set with no eligible media files. If the seeded `testdata/media/` library
contains no empty set, skip this step. Otherwise, confirm the response
is HTTP 400 — not 500. (`writeService.RegenerateSetCover` returns
`ErrEmptySetForCover` which maps to 400.)
diff --git a/player-server/test/e2e-llm/scenarios/S16-media-list-pagination.md b/player-server/test/e2e-llm/scenarios/S16-media-list-pagination.md
index acd562b..f0f4d33 100644
--- a/player-server/test/e2e-llm/scenarios/S16-media-list-pagination.md
+++ b/player-server/test/e2e-llm/scenarios/S16-media-list-pagination.md
@@ -30,7 +30,7 @@ below will need to be updated.
The `MEDIA_ROOT` must point at a directory with at least 5 media items, at
least one of which is type `audio`, so the pagination, search and type
-assertions can find real data. (`./testmedia` satisfies this — see the harness
+assertions can find real data. (`./testdata/media` satisfies this — see the harness
README.)
---
@@ -97,7 +97,7 @@ README.)
10. Type filter — audio only: call `GET /api/v1/media?type=audio` with the
session cookie. Confirm the response is HTTP 200. If the array is
non-empty, confirm every returned item has `type` equal to `audio`. If
- the array is empty (the seeded `testmedia/` library has no audio files),
+ the array is empty (the seeded `testdata/media/` library has no audio files),
treat it as acceptable — the type filter still returned 200 with a
well-formed empty array.
diff --git a/player-server/test/e2e-web/README.md b/player-server/test/e2e-web/README.md
index 0141526..59acd33 100644
--- a/player-server/test/e2e-web/README.md
+++ b/player-server/test/e2e-web/README.md
@@ -32,15 +32,15 @@ The binary resolves the embedded `web/` static asset directory relative to its w
directory, so running it from a different location will cause static files to 404.
```sh
-# From player-server/ — use testmedia/ as the media library.
-MEDIA_ROOT=./testmedia SECURE_COOKIES=false DB_PATH=/tmp/player-e2e.db ./player
+# From player-server/ — use testdata/media/ as the media library.
+MEDIA_ROOT=./testdata/media SECURE_COOKIES=false DB_PATH=/tmp/player-e2e.db ./player
```
Key environment variables for the test server:
| Variable | Value | Reason |
|-----------------|----------------|-----------------------------------------------------|
-| `MEDIA_ROOT` | `./testmedia` | Provides pre-existing sets so the grid test passes. |
+| `MEDIA_ROOT` | `./testdata/media` | Provides pre-existing sets so the grid test passes. |
| `SECURE_COOKIES`| `false` | Allows the session cookie over plain HTTP. |
| `DB_PATH` | `/tmp/player-e2e.db` | Isolates the test database from production. |
diff --git a/player-server/test/e2e-web/tests/helpers/server.ts b/player-server/test/e2e-web/tests/helpers/server.ts
index 1efd8ef..89fec60 100644
--- a/player-server/test/e2e-web/tests/helpers/server.ts
+++ b/player-server/test/e2e-web/tests/helpers/server.ts
@@ -145,7 +145,7 @@ export async function ensureRegularUser(adminCookie: string): Promise<void> {
* do not need to wait for the full scan to finish; we just need at least
* one set to be present before running the browse and media-grid tests.
*
- * The large testmedia library may take several minutes to fully scan on a
+ * The large testdata/media library may take several minutes to fully scan on a
* slow machine, but the first set appears within seconds.
*/
export async function triggerRescan(adminCookie: string, timeoutMs = 30_000): Promise<void> {
diff --git a/player-server/test/e2e-web/tests/smoke.test.ts b/player-server/test/e2e-web/tests/smoke.test.ts
index d998b86..59875bb 100644
--- a/player-server/test/e2e-web/tests/smoke.test.ts
+++ b/player-server/test/e2e-web/tests/smoke.test.ts
@@ -18,7 +18,7 @@
* - The server must be started with SECURE_COOKIES=false so that the session
* cookie is accessible on plain HTTP.
* - The server must point at a MEDIA_ROOT that contains at least one set with
- * at least one media file. The testmedia/ directory in this repo satisfies
+ * at least one media file. The testdata/media/ directory in this repo satisfies
* that requirement when passed as MEDIA_ROOT.
*
* Run:
@@ -231,7 +231,7 @@ test('authenticated user can open the sets sidebar', async ({ page }) => {
const sidebar = page.locator('#sidebar');
await expect(sidebar).toBeVisible({ timeout: 5_000 });
- // At least one set item must exist (testmedia/ has sets after the rescan).
+ // At least one set item must exist (testdata/media/ has sets after the rescan).
const setRows = sidebar.locator('.set-row');
await expect(setRows.first()).toBeVisible({ timeout: 10_000 });
const count = await setRows.count();
diff --git a/player-server/testdata/LICENSES.md b/player-server/testdata/LICENSES.md
new file mode 100644
index 0000000..1f07efa
--- /dev/null
+++ b/player-server/testdata/LICENSES.md
@@ -0,0 +1,57 @@
+# Test media — licensing and provenance
+
+All files under `testdata/media/` are either in the public domain or
+distributed under a permissive license that allows redistribution as part
+of this repository. Each file is listed below with its source URL,
+license, and a short description so the provenance is auditable.
+
+If you add a new fixture file, add an entry here. Do not commit a media
+file whose license you cannot document.
+
+---
+
+## `audiobooks/aesops-fables/`
+
+Five short readings from *The Æsop for Children* (Project Gutenberg /
+Milo Winter), recorded for LibriVox. LibriVox recordings are released
+into the **public domain** worldwide; the underlying text is also public
+domain. Source item on Internet Archive:
+<https://archive.org/details/aesopforchildren_1308_librivox>.
+
+| File | Source URL | License |
+|---|---|---|
+| `10-chapter.mp3` | <https://archive.org/download/aesopforchildren_1308_librivox/theaesopforchildren_10_aesop_64kb.mp3> | Public Domain (LibriVox) |
+| `13-chapter.mp3` | <https://archive.org/download/aesopforchildren_1308_librivox/theaesopforchildren_13_aesop_64kb.mp3> | Public Domain (LibriVox) |
+| `16-chapter.mp3` | <https://archive.org/download/aesopforchildren_1308_librivox/theaesopforchildren_16_aesop_64kb.mp3> | Public Domain (LibriVox) |
+| `34-chapter.mp3` | <https://archive.org/download/aesopforchildren_1308_librivox/theaesopforchildren_34_aesop_64kb.mp3> | Public Domain (LibriVox) |
+| `37-chapter.mp3` | <https://archive.org/download/aesopforchildren_1308_librivox/theaesopforchildren_37_aesop_64kb.mp3> | Public Domain (LibriVox) |
+
+LibriVox dedication statement:
+<https://librivox.org/pages/public-domain/>.
+
+---
+
+## `images/`
+
+Four thumbnails from the NASA Image Library. NASA imagery is generally
+**not subject to copyright** in the United States; see
+<https://www.nasa.gov/multimedia/guidelines/index.html>. The "~thumb"
+suffix in each URL retrieves the small thumbnail variant.
+
+| File | Source URL | License |
+|---|---|---|
+| `jupiter-pia12235.jpg` | <https://images-assets.nasa.gov/image/PIA12235/PIA12235~thumb.jpg> | Public Domain (NASA) |
+| `cygnus-loop-pia17172.jpg` | <https://images-assets.nasa.gov/image/PIA17172/PIA17172~thumb.jpg> | Public Domain (NASA) |
+| `earth-pia18033.jpg` | <https://images-assets.nasa.gov/image/PIA18033/PIA18033~thumb.jpg> | Public Domain (NASA) |
+| `mars-pia22231.jpg` | <https://images-assets.nasa.gov/image/PIA22231/PIA22231~thumb.jpg> | Public Domain (NASA) |
+
+---
+
+## `videos/`
+
+| File | Source URL | License |
+|---|---|---|
+| `nile-red-sea-nasa.mp4` | <https://archive.org/download/Nile-and-Red-Sea.mov/Nile-and-Red-Sea.mp4> | Public Domain (NASA) |
+
+The Nile and Red Sea satellite footage is a NASA product released to the
+public domain. Mirror: <https://archive.org/details/Nile-and-Red-Sea.mov>.
diff --git a/player-server/testdata/README.md b/player-server/testdata/README.md
new file mode 100644
index 0000000..3cad541
--- /dev/null
+++ b/player-server/testdata/README.md
@@ -0,0 +1,56 @@
+# Test data
+
+This directory contains the small, license-cleared media library used by
+the automated test suites (Playwright `e2e-web/`, LLM `e2e-llm/`, and the
+Mage `E2E` target). It is committed to the repository so anyone can clone
+and run the suites without an external download step.
+
+See [`LICENSES.md`](./LICENSES.md) for the source URL and license of
+every file.
+
+## Layout
+
+```
+testdata/
+├── LICENSES.md
+├── README.md
+└── media/
+ ├── audiobooks/aesops-fables/ five LibriVox public-domain mp3 chapters
+ ├── images/ four NASA public-domain jpgs
+ └── videos/ one NASA public-domain mp4 short
+```
+
+Each top-level directory under `media/` becomes a distinct *set* when the
+server scans the library, so the suites exercise multi-set browsing,
+per-set permissions, and cover regeneration without needing fixtures
+elsewhere.
+
+## Pointing the server at this directory
+
+The server resolves `MEDIA_ROOT` relative to the working directory it
+was started from. Start it from `player-server/` so the relative path
+works:
+
+```sh
+cd player-server
+MEDIA_ROOT=./testdata/media \
+ SECURE_COOKIES=false \
+ DB_PATH=/tmp/player-e2e-llm.db \
+ ./player
+```
+
+Override with an absolute path if you want to run against a different
+library (your own media collection lives outside the repository).
+
+## Adding more fixture files
+
+1. Pick a file whose license permits redistribution (public domain,
+ CC0, or a permissive Creative Commons variant). When in doubt,
+ prefer NASA, LibriVox, or Wikimedia Commons CC0.
+2. Keep individual files small (a few MB) so the repository stays
+ lightweight. The whole `testdata/` tree should remain on the order
+ of ~10–20 MB.
+3. Drop the file into the appropriate `media/<set>/` directory and add
+ an entry to `LICENSES.md` with the source URL and license.
+4. If a test was written against the old fixture set, update its
+ assertions to match the new file count or names.
diff --git a/player-server/testdata/media/audiobooks/aesops-fables/10-chapter.mp3 b/player-server/testdata/media/audiobooks/aesops-fables/10-chapter.mp3
new file mode 100644
index 0000000..92ae217
--- /dev/null
+++ b/player-server/testdata/media/audiobooks/aesops-fables/10-chapter.mp3
Binary files differ
diff --git a/player-server/testdata/media/audiobooks/aesops-fables/13-chapter.mp3 b/player-server/testdata/media/audiobooks/aesops-fables/13-chapter.mp3
new file mode 100644
index 0000000..965957f
--- /dev/null
+++ b/player-server/testdata/media/audiobooks/aesops-fables/13-chapter.mp3
Binary files differ
diff --git a/player-server/testdata/media/audiobooks/aesops-fables/16-chapter.mp3 b/player-server/testdata/media/audiobooks/aesops-fables/16-chapter.mp3
new file mode 100644
index 0000000..e45405d
--- /dev/null
+++ b/player-server/testdata/media/audiobooks/aesops-fables/16-chapter.mp3
Binary files differ
diff --git a/player-server/testdata/media/audiobooks/aesops-fables/34-chapter.mp3 b/player-server/testdata/media/audiobooks/aesops-fables/34-chapter.mp3
new file mode 100644
index 0000000..ec1e736
--- /dev/null
+++ b/player-server/testdata/media/audiobooks/aesops-fables/34-chapter.mp3
Binary files differ
diff --git a/player-server/testdata/media/audiobooks/aesops-fables/37-chapter.mp3 b/player-server/testdata/media/audiobooks/aesops-fables/37-chapter.mp3
new file mode 100644
index 0000000..c6faf4e
--- /dev/null
+++ b/player-server/testdata/media/audiobooks/aesops-fables/37-chapter.mp3
Binary files differ
diff --git a/player-server/testdata/media/images/.thumbnails/cygnus-loop-pia17172.jpg b/player-server/testdata/media/images/.thumbnails/cygnus-loop-pia17172.jpg
new file mode 100644
index 0000000..2231be7
--- /dev/null
+++ b/player-server/testdata/media/images/.thumbnails/cygnus-loop-pia17172.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/.thumbnails/earth-pia18033.jpg b/player-server/testdata/media/images/.thumbnails/earth-pia18033.jpg
new file mode 100644
index 0000000..7ae5b3f
--- /dev/null
+++ b/player-server/testdata/media/images/.thumbnails/earth-pia18033.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/.thumbnails/jupiter-pia12235.jpg b/player-server/testdata/media/images/.thumbnails/jupiter-pia12235.jpg
new file mode 100644
index 0000000..ce648fa
--- /dev/null
+++ b/player-server/testdata/media/images/.thumbnails/jupiter-pia12235.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/.thumbnails/mars-pia22231.jpg b/player-server/testdata/media/images/.thumbnails/mars-pia22231.jpg
new file mode 100644
index 0000000..f0a0ec5
--- /dev/null
+++ b/player-server/testdata/media/images/.thumbnails/mars-pia22231.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/cygnus-loop-pia17172.jpg b/player-server/testdata/media/images/cygnus-loop-pia17172.jpg
new file mode 100644
index 0000000..7a49a46
--- /dev/null
+++ b/player-server/testdata/media/images/cygnus-loop-pia17172.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/earth-pia18033.jpg b/player-server/testdata/media/images/earth-pia18033.jpg
new file mode 100644
index 0000000..437b960
--- /dev/null
+++ b/player-server/testdata/media/images/earth-pia18033.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/jupiter-pia12235.jpg b/player-server/testdata/media/images/jupiter-pia12235.jpg
new file mode 100644
index 0000000..6401254
--- /dev/null
+++ b/player-server/testdata/media/images/jupiter-pia12235.jpg
Binary files differ
diff --git a/player-server/testdata/media/images/mars-pia22231.jpg b/player-server/testdata/media/images/mars-pia22231.jpg
new file mode 100644
index 0000000..c67306c
--- /dev/null
+++ b/player-server/testdata/media/images/mars-pia22231.jpg
Binary files differ
diff --git a/player-server/testdata/media/videos/.thumbnails/nile-red-sea-nasa.jpg b/player-server/testdata/media/videos/.thumbnails/nile-red-sea-nasa.jpg
new file mode 100644
index 0000000..fc8edcb
--- /dev/null
+++ b/player-server/testdata/media/videos/.thumbnails/nile-red-sea-nasa.jpg
Binary files differ
diff --git a/player-server/testdata/media/videos/nile-red-sea-nasa.mp4 b/player-server/testdata/media/videos/nile-red-sea-nasa.mp4
new file mode 100644
index 0000000..fd78031
--- /dev/null
+++ b/player-server/testdata/media/videos/nile-red-sea-nasa.mp4
Binary files differ