Compare commits

...

4 Commits

Author SHA1 Message Date
glenneth aed413e21b docs: Update README.org file structure to reflect current project state 2025-10-12 06:37:55 +03:00
glenneth d65874ee7f feat: Add Liquidsoap DJ controls via telnet integration
- Implement 5 DJ control API endpoints (skip, reload, queue, metadata, queue-status)
- Update Liquidsoap config to support dynamic track queuing with request.queue
- Add comprehensive DJ control tests to test-server.sh with dynamic track ID fetching
- Add test result logging with timestamped log files
- Update .gitignore to exclude test result logs
- Change liquidsoap-queue-track to use queue.push command
2025-10-12 06:37:55 +03:00
Glenn Thompson 69b6a9b681 Add Liquidsoap DJ controls via telnet integration
- Add usocket and cl-ppcre dependencies to asteroid.asd
- Implement telnet communication functions for Liquidsoap control
- Add 5 new DJ control API endpoints:
  * /api/asteroid/dj/skip - Skip to next track (admin only)
  * /api/asteroid/dj/reload-playlist - Reload music library (admin only)
  * /api/asteroid/dj/queue - Queue specific track (admin only)
  * /api/asteroid/dj/queue-status - View request queue (admin only)
  * /api/asteroid/dj/metadata - Enhanced metadata (authenticated users)
- Use correct Liquidsoap commands (music.skip, music.reload)
- Implement proper role-based access control and error handling
- All endpoints return JSON responses with appropriate HTTP status codes

Features tested and working:
- Real-time track skipping with natural streaming delay
- Enhanced metadata parsing from Liquidsoap
- Queue monitoring and management
- Secure admin-only access to DJ controls
2025-10-12 06:37:55 +03:00
glenneth e432484424 Resolve merge conflict: integrate track-ids fix with api-output refactoring 2025-10-12 06:37:55 +03:00
8 changed files with 446 additions and 9 deletions

1
.gitignore vendored
View File

@ -44,5 +44,6 @@ docker-compose.yml.backup.*
*.log *.log
logs/ logs/
performance-logs/ performance-logs/
test-results-*.log
# Temporary files # Temporary files

53
PR1-api-refactoring.md Normal file
View File

@ -0,0 +1,53 @@
# Refactor API endpoints to use Radiance's define-api macro
## Summary
Refactors all API endpoints from manual `define-page` implementations to use Radiance's built-in `define-api` macro, following framework best practices and conventions.
## Changes Made
### API Endpoint Refactoring
- **Converted 15+ API endpoints** from `define-page` to `define-api` format
- **Standardized URL patterns** to `/api/<module>/<endpoint>` structure
- **Implemented proper JSON responses** using `api-output` instead of manual JSON encoding
- **Added comprehensive error handling** with appropriate HTTP status codes (404, 500, 403)
### Key Technical Improvements
- **Automatic JSON formatting**: Leverages Radiance's built-in JSON API format
- **Parameter handling**: Uses lambda-list parameters instead of manual POST/GET parsing
- **Consistent error responses**: Standardized error format across all endpoints
- **Framework compliance**: Follows Radiance's recommended API patterns
### Endpoints Converted
- **Admin APIs**: `asteroid/admin/scan-library`, `asteroid/admin/tracks`
- **Playlist APIs**: `asteroid/playlists`, `asteroid/playlists/create`, `asteroid/playlists/add-track`, `asteroid/playlists/get`
- **Track APIs**: `asteroid/tracks`
- **Player APIs**: `asteroid/player/play`, `asteroid/player/pause`, `asteroid/player/stop`, `asteroid/player/resume`, `asteroid/player/status`
- **Status APIs**: `asteroid/status`, `asteroid/icecast-status`, `asteroid/auth-status`
### Added Comprehensive Test Suite
- **18 automated tests** covering all API endpoints
- **Response format validation** ensuring proper JSON output
- **Authentication testing** for protected endpoints
- **Static file serving verification**
- **HTML page loading tests**
## Testing
- ✅ **All 18 tests pass** via `./test-server.sh`
- ✅ **Proper JSON responses** instead of S-expressions
- ✅ **Authentication integration** working correctly
- ✅ **Error handling** with appropriate HTTP status codes
- ✅ **Backward compatibility** maintained for existing functionality
## Files Changed
- `asteroid.lisp` - Main API endpoint refactoring
- `auth-routes.lisp` - Authentication API endpoints (if applicable)
- Added comprehensive test suite
## Impact
- **Cleaner codebase** following Radiance framework conventions
- **Better maintainability** with standardized API patterns
- **Improved error handling** and status code management
- **Enhanced testing coverage** for all API functionality
- **Foundation for frontend integration** with consistent JSON responses
This refactoring establishes a solid foundation for frontend development and maintains full backward compatibility while improving code quality and framework compliance.

