From f6b7fa2df5ff2d6765c016e7574a5b4b244d49fd Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sat, 13 Jun 2026 16:09:42 -0400 Subject: [PATCH] feat: add Phase 9 API smoke tests (.http file) --- DeepDrftAPI/DeepDrftAPI.http | 466 +++++++++++++++++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 DeepDrftAPI/DeepDrftAPI.http diff --git a/DeepDrftAPI/DeepDrftAPI.http b/DeepDrftAPI/DeepDrftAPI.http new file mode 100644 index 0000000..24b7607 --- /dev/null +++ b/DeepDrftAPI/DeepDrftAPI.http @@ -0,0 +1,466 @@ +############################################################################### +# DeepDrftAPI — Phase 9 smoke tests +# IDE: VS 2022 / Rider / VS Code REST Client +# +# HOW TO USE: +# 1. Set @apiKey below. The real value is in DeepDrftAPI/environment/apikey.json +# under the key "ApiKeySettings.ApiKey". Do NOT commit a real key here. +# 2. Adjust @releaseId, @trackId, and @entryKey to IDs present in your DB. +# 3. Point the multipart upload requests at a real local file before sending. +# +# AUTH NOTE: +# Unauthenticated endpoints have no ApiKey header. +# ApiKey-gated endpoints use header "ApiKey: {{apiKey}}" (header name is literal +# "ApiKey", not "X-Api-Key" — confirmed in ApiKeyAuthenticationMiddleware.cs). +############################################################################### + +@host = http://localhost:5003 + +# REPLACE with value from DeepDrftAPI/environment/apikey.json → ApiKeySettings.ApiKey +@apiKey = REPLACE_WITH_YOUR_API_KEY + +# Placeholders — edit these to match IDs in your local DB +@releaseId = 1 +@trackId = 1 +@entryKey = replace-with-real-entry-key + + +############################################################################### +# 1. RELEASE READS +############################################################################### + +### 1a. List all releases (unauth) — expect 200 PagedResult +# exercises: GET api/release (no medium filter) +GET {{host}}/api/release +Accept: application/json + +### + +### 1b. List Session releases (unauth) — expect 200, only Session medium returned +# exercises: GET api/release?medium=session +GET {{host}}/api/release?medium=session +Accept: application/json + +### + +### 1c. List Mix releases (unauth) — expect 200, only Mix medium returned +# exercises: GET api/release?medium=mix +GET {{host}}/api/release?medium=mix +Accept: application/json + +### + +### 1d. List Cut releases (unauth) — expect 200, only Cut medium returned +# exercises: GET api/release?medium=cut +GET {{host}}/api/release?medium=cut +Accept: application/json + +### + +### 1e. Bad medium value (unauth) — expect 400 "Unrecognised medium: bogus" +# exercises: GET api/release?medium=bogus → BadRequest branch in ReleaseController +GET {{host}}/api/release?medium=bogus +Accept: application/json + +### + +### 1f. Single release by id (unauth) — expect 200 ReleaseDto with both metadata navs +# exercises: GET api/release/{id:long} +GET {{host}}/api/release/{{releaseId}} +Accept: application/json + +### + + +############################################################################### +# 2. TRACK READS +############################################################################### + +### 2a. Paged track list — all tracks (unauth) — expect 200 PagedResult +# exercises: GET api/track/page (no filters) +GET {{host}}/api/track/page +Accept: application/json + +### + +### 2b. Paged track list filtered by releaseId (unauth) — expect 200, tracks for that release +# exercises: GET api/track/page?releaseId= +GET {{host}}/api/track/page?releaseId={{releaseId}} +Accept: application/json + +### + +### 2c. Paged track list filtered by album title (unauth) — expect 200 +# exercises: GET api/track/page?album= +GET {{host}}/api/track/page?album=My+Album+Name +Accept: application/json + +### + +### 2d. Paged track list filtered by genre (unauth) — expect 200 +# exercises: GET api/track/page?genre= +GET {{host}}/api/track/page?genre=Electronic +Accept: application/json + +### + +### 2e. Single track metadata by SQL id (ApiKey) — expect 200 TrackDto +# exercises: GET api/track/meta/{id:long} +GET {{host}}/api/track/meta/{{trackId}} +ApiKey: {{apiKey}} +Accept: application/json + +### + +### 2f. Track audio stream — full file (unauth) — expect 200 with audio bytes +# exercises: GET api/track/{trackId} → streams WAV from FileDatabase vault +GET {{host}}/api/track/{{entryKey}} +Accept: audio/wav + +### + +### 2g. Track audio stream — Range request (unauth) — expect 206 Partial Content +# exercises: GET api/track/{trackId} with Range header → 206 + byte slice +GET {{host}}/api/track/{{entryKey}} +Range: bytes=44- +Accept: audio/wav + +### + +### 2h. Albums list (unauth) — expect 200 List with per-release track counts +# exercises: GET api/track/albums +GET {{host}}/api/track/albums +Accept: application/json + +### + +### 2i. Genres list (unauth) — expect 200 List distinct genres +# exercises: GET api/track/genres +GET {{host}}/api/track/genres +Accept: application/json + +### + +### 2j. Random track (unauth) — expect 200 TrackDto (or 404 when library is empty) +# exercises: GET api/track/random +GET {{host}}/api/track/random +Accept: application/json + +### + +### 2k. Waveform profile for track (unauth) — expect 200 WaveformProfileDto or 404 if not computed +# exercises: GET api/track/{trackId}/waveform +GET {{host}}/api/track/{{entryKey}}/waveform +Accept: application/json + +### + +### 2l. Waveform status for all tracks (ApiKey) — expect 200 List +# exercises: GET api/track/waveform-status (admin backfill view) +GET {{host}}/api/track/waveform-status +ApiKey: {{apiKey}} +Accept: application/json + +### + + +############################################################################### +# 3. MEDIUM WRITE PATH +############################################################################### + +### 3a. Upload a Cut track (ApiKey, multipart) — expect 200 TrackDto +# exercises: POST api/track/upload with medium=Cut +# REPLACE ./path/to/test.wav with a real local .wav (or .mp3 / .flac) file path. +POST {{host}}/api/track/upload +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="audioFile"; filename="test.wav" +Content-Type: audio/wav + +< ./path/to/test.wav +--boundary +Content-Disposition: form-data; name="trackName" + +Smoke Test Cut +--boundary +Content-Disposition: form-data; name="artist" + +Test Artist +--boundary +Content-Disposition: form-data; name="album" + +Smoke Test Album +--boundary +Content-Disposition: form-data; name="genre" + +Electronic +--boundary +Content-Disposition: form-data; name="releaseDate" + +2025-01-01 +--boundary +Content-Disposition: form-data; name="createdByUserId" + +1 +--boundary +Content-Disposition: form-data; name="medium" + +Cut +--boundary +Content-Disposition: form-data; name="releaseType" + +Single +--boundary +Content-Disposition: form-data; name="trackNumber" + +1 +--boundary-- + +### + +### 3b. Upload a Session track (ApiKey, multipart) — expect 200 TrackDto +# exercises: POST api/track/upload with medium=Session +# NOTE: Session releases are single-track. Use a unique album name to create a new release. +# REPLACE ./path/to/test.wav with a real local .wav/.mp3/.flac file. +POST {{host}}/api/track/upload +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="audioFile"; filename="test.wav" +Content-Type: audio/wav + +< ./path/to/test.wav +--boundary +Content-Disposition: form-data; name="trackName" + +Smoke Test Session +--boundary +Content-Disposition: form-data; name="artist" + +Test Artist +--boundary +Content-Disposition: form-data; name="album" + +Smoke Session Album 001 +--boundary +Content-Disposition: form-data; name="createdByUserId" + +1 +--boundary +Content-Disposition: form-data; name="medium" + +Session +--boundary +Content-Disposition: form-data; name="trackNumber" + +1 +--boundary-- + +### + +### 3c. Upload a Mix track (ApiKey, multipart) — expect 200 TrackDto +# exercises: POST api/track/upload with medium=Mix +# NOTE: Mix releases are also single-track. Use a unique album name. +# REPLACE ./path/to/test.wav with a real local .wav/.mp3/.flac file. +POST {{host}}/api/track/upload +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="audioFile"; filename="test.wav" +Content-Type: audio/wav + +< ./path/to/test.wav +--boundary +Content-Disposition: form-data; name="trackName" + +Smoke Test Mix +--boundary +Content-Disposition: form-data; name="artist" + +Test Artist +--boundary +Content-Disposition: form-data; name="album" + +Smoke Mix Album 001 +--boundary +Content-Disposition: form-data; name="createdByUserId" + +1 +--boundary +Content-Disposition: form-data; name="medium" + +Mix +--boundary +Content-Disposition: form-data; name="trackNumber" + +1 +--boundary-- + +### + +### 3d. Update track metadata — flip Medium to Session (ApiKey, JSON) — expect 200 +# exercises: PUT api/track/meta/{id:long} +# Medium: null means "no change"; provide it to change the release medium. +PUT {{host}}/api/track/meta/{{trackId}} +ApiKey: {{apiKey}} +Content-Type: application/json + +{ + "TrackName": "Updated Track Name", + "Artist": "Updated Artist", + "Album": "Updated Album", + "Genre": "Electronic", + "ReleaseDate": "2025-06-01", + "ImagePath": null, + "ReleaseType": null, + "Medium": "Session", + "TrackNumber": 1 +} + +### + + +############################################################################### +# 4. WAVE 7 — SESSION CARDINALITY 409 SEQUENCE +# +# Session and Mix releases enforce a single-track maximum. The sequence below +# demonstrates the enforcement: +# Step 1: upload the FIRST track to a Session release → expect 200 +# Step 2: upload a SECOND track to the SAME album+artist → expect 409 Conflict +# +# The 409 body will contain: "A Session release holds a single track; '' +# already has one — edit the existing track or choose a different release." +# +# Cut-medium releases have no cardinality limit, so an identical sequence with +# medium=Cut would produce 200 on both requests. +############################################################################### + +### 4a. [STEP 1] Upload first Session track — expect 200 TrackDto +# The album "Cardinality Test Session" does not yet exist; this creates it. +# REPLACE ./path/to/test.wav with a real local file. +POST {{host}}/api/track/upload +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="audioFile"; filename="test.wav" +Content-Type: audio/wav + +< ./path/to/test.wav +--boundary +Content-Disposition: form-data; name="trackName" + +Cardinality Track 1 +--boundary +Content-Disposition: form-data; name="artist" + +Cardinality Artist +--boundary +Content-Disposition: form-data; name="album" + +Cardinality Test Session +--boundary +Content-Disposition: form-data; name="createdByUserId" + +1 +--boundary +Content-Disposition: form-data; name="medium" + +Session +--boundary +Content-Disposition: form-data; name="trackNumber" + +1 +--boundary-- + +### + +### 4b. [STEP 2] Upload second Session track to the SAME album+artist — expect 409 Conflict +# Same album "Cardinality Test Session", same artist "Cardinality Artist". +# UnifiedTrackService.UploadAsync pre-checks cardinality before the vault write. +# REPLACE ./path/to/test.wav with a real local file. +POST {{host}}/api/track/upload +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="audioFile"; filename="test.wav" +Content-Type: audio/wav + +< ./path/to/test.wav +--boundary +Content-Disposition: form-data; name="trackName" + +Cardinality Track 2 +--boundary +Content-Disposition: form-data; name="artist" + +Cardinality Artist +--boundary +Content-Disposition: form-data; name="album" + +Cardinality Test Session +--boundary +Content-Disposition: form-data; name="createdByUserId" + +1 +--boundary +Content-Disposition: form-data; name="medium" + +Session +--boundary +Content-Disposition: form-data; name="trackNumber" + +2 +--boundary-- + +### + + +############################################################################### +# 5. MIX WAVEFORM +############################################################################### + +### 5a. Trigger waveform generation for a Mix release (ApiKey, no body) — expect 200 or 404 +# exercises: POST api/release/{id:long}/mix/waveform +# 404 if the release is not a Mix, has no track, or the track has no audio stored. +POST {{host}}/api/release/{{releaseId}}/mix/waveform +ApiKey: {{apiKey}} +Content-Length: 0 + +### + +### 5b. Fetch stored mix waveform (unauth) — expect 200 WaveformProfileDto or 404 +# exercises: GET api/release/{id:long}/mix/waveform +# 404 when release is not a Mix, has no WaveformEntryKey, or datum not yet computed (run 5a first). +GET {{host}}/api/release/{{releaseId}}/mix/waveform +Accept: application/json + +### + + +############################################################################### +# 6. SESSION HERO IMAGE +############################################################################### + +### 6a. Upload hero image for a Session release (ApiKey, multipart) — expect 200 or 404 +# exercises: POST api/release/{id:long}/session/hero-image +# 404 if release not found. 400 if no image file or unsupported content type. +# The release must be a Session (enforced in UnifiedReleaseService.SetHeroImageAsync). +# REPLACE ./path/to/hero.jpg with a real local JPEG or PNG file. +POST {{host}}/api/release/{{releaseId}}/session/hero-image +ApiKey: {{apiKey}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="image"; filename="hero.jpg" +Content-Type: image/jpeg + +< ./path/to/hero.jpg +--boundary-- + +###