############################################################################### # 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-- ###