Files
onvif-go/deviceio_test.go
0x524a 89851baa1f feat: implement Event and Device IO services with CLI integration
- Added Event and Device IO service implementations in `event.go` and `deviceio.go`.
- Created corresponding test files `event_test.go` and `deviceio_test.go` for unit testing.
- Enhanced CLI with new options for Event and Device IO operations, allowing users to interact with these services.
- Introduced example usage in `examples/test-event-deviceio/main.go` to demonstrate functionality.
- Updated golangci-lint configuration to include new files for linting checks.
2025-12-03 00:55:22 -05:00

923 lines
27 KiB
Go

package onvif
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
const testDeviceIOXMLHeader = `<?xml version="1.0" encoding="UTF-8"?>`
func newMockDeviceIOServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/soap+xml")
body := make([]byte, r.ContentLength)
_, _ = r.Body.Read(body)
bodyStr := string(body)
var response string
switch {
case strings.Contains(bodyStr, "GetServiceCapabilities") && strings.Contains(bodyStr, "deviceIO"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetServiceCapabilitiesResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:Capabilities
VideoSources="4"
VideoOutputs="2"
AudioSources="2"
AudioOutputs="2"
RelayOutputs="4"
SerialPorts="2"
DigitalInputs="8"
DigitalInputOptions="true"
SerialPortConfiguration="true"/>
</tmd:GetServiceCapabilitiesResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetDigitalInputConfigurationOptions"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetDigitalInputConfigurationOptionsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:DigitalInputConfigurationOptions>
<tmd:IdleState>open</tmd:IdleState>
<tmd:IdleState>closed</tmd:IdleState>
</tmd:DigitalInputConfigurationOptions>
</tmd:GetDigitalInputConfigurationOptionsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetDigitalInputs"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetDigitalInputsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:DigitalInputs token="input_001" IdleState="open"/>
<tmd:DigitalInputs token="input_002" IdleState="closed"/>
</tmd:GetDigitalInputsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "SetDigitalInputConfigurations"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:SetDigitalInputConfigurationsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetVideoOutputs"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetVideoOutputsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:VideoOutputs token="video_out_001">
<tmd:Layout>
<tt:Pane xmlns:tt="http://www.onvif.org/ver10/schema" Pane="main">
<tt:Area bottom="1.0" top="0.0" right="1.0" left="0.0"/>
</tt:Pane>
</tmd:Layout>
<tmd:Resolution>
<tmd:Width>1920</tmd:Width>
<tmd:Height>1080</tmd:Height>
</tmd:Resolution>
<tmd:RefreshRate>60.0</tmd:RefreshRate>
<tmd:AspectRatio>16:9</tmd:AspectRatio>
</tmd:VideoOutputs>
</tmd:GetVideoOutputsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetSerialPortConfigurationOptions"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetSerialPortConfigurationOptionsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:SerialPortConfigurationOptions token="serial_001">
<tmd:BaudRateList><tmd:Items>9600</tmd:Items><tmd:Items>19200</tmd:Items><tmd:Items>38400</tmd:Items></tmd:BaudRateList>
<tmd:ParityBitList><tmd:Items>None</tmd:Items><tmd:Items>Odd</tmd:Items><tmd:Items>Even</tmd:Items></tmd:ParityBitList>
<tmd:CharacterLengthList><tmd:Items>7</tmd:Items><tmd:Items>8</tmd:Items></tmd:CharacterLengthList>
<tmd:StopBitList><tmd:Items>1</tmd:Items><tmd:Items>2</tmd:Items></tmd:StopBitList>
</tmd:SerialPortConfigurationOptions>
</tmd:GetSerialPortConfigurationOptionsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetSerialPortConfiguration"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetSerialPortConfigurationResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:SerialPortConfiguration token="serial_001">
<tmd:Type>RS232</tmd:Type>
<tmd:BaudRate>9600</tmd:BaudRate>
<tmd:ParityBit>None</tmd:ParityBit>
<tmd:CharacterLength>8</tmd:CharacterLength>
<tmd:StopBit>1</tmd:StopBit>
</tmd:SerialPortConfiguration>
</tmd:GetSerialPortConfigurationResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetSerialPorts"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetSerialPortsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:SerialPorts token="serial_001">
<tmd:Type>RS232</tmd:Type>
</tmd:SerialPorts>
<tmd:SerialPorts token="serial_002">
<tmd:Type>RS485</tmd:Type>
</tmd:SerialPorts>
</tmd:GetSerialPortsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "SetSerialPortConfiguration"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:SetSerialPortConfigurationResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "SendReceiveSerialCommand"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:SendReceiveSerialCommandResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:SerialData>
<tt:Binary xmlns:tt="http://www.onvif.org/ver10/schema">OK</tt:Binary>
</tmd:SerialData>
</tmd:SendReceiveSerialCommandResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetVideoOutputConfigurationOptions"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetVideoOutputConfigurationOptionsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:VideoOutputConfigurationOptions>
<tmd:Name Min="1" Max="64"/>
<tmd:OutputTokensAvailable>video_out_001</tmd:OutputTokensAvailable>
<tmd:OutputTokensAvailable>video_out_002</tmd:OutputTokensAvailable>
</tmd:VideoOutputConfigurationOptions>
</tmd:GetVideoOutputConfigurationOptionsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetVideoOutputConfiguration"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetVideoOutputConfigurationResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:VideoOutputConfiguration token="config_001">
<tmd:Name>Main Output</tmd:Name>
<tmd:UseCount>2</tmd:UseCount>
<tmd:OutputToken>video_out_001</tmd:OutputToken>
</tmd:VideoOutputConfiguration>
</tmd:GetVideoOutputConfigurationResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "SetVideoOutputConfiguration"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:SetVideoOutputConfigurationResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetRelayOutputOptions"):
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tmd:GetRelayOutputOptionsResponse xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl">
<tmd:RelayOutputOptions token="relay_001">
<tmd:Mode>Monostable</tmd:Mode>
<tmd:Mode>Bistable</tmd:Mode>
<tmd:DelayTimes>PT1S</tmd:DelayTimes>
<tmd:DelayTimes>PT5S</tmd:DelayTimes>
<tmd:DelayTimes>PT10S</tmd:DelayTimes>
<tmd:Discrete>true</tmd:Discrete>
</tmd:RelayOutputOptions>
</tmd:GetRelayOutputOptionsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
default:
response = testDeviceIOXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<SOAP-ENV:Code><SOAP-ENV:Value>SOAP-ENV:Receiver</SOAP-ENV:Value></SOAP-ENV:Code>
<SOAP-ENV:Reason><SOAP-ENV:Text>Unknown action</SOAP-ENV:Text></SOAP-ENV:Reason>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
}
_, _ = w.Write([]byte(response))
}))
}
func TestGetDeviceIOServiceCapabilities(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
caps, err := client.GetDeviceIOServiceCapabilities(ctx)
if err != nil {
t.Fatalf("GetDeviceIOServiceCapabilities failed: %v", err)
}
if caps.VideoSources != 4 {
t.Errorf("Expected VideoSources to be 4, got %d", caps.VideoSources)
}
if caps.VideoOutputs != 2 {
t.Errorf("Expected VideoOutputs to be 2, got %d", caps.VideoOutputs)
}
if caps.AudioSources != 2 {
t.Errorf("Expected AudioSources to be 2, got %d", caps.AudioSources)
}
if caps.AudioOutputs != 2 {
t.Errorf("Expected AudioOutputs to be 2, got %d", caps.AudioOutputs)
}
if caps.RelayOutputs != 4 {
t.Errorf("Expected RelayOutputs to be 4, got %d", caps.RelayOutputs)
}
if caps.SerialPorts != 2 {
t.Errorf("Expected SerialPorts to be 2, got %d", caps.SerialPorts)
}
if caps.DigitalInputs != 8 {
t.Errorf("Expected DigitalInputs to be 8, got %d", caps.DigitalInputs)
}
if !caps.DigitalInputOptions {
t.Error("Expected DigitalInputOptions to be true")
}
if !caps.SerialPortConfiguration {
t.Error("Expected SerialPortConfiguration to be true")
}
}
func TestGetDigitalInputs(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
inputs, err := client.GetDigitalInputs(ctx)
if err != nil {
t.Fatalf("GetDigitalInputs failed: %v", err)
}
if len(inputs) != 2 {
t.Fatalf("Expected 2 digital inputs, got %d", len(inputs))
}
if inputs[0].Token != "input_001" {
t.Errorf("Expected first input token 'input_001', got '%s'", inputs[0].Token)
}
if inputs[0].IdleState != DigitalIdleOpen {
t.Errorf("Expected first input idle state 'open', got '%s'", inputs[0].IdleState)
}
if inputs[1].IdleState != DigitalIdleClosed {
t.Errorf("Expected second input idle state 'closed', got '%s'", inputs[1].IdleState)
}
}
func TestGetDigitalInputConfigurationOptions(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
options, err := client.GetDigitalInputConfigurationOptions(ctx, "input_001")
if err != nil {
t.Fatalf("GetDigitalInputConfigurationOptions failed: %v", err)
}
if len(options.IdleStateOptions) != 2 {
t.Errorf("Expected 2 idle state options, got %d", len(options.IdleStateOptions))
}
}
func TestGetDigitalInputConfigurationOptionsInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetDigitalInputConfigurationOptions(ctx, "")
if !errors.Is(err, ErrInvalidDigitalInputToken) {
t.Errorf("Expected ErrInvalidDigitalInputToken, got %v", err)
}
}
func TestSetDigitalInputConfigurations(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
inputs := []*DigitalInput{
{Token: "input_001", IdleState: DigitalIdleOpen},
{Token: "input_002", IdleState: DigitalIdleClosed},
}
err = client.SetDigitalInputConfigurations(ctx, inputs)
if err != nil {
t.Fatalf("SetDigitalInputConfigurations failed: %v", err)
}
}
func TestSetDigitalInputConfigurationsValidation(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test empty inputs.
err = client.SetDigitalInputConfigurations(ctx, []*DigitalInput{})
if !errors.Is(err, ErrDigitalInputConfigNil) {
t.Errorf("Expected ErrDigitalInputConfigNil, got %v", err)
}
// Test input with empty token.
inputs := []*DigitalInput{{Token: "", IdleState: DigitalIdleOpen}}
err = client.SetDigitalInputConfigurations(ctx, inputs)
if !errors.Is(err, ErrInvalidDigitalInputToken) {
t.Errorf("Expected ErrInvalidDigitalInputToken, got %v", err)
}
}
func TestGetVideoOutputs(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
outputs, err := client.GetVideoOutputs(ctx)
if err != nil {
t.Fatalf("GetVideoOutputs failed: %v", err)
}
if len(outputs) != 1 {
t.Fatalf("Expected 1 video output, got %d", len(outputs))
}
if outputs[0].Token != "video_out_001" {
t.Errorf("Expected video output token 'video_out_001', got '%s'", outputs[0].Token)
}
if outputs[0].Resolution == nil {
t.Fatal("Expected Resolution to be set")
}
if outputs[0].Resolution.Width != 1920 {
t.Errorf("Expected resolution width 1920, got %d", outputs[0].Resolution.Width)
}
if outputs[0].Resolution.Height != 1080 {
t.Errorf("Expected resolution height 1080, got %d", outputs[0].Resolution.Height)
}
if outputs[0].RefreshRate != 60.0 {
t.Errorf("Expected refresh rate 60.0, got %f", outputs[0].RefreshRate)
}
if outputs[0].AspectRatio != "16:9" {
t.Errorf("Expected aspect ratio '16:9', got '%s'", outputs[0].AspectRatio)
}
}
func TestGetSerialPorts(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
ports, err := client.GetSerialPorts(ctx)
if err != nil {
t.Fatalf("GetSerialPorts failed: %v", err)
}
if len(ports) != 2 {
t.Fatalf("Expected 2 serial ports, got %d", len(ports))
}
if ports[0].Token != "serial_001" {
t.Errorf("Expected first serial port token 'serial_001', got '%s'", ports[0].Token)
}
if ports[0].Type != SerialPortTypeRS232 {
t.Errorf("Expected first serial port type RS232, got '%s'", ports[0].Type)
}
if ports[1].Type != SerialPortTypeRS485 {
t.Errorf("Expected second serial port type RS485, got '%s'", ports[1].Type)
}
}
func TestGetSerialPortConfiguration(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
config, err := client.GetSerialPortConfiguration(ctx, "serial_001")
if err != nil {
t.Fatalf("GetSerialPortConfiguration failed: %v", err)
}
if config.Token != "serial_001" {
t.Errorf("Expected token 'serial_001', got '%s'", config.Token)
}
if config.Type != SerialPortTypeRS232 {
t.Errorf("Expected type RS232, got '%s'", config.Type)
}
if config.BaudRate != 9600 {
t.Errorf("Expected baud rate 9600, got %d", config.BaudRate)
}
if config.ParityBit != ParityNone {
t.Errorf("Expected parity None, got '%s'", config.ParityBit)
}
if config.CharacterLength != 8 {
t.Errorf("Expected character length 8, got %d", config.CharacterLength)
}
if config.StopBit != 1 {
t.Errorf("Expected stop bit 1, got %f", config.StopBit)
}
}
func TestGetSerialPortConfigurationInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetSerialPortConfiguration(ctx, "")
if !errors.Is(err, ErrInvalidSerialPortToken) {
t.Errorf("Expected ErrInvalidSerialPortToken, got %v", err)
}
}
func TestGetSerialPortConfigurationOptions(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
options, err := client.GetSerialPortConfigurationOptions(ctx, "serial_001")
if err != nil {
t.Fatalf("GetSerialPortConfigurationOptions failed: %v", err)
}
if len(options.BaudRateList) != 3 {
t.Errorf("Expected 3 baud rate options, got %d", len(options.BaudRateList))
}
if len(options.ParityBitList) != 3 {
t.Errorf("Expected 3 parity bit options, got %d", len(options.ParityBitList))
}
if len(options.CharacterLengthList) != 2 {
t.Errorf("Expected 2 character length options, got %d", len(options.CharacterLengthList))
}
if len(options.StopBitList) != 2 {
t.Errorf("Expected 2 stop bit options, got %d", len(options.StopBitList))
}
}
func TestGetSerialPortConfigurationOptionsInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetSerialPortConfigurationOptions(ctx, "")
if !errors.Is(err, ErrInvalidSerialPortToken) {
t.Errorf("Expected ErrInvalidSerialPortToken, got %v", err)
}
}
func TestSetSerialPortConfiguration(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
config := &SerialPortConfiguration{
Token: "serial_001",
Type: SerialPortTypeRS232,
BaudRate: 19200,
ParityBit: ParityNone,
CharacterLength: 8,
StopBit: 1,
}
err = client.SetSerialPortConfiguration(ctx, config)
if err != nil {
t.Fatalf("SetSerialPortConfiguration failed: %v", err)
}
}
func TestSetSerialPortConfigurationValidation(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test nil config.
err = client.SetSerialPortConfiguration(ctx, nil)
if !errors.Is(err, ErrSerialPortConfigNil) {
t.Errorf("Expected ErrSerialPortConfigNil, got %v", err)
}
// Test empty token.
config := &SerialPortConfiguration{Token: ""}
err = client.SetSerialPortConfiguration(ctx, config)
if !errors.Is(err, ErrInvalidSerialPortToken) {
t.Errorf("Expected ErrInvalidSerialPortToken, got %v", err)
}
}
func TestSendReceiveSerialCommand(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
response, err := client.SendReceiveSerialCommand(ctx, "serial_001", []byte("HELLO"), 5, 10)
if err != nil {
t.Fatalf("SendReceiveSerialCommand failed: %v", err)
}
if string(response) != "OK" {
t.Errorf("Expected response 'OK', got '%s'", string(response))
}
}
func TestSendReceiveSerialCommandValidation(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test empty token.
_, err = client.SendReceiveSerialCommand(ctx, "", []byte("HELLO"), 5, 10)
if !errors.Is(err, ErrInvalidSerialPortToken) {
t.Errorf("Expected ErrInvalidSerialPortToken, got %v", err)
}
// Test empty data.
_, err = client.SendReceiveSerialCommand(ctx, "serial_001", []byte{}, 5, 10)
if !errors.Is(err, ErrInvalidSerialData) {
t.Errorf("Expected ErrInvalidSerialData, got %v", err)
}
}
func TestDigitalIdleStateConstants(t *testing.T) {
if DigitalIdleOpen != "open" {
t.Errorf("DigitalIdleOpen should be 'open'")
}
if DigitalIdleClosed != "closed" {
t.Errorf("DigitalIdleClosed should be 'closed'")
}
}
func TestSerialPortTypeConstants(t *testing.T) {
if SerialPortTypeRS232 != "RS232" {
t.Errorf("SerialPortTypeRS232 should be 'RS232'")
}
if SerialPortTypeRS422 != "RS422" {
t.Errorf("SerialPortTypeRS422 should be 'RS422'")
}
if SerialPortTypeRS485 != "RS485" {
t.Errorf("SerialPortTypeRS485 should be 'RS485'")
}
if SerialPortTypeGeneric != "Generic" {
t.Errorf("SerialPortTypeGeneric should be 'Generic'")
}
}
func TestParityBitConstants(t *testing.T) {
if ParityNone != "None" {
t.Errorf("ParityNone should be 'None'")
}
if ParityOdd != "Odd" {
t.Errorf("ParityOdd should be 'Odd'")
}
if ParityEven != "Even" {
t.Errorf("ParityEven should be 'Even'")
}
if ParityMark != "Mark" {
t.Errorf("ParityMark should be 'Mark'")
}
if ParitySpace != "Space" {
t.Errorf("ParitySpace should be 'Space'")
}
}
func TestGetVideoOutputConfiguration(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
config, err := client.GetVideoOutputConfiguration(ctx, "video_out_001")
if err != nil {
t.Fatalf("GetVideoOutputConfiguration failed: %v", err)
}
if config.Token != "config_001" {
t.Errorf("Expected token 'config_001', got '%s'", config.Token)
}
if config.Name != "Main Output" {
t.Errorf("Expected name 'Main Output', got '%s'", config.Name)
}
if config.UseCount != 2 {
t.Errorf("Expected use count 2, got %d", config.UseCount)
}
if config.OutputToken != "video_out_001" {
t.Errorf("Expected output token 'video_out_001', got '%s'", config.OutputToken)
}
}
func TestGetVideoOutputConfigurationInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetVideoOutputConfiguration(ctx, "")
if !errors.Is(err, ErrInvalidVideoOutputToken) {
t.Errorf("Expected ErrInvalidVideoOutputToken, got %v", err)
}
}
func TestGetVideoOutputConfigurationOptions(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
options, err := client.GetVideoOutputConfigurationOptions(ctx, "video_out_001")
if err != nil {
t.Fatalf("GetVideoOutputConfigurationOptions failed: %v", err)
}
if options.Name.Min != 1 {
t.Errorf("Expected Name.Min to be 1, got %d", options.Name.Min)
}
if options.Name.Max != 64 {
t.Errorf("Expected Name.Max to be 64, got %d", options.Name.Max)
}
if len(options.OutputTokensAvailable) != 2 {
t.Errorf("Expected 2 output tokens available, got %d", len(options.OutputTokensAvailable))
}
}
func TestGetVideoOutputConfigurationOptionsInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetVideoOutputConfigurationOptions(ctx, "")
if !errors.Is(err, ErrInvalidVideoOutputToken) {
t.Errorf("Expected ErrInvalidVideoOutputToken, got %v", err)
}
}
func TestSetVideoOutputConfiguration(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
config := &VideoOutputConfiguration{
Token: "config_001",
Name: "Main Output",
UseCount: 2,
OutputToken: "video_out_001",
ForcePersistence: true,
}
err = client.SetVideoOutputConfiguration(ctx, config)
if err != nil {
t.Fatalf("SetVideoOutputConfiguration failed: %v", err)
}
}
func TestSetVideoOutputConfigurationValidation(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test nil config.
err = client.SetVideoOutputConfiguration(ctx, nil)
if !errors.Is(err, ErrVideoOutputConfigNil) {
t.Errorf("Expected ErrVideoOutputConfigNil, got %v", err)
}
// Test empty token.
config := &VideoOutputConfiguration{Token: ""}
err = client.SetVideoOutputConfiguration(ctx, config)
if !errors.Is(err, ErrInvalidVideoOutputToken) {
t.Errorf("Expected ErrInvalidVideoOutputToken, got %v", err)
}
}
func TestGetRelayOutputOptions(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
options, err := client.GetRelayOutputOptions(ctx, "relay_001")
if err != nil {
t.Fatalf("GetRelayOutputOptions failed: %v", err)
}
if options.Token != "relay_001" {
t.Errorf("Expected token 'relay_001', got '%s'", options.Token)
}
if len(options.Mode) != 2 {
t.Errorf("Expected 2 modes, got %d", len(options.Mode))
}
if options.Mode[0] != RelayModeMonostable {
t.Errorf("Expected first mode to be Monostable, got '%s'", options.Mode[0])
}
if options.Mode[1] != RelayModeBistable {
t.Errorf("Expected second mode to be Bistable, got '%s'", options.Mode[1])
}
if len(options.DelayTimes) != 3 {
t.Errorf("Expected 3 delay times, got %d", len(options.DelayTimes))
}
if !options.Discrete {
t.Error("Expected Discrete to be true")
}
}
func TestGetRelayOutputOptionsInvalidToken(t *testing.T) {
server := newMockDeviceIOServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
_, err = client.GetRelayOutputOptions(ctx, "")
if !errors.Is(err, ErrInvalidRelayOutputToken) {
t.Errorf("Expected ErrInvalidRelayOutputToken, got %v", err)
}
}