mirror of
https://github.com/Warky-Devs/nvr-notify-api.git
synced 2025-05-18 23:27:32 +00:00
233 lines
6.1 KiB
Go
233 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Command line flags
|
|
var (
|
|
serverURL string
|
|
username string
|
|
password string
|
|
concurrency int
|
|
scenarioFile string
|
|
outputResults bool
|
|
)
|
|
|
|
// TestScenario represents a collection of test events to send
|
|
type TestScenario struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Events []EventConfig `json:"events"`
|
|
}
|
|
|
|
// EventConfig represents a single event configuration
|
|
type EventConfig struct {
|
|
EventType string `json:"eventType"`
|
|
DeviceID string `json:"deviceId"`
|
|
ChannelID string `json:"channelId"`
|
|
DelaySeconds int `json:"delaySeconds"`
|
|
EventDetails map[string]interface{} `json:"eventDetails"`
|
|
}
|
|
|
|
// VivotekEvent matches the structure expected by the API
|
|
type VivotekEvent struct {
|
|
EventType string `json:"eventType"`
|
|
EventTime time.Time `json:"eventTime"`
|
|
DeviceID string `json:"deviceId"`
|
|
ChannelID string `json:"channelId"`
|
|
EventDetails map[string]interface{} `json:"eventDetails"`
|
|
}
|
|
|
|
// Result tracks the outcome of sending an event
|
|
type Result struct {
|
|
Event EventConfig `json:"event"`
|
|
StatusCode int `json:"statusCode"`
|
|
Response string `json:"response"`
|
|
Error string `json:"error,omitempty"`
|
|
Duration int64 `json:"durationMs"`
|
|
}
|
|
|
|
func init() {
|
|
// Define command line flags
|
|
flag.StringVar(&serverURL, "url", "http://localhost:8080/event", "API server URL")
|
|
flag.StringVar(&username, "user", "", "Basic auth username")
|
|
flag.StringVar(&password, "pass", "", "Basic auth password")
|
|
flag.IntVar(&concurrency, "concurrency", 1, "Number of concurrent requests")
|
|
flag.StringVar(&scenarioFile, "scenario", "test_scenario.json", "JSON file with test scenarios")
|
|
flag.BoolVar(&outputResults, "output", false, "Output results to results.json")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
// Load test scenario
|
|
scenario, err := loadScenario(scenarioFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load scenario: %v", err)
|
|
}
|
|
|
|
fmt.Printf("Running scenario: %s\n", scenario.Name)
|
|
fmt.Printf("Description: %s\n", scenario.Description)
|
|
fmt.Printf("Events: %d\n", len(scenario.Events))
|
|
fmt.Printf("Concurrency: %d\n", concurrency)
|
|
fmt.Println("=======================")
|
|
|
|
// Create a channel to hold the work and results
|
|
jobs := make(chan EventConfig, len(scenario.Events))
|
|
results := make(chan Result, len(scenario.Events))
|
|
|
|
// Start worker pool
|
|
var wg sync.WaitGroup
|
|
for w := 1; w <= concurrency; w++ {
|
|
wg.Add(1)
|
|
go worker(w, jobs, results, &wg)
|
|
}
|
|
|
|
// Add jobs to the queue
|
|
for _, event := range scenario.Events {
|
|
jobs <- event
|
|
}
|
|
close(jobs) // Close the jobs channel when all jobs are added
|
|
|
|
// Wait for all workers to finish in a separate goroutine
|
|
go func() {
|
|
wg.Wait()
|
|
close(results) // Close results when all workers are done
|
|
}()
|
|
|
|
// Collect results
|
|
var allResults []Result
|
|
for result := range results {
|
|
allResults = append(allResults, result)
|
|
if result.Error != "" {
|
|
fmt.Printf("❌ Error sending %s event: %s\n", result.Event.EventType, result.Error)
|
|
} else {
|
|
fmt.Printf("✅ Sent %s event to %s: Status %d (%dms)\n",
|
|
result.Event.EventType,
|
|
result.Event.DeviceID,
|
|
result.StatusCode,
|
|
result.Duration)
|
|
}
|
|
}
|
|
|
|
// Output results if requested
|
|
if outputResults && len(allResults) > 0 {
|
|
resultsJSON, err := json.MarshalIndent(allResults, "", " ")
|
|
if err != nil {
|
|
log.Printf("Failed to marshal results: %v", err)
|
|
} else {
|
|
err = os.WriteFile("results.json", resultsJSON, 0644)
|
|
if err != nil {
|
|
log.Printf("Failed to write results file: %v", err)
|
|
} else {
|
|
fmt.Println("Results written to results.json")
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println("=======================")
|
|
fmt.Printf("Test scenario completed: %d events sent\n", len(allResults))
|
|
}
|
|
|
|
// worker processes jobs from the jobs channel
|
|
func worker(id int, jobs <-chan EventConfig, results chan<- Result, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
for j := range jobs {
|
|
// Apply configured delay
|
|
if j.DelaySeconds > 0 {
|
|
time.Sleep(time.Duration(j.DelaySeconds) * time.Second)
|
|
}
|
|
|
|
// Send the event
|
|
result := sendEvent(j)
|
|
results <- result
|
|
}
|
|
}
|
|
|
|
// sendEvent sends a single event to the API
|
|
func sendEvent(config EventConfig) Result {
|
|
startTime := time.Now()
|
|
result := Result{
|
|
Event: config,
|
|
}
|
|
|
|
// Create the event
|
|
event := VivotekEvent{
|
|
EventType: config.EventType,
|
|
EventTime: time.Now(),
|
|
DeviceID: config.DeviceID,
|
|
ChannelID: config.ChannelID,
|
|
EventDetails: config.EventDetails,
|
|
}
|
|
|
|
// Marshal to JSON
|
|
payload, err := json.Marshal(event)
|
|
if err != nil {
|
|
result.Error = fmt.Sprintf("error creating JSON payload: %v", err)
|
|
return result
|
|
}
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("POST", serverURL, bytes.NewBuffer(payload))
|
|
if err != nil {
|
|
result.Error = fmt.Sprintf("error creating request: %v", err)
|
|
return result
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Add basic auth if credentials were provided
|
|
if username != "" {
|
|
req.SetBasicAuth(username, password)
|
|
}
|
|
|
|
// Send the request
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
result.Error = fmt.Sprintf("error sending request: %v", err)
|
|
return result
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Record status code
|
|
result.StatusCode = resp.StatusCode
|
|
result.Duration = time.Since(startTime).Milliseconds()
|
|
|
|
// Record error for non-2xx responses
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
result.Error = fmt.Sprintf("server returned status code %d", resp.StatusCode)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// loadScenario loads a test scenario from a JSON file
|
|
func loadScenario(filename string) (*TestScenario, error) {
|
|
file, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading scenario file: %v", err)
|
|
}
|
|
|
|
var scenario TestScenario
|
|
err = json.Unmarshal(file, &scenario)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing scenario file: %v", err)
|
|
}
|
|
|
|
return &scenario, nil
|
|
}
|