Increase test coverage, mock libcurl & uniformize error messages
This commit is contained in:
committed by
Brendan Le Glaunec
parent
c1ea6b167c
commit
74672f6625
@@ -14,11 +14,7 @@ func doNotWrite([]uint8, interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func routeAttack(camera Stream, route string, timeout time.Duration, enableLogs bool) bool {
|
||||
easy := curl.EasyInit()
|
||||
defer easy.Cleanup()
|
||||
|
||||
if easy != nil {
|
||||
func routeAttack(c Curler, camera Stream, route string, timeout time.Duration, enableLogs bool) bool {
|
||||
attackURL := fmt.Sprintf(
|
||||
"rtsp://%s:%s@%s:%d/%s",
|
||||
camera.Username,
|
||||
@@ -30,32 +26,32 @@ func routeAttack(camera Stream, route string, timeout time.Duration, enableLogs
|
||||
|
||||
if enableLogs {
|
||||
// Debug logs when logs are enabled
|
||||
easy.Setopt(curl.OPT_VERBOSE, 1)
|
||||
c.Setopt(curl.OPT_VERBOSE, 1)
|
||||
} else {
|
||||
// Do not write sdp in stdout
|
||||
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
|
||||
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
|
||||
}
|
||||
|
||||
// Do not send a body in the describe request
|
||||
easy.Setopt(curl.OPT_NOBODY, 1)
|
||||
c.Setopt(curl.OPT_NOBODY, 1)
|
||||
// Send a request to the URL of the camera we want to attack
|
||||
easy.Setopt(curl.OPT_URL, attackURL)
|
||||
c.Setopt(curl.OPT_URL, attackURL)
|
||||
// Set the RTSP STREAM URI as the camera URL
|
||||
easy.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL)
|
||||
c.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL)
|
||||
// 2 is CURL_RTSPREQ_DESCRIBE
|
||||
easy.Setopt(curl.OPT_RTSP_REQUEST, 2)
|
||||
c.Setopt(curl.OPT_RTSP_REQUEST, 2)
|
||||
// Set custom timeout
|
||||
easy.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
||||
c.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
||||
|
||||
// Perform the request
|
||||
err := easy.Perform()
|
||||
err := c.Perform()
|
||||
if err != nil {
|
||||
fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String())
|
||||
return false
|
||||
}
|
||||
|
||||
// Get return code for the request
|
||||
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE)
|
||||
rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -65,15 +61,10 @@ func routeAttack(camera Stream, route string, timeout time.Duration, enableLogs
|
||||
if rc == 200 || rc == 401 || rc == 403 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func credAttack(camera Stream, username string, password string, timeout time.Duration, enableLogs bool) bool {
|
||||
easy := curl.EasyInit()
|
||||
defer easy.Cleanup()
|
||||
|
||||
if easy != nil {
|
||||
func credAttack(c Curler, camera Stream, username string, password string, timeout time.Duration, enableLogs bool) bool {
|
||||
attackURL := fmt.Sprintf(
|
||||
"rtsp://%s:%s@%s:%d/%s",
|
||||
username,
|
||||
@@ -85,32 +76,32 @@ func credAttack(camera Stream, username string, password string, timeout time.Du
|
||||
|
||||
if enableLogs {
|
||||
// Debug logs when logs are enabled
|
||||
easy.Setopt(curl.OPT_VERBOSE, 1)
|
||||
c.Setopt(curl.OPT_VERBOSE, 1)
|
||||
} else {
|
||||
// Do not write sdp in stdout
|
||||
easy.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
|
||||
c.Setopt(curl.OPT_WRITEFUNCTION, doNotWrite)
|
||||
}
|
||||
|
||||
// Do not send a body in the describe request
|
||||
easy.Setopt(curl.OPT_NOBODY, 1)
|
||||
c.Setopt(curl.OPT_NOBODY, 1)
|
||||
// Send a request to the URL of the camera we want to attack
|
||||
easy.Setopt(curl.OPT_URL, attackURL)
|
||||
c.Setopt(curl.OPT_URL, attackURL)
|
||||
// Set the RTSP STREAM URI as the camera URL
|
||||
easy.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL)
|
||||
c.Setopt(curl.OPT_RTSP_STREAM_URI, attackURL)
|
||||
// 2 is CURL_RTSPREQ_DESCRIBE
|
||||
easy.Setopt(curl.OPT_RTSP_REQUEST, 2)
|
||||
c.Setopt(curl.OPT_RTSP_REQUEST, 2)
|
||||
// Set custom timeout
|
||||
easy.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
||||
c.Setopt(curl.OPT_TIMEOUT_MS, int(timeout/time.Millisecond))
|
||||
|
||||
// Perform the request
|
||||
err := easy.Perform()
|
||||
err := c.Perform()
|
||||
if err != nil {
|
||||
fmt.Printf("\nERROR: curl timeout on camera '%s' reached after %s.\nconsider increasing the timeout (-T, --timeout parameter) to at least 5000ms if scanning an unstable network.\n", camera.Address, timeout.String())
|
||||
return false
|
||||
}
|
||||
|
||||
// Get return code for the request
|
||||
rc, err := easy.Getinfo(curl.INFO_RESPONSE_CODE)
|
||||
rc, err := c.Getinfo(curl.INFO_RESPONSE_CODE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -120,14 +111,13 @@ func credAttack(camera Stream, username string, password string, timeout time.Du
|
||||
if rc == 200 || rc == 404 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func attackCameraCredentials(target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
|
||||
func attackCameraCredentials(c Curler, target Stream, credentials Credentials, resultsChan chan<- Stream, timeout time.Duration, log bool) {
|
||||
for _, username := range credentials.Usernames {
|
||||
for _, password := range credentials.Passwords {
|
||||
ok := credAttack(target, username, password, timeout, log)
|
||||
ok := credAttack(c, target, username, password, timeout, log)
|
||||
if ok {
|
||||
target.CredentialsFound = true
|
||||
target.Username = username
|
||||
@@ -141,9 +131,9 @@ func attackCameraCredentials(target Stream, credentials Credentials, resultsChan
|
||||
resultsChan <- target
|
||||
}
|
||||
|
||||
func attackCameraRoute(target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
|
||||
func attackCameraRoute(c Curler, target Stream, routes Routes, resultsChan chan<- Stream, timeout time.Duration, log bool) {
|
||||
for _, route := range routes {
|
||||
ok := routeAttack(target, route, timeout, log)
|
||||
ok := routeAttack(c, target, route, timeout, log)
|
||||
if ok {
|
||||
target.RouteFound = true
|
||||
target.Route = route
|
||||
@@ -157,12 +147,7 @@ func attackCameraRoute(target Stream, routes Routes, resultsChan chan<- Stream,
|
||||
|
||||
// AttackCredentials attempts to guess the provided targets' credentials using the given
|
||||
// dictionary or the default dictionary if none was provided by the user.
|
||||
func AttackCredentials(targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) {
|
||||
err := curl.GlobalInit(curl.GLOBAL_ALL)
|
||||
if err != nil {
|
||||
return targets, errors.Wrap(err, "could not initialize curl")
|
||||
}
|
||||
|
||||
func AttackCredentials(c Curler, targets []Stream, credentials Credentials, timeout time.Duration, log bool) ([]Stream, error) {
|
||||
attacks := make(chan Stream)
|
||||
defer close(attacks)
|
||||
|
||||
@@ -170,10 +155,10 @@ func AttackCredentials(targets []Stream, credentials Credentials, timeout time.D
|
||||
for _, target := range targets {
|
||||
err := validate.Struct(target)
|
||||
if err != nil {
|
||||
return targets, errors.Wrap(err, "invalid streams")
|
||||
return targets, errors.Wrap(err, "invalid targets")
|
||||
}
|
||||
|
||||
go attackCameraCredentials(target, credentials, attacks, timeout, log)
|
||||
go attackCameraCredentials(c, target, credentials, attacks, timeout, log)
|
||||
}
|
||||
|
||||
attackResults := []Stream{}
|
||||
@@ -197,12 +182,7 @@ func AttackCredentials(targets []Stream, credentials Credentials, timeout time.D
|
||||
|
||||
// AttackRoute attempts to guess the provided targets' streaming routes using the given
|
||||
// dictionary or the default dictionary if none was provided by the user.
|
||||
func AttackRoute(targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) {
|
||||
err := curl.GlobalInit(curl.GLOBAL_ALL)
|
||||
if err != nil {
|
||||
return targets, errors.Wrap(err, "could not initialize curl")
|
||||
}
|
||||
|
||||
func AttackRoute(c Curler, targets []Stream, routes Routes, timeout time.Duration, log bool) ([]Stream, error) {
|
||||
attacks := make(chan Stream)
|
||||
defer close(attacks)
|
||||
|
||||
@@ -210,10 +190,10 @@ func AttackRoute(targets []Stream, routes Routes, timeout time.Duration, log boo
|
||||
for _, target := range targets {
|
||||
err := validate.Struct(target)
|
||||
if err != nil {
|
||||
return targets, errors.Wrap(err, "invalid streams")
|
||||
return targets, errors.Wrap(err, "invalid targets")
|
||||
}
|
||||
|
||||
go attackCameraRoute(target, routes, attacks, timeout, log)
|
||||
go attackCameraRoute(c, target, routes, attacks, timeout, log)
|
||||
}
|
||||
|
||||
attackResults := []Stream{}
|
||||
|
||||
+208
-25
@@ -1,16 +1,35 @@
|
||||
package cmrdr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
curl "github.com/andelf/go-curl"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Again, since these tests use the curl library, I don't want to spend ages trying to mock
|
||||
// the lib right now.
|
||||
type CurlerMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *CurlerMock) Setopt(opt int, param interface{}) error {
|
||||
args := m.Called(opt, param)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *CurlerMock) Perform() error {
|
||||
args := m.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *CurlerMock) Getinfo(info curl.CurlInfo) (interface{}, error) {
|
||||
args := m.Called(info)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func TestAttackCredentials(t *testing.T) {
|
||||
validStream1 := Stream{
|
||||
@@ -25,58 +44,135 @@ func TestAttackCredentials(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
invalidStream := Stream{
|
||||
Device: "InvalidDevice",
|
||||
}
|
||||
|
||||
fakeTargets := []Stream{validStream1, validStream2}
|
||||
invalidTargets := []Stream{invalidStream}
|
||||
fakeCredentials := Credentials{
|
||||
Usernames: []string{"admin", "root"},
|
||||
Passwords: []string{"12345", "root"},
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
targets []Stream
|
||||
credentials Credentials
|
||||
timeout time.Duration
|
||||
log bool
|
||||
|
||||
status int
|
||||
|
||||
performErr error
|
||||
getInfoErr error
|
||||
invalidTargets bool
|
||||
|
||||
expectedStreams []Stream
|
||||
expectedErrMsg string
|
||||
}{
|
||||
// Valid baseline
|
||||
// Credentials found
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
status: 404,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
},
|
||||
// Camera accessed
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
status: 200,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
},
|
||||
// Invalid targets
|
||||
{
|
||||
targets: invalidTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
invalidTargets: true,
|
||||
|
||||
expectedErrMsg: "invalid targets",
|
||||
expectedStreams: invalidTargets,
|
||||
},
|
||||
// curl perform fails
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
performErr: errors.New("dummy error"),
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no credentials found",
|
||||
},
|
||||
// curl getinfo fails
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
getInfoErr: errors.New("dummy error"),
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no credentials found",
|
||||
},
|
||||
// Credentials not found
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
log: true,
|
||||
|
||||
status: 403,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no credentials found",
|
||||
},
|
||||
// Valid baseline without logs
|
||||
// Logging disabled
|
||||
{
|
||||
targets: fakeTargets,
|
||||
credentials: fakeCredentials,
|
||||
timeout: 1 * time.Millisecond,
|
||||
log: false,
|
||||
|
||||
status: 403,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no credentials found",
|
||||
},
|
||||
// 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)
|
||||
for i, test := range testCases {
|
||||
curlerMock := &CurlerMock{}
|
||||
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if !test.invalidTargets {
|
||||
curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
|
||||
curlerMock.On("Perform").Return(test.performErr)
|
||||
if test.performErr == nil {
|
||||
curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
|
||||
}
|
||||
}
|
||||
|
||||
results, err := AttackCredentials(curlerMock, test.targets, test.credentials, test.timeout, test.log)
|
||||
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success in AttackCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.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 {
|
||||
for _, stream := range test.expectedStreams {
|
||||
foundStream := false
|
||||
for _, result := range results {
|
||||
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
|
||||
@@ -86,8 +182,8 @@ func TestAttackCredentials(t *testing.T) {
|
||||
assert.Equal(t, true, foundStream, "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
|
||||
|
||||
assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
|
||||
curlerMock.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,19 +200,92 @@ func TestAttackRoute(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
invalidStream := Stream{
|
||||
Device: "InvalidDevice",
|
||||
}
|
||||
|
||||
fakeTargets := []Stream{validStream1, validStream2}
|
||||
fakeRoutes := Routes{"live.sdp", "media.amp"}
|
||||
invalidTargets := []Stream{invalidStream}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
targets []Stream
|
||||
routes Routes
|
||||
timeout time.Duration
|
||||
log bool
|
||||
|
||||
status int
|
||||
|
||||
performErr error
|
||||
getInfoErr error
|
||||
invalidTargets bool
|
||||
|
||||
expectedStreams []Stream
|
||||
expectedErrMsg string
|
||||
}{
|
||||
// Valid baseline
|
||||
// Route found
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
status: 403,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
},
|
||||
// Route found
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
status: 401,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
},
|
||||
// Camera accessed
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
status: 200,
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
},
|
||||
// Invalid targets
|
||||
{
|
||||
targets: invalidTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
invalidTargets: true,
|
||||
|
||||
expectedErrMsg: "invalid targets",
|
||||
expectedStreams: invalidTargets,
|
||||
},
|
||||
// curl perform fails
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
performErr: errors.New("dummy error"),
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no routes found",
|
||||
},
|
||||
// curl getinfo fails
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
timeout: 1 * time.Millisecond,
|
||||
|
||||
getInfoErr: errors.New("dummy error"),
|
||||
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no routes found",
|
||||
},
|
||||
// Routes not found
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
@@ -126,7 +295,7 @@ func TestAttackRoute(t *testing.T) {
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no routes found",
|
||||
},
|
||||
// Valid baseline without logs
|
||||
// Logs disabled
|
||||
{
|
||||
targets: fakeTargets,
|
||||
routes: fakeRoutes,
|
||||
@@ -136,23 +305,32 @@ func TestAttackRoute(t *testing.T) {
|
||||
expectedStreams: fakeTargets,
|
||||
expectedErrMsg: "no routes found",
|
||||
},
|
||||
// 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)
|
||||
for i, test := range testCases {
|
||||
curlerMock := &CurlerMock{}
|
||||
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if !test.invalidTargets {
|
||||
curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
|
||||
curlerMock.On("Perform").Return(test.performErr)
|
||||
if test.performErr == nil {
|
||||
curlerMock.On("Getinfo", mock.Anything).Return(test.status, test.getInfoErr)
|
||||
}
|
||||
}
|
||||
|
||||
results, err := AttackRoute(curlerMock, test.targets, test.routes, test.timeout, test.log)
|
||||
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success in AttackRoute test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.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 {
|
||||
for _, stream := range test.expectedStreams {
|
||||
foundStream := false
|
||||
for _, result := range results {
|
||||
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
|
||||
@@ -162,6 +340,11 @@ func TestAttackRoute(t *testing.T) {
|
||||
assert.Equal(t, true, foundStream, "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
|
||||
assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
|
||||
curlerMock.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDotWrite(t *testing.T) {
|
||||
assert.Equal(t, true, doNotWrite(nil, nil))
|
||||
}
|
||||
|
||||
+13
-6
@@ -8,12 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/EtixLabs/cameradar"
|
||||
|
||||
curl "github.com/andelf/go-curl"
|
||||
"github.com/fatih/color"
|
||||
"github.com/gernest/wow"
|
||||
"github.com/gernest/wow/spin"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
@@ -92,6 +93,13 @@ func main() {
|
||||
|
||||
w := startSpinner(options.EnableLogs)
|
||||
|
||||
err = curl.GlobalInit(curl.GLOBAL_ALL)
|
||||
c := curl.EasyInit()
|
||||
if err != nil || c == nil {
|
||||
printErr(errors.New("libcurl initialization failed"))
|
||||
}
|
||||
defer curl.GlobalCleanup()
|
||||
|
||||
updateSpinner(w, "Loading dictionaries...", options.EnableLogs)
|
||||
gopath := os.Getenv("GOPATH")
|
||||
options.Credentials = strings.Replace(options.Credentials, "<GOPATH>", gopath, 1)
|
||||
@@ -116,15 +124,14 @@ func main() {
|
||||
}
|
||||
|
||||
// Most cameras will be accessed successfully with these two attacks
|
||||
|
||||
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their routes...", options.EnableLogs)
|
||||
streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
streams, err = cmrdr.AttackRoute(c, streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
if err != nil && len(streams) > 0 {
|
||||
printErr(err)
|
||||
}
|
||||
|
||||
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Attacking their credentials...", options.EnableLogs)
|
||||
streams, err = cmrdr.AttackCredentials(streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
streams, err = cmrdr.AttackCredentials(c, streams, credentials, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
if err != nil && len(streams) > 0 {
|
||||
printErr(err)
|
||||
}
|
||||
@@ -135,7 +142,7 @@ func main() {
|
||||
if stream.RouteFound == false || stream.CredentialsFound == false {
|
||||
|
||||
updateSpinner(w, "Found "+fmt.Sprint(len(streams))+" streams. Final attack...", options.EnableLogs)
|
||||
streams, err = cmrdr.AttackRoute(streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
streams, err = cmrdr.AttackRoute(c, streams, routes, time.Duration(options.Timeout)*time.Millisecond, options.EnableLogs)
|
||||
if err != nil && len(streams) > 0 {
|
||||
printErr(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package cmrdr
|
||||
|
||||
import (
|
||||
curl "github.com/andelf/go-curl"
|
||||
)
|
||||
|
||||
// Curler is an interface that implements the CURL interface of the go-curl library
|
||||
// Used for mocking
|
||||
type Curler interface {
|
||||
Setopt(opt int, param interface{}) error
|
||||
Perform() error
|
||||
Getinfo(info curl.CurlInfo) (interface{}, error)
|
||||
}
|
||||
+31
-31
@@ -37,7 +37,7 @@ func TestNmapRun(t *testing.T) {
|
||||
execCommand = fakeExecCommand
|
||||
defer func() { execCommand = exec.Command }()
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
targets string
|
||||
ports string
|
||||
resultFilePath string
|
||||
@@ -65,14 +65,14 @@ func TestNmapRun(t *testing.T) {
|
||||
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 {
|
||||
for _, test := range testCases {
|
||||
err := NmapRun(test.targets, test.ports, test.resultFilePath, test.nmapSpeed, test.enableLogs)
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success. expected error: %s\n", test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
|
||||
} else {
|
||||
if err != nil {
|
||||
fmt.Printf("unexpected error: %v\n", err)
|
||||
@@ -107,7 +107,7 @@ func TestNmapParseResults(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
fileExists bool
|
||||
streamsXML *nmapResult
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestNmapParseResults(t *testing.T) {
|
||||
fileExists: true,
|
||||
},
|
||||
// File exists
|
||||
// Two invalid streams, no error
|
||||
// Two invalid targets, no error
|
||||
{
|
||||
fileExists: true,
|
||||
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
|
||||
@@ -261,11 +261,11 @@ func TestNmapParseResults(t *testing.T) {
|
||||
expectedErrMsg: "expected element type <nmaprun> but have <failure>",
|
||||
},
|
||||
}
|
||||
for i, vector := range vectors {
|
||||
for i, test := range testCases {
|
||||
filePath := "/tmp/cameradar_test_parse_results_" + fmt.Sprint(i) + ".xml"
|
||||
|
||||
// create file
|
||||
if vector.fileExists {
|
||||
if test.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)
|
||||
@@ -273,10 +273,10 @@ func TestNmapParseResults(t *testing.T) {
|
||||
}
|
||||
|
||||
// marshal and write
|
||||
if vector.streamsXML != nil {
|
||||
streams, err := xml.Marshal(vector.streamsXML)
|
||||
if test.streamsXML != nil {
|
||||
streams, err := xml.Marshal(test.streamsXML)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid streams for NmapParseResults: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML)
|
||||
fmt.Printf("invalid targets for NmapParseResults: %v. iteration: %d. streams: %v\n", err, i, test.streamsXML)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -295,18 +295,18 @@ func TestNmapParseResults(t *testing.T) {
|
||||
}
|
||||
|
||||
results, err := NmapParseResults(filePath)
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success. expected error: %s\n", vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success. expected error: %s\n", test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.expectedErrMsg, "wrong error message")
|
||||
} else {
|
||||
if err != nil {
|
||||
fmt.Printf("unexpected error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, stream := range vector.expectedStreams {
|
||||
for _, stream := range test.expectedStreams {
|
||||
foundStream := false
|
||||
for _, result := range results {
|
||||
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
|
||||
@@ -316,7 +316,7 @@ func TestNmapParseResults(t *testing.T) {
|
||||
assert.Equal(t, true, foundStream, "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
|
||||
assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ func TestDiscover(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
targets string
|
||||
ports string
|
||||
resultFilePath string
|
||||
@@ -526,7 +526,7 @@ func TestDiscover(t *testing.T) {
|
||||
enableLogs: false,
|
||||
},
|
||||
// File exists
|
||||
// Two invalid streams, no error
|
||||
// Two invalid targets, no error
|
||||
{
|
||||
fileExists: true,
|
||||
expectedStreams: []Stream{invalidStreamNoPort, invalidStreamNoAddress},
|
||||
@@ -642,11 +642,11 @@ func TestDiscover(t *testing.T) {
|
||||
enableLogs: false,
|
||||
},
|
||||
}
|
||||
for i, vector := range vectors {
|
||||
for i, test := range testCases {
|
||||
filePath := "/tmp/cameradar_test_discover_" + fmt.Sprint(i) + ".xml"
|
||||
|
||||
// create file
|
||||
if vector.fileExists {
|
||||
if test.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)
|
||||
@@ -654,10 +654,10 @@ func TestDiscover(t *testing.T) {
|
||||
}
|
||||
|
||||
// marshal and write
|
||||
if vector.streamsXML != nil {
|
||||
streams, err := xml.Marshal(vector.streamsXML)
|
||||
if test.streamsXML != nil {
|
||||
streams, err := xml.Marshal(test.streamsXML)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid streams for Discover: %v. iteration: %d. streams: %v\n", err, i, vector.streamsXML)
|
||||
fmt.Printf("invalid targets for Discover: %v. iteration: %d. streams: %v\n", err, i, test.streamsXML)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -675,20 +675,20 @@ func TestDiscover(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
results, err := Discover(vector.targets, vector.ports, filePath, vector.nmapSpeed, vector.enableLogs)
|
||||
results, err := Discover(test.targets, test.ports, filePath, test.nmapSpeed, test.enableLogs)
|
||||
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success in Discover test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success in Discover test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.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 {
|
||||
for _, stream := range test.expectedStreams {
|
||||
foundStream := false
|
||||
for _, result := range results {
|
||||
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
|
||||
@@ -698,6 +698,6 @@ func TestDiscover(t *testing.T) {
|
||||
assert.Equal(t, true, foundStream, "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(vector.expectedStreams), len(results), "wrong streams parsed")
|
||||
assert.Equal(t, len(test.expectedStreams), len(results), "wrong streams parsed")
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -31,7 +31,7 @@ func TestReplace(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
streams []Stream
|
||||
newStream Stream
|
||||
|
||||
@@ -45,10 +45,10 @@ func TestReplace(t *testing.T) {
|
||||
expectedStreams: []Stream{validStream1, validStream2, invalidStreamNoPortModified},
|
||||
},
|
||||
}
|
||||
for _, vector := range vectors {
|
||||
streams := replace(vector.streams, vector.newStream)
|
||||
for _, test := range testCases {
|
||||
streams := replace(test.streams, test.newStream)
|
||||
|
||||
for _, stream := range vector.streams {
|
||||
for _, stream := range test.streams {
|
||||
foundStream := false
|
||||
for _, result := range streams {
|
||||
if result.Address == stream.Address && result.Device == stream.Device && result.Port == stream.Port {
|
||||
@@ -69,7 +69,7 @@ func TestGetCameraRTSPURL(t *testing.T) {
|
||||
Port: 1337,
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
stream Stream
|
||||
|
||||
expectedRTSPURL string
|
||||
@@ -81,9 +81,9 @@ func TestGetCameraRTSPURL(t *testing.T) {
|
||||
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")
|
||||
for _, test := range testCases {
|
||||
output := GetCameraRTSPURL(test.stream)
|
||||
assert.Equal(t, test.expectedRTSPURL, output, "wrong RTSP URL generated")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestGetCameraAdminPanelURL(t *testing.T) {
|
||||
Address: "1.2.3.4",
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
stream Stream
|
||||
|
||||
expectedRTSPURL string
|
||||
@@ -104,8 +104,8 @@ func TestGetCameraAdminPanelURL(t *testing.T) {
|
||||
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")
|
||||
for _, test := range testCases {
|
||||
output := GetCameraAdminPanelURL(test.stream)
|
||||
assert.Equal(t, test.expectedRTSPURL, output, "wrong Admin Panel URL generated")
|
||||
}
|
||||
}
|
||||
|
||||
+109
-17
@@ -16,7 +16,7 @@ func TestLoadCredentials(t *testing.T) {
|
||||
Passwords: []string{"12345", "root"},
|
||||
}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
fileExists bool
|
||||
|
||||
@@ -47,17 +47,17 @@ func TestLoadCredentials(t *testing.T) {
|
||||
input: []byte("{\"invalid\":\"json\"}"),
|
||||
},
|
||||
}
|
||||
for i, vector := range vectors {
|
||||
for i, test := range testCases {
|
||||
filePath := "/tmp/cameradar_test_load_credentials_" + fmt.Sprint(i) + ".xml"
|
||||
// create file
|
||||
if vector.fileExists {
|
||||
if test.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)
|
||||
err = ioutil.WriteFile(filePath, test.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)
|
||||
@@ -65,18 +65,18 @@ func TestLoadCredentials(t *testing.T) {
|
||||
}
|
||||
|
||||
result, err := LoadCredentials(filePath)
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success in LoadCredentials test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success in LoadCredentials test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.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 {
|
||||
for _, expectedUsername := range test.expectedOutput.Usernames {
|
||||
foundUsername := false
|
||||
for _, username := range result.Usernames {
|
||||
if username == expectedUsername {
|
||||
@@ -85,7 +85,7 @@ func TestLoadCredentials(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, true, foundUsername, "wrong usernames parsed")
|
||||
}
|
||||
for _, expectedPassword := range vector.expectedOutput.Passwords {
|
||||
for _, expectedPassword := range test.expectedOutput.Passwords {
|
||||
foundPassword := false
|
||||
for _, password := range result.Passwords {
|
||||
if password == expectedPassword {
|
||||
@@ -102,7 +102,7 @@ func TestLoadRoutes(t *testing.T) {
|
||||
routesJSONString := []byte("admin\nroot")
|
||||
validRoutes := Routes{"admin", "root"}
|
||||
|
||||
vectors := []struct {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
fileExists bool
|
||||
|
||||
@@ -127,17 +127,17 @@ func TestLoadRoutes(t *testing.T) {
|
||||
input: []byte(""),
|
||||
},
|
||||
}
|
||||
for i, vector := range vectors {
|
||||
for i, test := range testCases {
|
||||
filePath := "/tmp/cameradar_test_load_routes_" + fmt.Sprint(i) + ".xml"
|
||||
// create file
|
||||
if vector.fileExists {
|
||||
if test.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)
|
||||
err = ioutil.WriteFile(filePath, test.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)
|
||||
@@ -145,18 +145,18 @@ func TestLoadRoutes(t *testing.T) {
|
||||
}
|
||||
|
||||
result, err := LoadRoutes(filePath)
|
||||
if len(vector.expectedErrMsg) > 0 {
|
||||
if len(test.expectedErrMsg) > 0 {
|
||||
if err == nil {
|
||||
fmt.Printf("unexpected success in LoadRoutes test, iteration %d. expected error: %s\n", i, vector.expectedErrMsg)
|
||||
fmt.Printf("unexpected success in LoadRoutes test, iteration %d. expected error: %s\n", i, test.expectedErrMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
assert.Contains(t, err.Error(), vector.expectedErrMsg, "wrong error message")
|
||||
assert.Contains(t, err.Error(), test.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 {
|
||||
for _, expectedRoute := range test.expectedOutput {
|
||||
foundRoute := false
|
||||
for _, route := range result {
|
||||
if route == expectedRoute {
|
||||
@@ -168,3 +168,95 @@ func TestLoadRoutes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCredentialsFromString(t *testing.T) {
|
||||
defaultCredentials := Credentials{
|
||||
Usernames: []string{
|
||||
"",
|
||||
"admin",
|
||||
"Admin",
|
||||
"Administrator",
|
||||
"root",
|
||||
"supervisor",
|
||||
"ubnt",
|
||||
"service",
|
||||
"Dinion",
|
||||
"administrator",
|
||||
"admin1",
|
||||
},
|
||||
Passwords: []string{
|
||||
"",
|
||||
"admin",
|
||||
"9999",
|
||||
"123456",
|
||||
"pass",
|
||||
"camera",
|
||||
"1234",
|
||||
"12345",
|
||||
"fliradmin",
|
||||
"system",
|
||||
"jvc",
|
||||
"meinsm",
|
||||
"root",
|
||||
"4321",
|
||||
"111111",
|
||||
"1111111",
|
||||
"password",
|
||||
"ikwd",
|
||||
"supervisor",
|
||||
"ubnt",
|
||||
"wbox123",
|
||||
"service",
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
str string
|
||||
expectedResult Credentials
|
||||
}{
|
||||
{
|
||||
str: "{\"usernames\":[\"\",\"admin\",\"Admin\",\"Administrator\",\"root\",\"supervisor\",\"ubnt\",\"service\",\"Dinion\",\"administrator\",\"admin1\"],\"passwords\":[\"\",\"admin\",\"9999\",\"123456\",\"pass\",\"camera\",\"1234\",\"12345\",\"fliradmin\",\"system\",\"jvc\",\"meinsm\",\"root\",\"4321\",\"111111\",\"1111111\",\"password\",\"ikwd\",\"supervisor\",\"ubnt\",\"wbox123\",\"service\"]}",
|
||||
expectedResult: defaultCredentials,
|
||||
},
|
||||
{
|
||||
str: "{}",
|
||||
expectedResult: Credentials{},
|
||||
},
|
||||
{
|
||||
str: "{\"invalid_field\":42}",
|
||||
expectedResult: Credentials{},
|
||||
},
|
||||
{
|
||||
str: "not json",
|
||||
expectedResult: Credentials{},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
parsedCredentials, _ := ParseCredentialsFromString(test.str)
|
||||
assert.Equal(t, test.expectedResult, parsedCredentials, "unexpected result, parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRoutesFromString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
str string
|
||||
expectedResult Routes
|
||||
}{
|
||||
{
|
||||
str: "a\nb\nc",
|
||||
expectedResult: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
str: "a",
|
||||
expectedResult: []string{"a"},
|
||||
},
|
||||
{
|
||||
str: "",
|
||||
expectedResult: []string{""},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
parsedRoutes := ParseRoutesFromString(test.str)
|
||||
assert.Equal(t, test.expectedResult, parsedRoutes, "unexpected result, parse error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user