277 lines
6.9 KiB
Go
277 lines
6.9 KiB
Go
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"io"
|
|||
|
|
"mime/multipart"
|
|||
|
|
"net/http"
|
|||
|
|
"os"
|
|||
|
|
"path/filepath"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Configuration
|
|||
|
|
var (
|
|||
|
|
baseURL = getEnv("API_URL", "http://localhost:8080/api/v1")
|
|||
|
|
testEmail = getEnv("TEST_EMAIL", "test@veza.local")
|
|||
|
|
testPassword = getEnv("TEST_PASSWORD", "TestPassword123!")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func main() {
|
|||
|
|
fmt.Println("🚀 Starting Veza Smoke Tests...")
|
|||
|
|
fmt.Printf("Target: %s\n", baseURL)
|
|||
|
|
|
|||
|
|
// 1. Authentication
|
|||
|
|
fmt.Println("\n🔐 Authenticating...")
|
|||
|
|
|
|||
|
|
token, err := authenticate()
|
|||
|
|
if err != nil {
|
|||
|
|
fatal("Authentication failed: %v", err)
|
|||
|
|
}
|
|||
|
|
fmt.Println("✅ Authentication successful")
|
|||
|
|
|
|||
|
|
// 2. Create Dummy Audio File
|
|||
|
|
fmt.Println("\n🎵 Creating dummy audio file...")
|
|||
|
|
filePath, err := createDummyAudioFile()
|
|||
|
|
if err != nil {
|
|||
|
|
fatal("Failed to create dummy file: %v", err)
|
|||
|
|
}
|
|||
|
|
defer os.Remove(filePath)
|
|||
|
|
fmt.Printf("✅ Created dummy audio file: %s\n", filePath)
|
|||
|
|
|
|||
|
|
// 3. Upload Track
|
|||
|
|
fmt.Println("\n⬆️ Uploading track...")
|
|||
|
|
|
|||
|
|
trackID, err := uploadTrack(token, filePath)
|
|||
|
|
if err != nil {
|
|||
|
|
fatal("Track upload failed: %v", err)
|
|||
|
|
}
|
|||
|
|
fmt.Printf("✅ Track uploaded successfully (ID: %v)\n", trackID)
|
|||
|
|
|
|||
|
|
// 4. Verify Webhook / Status (Polling)
|
|||
|
|
fmt.Println("\n🔄 Waiting for processing...")
|
|||
|
|
err = waitForProcessing(token, trackID)
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Printf("⚠️ Processing warning: %v (might be expected if workers are not running)\n", err)
|
|||
|
|
} else {
|
|||
|
|
fmt.Println("✅ Track processed")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. Playback Verification (Metadata check)
|
|||
|
|
fmt.Println("\n▶️ Verifying playback accessibility...")
|
|||
|
|
err = verifyPlayback(token, trackID)
|
|||
|
|
if err != nil {
|
|||
|
|
fatal("Playback verification failed: %v", err)
|
|||
|
|
}
|
|||
|
|
fmt.Println("✅ Playback metadata accessible")
|
|||
|
|
|
|||
|
|
fmt.Println("\n🎉 All smoke tests passed!")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func getEnv(key, fallback string) string {
|
|||
|
|
if value, ok := os.LookupEnv(key);
|
|||
|
|
ok {
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
return fallback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func fatal(format string, args ...interface{}) {
|
|||
|
|
fmt.Printf("❌ "+format+"\n", args...)
|
|||
|
|
os.Exit(1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type LoginResponse struct {
|
|||
|
|
AccessToken string `json:"access_token"`
|
|||
|
|
RefreshToken string `json:"refresh_token"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func authenticate() (string, error) {
|
|||
|
|
jsonData := map[string]interface{}{
|
|||
|
|
"email": testEmail,
|
|||
|
|
"password": testPassword,
|
|||
|
|
"remember_me": false,
|
|||
|
|
}
|
|||
|
|
jsonBytes, _ := json.Marshal(jsonData)
|
|||
|
|
|
|||
|
|
req, err := http.NewRequest("POST", baseURL+"/auth/login", bytes.NewBuffer(jsonBytes))
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
req.Header.Set("Content-Type", "application/json")
|
|||
|
|
|
|||
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|||
|
|
resp, err := client.Do(req)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
if resp.StatusCode != http.StatusOK {
|
|||
|
|
body, _ := io.ReadAll(resp.Body)
|
|||
|
|
return "", fmt.Errorf("login failed (status %d): %s", resp.StatusCode, string(body))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result LoginResponse
|
|||
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if result.AccessToken == "" {
|
|||
|
|
return "", fmt.Errorf("no access token received")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result.AccessToken, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func createDummyAudioFile() (string, error) {
|
|||
|
|
// Create a 1-second silent wav file or just random bytes pretending to be mp3
|
|||
|
|
// For smoke test validation, random bytes might be rejected if validation is strict (magic numbers).
|
|||
|
|
// Let's create a file with MP3 magic number if possible, or just use text and hope validation is loose enough for this test,
|
|||
|
|
// OR actually generate a valid minimal mp3 header.
|
|||
|
|
// MP3 magic number: ID3 or FF FB.
|
|||
|
|
|
|||
|
|
// Minimal fake MP3
|
|||
|
|
filename := "smoke_test_track.mp3"
|
|||
|
|
f, err := os.Create(filename)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", err
|
|||
|
|
}
|
|||
|
|
defer f.Close()
|
|||
|
|
|
|||
|
|
// ID3 header + junk
|
|||
|
|
f.Write([]byte("ID3\x03\x00\x00\x00\x00\x00\x0a")) // Fake ID3 tag
|
|||
|
|
f.Write(make([]byte, 1024*10)) // 10KB of junk
|
|||
|
|
|
|||
|
|
return filename, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type UploadResponse struct {
|
|||
|
|
Message string `json:"message"`
|
|||
|
|
Track struct {
|
|||
|
|
ID int64 `json:"id"`
|
|||
|
|
} `json:"track"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func uploadTrack(token, filePath string) (int64, error) {
|
|||
|
|
file, err := os.Open(filePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
defer file.Close()
|
|||
|
|
|
|||
|
|
body := &bytes.Buffer{}
|
|||
|
|
writer := multipart.NewWriter(body)
|
|||
|
|
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
|
|||
|
|
if err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
io.Copy(part, file)
|
|||
|
|
writer.Close()
|
|||
|
|
|
|||
|
|
req, err := http.NewRequest("POST", baseURL+"/tracks/upload", body)
|
|||
|
|
if err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|||
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|||
|
|
|
|||
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|||
|
|
resp, err := client.Do(req)
|
|||
|
|
if err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|||
|
|
|
|||
|
|
if resp.StatusCode != http.StatusCreated {
|
|||
|
|
return 0, fmt.Errorf("upload failed (status %d): %s", resp.StatusCode, string(respBody))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result UploadResponse
|
|||
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|||
|
|
return 0, fmt.Errorf("failed to parse upload response: %v. Body: %s", err, string(respBody))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if result.Track.ID == 0 {
|
|||
|
|
return 0, fmt.Errorf("no track ID returned")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result.Track.ID, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type TrackStatusResponse struct {
|
|||
|
|
Status string `json:"status"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func waitForProcessing(token string, trackID int64) error {
|
|||
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|||
|
|
maxRetries := 5 // Short wait
|
|||
|
|
|
|||
|
|
for i := 0; i < maxRetries; i++ {
|
|||
|
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%d", baseURL, trackID), nil)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|||
|
|
|
|||
|
|
resp, err := client.Do(req)
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Printf(" Attempt %d: Error %v\n", i+1, err)
|
|||
|
|
time.Sleep(1 * time.Second)
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
if resp.StatusCode == 200 {
|
|||
|
|
// Parsing status
|
|||
|
|
// Assuming GET /tracks/:id returns the track object with a 'status' field
|
|||
|
|
var track map[string]interface{}
|
|||
|
|
json.NewDecoder(resp.Body).Decode(&track)
|
|||
|
|
status, _ := track["status"].(string)
|
|||
|
|
fmt.Printf(" Attempt %d: Status = %s\n", i+1, status)
|
|||
|
|
|
|||
|
|
if status == "ready" || status == "processing" || status == "uploading" {
|
|||
|
|
// processing/uploading is acceptable for "Upload successful" smoke test
|
|||
|
|
// ready means transcoding finished
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
fmt.Printf(" Attempt %d: Status Code %d\n", i+1, resp.StatusCode)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
time.Sleep(1 * time.Second)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return fmt.Errorf("track did not reach stable state")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func verifyPlayback(token string, trackID int64) error {
|
|||
|
|
// Check if we can get the track details.
|
|||
|
|
// The actual stream URL might be in the track details or a separate endpoint.
|
|||
|
|
// Based on API Spec: GET /tracks/:id
|
|||
|
|
|
|||
|
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%d", baseURL, trackID), nil)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|||
|
|
|
|||
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|||
|
|
resp, err := client.Do(req)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
if resp.StatusCode != 200 {
|
|||
|
|
return fmt.Errorf("failed to get track details (status %d)", resp.StatusCode)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|