mirror of
https://github.com/Warky-Devs/nvr-notify-api.git
synced 2026-01-30 12:44:27 +00:00
Test case
This commit is contained in:
232
cmd/client/batch/main.go
Normal file
232
cmd/client/batch/main.go
Normal file
@@ -0,0 +1,232 @@
|
||||
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
|
||||
}
|
||||
88
cmd/client/batch/test_scenarion.json
Normal file
88
cmd/client/batch/test_scenarion.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "Comprehensive Vivotek NVR Event Test",
|
||||
"description": "Tests various event types and scenarios for the Vivotek NVR API",
|
||||
"events": [
|
||||
{
|
||||
"eventType": "MotionDetection",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera01",
|
||||
"delaySeconds": 0,
|
||||
"eventDetails": {
|
||||
"zoneId": "MainEntrance",
|
||||
"confidence": 95
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "MotionDetection",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera02",
|
||||
"delaySeconds": 1,
|
||||
"eventDetails": {
|
||||
"zoneId": "BackYard",
|
||||
"confidence": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "VideoLoss",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera03",
|
||||
"delaySeconds": 2,
|
||||
"eventDetails": {
|
||||
"duration": 15,
|
||||
"cause": "cable disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "DeviceConnection",
|
||||
"deviceId": "NVR002",
|
||||
"channelId": "",
|
||||
"delaySeconds": 3,
|
||||
"eventDetails": {
|
||||
"status": "disconnected",
|
||||
"reason": "network failure"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "DeviceConnection",
|
||||
"deviceId": "NVR002",
|
||||
"channelId": "",
|
||||
"delaySeconds": 5,
|
||||
"eventDetails": {
|
||||
"status": "connected",
|
||||
"reason": "network restored"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "MotionDetection",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera01",
|
||||
"delaySeconds": 2,
|
||||
"eventDetails": {
|
||||
"zoneId": "MainEntrance",
|
||||
"confidence": 98,
|
||||
"objectType": "person"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "TamperDetection",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera04",
|
||||
"delaySeconds": 3,
|
||||
"eventDetails": {
|
||||
"type": "covered",
|
||||
"severity": "high"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventType": "StorageFailure",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "",
|
||||
"delaySeconds": 2,
|
||||
"eventDetails": {
|
||||
"disk": "HDD1",
|
||||
"errorCode": "S-404",
|
||||
"remainingSpace": "50MB"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
133
cmd/client/test.readme.md
Normal file
133
cmd/client/test.readme.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Vivotek NVR API Test Client
|
||||
|
||||
This repository contains two test clients for the Vivotek NVR Event Handler API:
|
||||
|
||||
1. **Single Event Test Client** (`vivotek-test-client.go`) - For sending individual test events with customizable parameters
|
||||
2. **Batch Test Client** (`vivotek-test-batch.go`) - For running complex test scenarios defined in JSON
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.18 or higher
|
||||
- Network access to your Vivotek NVR API server
|
||||
|
||||
## Single Event Test Client
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
go build -o vivotek-test vivotek-test-client.go
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
./vivotek-test [options]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `-url` | API server URL | `http://localhost:8080/event` |
|
||||
| `-type` | Event type | `MotionDetection` |
|
||||
| `-device` | Device ID | `NVR12345` |
|
||||
| `-channel` | Channel ID | `Camera01` |
|
||||
| `-zone` | Detection zone (for motion events) | `Zone1` |
|
||||
| `-user` | Basic auth username | `""` |
|
||||
| `-pass` | Basic auth password | `""` |
|
||||
| `-insecure` | Skip TLS verification | `false` |
|
||||
| `-repeat` | Number of events to send | `1` |
|
||||
| `-interval` | Interval between events in seconds | `5` |
|
||||
|
||||
### Examples
|
||||
|
||||
Test a motion detection event:
|
||||
```bash
|
||||
./vivotek-test -type=MotionDetection -device=NVR001 -channel=Camera01 -zone=FrontDoor
|
||||
```
|
||||
|
||||
Test video loss with authentication:
|
||||
```bash
|
||||
./vivotek-test -type=VideoLoss -device=NVR001 -channel=Camera02 -user=admin -pass=password
|
||||
```
|
||||
|
||||
Generate multiple events:
|
||||
```bash
|
||||
./vivotek-test -type=DeviceConnection -device=NVR002 -repeat=5 -interval=10
|
||||
```
|
||||
|
||||
## Batch Test Client
|
||||
|
||||
The batch test client allows you to define complex test scenarios in a JSON file and execute them with a single command.
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
go build -o vivotek-batch vivotek-test-batch.go
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
./vivotek-batch [options]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `-url` | API server URL | `http://localhost:8080/event` |
|
||||
| `-user` | Basic auth username | `""` |
|
||||
| `-pass` | Basic auth password | `""` |
|
||||
| `-concurrency` | Number of concurrent requests | `1` |
|
||||
| `-scenario` | JSON file with test scenarios | `test_scenario.json` |
|
||||
| `-output` | Output results to results.json | `false` |
|
||||
|
||||
### Test Scenario Format
|
||||
|
||||
The test scenario file uses the following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Test Scenario Name",
|
||||
"description": "Description of the test scenario",
|
||||
"events": [
|
||||
{
|
||||
"eventType": "MotionDetection",
|
||||
"deviceId": "NVR001",
|
||||
"channelId": "Camera01",
|
||||
"delaySeconds": 0,
|
||||
"eventDetails": {
|
||||
"zoneId": "Zone1",
|
||||
"confidence": 95
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Run the default test scenario:
|
||||
```bash
|
||||
./vivotek-batch
|
||||
```
|
||||
|
||||
Run a custom scenario with authentication:
|
||||
```bash
|
||||
./vivotek-batch -scenario=my_scenario.json -user=admin -pass=password
|
||||
```
|
||||
|
||||
Run with multiple concurrent requests:
|
||||
```bash
|
||||
./vivotek-batch -concurrency=5 -output
|
||||
```
|
||||
|
||||
## Tips for Testing
|
||||
|
||||
1. **Start with Single Events**: Use the single event client first to verify basic connectivity
|
||||
2. **Check Server Logs**: Monitor the server logs while running tests to see how events are being processed
|
||||
3. **Verify Telegram**: If you've configured Telegram notifications, check that they're being sent
|
||||
4. **Increase Load Gradually**: When testing performance, start with low concurrency and gradually increase
|
||||
5. **Custom Scenarios**: Create different scenarios for different testing purposes (basic functionality, stress testing, etc.)
|
||||
190
cmd/client/test/main.go
Normal file
190
cmd/client/test/main.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Command line flags
|
||||
var (
|
||||
serverURL string
|
||||
eventType string
|
||||
deviceID string
|
||||
channelID string
|
||||
zone string
|
||||
username string
|
||||
password string
|
||||
insecure bool
|
||||
repeatCount int
|
||||
interval int
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Define command line flags
|
||||
flag.StringVar(&serverURL, "url", "http://localhost:8080/event", "API server URL")
|
||||
flag.StringVar(&eventType, "type", "MotionDetection", "Event type (MotionDetection, VideoLoss, DeviceConnection)")
|
||||
flag.StringVar(&deviceID, "device", "NVR12345", "Device ID")
|
||||
flag.StringVar(&channelID, "channel", "Camera01", "Channel ID")
|
||||
flag.StringVar(&zone, "zone", "Zone1", "Detection zone (for motion events)")
|
||||
flag.StringVar(&username, "user", "", "Basic auth username")
|
||||
flag.StringVar(&password, "pass", "", "Basic auth password")
|
||||
flag.BoolVar(&insecure, "insecure", false, "Skip TLS verification")
|
||||
flag.IntVar(&repeatCount, "repeat", 1, "Number of events to send")
|
||||
flag.IntVar(&interval, "interval", 5, "Interval between events in seconds")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Print client configuration
|
||||
fmt.Println("Vivotek API Test Client")
|
||||
fmt.Println("=======================")
|
||||
fmt.Printf("Server URL: %s\n", serverURL)
|
||||
fmt.Printf("Event Type: %s\n", eventType)
|
||||
fmt.Printf("Device ID: %s\n", deviceID)
|
||||
fmt.Printf("Channel ID: %s\n", channelID)
|
||||
if eventType == "MotionDetection" {
|
||||
fmt.Printf("Zone: %s\n", zone)
|
||||
}
|
||||
fmt.Printf("Auth: %v\n", username != "")
|
||||
fmt.Printf("Sending %d events with %d second intervals\n", repeatCount, interval)
|
||||
fmt.Println("=======================")
|
||||
|
||||
// Configure HTTP client with optional TLS settings
|
||||
httpClient := &http.Client{}
|
||||
if insecure {
|
||||
httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
|
||||
// Send events
|
||||
for i := 1; i <= repeatCount; i++ {
|
||||
if i > 1 {
|
||||
fmt.Printf("Waiting %d seconds...\n", interval)
|
||||
time.Sleep(time.Duration(interval) * time.Second)
|
||||
}
|
||||
|
||||
fmt.Printf("Sending event %d of %d\n", i, repeatCount)
|
||||
err := sendEvent(httpClient)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to send event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("All events sent successfully!")
|
||||
}
|
||||
|
||||
func sendEvent(client *http.Client) error {
|
||||
// Create event details based on event type
|
||||
eventDetails := make(map[string]interface{})
|
||||
|
||||
switch eventType {
|
||||
case "MotionDetection":
|
||||
eventDetails["zoneId"] = zone
|
||||
eventDetails["confidence"] = 85
|
||||
case "VideoLoss":
|
||||
eventDetails["duration"] = 30
|
||||
eventDetails["cause"] = "cable disconnected"
|
||||
case "DeviceConnection":
|
||||
eventDetails["status"] = "disconnected"
|
||||
eventDetails["reason"] = "network failure"
|
||||
}
|
||||
|
||||
// Create the event payload
|
||||
event := VivotekEvent{
|
||||
EventType: eventType,
|
||||
EventTime: time.Now(),
|
||||
DeviceID: deviceID,
|
||||
ChannelID: channelID,
|
||||
EventDetails: eventDetails,
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
payload, err := json.MarshalIndent(event, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating JSON payload: %v", err)
|
||||
}
|
||||
|
||||
// Print the payload for debugging
|
||||
fmt.Println("Event payload:")
|
||||
fmt.Println(string(payload))
|
||||
|
||||
// Create the request
|
||||
req, err := http.NewRequest("POST", serverURL, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the response
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response: %v", err)
|
||||
}
|
||||
|
||||
// Check the status code
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("server returned error: %d - %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
// Print the response
|
||||
fmt.Printf("Response status: %d\n", resp.StatusCode)
|
||||
fmt.Printf("Response: %s\n", string(respBody))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventGenerator returns a function that creates custom events
|
||||
func EventGenerator() func(string, string, string) VivotekEvent {
|
||||
return func(eventType, deviceID, channelID string) VivotekEvent {
|
||||
eventDetails := make(map[string]interface{})
|
||||
switch eventType {
|
||||
case "MotionDetection":
|
||||
eventDetails["zoneId"] = "Zone1"
|
||||
eventDetails["confidence"] = 85
|
||||
case "VideoLoss":
|
||||
eventDetails["duration"] = 30
|
||||
case "DeviceConnection":
|
||||
eventDetails["status"] = "connected"
|
||||
}
|
||||
|
||||
return VivotekEvent{
|
||||
EventType: eventType,
|
||||
EventTime: time.Now(),
|
||||
DeviceID: deviceID,
|
||||
ChannelID: channelID,
|
||||
EventDetails: eventDetails,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user