276 lines
6.9 KiB
Go
276 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 string `json:"id"`
|
||
} `json:"track"`
|
||
}
|
||
|
||
func uploadTrack(token, filePath string) (string, error) {
|
||
file, err := os.Open(filePath)
|
||
if err != nil {
|
||
return "", 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 "", 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 "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode != http.StatusCreated {
|
||
return "", fmt.Errorf("upload failed (status %d): %s", resp.StatusCode, string(respBody))
|
||
}
|
||
|
||
var result UploadResponse
|
||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||
return "", fmt.Errorf("failed to parse upload response: %v. Body: %s", err, string(respBody))
|
||
}
|
||
|
||
if result.Track.ID == "" {
|
||
return "", fmt.Errorf("no track ID returned")
|
||
}
|
||
|
||
return result.Track.ID, nil
|
||
}
|
||
|
||
type TrackStatusResponse struct {
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
func waitForProcessing(token string, trackID string) 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/%s", 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 string) 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/%s", 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
|
||
}
|