64
PR2-js-fixes.md Normal file
View File

@ -0,0 +1,64 @@
# Fix frontend JavaScript to work with define-api endpoints
## Summary
Updates all frontend JavaScript files to properly work with the refactored `define-api` endpoints, including API path corrections and RADIANCE response format handling.
## Changes Made
### API Endpoint Path Updates
- **Fixed API paths** in `users.js` and `profile.js` from `/asteroid/api/` to `/api/asteroid/`
- **Corrected endpoint references** to match new `define-api` URL structure
- **Updated all fetch calls** to use consistent API path format
### RADIANCE API Response Handling
- **Added response wrapper handling** for RADIANCE's `{status: 200, data: {...}}` format
- **Implemented fallback logic** with `result.data || result` pattern
- **Fixed icecast-status parsing** in both `front-page.js` and `player.js`
- **Enhanced error handling** for API response processing
### JavaScript Fixes
- **Fixed missing function declaration** in `player.js` (`loadTracks` function)
- **Improved error handling** in track loading with proper user feedback
- **Added debug logging** for API response troubleshooting
- **Enhanced live metadata updates** for real-time track information
### Files Updated
- **`static/js/front-page.js`** - Fixed icecast-status response parsing for live metadata
- **`static/js/player.js`** - Fixed function declaration and RADIANCE response handling
- **`static/js/users.js`** - Updated all API paths from `/asteroid/api/` to `/api/asteroid/`
- **`static/js/profile.js`** - Updated all API paths from `/asteroid/api/` to `/api/asteroid/`
## Key Improvements
### Live Metadata Integration
- **Real-time track updates** now working correctly on front page
- **Automatic refresh** every 10 seconds showing current playing track
- **Proper parsing** of "Artist - Track" format from Icecast
- **Fallback handling** for missing or malformed metadata
### Enhanced User Experience
- **Consistent API communication** across all frontend components
- **Better error messaging** when API calls fail
- **Improved loading states** and user feedback
- **Seamless integration** with authentication system
## Testing
- ✅ **All 18 tests pass** via `./test-server.sh`
- ✅ **Live metadata working** - Shows actual track names instead of "The Void - Silence"
- ✅ **Frontend API calls successful** - All JavaScript fetch operations working
- ✅ **User interface responsive** - All interactive elements functioning
- ✅ **Authentication integration** - Proper handling of protected endpoints
## Technical Details
- **RADIANCE API wrapper**: Properly handles `{status: 200, message: "Ok", data: {...}}` format
- **Error resilience**: Graceful fallback when API responses are malformed
- **Consistent patterns**: Standardized approach to API response handling across all files
- **Debug support**: Added logging for troubleshooting API communication
## Impact
- **Fully functional frontend** working with refactored backend APIs
- **Live streaming metadata** displaying real track information
- **Enhanced user experience** with proper error handling and feedback
- **Maintainable codebase** with consistent API communication patterns
This update completes the frontend integration with the `define-api` refactoring, ensuring all user-facing functionality works seamlessly with the new backend API structure.

View File

