Add unit tests and functional test in CI

* Unit tests Discover 90%

The NmapRun function needs a refacto to make it use adaptors instead of directly calling exec.Command, exec.Command.StdoutPipe, exec.Command.Start, bufio.Scanner.Scan and bufio.Scanner.Err
It makes me uncomfortable to push a test file that covers only 90%, but it's better than none, and the 10 missing %s are not very error-prone so it should be okay to delay this part a bit. For now it's more urgent to test as much of the code as possible

* Unit tests Helpers 100%

* Unit tests Loaders 100% - Attack 85%

Once again, the Attack functions are not as simple as the rest to unit test, so I will refacto all of this to use a CURL adaptor later, but for now the total is of 88.6% of coverage, which is good enough for something I spent 2 hours on

* Add testing to CI validation process

* CI now does functional testing with RTSPATT

* Change travis language to bash
This commit is contained in:
Brendan LE GLAUNEC
2017-09-29 19:22:31 +02:00
committed by Brendan Le Glaunec
parent be63c6a231
commit cb74761675
8 changed files with 1235 additions and 13 deletions
+21 -2
View File
@@ -1,4 +1,4 @@
language: generic
language: bash
sudo: required
dist: trusty
@@ -11,7 +11,26 @@ install:
- docker build -t cameradar .
script:
- docker run cameradar
- go get github.com/andelf/go-curl
- go get github.com/pkg/errors
- go get gopkg.in/go-playground/validator.v9
- go get github.com/stretchr/testify/assert
# Run unit tests
- go test
# Launch a fake camera to check if cameradar is able to access it
- docker run -d --name=fake_camera -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -p 8554:8554 ullaakut/rtspatt
# Launch cameradar on the local machine
- docker run --net=host -t cameradar -t 0.0.0.0 -l > logs.txt
- docker logs fake_camera > camera_logs.txt
# Stop the fake camera
- docker stop fake_camera
# Print logs
- cat camera_logs.txt
- cat logs.txt
# check if file contains more than one line
# 1 line: Error message because no streams were found
# More lines: Logs for all found cameras
- if [[ $(wc -l <logs.txt) -lt 2 ]]; then exit 1; fi
after_success:
- echo "Test Success - Branch($TRAVIS_BRANCH) Pull Request($TRAVIS_PULL_REQUEST) Tag($TRAVIS_TAG)"
+2 -2
View File
@@ -190,7 +190,7 @@ func AttackCredentials(targets []Stream, credentials Credentials, timeout time.D
}
}
if found == 0 {
return targets, errors.New("No credentials found")
return targets, errors.New("no credentials found")
}
return targets, nil
@@ -225,7 +225,7 @@ func AttackRoute(targets []Stream, routes Routes, timeout time.Duration, log boo
}
}
if found == 0 {
return targets, errors.New("No routes found")
return targets, errors.New("no routes found")
}
return targets, nil
+175
View File
@@ -0,0 +1,175 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmrdr
import (
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// Again, since these tests use the curl library, I don't want to spend ages trying to mock
// the lib right now.
func TestAttackCredentials(t *testing.T) {
validStream1 := Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
}
validStream2 := Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
}
fakeTargets := []Stream{validStream1, validStream2}
fakeCredentials := Credentials{
Usernames: []string{"admin", "root"},
Passwords: []string{"12345", "root"},
}
vectors := []struct {
targets []Stream
credentials Credentials
timeout time.Duration
log bool
expectedStreams []Stream
expectedErrMsg string
}{
// Valid baseline
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
log: true,
expectedStreams: fakeTargets,
},
// Valid baseline without logs
{
targets: fakeTargets,
credentials: fakeCredentials,
timeout: 1 * time.Millisecond,
log: false,
expectedStreams: fakeTargets,
},
// TODO: Refacto and make tests with all possible error cases
}
for i, vector := range vectors {
results, err := AttackCredentials(vector.targets, vector.credentials, vector.timeout, vector.log)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error in AttackCredentials test, iteration %d: %v\n", i, err)
os.Exit(1)
}
for _, stream := range vector.expectedStreams {
foundStream := false
for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
foundStream = true
}
}
assert.Equal(t, true, foundStream, "wrong streams parsed")
}
}
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
}
}
func TestAttackRoute(t *testing.T) {
validStream1 := Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
}
validStream2 := Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
}
fakeTargets := []Stream{validStream1, validStream2}
fakeRoutes := Routes{"live.sdp", "media.amp"}
vectors := []struct {
targets []Stream
routes Routes
timeout time.Duration
log bool
expectedStreams []Stream
expectedErrMsg string
}{
// Valid baseline
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
log: true,
expectedStreams: fakeTargets,
},
// Valid baseline without logs
{
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
log: false,
expectedStreams: fakeTargets,
},
// TODO: Refacto and make tests with all possible error cases
}
for i, vector := range vectors {
results, err := AttackRoute(vector.targets, vector.routes, vector.timeout, vector.log)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error in AttackRoute test, iteration %d: %v\n", i, err)
os.Exit(1)
}
for _, stream := range vector.expectedStreams {
foundStream := false
for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
foundStream = true
}
}
assert.Equal(t, true, foundStream, "wrong streams parsed")
}
}
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
}
}
+16 -8
View File
@@ -17,7 +17,6 @@ import (
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"os/exec"
"github.com/pkg/errors"
@@ -27,7 +26,7 @@ import (
// These constants detail the different level of nmap speed presets
// that determine the timeout values and wether or not nmap makes use of parallelism
const (
// PARANOID NO PARALLELISM | 5min timeout | 100ms to 10s round-trip time timeout | 5mn scan delay
// PARANOIAC NO PARALLELISM | 5min timeout | 100ms to 10s round-trip time timeout | 5mn scan delay
PARANOIAC = 0
// SNEAKY NO PARALLELISM | 15sec timeout | 100ms to 10s round-trip time timeout | 15s scan delay
SNEAKY = 1
@@ -41,10 +40,19 @@ const (
INSANE = 5
)
// Allows unit tests to override the exec function to avoid launching a real command
// during the tests. The NmapRun method will soon be refactored with an adaptor in order
// to make it possible to mock all external calls.
var execCommand = exec.Command
// NmapRun runs nmap on the specified targets's specified ports, using the given nmap speed.
func NmapRun(targets, ports, resultFilePath string, nmapSpeed int, enableLogs bool) error {
if nmapSpeed < PARANOIAC || nmapSpeed > INSANE {
return fmt.Errorf("invalid nmap speed value '%d'. Should be between '%d' and '%d'", nmapSpeed, PARANOIAC, INSANE)
}
// Prepare nmap command
cmd := exec.Command(
cmd := execCommand(
"nmap",
fmt.Sprintf("-T%d", nmapSpeed),
"-A",
@@ -58,24 +66,24 @@ func NmapRun(targets, ports, resultFilePath string, nmapSpeed int, enableLogs bo
// Pipe stdout to be able to write the logs in realtime
stdout, err := cmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "Couldn't get stdout pipe")
return errors.Wrap(err, "couldn't get stdout pipe")
}
// Execute the nmap command
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "Coudln't run nmap command")
return errors.Wrap(err, "coudln't run nmap command")
}
// Scan the pipe until an end of file or an error occurs
in := bufio.NewScanner(stdout)
for in.Scan() {
if enableLogs {
log.Printf(in.Text())
fmt.Printf(in.Text())
}
}
if err := in.Err(); err != nil {
if enableLogs {
log.Printf("error: %s", err)
fmt.Printf("error: %s\n", err)
}
}
@@ -90,7 +98,7 @@ func NmapParseResults(nmapResultFilePath string) ([]Stream, error) {
// Open & Read XML file
content, err := ioutil.ReadFile(nmapResultFilePath)
if err != nil {
return streams, errors.Wrap(err, "Could not read nmap result file at "+nmapResultFilePath+":")
return streams, errors.Wrap(err, "could not read nmap result file at "+nmapResultFilePath+":")
}
// Unmarshal content of XML file into data structure
+715
View File
@@ -0,0 +1,715 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmrdr
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
// HACK: See https://golang.org/src/os/exec/exec_test.go
func fakeExecCommand(command string, args ...string) *exec.Cmd {
cs := []string{"-test.run=TestExecCommandHelper", "--", command}
cs = append(cs, args...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1",
"STDOUT= ",
"EXIT_STATUS=0"}
return cmd
}
func TestExecCommandHelper(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
fmt.Fprintf(os.Stdout, os.Getenv("STDOUT"))
i, _ := strconv.Atoi(os.Getenv("EXIT_STATUS"))
os.Exit(i)
}
func TestNmapRun(t *testing.T) {
execCommand = fakeExecCommand
defer func() { execCommand = exec.Command }()
vectors := []struct {
targets string
ports string
resultFilePath string
nmapSpeed int
enableLogs bool
expectedErrMsg string
}{
// Valid baseline with logs enabled
{
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: true,
},
// Invalid speed
{
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: INSANE + 1,
enableLogs: false,
expectedErrMsg: "invalid nmap speed value",
},
}
for _, vector := range vectors {
err := NmapRun(vector.targets, vector.ports, vector.resultFilePath, vector.nmapSpeed, vector.enableLogs)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error: %v\n", err)
os.Exit(1)
}
}
}
}
func TestNmapParseResults(t *testing.T) {
validStream1 := Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
}
validStream2 := Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
}
invalidStreamNoPort := Stream{
Device: "invalidDevice",
Address: "fakeAddress",
Port: 0,
}
invalidStreamNoAddress := Stream{
Device: "invalidDevice",
Address: "",
Port: 1337,
}
vectors := []struct {
fileExists bool
streamsXML *nmapResult
expectedStreams []Stream
expectedErrMsg string
}{
// File exists
// Two valid streams, no error
{
expectedStreams: []Stream{validStream1, validStream2},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: validStream1.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream1.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream1.Device,
},
},
},
},
},
host{
Address: address{
Addr: validStream2.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream2.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream2.Device,
},
},
},
},
},
},
},
fileExists: true,
},
// File exists
// Two invalid streams, no error
{
fileExists: true,
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: invalidStreamNoAddress.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: invalidStreamNoAddress.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: invalidStreamNoAddress.Device,
},
},
},
},
},
host{
Address: address{
Addr: invalidStreamNoPort.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: invalidStreamNoPort.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: invalidStreamNoPort.Device,
},
},
},
},
},
},
},
},
// File does not exist, error
{
fileExists: false,
expectedErrMsg: "could not read nmap result file",
},
// No valid streams found
{
fileExists: true,
expectedStreams: []Stream{},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: "Camera with closed ports",
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: 0,
State: state{
State: "closed",
},
Service: service{
Name: "rtsp",
Product: "Camera without closed ports",
},
},
},
},
},
host{
Address: address{
Addr: "Camera with closed ports",
AddrType: "ipv4",
},
},
},
},
},
// XML Unmarshal error
{
fileExists: true,
expectedStreams: []Stream{},
expectedErrMsg: "expected element type <nmaprun> but have <failure>",
},
}
for i, vector := range vectors {
filePath := "/tmp/cameradar_test_parse_results_" + fmt.Sprint(i) + ".xml"
// create file
if vector.fileExists {
_, err := os.Create(filePath)
if err != nil {
fmt.Printf("could not create xml file for NmapParseResults: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
// marshal and write
if vector.streamsXML != nil {
streams, err := xml.Marshal(vector.streamsXML)
if err != nil {
fmt.Printf("invalid streams for NmapParseResults: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML)
os.Exit(1)
}
err = ioutil.WriteFile(filePath, streams, 0644)
if err != nil {
fmt.Printf("could not write xml file for NmapParseResults: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
} else {
err := ioutil.WriteFile(filePath, []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><failure>"), 0644)
if err != nil {
fmt.Printf("could not write xml file for NmapParseResults: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
}
}
results, err := NmapParseResults(filePath)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error: %v\n", err)
os.Exit(1)
}
for _, stream := range vector.expectedStreams {
foundStream := false
for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
foundStream = true
}
}
assert.Equal(t, true, foundStream, "wrong streams parsed")
}
}
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
}
}
func TestDiscover(t *testing.T) {
execCommand = fakeExecCommand
defer func() { execCommand = exec.Command }()
validStream1 := Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
}
validStream2 := Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
}
invalidStreamNoPort := Stream{
Device: "invalidDevice",
Address: "fakeAddress",
Port: 0,
}
invalidStreamNoAddress := Stream{
Device: "invalidDevice",
Address: "",
Port: 1337,
}
vectors := []struct {
targets string
ports string
resultFilePath string
nmapSpeed int
enableLogs bool
fileExists bool
streamsXML *nmapResult
expectedStreams []Stream
expectedErrMsg string
}{
// Valid baseline
{
expectedStreams: []Stream{validStream1, validStream2},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: validStream1.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream1.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream1.Device,
},
},
},
},
},
host{
Address: address{
Addr: validStream2.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream2.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream2.Device,
},
},
},
},
},
},
},
fileExists: true,
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
// Invalid speed
{
expectedStreams: []Stream{},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: validStream1.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream1.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream1.Device,
},
},
},
},
},
host{
Address: address{
Addr: validStream2.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream2.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream2.Device,
},
},
},
},
},
},
},
fileExists: true,
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: INSANE + 1,
enableLogs: false,
expectedErrMsg: "invalid nmap speed value",
},
// File exists
// Two valid streams, no error
{
expectedStreams: []Stream{validStream1, validStream2},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: validStream1.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream1.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream1.Device,
},
},
},
},
},
host{
Address: address{
Addr: validStream2.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: validStream2.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: validStream2.Device,
},
},
},
},
},
},
},
fileExists: true,
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
// File exists
// Two invalid streams, no error
{
fileExists: true,
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: invalidStreamNoAddress.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: invalidStreamNoAddress.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: invalidStreamNoAddress.Device,
},
},
},
},
},
host{
Address: address{
Addr: invalidStreamNoPort.Address,
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: invalidStreamNoPort.Port,
State: state{
State: "open",
},
Service: service{
Name: "rtsp",
Product: invalidStreamNoPort.Device,
},
},
},
},
},
},
},
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
// File does not exist, error
{
fileExists: false,
expectedErrMsg: "could not read nmap result file",
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
// No valid streams found
{
fileExists: true,
expectedStreams: []Stream{},
streamsXML: &nmapResult{
Hosts: []host{
host{
Address: address{
Addr: "Camera with closed ports",
AddrType: "ipv4",
},
Ports: ports{
Ports: []port{
port{
PortID: 0,
State: state{
State: "closed",
},
Service: service{
Name: "rtsp",
Product: "Camera without closed ports",
},
},
},
},
},
host{
Address: address{
Addr: "Camera with closed ports",
AddrType: "ipv4",
},
},
},
},
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
// XML Unmarshal error
{
fileExists: true,
expectedStreams: []Stream{},
expectedErrMsg: "expected element type <nmaprun> but have <failure>",
targets: "localhost",
ports: "554",
resultFilePath: "/tmp/results.xml",
nmapSpeed: PARANOIAC,
enableLogs: false,
},
}
for i, vector := range vectors {
filePath := "/tmp/cameradar_test_discover_" + fmt.Sprint(i) + ".xml"
// create file
if vector.fileExists {
_, err := os.Create(filePath)
if err != nil {
fmt.Printf("could not create xml file for Discover: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
// marshal and write
if vector.streamsXML != nil {
streams, err := xml.Marshal(vector.streamsXML)
if err != nil {
fmt.Printf("invalid streams for Discover: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML)
os.Exit(1)
}
err = ioutil.WriteFile(filePath, streams, 0644)
if err != nil {
fmt.Printf("could not write xml file for Discover: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
} else {
err := ioutil.WriteFile(filePath, []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><failure>"), 0644)
if err != nil {
fmt.Printf("could not write xml file for Discover: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
}
}
results, err := Discover(vector.targets, vector.ports, filePath, vector.nmapSpeed, vector.enableLogs)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success in Discover test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error in Discover test, iteration %d: %v\n", i, err)
os.Exit(1)
}
for _, stream := range vector.expectedStreams {
foundStream := false
for _, result := range results {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
foundStream = true
}
}
assert.Equal(t, true, foundStream, "wrong streams parsed")
}
}
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
}
}
+123
View File
@@ -0,0 +1,123 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmrdr
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestReplace(t *testing.T) {
validStream1 := Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
}
validStream2 := Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
}
invalidStreamNoPort := Stream{
Device: "invalidDevice",
Address: "fakeAddress",
Port: 0,
}
invalidStreamNoPortModified := Stream{
Device: "updatedDevice",
Address: "fakeAddress",
Port: 1337,
}
vectors := []struct {
streams []Stream
newStream Stream
expectedStreams []Stream
}{
// Valid baseline
{
streams: []Stream{validStream1, validStream2, invalidStreamNoPort},
newStream: invalidStreamNoPortModified,
expectedStreams: []Stream{validStream1, validStream2, invalidStreamNoPortModified},
},
}
for _, vector := range vectors {
streams := replace(vector.streams, vector.newStream)
for _, stream := range vector.streams {
foundStream := false
for _, result := range streams {
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
foundStream = true
}
}
assert.Equal(t, true, foundStream, "wrong streams parsed")
}
}
}
func TestGetCameraRTSPURL(t *testing.T) {
validStream := Stream{
Address: "1.2.3.4",
Username: "ullaakut",
Password: "ba69897483886f0d2b0afb6345b76c0c",
Route: "cameradar.sdp",
Port: 1337,
}
vectors := []struct {
stream Stream
expectedRTSPURL string
}{
// Valid baseline
{
stream: validStream,
expectedRTSPURL: "rtsp://ullaakut:ba69897483886f0d2b0afb6345b76c0c@1.2.3.4:1337/cameradar.sdp",
},
}
for _, vector := range vectors {
output := GetCameraRTSPURL(vector.stream)
assert.Equal(t, vector.expectedRTSPURL, output, "wrong RTSP URL generated")
}
}
func TestGetCameraAdminPanelURL(t *testing.T) {
validStream := Stream{
Address: "1.2.3.4",
}
vectors := []struct {
stream Stream
expectedRTSPURL string
}{
// Valid baseline
{
stream: validStream,
expectedRTSPURL: "http://1.2.3.4/",
},
}
for _, vector := range vectors {
output := GetCameraAdminPanelURL(vector.stream)
assert.Equal(t, vector.expectedRTSPURL, output, "wrong Admin Panel URL generated")
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ func LoadCredentials(path string) (Credentials, error) {
// Open & Read XML file
content, err := ioutil.ReadFile(path)
if err != nil {
return creds, errors.Wrap(err, "Could not read credentials dictionary file at "+path+":")
return creds, errors.Wrap(err, "could not read credentials dictionary file at "+path+":")
}
// Unmarshal content of JSON file into data structure
+182
View File
@@ -0,0 +1,182 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmrdr
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadCredentials(t *testing.T) {
credentialsJSONString := []byte("{\"usernames\":[\"admin\",\"root\"],\"passwords\":[\"12345\",\"root\"]}")
validCredentials := Credentials{
Usernames: []string{"admin", "root"},
Passwords: []string{"12345", "root"},
}
vectors := []struct {
input []byte
fileExists bool
expectedOutput Credentials
expectedErrMsg string
}{
// Valid baseline
{
fileExists: true,
input: credentialsJSONString,
expectedOutput: validCredentials,
},
// File does not exist
{
fileExists: false,
input: credentialsJSONString,
expectedErrMsg: "could not read credentials dictionary file at",
},
// Invalid format
{
fileExists: true,
input: []byte("not json"),
expectedErrMsg: "invalid character",
},
// No streams in dictionary
{
fileExists: true,
input: []byte("{\"invalid\":\"json\"}"),
},
}
for i, vector := range vectors {
filePath := "/tmp/cameradar_test_load_credentials_" + fmt.Sprint(i) + ".xml"
// create file
if vector.fileExists {
_, err := os.Create(filePath)
if err != nil {
fmt.Printf("could not create xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
err = ioutil.WriteFile(filePath, vector.input, 0644)
if err != nil {
fmt.Printf("could not write xml file for LoadCredentials: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
}
result, err := LoadCredentials(filePath)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success in LoadCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error in LoadCredentials test, iteration %d: %v\n", i, err)
os.Exit(1)
}
for _, expectedUsername := range vector.expectedOutput.Usernames {
foundUsername := false
for _, username := range result.Usernames {
if username == expectedUsername {
foundUsername = true
}
}
assert.Equal(t, true, foundUsername, "wrong usernames parsed")
}
for _, expectedPassword := range vector.expectedOutput.Passwords {
foundPassword := false
for _, password := range result.Passwords {
if password == expectedPassword {
foundPassword = true
}
}
assert.Equal(t, true, foundPassword, "wrong passwords parsed")
}
}
}
}
func TestLoadRoutes(t *testing.T) {
routesJSONString := []byte("admin\nroot")
validRoutes := Routes{"admin", "root"}
vectors := []struct {
input []byte
fileExists bool
expectedOutput Routes
expectedErrMsg string
}{
// Valid baseline
{
fileExists: true,
input: routesJSONString,
expectedOutput: validRoutes,
},
// File does not exist
{
fileExists: false,
input: routesJSONString,
expectedErrMsg: "no such file or directory",
},
// No streams in dictionary
{
fileExists: true,
input: []byte(""),
},
}
for i, vector := range vectors {
filePath := "/tmp/cameradar_test_load_routes_" + fmt.Sprint(i) + ".xml"
// create file
if vector.fileExists {
_, err := os.Create(filePath)
if err != nil {
fmt.Printf("could not create xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
err = ioutil.WriteFile(filePath, vector.input, 0644)
if err != nil {
fmt.Printf("could not write xml file for LoadRoutes: %v. iteration: %d. file path: %s\n", err, i, filePath)
os.Exit(1)
}
}
result, err := LoadRoutes(filePath)
if len(vector.expectedErrMsg) > 0 {
if err == nil {
fmt.Printf("unexpected success in LoadRoutes test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
os.Exit(1)
}
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
} else {
if err != nil {
fmt.Printf("unexpected error in LoadRoutes test, iteration %d: %v\n", i, err)
os.Exit(1)
}
for _, expectedRoute := range vector.expectedOutput {
foundRoute := false
for _, route := range result {
if route == expectedRoute {
foundRoute = true
}
}
assert.Equal(t, true, foundRoute, "wrong routes parsed")
}
}
}
}