mirror of
https://github.com/activecm/rita
synced 2026-06-08 13:02:45 +00:00
b923c39e2d
* Update on config structure, functionality, and tests Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Extend subnet type to read/write from db, update tests Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com> * updated read file config test and subnet tests * fixed config and util tests, updated subnet related functions Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Remove error return from GetDefaultConfig Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com> * added json tags to database struct * Updated beacon weights validation for config * updates to score thresholds validation tags * changes to config subnet validation and testing * Update subnet.go * Write missing host entries to http to populate http_proto * Updating some fields to uint64 * WIP update some field types * Update zeek count types and fix tests * Add clickhouse credentials * Misc fixes * Update pointer * Add ability to mark datasets as sample datasets * fix column name * Fix datasets exiting import if hour is empty * Fix zeek count parsing from TSV files * Remove storing dns conns in arrays, Fix historical first seen dns lag * Remove unused columns * Update config.hjson * Update config.hjson * updated impact category score functions to use float64 Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Update subnet.go * Store import version in imports table * Fix duplicated SNI/IP long connections * Update subnet_test.go * Cleanup output * Rolling files updates (#39) * Limit number of days to import for rolling datasets * Fix breaking imports when import was interrupted * Remove debug output --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> * Omit parts of env from output * Set max for threat intel datasize * Remove SELinux neutering for QA * Add network size column * Fix http_proto for missing host, update tests for missing host fixes * Add online feeds to default config * Update sshprep (#45) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Update sshprep Add Bradley's suggestion of using head -1 to limit to a single address. --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> * Installer Behavior Tweaks (#41) * Add --yes flag to add-apt-repository command * Add missing sudo flags, make sure we're using the SUDO variable instead * Add ability to perform zone transfers (#48) * Store zone transfer records Co-Authored-By: moth <25512187+0x6d6f7468@users.noreply.github.com> * Update config * Add tests * Tests, connectivity test * Update tests --------- Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com> * Support RedHat/RHEL as a valid target (#47) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Supporrt RedHat/RHEL as a valid target --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * Fix tests (#49) * Fix tests * Update WalkFiles to use UTC * fixed issue with rolling datasets over 24hours old not getting historical first seen timestamp set (#52) * Change values from float32 to float64 (#50) * Switch float32 to float64 * Update threat category calculation to match CalculateBucketedScore (#51) --------- Co-authored-by: Liza Tsibur <liza@activecountermeasures.com> * Bump max query execution time default value * Use string instead of error for ZoneTransferConnectivityErrors struct fields (#61) * Upgrade Golang to version 1.24 (#59) (#60) * Replace get_url with shell and curl (#58) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Replace get_url with shell and curl * Use get_url by default, fall back to curl if it fails --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * add automated log transfer, AC-Hunter issue 135 (#62) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * add automated log transfer, PR135 * cron requires non-executable permission * Specify suggested YAML plugin and config in VSCode workspace * Linting and light cleanup * Update generate_installer.sh Download zeek_log_transport.sh to send to the sensor. * Create cron file if remote zeek installation * Only run zeek log import steps for remote sensor installations --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * Temporarily disable RITA/Zeek log transport until installer is modular (#66) * Uniform -y flag usage for repo management/package installation; Uniform SUDO variable usage (#68) * Resolve Installer Side Effects and Formalize RHEL Support (#73) * Add missing necessary wildcards for RHEL versions * Remove Ansible task replacing python3-requests to avoid RHEL distro installation side effects * Update supported distros in README * Update scoring defaults * Resolve Ansible Reboot Errors (#75) * Clean up conditionals; Fix reboot step for Ubuntu * Suppress erroneous error output on RPM systems, ignore errors on reboot necessity checks * Ignore missing host rows for openhttp (#76) * Fix integration tests due to prevalence (#77) --------- Co-authored-by: Liza Tsibur <liza@activecountermeasures.com> Co-authored-by: moth <moth@blackhillsinfosec.com> Co-authored-by: William Stearns <william.l.stearns@gmail.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com>
1410 lines
36 KiB
Go
1410 lines
36 KiB
Go
package util
|
|
|
|
import (
|
|
"crypto/md5" // #nosec G501
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-github/github"
|
|
"github.com/google/uuid"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewFixedStringHash(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
expected FixedString
|
|
expectedErr bool
|
|
}{
|
|
{
|
|
name: "Single string",
|
|
args: []string{"hello"},
|
|
expected: FixedString{
|
|
// #nosec G401 : this md5 is used for hashing, not for security
|
|
Data: md5.Sum([]byte("hello")),
|
|
},
|
|
expectedErr: false,
|
|
},
|
|
{
|
|
name: "Multiple strings",
|
|
args: []string{"hello", "world"},
|
|
expected: FixedString{
|
|
Data: md5.Sum([]byte("helloworld")), // #nosec G401
|
|
},
|
|
expectedErr: false,
|
|
},
|
|
|
|
{
|
|
name: "Combination of strings",
|
|
args: []string{"foo", "bar", "baz"},
|
|
expected: FixedString{
|
|
Data: md5.Sum([]byte("foobarbaz")), // #nosec G401
|
|
},
|
|
expectedErr: false,
|
|
},
|
|
{
|
|
name: "Whitespace strings",
|
|
args: []string{" ", " "},
|
|
expected: FixedString{
|
|
Data: md5.Sum([]byte(" ")), // #nosec G401
|
|
},
|
|
expectedErr: false,
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
args: []string{""},
|
|
expected: FixedString{
|
|
Data: md5.Sum([]byte("")), // #nosec G401
|
|
},
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "Multiple empty strings",
|
|
args: []string{"", ""},
|
|
expected: FixedString{
|
|
Data: md5.Sum([]byte("")), // #nosec G401
|
|
},
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "No arguments",
|
|
args: []string{},
|
|
expected: FixedString{},
|
|
expectedErr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := NewFixedStringHash(test.args...)
|
|
if test.expectedErr {
|
|
require.Error(t, err, "error was expected")
|
|
} else {
|
|
require.NoError(t, err, "generating hash should not produce an error")
|
|
require.Equal(t, test.expected, result, "hash should match expected value")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewFixedStringFromHex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected FixedString
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Valid Hex String",
|
|
input: "00112233445566778899aabbccddeeff",
|
|
expected: FixedString{
|
|
Data: [16]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Valid Hex String Shorter than 16 bytes",
|
|
input: "0011223344556677",
|
|
expected: FixedString{
|
|
Data: [16]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Valid Hex String Longer than 16 bytes",
|
|
input: "00112233445566778899aabbccddeeffaabbccddeeff",
|
|
expected: FixedString{
|
|
Data: [16]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Invalid Hex String",
|
|
input: "invalidhexstring",
|
|
expected: FixedString{},
|
|
expectedError: fmt.Errorf("error decoding hex string: "),
|
|
},
|
|
{
|
|
name: "Empty Hex String",
|
|
input: "",
|
|
expected: FixedString{},
|
|
expectedError: fmt.Errorf("hex string is empty"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := NewFixedStringFromHex(test.input)
|
|
|
|
if test.expectedError != nil {
|
|
require.Error(t, err, "error should not be nil")
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "converting hex to fixed string should not produce an error")
|
|
require.Equal(t, test.expected, result, "the result should match the expected value")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixedString_Hex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input FixedString
|
|
expected string
|
|
}{
|
|
{
|
|
name: "All Zeros",
|
|
input: FixedString{Data: [16]byte{}},
|
|
expected: "00000000000000000000000000000000",
|
|
},
|
|
{
|
|
name: "Mixed Data",
|
|
input: FixedString{Data: [16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}},
|
|
expected: "000102030405060708090A0B0C0D0E0F",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := test.input.Hex()
|
|
require.Equal(t, test.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixedString_MarshalBinary(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input FixedString
|
|
expected []byte
|
|
}{
|
|
{
|
|
name: "All Zeros",
|
|
input: FixedString{Data: [16]byte{}},
|
|
expected: make([]byte, 16),
|
|
},
|
|
{
|
|
name: "Mixed Data",
|
|
input: FixedString{Data: [16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}},
|
|
expected: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := test.input.MarshalBinary()
|
|
require.NoError(t, err)
|
|
require.Equal(t, test.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixedString_UnmarshalBinary(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expected FixedString
|
|
}{
|
|
{
|
|
name: "All Zeros",
|
|
input: make([]byte, 16),
|
|
expected: FixedString{Data: [16]byte{}},
|
|
},
|
|
{
|
|
name: "Mixed Data",
|
|
input: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF},
|
|
expected: FixedString{Data: [16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var result FixedString
|
|
err := result.UnmarshalBinary(test.input)
|
|
require.NoError(t, err)
|
|
require.Equal(t, test.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFixedString_Value(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input FixedString
|
|
expected driver.Value
|
|
}{
|
|
{
|
|
name: "Default Value",
|
|
input: FixedString{},
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "With Value",
|
|
input: FixedString{val: "example"},
|
|
expected: "example",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := test.input.Value()
|
|
require.NoError(t, err)
|
|
require.Equal(t, test.expected, *result.(*string))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidFQDN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Valid FQDN",
|
|
value: "example.com",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Valid FQDN with Multiple Subdomains",
|
|
value: "sub.example.com",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Valid FQDN with Hyphen",
|
|
value: "sub-domain.example.com",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Single Label",
|
|
value: "example",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Trailing Dot",
|
|
value: "example.com.",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid Underscore",
|
|
value: "sub_domain.example.com",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid Spaces",
|
|
value: "example .com",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid Special Characters",
|
|
value: "exa$mple.com",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "TLD Too Short",
|
|
value: "example.c",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty String",
|
|
value: "",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := ValidFQDN(test.value)
|
|
require.Equal(t, test.expected, result, "the result should match the expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainsIP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
subnets []Subnet
|
|
ip net.IP
|
|
contained bool
|
|
}{
|
|
{
|
|
name: "IP in subnet",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.IP{192, 168, 1, 0}.To16(), Mask: net.CIDRMask(24+96, 128)}},
|
|
},
|
|
ip: net.IP{192, 168, 1, 1},
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "IP not in subnet",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.IP{192, 168, 1, 0}.To16(), Mask: net.CIDRMask(24+96, 128)}},
|
|
},
|
|
ip: net.IP{10, 0, 0, 1},
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "IP in multiple subnets",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.IP{192, 168, 1, 0}.To16(), Mask: net.CIDRMask(24+96, 128)}},
|
|
{&net.IPNet{IP: net.IP{10, 0, 0, 0}.To16(), Mask: net.CIDRMask(8+96, 128)}},
|
|
},
|
|
ip: net.IP{10, 0, 0, 1},
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "IPv6 address in subnet",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.ParseIP("2001:db8::"), Mask: net.CIDRMask(32, 128)}},
|
|
},
|
|
ip: net.ParseIP("2001:db8::1"),
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "IPv6 address not in subnet",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.ParseIP("2001:db8::"), Mask: net.CIDRMask(32, 128)}},
|
|
},
|
|
ip: net.ParseIP("2001:db9::1"),
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "Empty subnets list",
|
|
subnets: []Subnet{},
|
|
ip: net.IP{192, 168, 1, 1},
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "IP in overlapping subnets",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.IP{192, 168, 0, 0}.To16(), Mask: net.CIDRMask(16+96, 128)}},
|
|
{&net.IPNet{IP: net.IP{192, 168, 1, 0}.To16(), Mask: net.CIDRMask(24+96, 128)}},
|
|
},
|
|
ip: net.IP{192, 168, 1, 1},
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "IP in smaller overlapping subnet",
|
|
subnets: []Subnet{
|
|
{&net.IPNet{IP: net.IP{192, 168, 0, 0}.To16(), Mask: net.CIDRMask(16+96, 128)}},
|
|
{&net.IPNet{IP: net.IP{192, 168, 1, 0}.To16(), Mask: net.CIDRMask(28+96, 128)}},
|
|
},
|
|
ip: net.IP{192, 168, 1, 1},
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "IP in IPv4 mapped IPv6 address",
|
|
subnets: NewTestSubnetList(t, []string{"::ffff:192.168.0.0/112", "::ffff:192.168.1.0/124"}),
|
|
ip: net.IP{192, 168, 1, 1},
|
|
contained: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := ContainsIP(test.subnets, test.ip)
|
|
require.Equal(t, test.contained, result, "contained should match expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPIsPubliclyRoutable(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ip net.IP
|
|
routable bool
|
|
}{
|
|
{
|
|
name: "Private IPv4 Address 1",
|
|
ip: net.IP{10, 0, 0, 1},
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Private IPv4 Address 2",
|
|
ip: net.IP{172, 16, 0, 1},
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Private IPv4 Address 3",
|
|
ip: net.IP{192, 168, 1, 1},
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Private IPv6 address",
|
|
ip: net.ParseIP("fc00::1"),
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Public IPv4 Address 1",
|
|
ip: net.IP{8, 8, 8, 8},
|
|
routable: true,
|
|
},
|
|
{
|
|
name: "Public IPv4 Address 2",
|
|
ip: net.IP{172, 217, 22, 14},
|
|
routable: true,
|
|
},
|
|
{
|
|
name: "Public IPv4 Address 3",
|
|
ip: net.IP{192, 0, 2, 0},
|
|
routable: true,
|
|
},
|
|
{
|
|
name: "Public IPv6 address",
|
|
ip: net.ParseIP("2001:4860:4860::8888"),
|
|
routable: true,
|
|
},
|
|
{
|
|
name: "Loopback IPv4 address",
|
|
ip: net.IP{127, 0, 0, 1},
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Link-local IPv4 Address",
|
|
ip: net.IP{169, 254, 0, 1},
|
|
routable: false,
|
|
},
|
|
|
|
{
|
|
name: "Loopback IPv6 address",
|
|
ip: net.ParseIP("::1"),
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Link-local IPv6 address",
|
|
ip: net.ParseIP("fe80::1"),
|
|
routable: false,
|
|
},
|
|
{
|
|
name: "Multicast IPv6 address",
|
|
ip: net.ParseIP("ff02::1"),
|
|
routable: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := IPIsPubliclyRoutable(test.ip)
|
|
require.Equal(t, test.routable, result, "routable should match expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseNetworkID(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip net.IP
|
|
agentUUID string
|
|
expectedNetworkID uuid.UUID
|
|
}{
|
|
{ // agent uuids get overridden if ip is public
|
|
name: "Public IPv4 - Valid Agent UUID",
|
|
ip: net.IP{8, 8, 8, 8},
|
|
agentUUID: "02b1300b-dc4f-46dd-967e-698ccde5a920",
|
|
expectedNetworkID: PublicNetworkUUID,
|
|
},
|
|
{
|
|
name: "Public IPv4 - Invalid Agent UUID",
|
|
ip: net.IP{8, 8, 8, 8},
|
|
agentUUID: "bingbong",
|
|
expectedNetworkID: PublicNetworkUUID,
|
|
},
|
|
{
|
|
name: "Public IPv6 - Valid Agent UUID",
|
|
ip: net.ParseIP("2001:4860:4860::8888"),
|
|
agentUUID: "eb3a01e7-ac8a-461c-9582-b1d8727d240c",
|
|
expectedNetworkID: PublicNetworkUUID,
|
|
},
|
|
{
|
|
name: "Public IPv6 - Invalid Agent UUID",
|
|
ip: net.ParseIP("2001:4860:4860::8888"),
|
|
agentUUID: "bingbong",
|
|
expectedNetworkID: PublicNetworkUUID,
|
|
},
|
|
{
|
|
name: "Private IPv4 - Valid Agent UUID",
|
|
ip: net.IP{192, 168, 1, 1},
|
|
agentUUID: "a9f1052c-fea4-4209-8362-7f33d2630bf2",
|
|
},
|
|
{
|
|
name: "Private IPv4 with Invalid Agent UUID",
|
|
ip: net.IP{192, 168, 1, 1},
|
|
agentUUID: "invalid-bing-bong",
|
|
expectedNetworkID: UnknownPrivateNetworkUUID,
|
|
},
|
|
{
|
|
name: "Private IPv6 - Valid Agent UUID",
|
|
ip: net.ParseIP("fc00::1"),
|
|
agentUUID: "2b648d28-26a1-460f-b417-651192562258",
|
|
},
|
|
{
|
|
name: "Private IPv6 - Invalid Agent UUID",
|
|
ip: net.ParseIP("fc00::1"),
|
|
agentUUID: "invalid-bing-bong",
|
|
expectedNetworkID: UnknownPrivateNetworkUUID,
|
|
},
|
|
{
|
|
name: "Private IP with Empty Agent UUID",
|
|
ip: net.IP{192, 168, 1, 1},
|
|
agentUUID: "",
|
|
expectedNetworkID: UnknownPrivateNetworkUUID,
|
|
},
|
|
{
|
|
name: "Loopback IP",
|
|
ip: net.IP{127, 0, 0, 1},
|
|
agentUUID: "some-agent-id",
|
|
expectedNetworkID: UnknownPrivateNetworkUUID,
|
|
},
|
|
{
|
|
name: "Link-local IP",
|
|
ip: net.IP{169, 254, 0, 1},
|
|
agentUUID: "some-agent-id",
|
|
expectedNetworkID: UnknownPrivateNetworkUUID,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := ParseNetworkID(test.ip, test.agentUUID)
|
|
|
|
// if expected is not nil, check if the result matches the expected value
|
|
// otherwise, parse the expected value and vet against the result
|
|
if test.expectedNetworkID != uuid.Nil {
|
|
require.Equal(t, test.expectedNetworkID, result, "network uuid should match expected value")
|
|
} else {
|
|
uuid, err := uuid.Parse(test.agentUUID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uuid, result, "network uuid should match expected value")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainsDomain(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
host string
|
|
domains []string
|
|
contained bool
|
|
}{
|
|
{
|
|
name: "Exact Match",
|
|
domains: []string{"bingbong", "test.com"},
|
|
host: "bingbong",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "No Match",
|
|
domains: []string{"bingbong", "test.com"},
|
|
host: "notindomain.com",
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "Wildcard Match",
|
|
domains: []string{"*.bingbong", "test.com"},
|
|
host: "sub.bingbong",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "Wildcard Top Domain",
|
|
domains: []string{"*.bingbong", "test.com"},
|
|
host: "bingbong",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "Wildcard Root",
|
|
domains: []string{"*.com"},
|
|
host: "bingbong.com",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "Wildcard, No Match",
|
|
domains: []string{"*.bingbong", "test.com"},
|
|
host: "sub.test.com",
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "Multiple Wildcards, Match",
|
|
domains: []string{"*.bingbong", "*.test.com"},
|
|
host: "sub.test.com",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "Multiple Wildcards, No Match",
|
|
domains: []string{"*.bingbong", "*.test.com"},
|
|
host: "sub.another.com",
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "Wildcard Match with Subdomain",
|
|
domains: []string{"*.bingbong", "test.com"},
|
|
host: "super.sub.bingbong",
|
|
contained: true,
|
|
},
|
|
{
|
|
name: "Empty Domains List",
|
|
domains: []string{},
|
|
host: "bingbong",
|
|
contained: false,
|
|
},
|
|
{
|
|
name: "Empty Host",
|
|
domains: []string{"bingbong", "test.com"},
|
|
host: "",
|
|
contained: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
contained := ContainsDomain(test.domains, test.host)
|
|
require.Equal(t, test.contained, contained, "contained should match expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSortUInt32s(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
data []uint32
|
|
expected []uint32
|
|
}{
|
|
{
|
|
name: "Already sorted",
|
|
data: []uint32{1, 2, 3, 4, 5},
|
|
expected: []uint32{1, 2, 3, 4, 5},
|
|
},
|
|
{
|
|
name: "Reverse order",
|
|
data: []uint32{5, 4, 3, 2, 1},
|
|
expected: []uint32{1, 2, 3, 4, 5},
|
|
},
|
|
{
|
|
name: "Unsorted",
|
|
data: []uint32{3, 1, 4, 5, 2},
|
|
expected: []uint32{1, 2, 3, 4, 5},
|
|
},
|
|
{
|
|
name: "With duplicates",
|
|
data: []uint32{3, 1, 4, 1, 5, 2, 3},
|
|
expected: []uint32{1, 1, 2, 3, 3, 4, 5},
|
|
},
|
|
{
|
|
name: "Single element",
|
|
data: []uint32{1},
|
|
expected: []uint32{1},
|
|
},
|
|
{
|
|
name: "Empty slice",
|
|
data: []uint32{},
|
|
expected: []uint32{},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
SortUInt32s(test.data)
|
|
require.Equal(t, test.expected, test.data, "the sorted data should match the expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUInt32sAreSorted(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
data []uint32
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Sorted data",
|
|
data: []uint32{1, 2, 3, 4, 5},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Unsorted data",
|
|
data: []uint32{5, 3, 4, 1, 2},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty data",
|
|
data: []uint32{},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Single element",
|
|
data: []uint32{42},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "All elements equal",
|
|
data: []uint32{7, 7, 7, 7},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := UInt32sAreSorted(test.data)
|
|
require.Equal(t, test.expected, result, "the result should match the expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTimestamp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
timestamp time.Time
|
|
expectedTime time.Time
|
|
replaced bool
|
|
}{
|
|
{
|
|
name: "Valid timestamp",
|
|
timestamp: time.Date(2024, time.June, 3, 23, 24, 10, 0, time.Local),
|
|
expectedTime: time.Date(2024, time.June, 3, 23, 24, 10, 0, time.Local),
|
|
replaced: false,
|
|
},
|
|
{
|
|
name: "Log Floating-Pont Timestamp",
|
|
timestamp: time.Unix(1517336108, int64((0.231879)*1e9)), // 1517336108.231879
|
|
expectedTime: time.Unix(1517336108, 231879000),
|
|
replaced: false,
|
|
},
|
|
{
|
|
name: "Unset Timestamp",
|
|
timestamp: time.Time{},
|
|
expectedTime: time.Unix(0, 1),
|
|
replaced: true,
|
|
},
|
|
{
|
|
name: "MaxInt64 timestamp",
|
|
timestamp: time.Unix(math.MaxInt64, 0),
|
|
expectedTime: time.Unix(0, 1),
|
|
replaced: true,
|
|
},
|
|
{
|
|
name: "Negative timestamp",
|
|
timestamp: time.Unix(-1, 0),
|
|
expectedTime: time.Unix(0, 1),
|
|
replaced: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ts, replaced := ValidateTimestamp(test.timestamp)
|
|
require.Equal(t, test.expectedTime, ts, "timestamp should match expected value")
|
|
require.Equal(t, test.replaced, replaced, "replaced should match expected value")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRelativeFirstSeenTimestamp(t *testing.T) {
|
|
|
|
t.Run("Use Current Time", func(t *testing.T) {
|
|
// make random max time
|
|
maxTime := time.Now().UTC().Add(-5 * time.Hour)
|
|
currentTime := time.Now()
|
|
ts := GetRelativeFirstSeenTimestamp(true, maxTime)
|
|
require.WithinDuration(t, currentTime, ts, time.Second)
|
|
require.NotEqual(t, maxTime, ts)
|
|
})
|
|
|
|
t.Run("Use Max Time", func(t *testing.T) {
|
|
maxTime := time.Now().UTC().Add(-5 * time.Hour)
|
|
ts := GetRelativeFirstSeenTimestamp(false, maxTime)
|
|
require.Equal(t, maxTime, ts)
|
|
})
|
|
}
|
|
|
|
func TestParseRelativePath(t *testing.T) {
|
|
home, err := os.UserHomeDir()
|
|
require.NoError(t, err)
|
|
|
|
workingDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
|
|
currentDir := path.Dir(path.Join(workingDir))
|
|
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
mockGetUserHomeDir func() (string, error)
|
|
mockGetWorkingDir func() (string, error)
|
|
expected string
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "Home directory",
|
|
path: "~/data",
|
|
expected: home + "/data",
|
|
},
|
|
{
|
|
name: "Current directory path",
|
|
path: "./",
|
|
expected: workingDir,
|
|
// expectedPath: filepath.Join(currentDir, "./mydir"),
|
|
},
|
|
{
|
|
name: "Relative directory - 1 deep",
|
|
path: "./data",
|
|
expected: workingDir + "/data",
|
|
},
|
|
{
|
|
name: "Relative directory - 2 deep",
|
|
path: "../data",
|
|
expected: currentDir + "/data",
|
|
},
|
|
{
|
|
name: "Absolute path",
|
|
path: "/home/logs",
|
|
expected: "/home/logs",
|
|
},
|
|
{
|
|
name: "Empty path",
|
|
expected: "",
|
|
expectedErr: ErrInvalidPath,
|
|
},
|
|
{
|
|
name: "Error Getting User Home Directory",
|
|
path: "~/data",
|
|
mockGetUserHomeDir: func() (string, error) {
|
|
return "", fmt.Errorf("forced get user home dir error")
|
|
},
|
|
expectedErr: fmt.Errorf("forced get user home dir error"),
|
|
},
|
|
{
|
|
name: "Error Getting Working Directory",
|
|
path: "./data",
|
|
mockGetWorkingDir: func() (string, error) {
|
|
return "", fmt.Errorf("forced get working dir error")
|
|
},
|
|
expectedErr: fmt.Errorf("forced get working dir error"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// restore the original functions after the test
|
|
origGetUserHomeDir := getUserHomeDir
|
|
origGetWorkingDir := getWorkingDir
|
|
defer func() { getUserHomeDir = origGetUserHomeDir; getWorkingDir = origGetWorkingDir }()
|
|
// mock functions if needed
|
|
if test.mockGetUserHomeDir != nil {
|
|
getUserHomeDir = test.mockGetUserHomeDir
|
|
}
|
|
if test.mockGetWorkingDir != nil {
|
|
getWorkingDir = test.mockGetWorkingDir
|
|
}
|
|
|
|
result, err := ParseRelativePath(test.path)
|
|
if test.expectedErr != nil {
|
|
require.EqualError(t, err, test.expectedErr.Error(), "error should match expected value")
|
|
} else {
|
|
require.NoError(t, err, "parsing relative path should not produce an error")
|
|
require.Equal(t, test.expected, result, "relative path should match expected value, got: %s, expected: %s", result, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDirectory(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(afs afero.Fs)
|
|
dir string
|
|
mockPathExists func(fs afero.Fs, path string) (bool, error)
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Directory is Valid",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afs.Mkdir("/nonemptydir", 0755))
|
|
require.NoError(t, afero.WriteFile(afs, "/nonemptydir/file.txt", []byte("content"), 0644))
|
|
},
|
|
dir: "/nonemptydir",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Directory Does Not Exist",
|
|
setup: func(_ afero.Fs) {},
|
|
dir: "/nonexistent",
|
|
expectedError: ErrDirDoesNotExist,
|
|
},
|
|
{
|
|
name: "Path is Not a Directory",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afero.WriteFile(afs, "/file.txt", []byte("content"), 0644))
|
|
},
|
|
dir: "/file.txt",
|
|
expectedError: ErrPathIsNotDir,
|
|
},
|
|
{
|
|
name: "Directory is Empty",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afs.Mkdir("/emptydir", 0755))
|
|
},
|
|
dir: "/emptydir",
|
|
expectedError: ErrDirIsEmpty,
|
|
},
|
|
{
|
|
name: "Validate Path Error",
|
|
setup: func(afs afero.Fs) {},
|
|
dir: "/some/path",
|
|
mockPathExists: func(fs afero.Fs, path string) (bool, error) {
|
|
return false, fmt.Errorf("forced existence check error")
|
|
},
|
|
expectedError: fmt.Errorf("forced existence check error"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// restore the original functions after the test
|
|
origPathExists := pathExists
|
|
defer func() { pathExists = origPathExists }()
|
|
|
|
// mock functions if needed
|
|
if test.mockPathExists != nil {
|
|
pathExists = test.mockPathExists
|
|
}
|
|
|
|
afs := afero.NewMemMapFs()
|
|
test.setup(afs)
|
|
|
|
err := ValidateDirectory(afs, test.dir)
|
|
if test.expectedError != nil {
|
|
require.Error(t, err, "error should not be nil")
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "validating directory should not produce an error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(afs afero.Fs)
|
|
file string
|
|
mockPathExists func(fs afero.Fs, path string) (bool, error)
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "File is Valid",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afero.WriteFile(afs, "/file.txt", []byte("content"), 0644))
|
|
},
|
|
file: "/file.txt",
|
|
},
|
|
{
|
|
name: "File is Empty",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afero.WriteFile(afs, "/emptyfile.txt", []byte(""), 0644))
|
|
},
|
|
file: "/emptyfile.txt",
|
|
expectedError: ErrFileIsEmtpy,
|
|
},
|
|
{
|
|
name: "File Does Not Exist",
|
|
setup: func(_ afero.Fs) {},
|
|
file: "/nonexistent",
|
|
expectedError: ErrFileDoesNotExist,
|
|
},
|
|
{
|
|
name: "Path is a Directory",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afs.Mkdir("/directory", 0755))
|
|
},
|
|
file: "/directory",
|
|
expectedError: ErrPathIsDir,
|
|
},
|
|
{
|
|
name: "Validate Path Error",
|
|
setup: func(afs afero.Fs) {},
|
|
file: "/some/path",
|
|
mockPathExists: func(fs afero.Fs, path string) (bool, error) {
|
|
return false, fmt.Errorf("forced existence check error")
|
|
},
|
|
expectedError: fmt.Errorf("forced existence check error"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// restore the original functions after the test
|
|
origPathExists := pathExists
|
|
defer func() { pathExists = origPathExists }()
|
|
|
|
// mock functions if needed
|
|
if test.mockPathExists != nil {
|
|
pathExists = test.mockPathExists
|
|
}
|
|
|
|
afs := afero.NewMemMapFs()
|
|
test.setup(afs)
|
|
|
|
err := ValidateFile(afs, test.file)
|
|
if test.expectedError != nil {
|
|
require.Error(t, err, "error should not be nil")
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "validating file should not produce an error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidatePath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(afs afero.Fs)
|
|
path string
|
|
mockPathExists func(fs afero.Fs, path string) (bool, error)
|
|
mockIsDirectory func(fs afero.Fs, path string) (bool, error)
|
|
mockIsEmpty func(fs afero.Fs, path string) (bool, error)
|
|
expected [3]bool // exists, isDir, isEmpty
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Path is Valid Non-Empty File",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afero.WriteFile(afs, "/file.txt", []byte("content"), 0644))
|
|
},
|
|
path: "/file.txt",
|
|
expected: [3]bool{true, false, false},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Path is Valid Empty File",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afero.WriteFile(afs, "/file.txt", []byte(""), 0644))
|
|
},
|
|
path: "/file.txt",
|
|
expected: [3]bool{true, false, true},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Path is Valid Non-Empty Directory",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afs.Mkdir("/nonemptydir", 0755))
|
|
require.NoError(t, afero.WriteFile(afs, "/nonemptydir/file.txt", []byte("content"), 0644))
|
|
},
|
|
path: "/nonemptydir",
|
|
expected: [3]bool{true, true, false},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Path is Valid Empty Directory",
|
|
setup: func(afs afero.Fs) {
|
|
require.NoError(t, afs.Mkdir("/emptydir", 0755))
|
|
},
|
|
path: "/emptydir",
|
|
expected: [3]bool{true, true, true},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Non-Existent Path",
|
|
setup: func(_ afero.Fs) {},
|
|
path: "/nonexistent",
|
|
expected: [3]bool{false, false, false},
|
|
expectedError: nil, // no error, just not found
|
|
},
|
|
{
|
|
name: "Empty Path",
|
|
setup: func(_ afero.Fs) {},
|
|
path: "",
|
|
expected: [3]bool{false, false, false},
|
|
expectedError: ErrInvalidPath,
|
|
},
|
|
{
|
|
name: "Nil filesystem",
|
|
setup: func(_ afero.Fs) {},
|
|
path: "/some/path",
|
|
expected: [3]bool{false, false, false},
|
|
expectedError: ErrFileSystemIsNil,
|
|
},
|
|
{
|
|
name: "Path Existece Check Error",
|
|
setup: func(_ afero.Fs) {},
|
|
path: "/some/path",
|
|
mockPathExists: func(fs afero.Fs, path string) (bool, error) { return false, fmt.Errorf("existence check forced error") },
|
|
expected: [3]bool{false, false, false},
|
|
expectedError: fmt.Errorf("existence check forced error"),
|
|
},
|
|
{
|
|
name: "Is Directory Check Error",
|
|
setup: func(afs afero.Fs) { require.NoError(t, afs.Mkdir("/emptydir", 0755)) },
|
|
path: "/emptydir",
|
|
mockIsDirectory: func(fs afero.Fs, path string) (bool, error) { return false, fmt.Errorf("isDir check forced error") },
|
|
expected: [3]bool{false, false, false},
|
|
expectedError: fmt.Errorf("isDir check forced error"),
|
|
},
|
|
{
|
|
name: "Is Empty Check Error",
|
|
setup: func(afs afero.Fs) { require.NoError(t, afs.Mkdir("/emptydir", 0755)) },
|
|
path: "/emptydir",
|
|
mockIsEmpty: func(fs afero.Fs, path string) (bool, error) { return false, fmt.Errorf("isEmpty check forced error") },
|
|
expected: [3]bool{true, true, false},
|
|
expectedError: fmt.Errorf("isEmpty check forced error"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// restore the original functions after the test
|
|
origPathExists := pathExists
|
|
origIsDirectory := isDirectory
|
|
origIsEmpty := isEmpty
|
|
defer func() { pathExists = origPathExists; isDirectory = origIsDirectory; isEmpty = origIsEmpty }()
|
|
|
|
// mock functions if needed
|
|
if test.mockPathExists != nil {
|
|
pathExists = test.mockPathExists
|
|
}
|
|
if test.mockIsDirectory != nil {
|
|
isDirectory = test.mockIsDirectory
|
|
}
|
|
if test.mockIsEmpty != nil {
|
|
isEmpty = test.mockIsEmpty
|
|
}
|
|
|
|
var afs afero.Fs
|
|
if test.name != "Nil filesystem" {
|
|
afs = afero.NewMemMapFs()
|
|
}
|
|
test.setup(afs)
|
|
|
|
exists, isDir, isEmpty, err := validatePath(afs, test.path)
|
|
|
|
if test.expectedError != nil {
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "validating path should not produce an error")
|
|
require.Equal(t, test.expected[0], exists, "exist flag should be %v", test.expected[0])
|
|
require.Equal(t, test.expected[1], isDir, "isDir flag should be %v", test.expected[1])
|
|
require.Equal(t, test.expected[2], isEmpty, "isEmpty flag should be %v", test.expected[2])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetFileContents(t *testing.T) {
|
|
// define test cases
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
fileContents []byte
|
|
mockReadFile func(afero.Fs, string) ([]byte, error)
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Valid Generated file",
|
|
path: "/valid/file/path",
|
|
fileContents: []byte("file contents"),
|
|
},
|
|
{
|
|
name: "Empty File",
|
|
path: "/invalid/file/path",
|
|
fileContents: []byte(""),
|
|
expectedError: ErrFileIsEmtpy,
|
|
},
|
|
{
|
|
name: "Invalid File Path",
|
|
path: "/missing/file/path",
|
|
expectedError: ErrFileDoesNotExist,
|
|
},
|
|
{
|
|
name: "Read File Error",
|
|
path: "/valid/file/path",
|
|
fileContents: []byte("file contents"),
|
|
mockReadFile: func(_ afero.Fs, _ string) ([]byte, error) {
|
|
return nil, fmt.Errorf("forced read file error")
|
|
},
|
|
expectedError: fmt.Errorf("forced read file error"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// restore the original function after the test
|
|
originalReadFileFunc := readFile
|
|
defer func() { readFile = originalReadFileFunc }()
|
|
|
|
// mock the readFile function
|
|
if test.mockReadFile != nil {
|
|
readFile = test.mockReadFile
|
|
}
|
|
|
|
// create a new memory filesystem
|
|
afs := afero.NewMemMapFs()
|
|
|
|
// create the file if the test case specifies contents
|
|
if test.fileContents != nil {
|
|
require.NoError(t, afero.WriteFile(afs, test.path, test.fileContents, 0644), "failed to create file")
|
|
}
|
|
|
|
// call readFile and check the results
|
|
result, err := GetFileContents(afs, test.path)
|
|
|
|
// validate results
|
|
if test.expectedError != nil {
|
|
require.Error(t, err, "expected an error but got none")
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
|
|
} else {
|
|
require.NoError(t, err, "did not expect an error but got one")
|
|
require.Equal(t, test.fileContents, result, "file contents should match expected value")
|
|
}
|
|
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestCheckForNewerVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
latestVersion string
|
|
currentVersion string
|
|
expectedNewer bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Newer version available",
|
|
latestVersion: "v1.1.0",
|
|
currentVersion: "v1.0.0",
|
|
expectedNewer: true,
|
|
},
|
|
{
|
|
name: "No newer version",
|
|
latestVersion: "v1.0.0",
|
|
currentVersion: "v1.0.0",
|
|
expectedNewer: false,
|
|
},
|
|
{
|
|
name: "Invalid current version",
|
|
latestVersion: "v1.1.0",
|
|
currentVersion: "invalid-version",
|
|
expectedNewer: false,
|
|
expectedError: ErrParsingCurrentVersion,
|
|
},
|
|
{
|
|
name: "Invalid latest version",
|
|
latestVersion: "invalid-version",
|
|
currentVersion: "v1.0.0",
|
|
expectedNewer: false,
|
|
expectedError: ErrParsingLatestVersion,
|
|
},
|
|
{
|
|
name: "Error Fetching Latest Release",
|
|
expectedError: ErrFetchingLatestRelease,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a test server
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
if tt.expectedError == ErrFetchingLatestRelease {
|
|
http.Error(w, "error", http.StatusInternalServerError)
|
|
} else {
|
|
fmt.Fprintf(w, `{"tag_name": "%s"}`, tt.latestVersion)
|
|
}
|
|
}))
|
|
defer ts.Close()
|
|
|
|
// Override the GitHub client base URL
|
|
client := github.NewClient(nil)
|
|
newBaseURL, err := client.BaseURL.Parse(ts.URL + "/")
|
|
require.NoError(t, err, "failed to parse base URL")
|
|
client.BaseURL = newBaseURL
|
|
|
|
// Check for newer version
|
|
newer, version, err := CheckForNewerVersion(client, tt.currentVersion)
|
|
|
|
// Check for expected error
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err, "error was expected")
|
|
require.ErrorContains(t, err, tt.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "checking for newer version should not produce an error")
|
|
|
|
// Check the expected values
|
|
require.Equal(t, tt.expectedNewer, newer)
|
|
require.Equal(t, tt.latestVersion, version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetLatestReleaseVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
owner string
|
|
repo string
|
|
latestVersion string
|
|
expected string
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Valid Latest Release",
|
|
owner: "activecm",
|
|
repo: "rita",
|
|
latestVersion: "v2.0.0",
|
|
expected: "v2.0.0",
|
|
},
|
|
{
|
|
name: "Error Fetching Latest Release",
|
|
owner: "activecm",
|
|
repo: "rita",
|
|
expected: "",
|
|
expectedError: ErrFetchingLatestRelease,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// Create a test server
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
if test.expectedError != nil {
|
|
http.Error(w, "error", http.StatusInternalServerError)
|
|
} else {
|
|
fmt.Fprintf(w, `{"tag_name": "%s"}`, test.latestVersion)
|
|
}
|
|
}))
|
|
defer ts.Close()
|
|
|
|
// Override the GitHub client base URL
|
|
client := github.NewClient(nil)
|
|
newBaseURL, err := client.BaseURL.Parse(ts.URL + "/")
|
|
require.NoError(t, err, "failed to parse base URL")
|
|
client.BaseURL = newBaseURL
|
|
|
|
result, err := GetLatestReleaseVersion(client, test.owner, test.repo)
|
|
|
|
if test.expectedError != nil {
|
|
require.Error(t, err, "error should not be nil")
|
|
require.ErrorContains(t, err, test.expectedError.Error(), "error should contain expected value")
|
|
} else {
|
|
require.NoError(t, err, "fetching latest release should not produce an error")
|
|
require.Equal(t, test.expected, result, "the result should match the expected value")
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|