@ -50,19 +50,76 @@ This branch implements a complete internet radio streaming system for Asteroid R
asteroid/ asteroid/
├── asteroid.lisp # Main server with RADIANCE routes ├── asteroid.lisp # Main server with RADIANCE routes
├── asteroid.asd # System definition with dependencies ├── asteroid.asd # System definition with dependencies
├── asteroid # Compiled executable binary
├── module.lisp # RADIANCE module definition
├── database.lisp # Database abstraction layer
├── auth-routes.lisp # Authentication and authorization routes
├── user-management.lisp # User management system
├── users.lisp # User utilities
├── playlist-management.lisp # Playlist creation and management
├── stream-media.lisp # Media streaming functionality
├── app-utils.lisp # Application utilities
├── template-utils.lisp # Template rendering utilities
├── build-executable.lisp # Build script for binary
├── setup-environment.lisp # Environment setup script
├── asteroid-radio.liq # Liquidsoap streaming configuration ├── asteroid-radio.liq # Liquidsoap streaming configuration
├── playlist.m3u # Generated playlist for streaming ├── playlist.m3u # Generated playlist for streaming
├── start-asteroid-radio.sh # Launch script for all services ├── Makefile # Build automation
├── stop-asteroid-radio.sh # Stop script for all services
├── template/ # CLIP HTML templates ├── template/ # CLIP HTML templates
│ ├── front-page.chtml # Main page with live stream │ ├── front-page.chtml # Main page with live stream
│ ├── admin.chtml # Admin dashboard │ ├── admin.chtml # Admin dashboard
│ └── player.chtml # Web player interface │ ├── player.chtml # Web player interface
├── static/ # CSS and assets │ ├── login.chtml # User login page
│ └── asteroid.lass # LASS stylesheet │ ├── register.chtml # User registration page
└── music/ # Music library │ ├── profile.chtml # User profile management
├── incoming/ # Upload staging area │ └── users.chtml # User management interface
└── library/ # Processed music files ├── static/ # CSS, JavaScript, and assets
│ ├── asteroid.lass # LASS stylesheet source
│ ├── asteroid.css # Compiled CSS
│ └── js/ # JavaScript modules
│ ├── admin.js # Admin panel functionality
│ ├── player.js # Audio player controls
│ ├── front-page.js # Front page interactions
│ ├── auth-ui.js # Authentication UI
│ ├── profile.js # Profile management
│ └── users.js # User management UI
├── config/ # Configuration files
│ └── radiance-postgres.lisp # PostgreSQL configuration
├── data/ # Runtime data
│ └── sessions/ # User session storage
├── docs/ # Documentation
│ ├── README.org # Documentation overview
│ ├── API-ENDPOINTS.org # API endpoint reference
│ ├── API-REFERENCE.org # Detailed API documentation
│ ├── DEVELOPMENT.org # Development guide
│ ├── INSTALLATION.org # Installation instructions
│ ├── TESTING.org # Testing documentation
│ ├── PROJECT-OVERVIEW.org # Project architecture overview
│ ├── POSTGRESQL-SETUP.org # Database setup guide
│ ├── DOCKER-STREAMING.org # Docker deployment guide
│ ├── PLAYLIST-SYSTEM.org # Playlist system documentation
│ ├── TRACK-PAGINATION-SYSTEM.org # Pagination documentation
│ └── USER-MANAGEMENT-SYSTEM.org # User system documentation
├── docker/ # Docker deployment
│ ├── docker-compose.yml # Docker Compose configuration
│ ├── Dockerfile.liquidsoap # Liquidsoap container
│ ├── asteroid-radio-docker.liq # Docker Liquidsoap config
│ ├── icecast.xml # Icecast configuration
│ ├── init-db.sql # Database initialization
│ ├── start.sh # Docker startup script
│ └── stop.sh # Docker shutdown script
├── music/ # Music library
│ ├── incoming/ # Upload staging area
│ └── library/ # Processed music files
├── test-server.sh # Server testing script
├── test-user-api.sh # User API testing script
├── run-all-tests.sh # Comprehensive test suite
├── comprehensive-performance-test.sh # Performance testing
├── analyze-performance.py # Performance analysis tool
├── simple-analysis.py # Simple analysis utilities
├── design.org # Design documentation
├── TODO.org # Project TODO list
└── project-summary.org # Project summary and status
#+END_SRC #+END_SRC
* Track Upload Workflow * Track Upload Workflow

View File

@ -27,6 +27,8 @@
:cl-fad :cl-fad
:bordeaux-threads :bordeaux-threads
:drakma :drakma
:usocket
:cl-ppcre
(:interface :auth) (:interface :auth)
(:interface :database) (:interface :database)
(:interface :user)) (:interface :user))

View File

@ -278,6 +278,68 @@
("position" . ,*current-position*) ("position" . ,*current-position*)
("queue-length" . ,(length *play-queue*)))) ("queue-length" . ,(length *play-queue*))))
;; Liquidsoap Telnet Control Functions
(defun liquidsoap-telnet-command (command)
"Send a command to Liquidsoap telnet server and return response"
(handler-case
(let ((socket (usocket:socket-connect "127.0.0.1" 1234)))
(unwind-protect
(progn
(format (usocket:socket-stream socket) "~A~%" command)
(force-output (usocket:socket-stream socket))
(let ((response-lines '())
(line nil))
;; Read all lines until "END"
(loop while (and (setf line (read-line (usocket:socket-stream socket) nil))
(not (string= line "END")))
do (push line response-lines))
(format nil "~{~A~%~}" (reverse response-lines))))
(usocket:socket-close socket)))
(error (e)
(format t "Error communicating with Liquidsoap: ~A~%" e)
nil)))
(defun liquidsoap-skip-track ()
"Skip to the next track in Liquidsoap"
(liquidsoap-telnet-command "music.skip"))
(defun liquidsoap-reload-playlist ()
"Reload the playlist in Liquidsoap"
(liquidsoap-telnet-command "music.reload"))
(defun liquidsoap-get-metadata ()
"Get current track metadata from Liquidsoap"
(let ((response (liquidsoap-telnet-command "output.icecast.metadata")))
(when response
(parse-liquidsoap-metadata response))))
(defun liquidsoap-get-queue ()
"Get the current request queue from Liquidsoap"
(liquidsoap-telnet-command "request.all"))
(defun liquidsoap-queue-track (file-path)
"Queue a specific track in Liquidsoap"
(liquidsoap-telnet-command (format nil "queue.push ~A" file-path)))
(defun parse-liquidsoap-metadata (metadata-string)
"Parse Liquidsoap metadata string into artist/title/album"
(let ((artist "Unknown Artist")
(title "Unknown Track")
(album "Unknown Album"))
(when metadata-string
;; Parse the first track (most recent) from the metadata
;; Format: --- 2 ---\nkey="value"\nkey="value"...
(let ((lines (cl-ppcre:split "\\n" metadata-string)))
(dolist (line lines)
(cond
((cl-ppcre:scan "^artist=" line)
(setf artist (cl-ppcre:regex-replace "^artist=\"([^\"]+)\"" line "\\1")))
((cl-ppcre:scan "^title=" line)
(setf title (cl-ppcre:regex-replace "^title=\"([^\"]+)\"" line "\\1")))
((cl-ppcre:scan "^album=" line)
(setf album (cl-ppcre:regex-replace "^album=\"([^\"]+)\"" line "\\1")))))))
(list :artist artist :title title :album album)))
;; Define CLIP attribute processor for data-text ;; Define CLIP attribute processor for data-text
(clip:define-attribute-processor data-text (node value) (clip:define-attribute-processor data-text (node value)
@ -361,6 +423,102 @@
(api-output `(("status" . "success") (api-output `(("status" . "success")
("player" . ,(get-player-status))))) ("player" . ,(get-player-status)))))
;; DJ Control API Endpoints
(define-api asteroid/dj/skip () ()
"Skip to the next track via Liquidsoap telnet"
(require-role :admin) ; Only admins can use DJ controls
(handler-case
(let ((result (liquidsoap-skip-track)))
(if result
(api-output `(("status" . "success")
("message" . "Track skipped successfully")
("liquidsoap-response" . ,result)))
(api-output `(("status" . "error")
("message" . "Failed to communicate with Liquidsoap"))
:status 503)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Skip error: ~a" e)))
:status 500))))
(define-api asteroid/dj/reload-playlist () ()
"Reload the playlist in Liquidsoap"
(require-role :admin)
(handler-case
(let ((result (liquidsoap-reload-playlist)))
(if result
(api-output `(("status" . "success")
("message" . "Playlist reloaded successfully")
("liquidsoap-response" . ,result)))
(api-output `(("status" . "error")
("message" . "Failed to communicate with Liquidsoap"))
:status 503)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Reload error: ~a" e)))
:status 500))))
(define-api asteroid/dj/queue (track-id) ()
"Queue a specific track by ID"
(require-role :admin)
(handler-case
(let* ((id (parse-integer track-id :junk-allowed t))
(track (get-track-by-id id)))
(if track
(let* ((file-path (first (gethash "file-path" track)))
(result (liquidsoap-queue-track file-path)))
(if result
(api-output `(("status" . "success")
("message" . "Track queued successfully")
("track" . (("id" . ,id)
("title" . ,(first (gethash "title" track)))
("artist" . ,(first (gethash "artist" track)))))
("liquidsoap-response" . ,result)))
(api-output `(("status" . "error")
("message" . "Failed to queue track in Liquidsoap"))
:status 503)))
(api-output `(("status" . "error")
("message" . "Track not found"))
:status 404)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Queue error: ~a" e)))
:status 500))))
(define-api asteroid/dj/queue-status () ()
"Get the current Liquidsoap request queue"
(require-role :admin)
(handler-case
(let ((result (liquidsoap-get-queue)))
(if result
(api-output `(("status" . "success")
("queue" . ,result)))
(api-output `(("status" . "error")
("message" . "Failed to get queue from Liquidsoap"))
:status 503)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Queue status error: ~a" e)))
:status 500))))
(define-api asteroid/dj/metadata () ()
"Get enhanced metadata directly from Liquidsoap"
(require-authentication) ; Any authenticated user can see metadata
(handler-case
(let ((metadata (liquidsoap-get-metadata)))
(if metadata
(api-output `(("status" . "success")
("metadata" . (("artist" . ,(getf metadata :artist))
("title" . ,(getf metadata :title))
("album" . ,(getf metadata :album))))))
(api-output `(("status" . "error")
("message" . "Failed to get metadata from Liquidsoap"))
:status 503)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Metadata error: ~a" e)))
:status 500))))
;; Profile API Routes - TEMPORARILY COMMENTED OUT ;; Profile API Routes - TEMPORARILY COMMENTED OUT
#| #|
(define-page api-user-profile #@"/api/user/profile" () (define-page api-user-profile #@"/api/user/profile" ()

View File

@ -14,14 +14,21 @@ settings.server.telnet.set(true)
settings.server.telnet.port.set(1234) settings.server.telnet.port.set(1234)
settings.server.telnet.bind_addr.set("0.0.0.0") settings.server.telnet.bind_addr.set("0.0.0.0")
# Create request queue for dynamic track queuing
queue = request.queue(id="queue")
# Create playlist source from mounted music directory # Create playlist source from mounted music directory
# Use playlist.safe which starts playing immediately without full scan # Use playlist.safe which starts playing immediately without full scan
radio = playlist.safe( playlist_source = playlist.safe(
id="music",
mode="randomize", mode="randomize",
reload=3600, reload=3600,
"/app/music/" "/app/music/"
) )
# Combine queue and playlist - queue takes priority
radio = fallback(track_sensitive=true, [queue, playlist_source])
# Add some audio processing # Add some audio processing
radio = amplify(1.0, radio) radio = amplify(1.0, radio)
radio = normalize(radio) radio = normalize(radio)

View File

@ -15,6 +15,7 @@ NC='\033[0m' # No Color
BASE_URL="${ASTEROID_URL:-http://localhost:8080}" BASE_URL="${ASTEROID_URL:-http://localhost:8080}"
API_BASE="${BASE_URL}/api/asteroid" API_BASE="${BASE_URL}/api/asteroid"
VERBOSE="${VERBOSE:-0}" VERBOSE="${VERBOSE:-0}"
LOG_FILE="test-results-$(date +%Y%m%d-%H%M%S).log"
# Test counters # Test counters
TESTS_RUN=0 TESTS_RUN=0
@ -22,28 +23,39 @@ TESTS_PASSED=0
TESTS_FAILED=0 TESTS_FAILED=0
# Helper functions # Helper functions
log_to_file() {
# Strip ANSI color codes for log file
echo "$1" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"
}
print_header() { print_header() {
local msg="\n========================================\n$1\n========================================\n"
echo -e "\n${BLUE}========================================${NC}" echo -e "\n${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}" echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}\n" echo -e "${BLUE}========================================${NC}\n"
log_to_file "$msg"
} }
print_test() { print_test() {
echo -e "${YELLOW}TEST:${NC} $1" echo -e "${YELLOW}TEST:${NC} $1"
log_to_file "TEST: $1"
} }
print_pass() { print_pass() {
echo -e "${GREEN}✓ PASS:${NC} $1" echo -e "${GREEN}✓ PASS:${NC} $1"
log_to_file "✓ PASS: $1"
TESTS_PASSED=$((TESTS_PASSED + 1)) TESTS_PASSED=$((TESTS_PASSED + 1))
} }
print_fail() { print_fail() {
echo -e "${RED}✗ FAIL:${NC} $1" echo -e "${RED}✗ FAIL:${NC} $1"
log_to_file "✗ FAIL: $1"
TESTS_FAILED=$((TESTS_FAILED + 1)) TESTS_FAILED=$((TESTS_FAILED + 1))
} }
print_info() { print_info() {
echo -e "${BLUE}INFO:${NC} $1" echo -e "${BLUE}INFO:${NC} $1"
log_to_file "INFO: $1"
} }
# Test function wrapper # Test function wrapper
@ -212,6 +224,66 @@ test_playlist_endpoints() {
print_info "Note: Playlist creation requires authentication" print_info "Note: Playlist creation requires authentication"
} }
# Test DJ Control Endpoints (Liquidsoap telnet integration)
test_dj_control_endpoints() {
print_header "Testing DJ Control Endpoints"
print_info "Note: DJ control endpoints require admin authentication"
# Test metadata endpoint (available to authenticated users)
test_api_endpoint "/dj/metadata" \
"DJ metadata endpoint" \
"metadata"
# Test queue status endpoint
test_api_endpoint "/dj/queue-status" \
"DJ queue status endpoint" \
"queue"
# Test skip track endpoint (requires admin)
run_test "DJ skip track endpoint"
local skip_response=$(curl -s -X POST "${API_BASE}/dj/skip")
if echo "$skip_response" | grep -q "status"; then
print_pass "DJ skip track endpoint responds"
else
print_fail "DJ skip track endpoint not responding"
fi
# Test reload playlist endpoint (requires admin)
run_test "DJ reload playlist endpoint"
local reload_response=$(curl -s -X POST "${API_BASE}/dj/reload-playlist")
if echo "$reload_response" | grep -q "status"; then
print_pass "DJ reload playlist endpoint responds"
else
print_fail "DJ reload playlist endpoint not responding"
fi
# Test queue track endpoint with dynamic track ID
run_test "DJ queue track endpoint"
# Fetch a random track ID from the database
local track_response=$(curl -s "${API_BASE}/tracks?page=1&per-page=1")
# Extract track ID using grep (works without jq)
local track_id=$(echo "$track_response" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
if [ -n "$track_id" ]; then
print_info "Using track ID: $track_id for queue test"
local queue_response=$(curl -s -X POST "${API_BASE}/dj/queue?track-id=${track_id}")
if echo "$queue_response" | grep -q "queued successfully\|Track not found"; then
print_pass "DJ queue track endpoint responds (track ID: $track_id)"
else
print_fail "DJ queue track endpoint not responding properly"
if [ $VERBOSE -eq 1 ]; then
echo "Response: $queue_response"
fi
fi
else
print_info "No tracks available in database, skipping queue test"
fi
}
# Test Page Endpoints (HTML pages) # Test Page Endpoints (HTML pages)
test_page_endpoints() { test_page_endpoints() {
print_header "Testing HTML Page Endpoints" print_header "Testing HTML Page Endpoints"
@ -278,21 +350,42 @@ test_api_format() {
print_summary() { print_summary() {
print_header "Test Summary" print_header "Test Summary"
local summary="Tests Run: $TESTS_RUN\nTests Passed: $TESTS_PASSED\nTests Failed: $TESTS_FAILED"
echo "Tests Run: $TESTS_RUN" echo "Tests Run: $TESTS_RUN"
echo -e "Tests Passed: ${GREEN}$TESTS_PASSED${NC}" echo -e "Tests Passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Tests Failed: ${RED}$TESTS_FAILED${NC}" echo -e "Tests Failed: ${RED}$TESTS_FAILED${NC}"
log_to_file "$summary"
if [ $TESTS_FAILED -eq 0 ]; then if [ $TESTS_FAILED -eq 0 ]; then
echo -e "\n${GREEN}✓ All tests passed!${NC}\n" echo -e "\n${GREEN}✓ All tests passed!${NC}\n"
log_to_file "\n✓ All tests passed!\n"
echo -e "${BLUE}Log file saved to: ${LOG_FILE}${NC}\n"
exit 0 exit 0
else else
echo -e "\n${RED}✗ Some tests failed${NC}\n" echo -e "\n${RED}✗ Some tests failed${NC}\n"
log_to_file "\n✗ Some tests failed\n"
echo -e "${BLUE}Log file saved to: ${LOG_FILE}${NC}\n"
exit 1 exit 1
fi fi
} }
# Main test execution # Main test execution
main() { main() {
# Initialize log file with header
cat > "$LOG_FILE" << EOF
╔═══════════════════════════════════════════════════════════════╗
║ Asteroid Radio Server Test Suite ║
║ Test Results Log ║
╚═══════════════════════════════════════════════════════════════╝
Test Run Date: $(date '+%Y-%m-%d %H:%M:%S')
Server URL: ${BASE_URL}
API Base: ${API_BASE}
Verbose Mode: ${VERBOSE}
EOF
echo -e "${BLUE}" echo -e "${BLUE}"
echo "╔═══════════════════════════════════════╗" echo "╔═══════════════════════════════════════╗"
echo "║ Asteroid Radio Server Test Suite ║" echo "║ Asteroid Radio Server Test Suite ║"
@ -301,6 +394,7 @@ main() {
print_info "Testing server at: ${BASE_URL}" print_info "Testing server at: ${BASE_URL}"
print_info "Verbose mode: ${VERBOSE}" print_info "Verbose mode: ${VERBOSE}"
print_info "Log file: ${LOG_FILE}"
echo "" echo ""
# Run all test suites # Run all test suites
@ -310,6 +404,7 @@ main() {
test_track_endpoints test_track_endpoints
test_player_endpoints test_player_endpoints
test_playlist_endpoints test_playlist_endpoints
test_dj_control_endpoints
test_admin_endpoints test_admin_endpoints
test_page_endpoints test_page_endpoints
test_static_files test_static_files