This commit is contained in:
1 parent
bca550374f
commit
f854cbace5
8 files changed
+343
-25
No files matched your search
@@ -178,26 +178,3 @@ func TestTargetNaming(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
size int64
|
||||
expected string
|
||||
}{
|
||||
{500, "500 B"},
|
||||
{1024, "1.0 KiB"},
|
||||
{1500, "1.5 KiB"},
|
||||
{1048576, "1.0 MiB"},
|
||||
{1572864, "1.5 MiB"},
|
||||
{1073741824, "1.0 GiB"},
|
||||
{1610612736, "1.5 GiB"},
|
||||
{1099511627776, "1.0 TiB"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := formatBytes(test.size)
|
||||
if result != test.expected {
|
||||
t.Errorf("For %d bytes expected '%s', got '%s'", test.size, test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var osExit = os.Exit
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Backup your data",
|
||||
@@ -14,6 +16,6 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
osExit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Вспомогательная структура для перехвата вызовов osExit
|
||||
type exitCapture struct {
|
||||
code int
|
||||
called bool
|
||||
}
|
||||
|
||||
func (e *exitCapture) capture(code int) {
|
||||
e.code = code
|
||||
e.called = true
|
||||
}
|
||||
|
||||
func TestExecute_Success(t *testing.T) {
|
||||
// Сохраняем оригинальный osExit
|
||||
originalExit := osExit
|
||||
defer func() { osExit = originalExit }()
|
||||
|
||||
// Настраиваем перехват
|
||||
capture := &exitCapture{}
|
||||
osExit = capture.capture
|
||||
|
||||
// Запускаем выполнение
|
||||
Execute()
|
||||
|
||||
// Проверяем, что выход не вызывался
|
||||
if capture.called {
|
||||
t.Errorf("osExit called with code %d, expected no call", capture.code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecute_Error(t *testing.T) {
|
||||
originalExit := osExit
|
||||
defer func() { osExit = originalExit }()
|
||||
|
||||
capture := &exitCapture{}
|
||||
osExit = capture.capture
|
||||
|
||||
// Подменяем rootCmd на команду, возвращающую ошибку
|
||||
originalCmd := rootCmd
|
||||
defer func() { rootCmd = originalCmd }()
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "error-cmd",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return assert.AnError // Имитация ошибки
|
||||
},
|
||||
}
|
||||
|
||||
// Вызываем Execute
|
||||
Execute()
|
||||
|
||||
// Проверяем, что osExit вызван с кодом 1
|
||||
if !capture.called {
|
||||
t.Fatal("osExit not called")
|
||||
}
|
||||
if capture.code != 1 {
|
||||
t.Errorf("expected exit code 1, got %d", capture.code)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -47,6 +48,15 @@ func parseServerTarget(target string) (*ServerTargetInfo, bool) {
|
||||
return nil, false // Invalid format
|
||||
}
|
||||
|
||||
addressport := strings.SplitN(parts[0], ":", 2)
|
||||
if len(addressport) != 2 {
|
||||
return nil, false // Invalid format
|
||||
}
|
||||
|
||||
if _, err := strconv.Atoi(addressport[1]); err != nil {
|
||||
return nil, false // Invalid port
|
||||
}
|
||||
|
||||
address := parts[0]
|
||||
filename := parts[1]
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsNetworkAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"Valid IPv4 with port", "192.168.1.1:8080", true},
|
||||
{"Valid hostname with port", "example.com:443", true},
|
||||
{"IPv6 with port", "[::1]:8080", true},
|
||||
{"Missing port", "127.0.0.1", false},
|
||||
{"Port only", ":8080", true}, // технически валидный формат
|
||||
{"Empty string", "", false},
|
||||
{"Non-address string", "hello/world", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := isNetworkAddress(tt.input)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
size int64
|
||||
expected string
|
||||
}{
|
||||
{500, "500 B"},
|
||||
{1024, "1.0 KiB"},
|
||||
{1500, "1.5 KiB"},
|
||||
{1048576, "1.0 MiB"},
|
||||
{1572864, "1.5 MiB"},
|
||||
{1073741824, "1.0 GiB"},
|
||||
{1610612736, "1.5 GiB"},
|
||||
{1099511627776, "1.0 TiB"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := formatBytes(test.size)
|
||||
if result != test.expected {
|
||||
t.Errorf("For %d bytes expected '%s', got '%s'", test.size, test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseServerTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantInfo *ServerTargetInfo
|
||||
wantSuccess bool
|
||||
}{
|
||||
{
|
||||
name: "Valid address and filename",
|
||||
input: "localhost:8080/backup.tar.gz",
|
||||
wantInfo: &ServerTargetInfo{
|
||||
URL: "http://localhost:8080/backup/backup.tar.gz",
|
||||
Address: "localhost:8080",
|
||||
Filename: "backup.tar.gz",
|
||||
},
|
||||
wantSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Missing filename",
|
||||
input: "127.0.0.1:8000/",
|
||||
wantInfo: nil,
|
||||
wantSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Missing port",
|
||||
input: "example.com/backup.gz",
|
||||
wantInfo: nil,
|
||||
wantSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid host:port",
|
||||
input: "invalid_host:port/backup",
|
||||
wantInfo: nil,
|
||||
wantSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
input: "",
|
||||
wantInfo: nil,
|
||||
wantSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "No slash separator",
|
||||
input: "localhost:8080",
|
||||
wantInfo: nil,
|
||||
wantSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple slashes",
|
||||
input: "host:1234/path/to/backup.gz",
|
||||
wantInfo: &ServerTargetInfo{
|
||||
URL: "http://host:1234/backup/path/to/backup.gz",
|
||||
Address: "host:1234",
|
||||
Filename: "path/to/backup.gz",
|
||||
},
|
||||
wantSuccess: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotInfo, gotSuccess := parseServerTarget(tt.input)
|
||||
require.Equal(t, tt.wantSuccess, gotSuccess)
|
||||
|
||||
if tt.wantSuccess {
|
||||
require.Equal(t, tt.wantInfo, gotInfo)
|
||||
} else {
|
||||
require.Nil(t, gotInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressTracker(t *testing.T) {
|
||||
t.Run("Write sends delta", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
ch := make(chan int64, 10)
|
||||
data := []byte("test data")
|
||||
|
||||
pt := &progressTracker{
|
||||
Writer: &buf,
|
||||
progressCh: ch,
|
||||
}
|
||||
|
||||
n, err := pt.Write(data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(data), n)
|
||||
require.Equal(t, data, buf.Bytes())
|
||||
|
||||
require.Len(t, ch, 1)
|
||||
require.Equal(t, int64(len(data)), <-ch)
|
||||
})
|
||||
|
||||
t.Run("Read sends cumulative total", func(t *testing.T) {
|
||||
data := []byte("sample data")
|
||||
src := bytes.NewReader(data)
|
||||
ch := make(chan int64, 10)
|
||||
|
||||
pt := &progressTracker{
|
||||
Reader: src,
|
||||
progressCh: ch,
|
||||
}
|
||||
|
||||
// First read
|
||||
buf1 := make([]byte, 6)
|
||||
n1, err1 := pt.Read(buf1)
|
||||
require.NoError(t, err1)
|
||||
require.Equal(t, 6, n1)
|
||||
|
||||
// Second read
|
||||
buf2 := make([]byte, 10)
|
||||
n2, err2 := pt.Read(buf2)
|
||||
require.NoError(t, err2)
|
||||
require.Equal(t, 5, n2)
|
||||
|
||||
require.Len(t, ch, 2)
|
||||
require.Equal(t, int64(6), <-ch)
|
||||
require.Equal(t, int64(11), <-ch)
|
||||
})
|
||||
|
||||
t.Run("Read handles EOF correctly", func(t *testing.T) {
|
||||
src := bytes.NewReader([]byte{1, 2, 3})
|
||||
ch := make(chan int64, 3)
|
||||
|
||||
pt := &progressTracker{
|
||||
Reader: src,
|
||||
progressCh: ch,
|
||||
}
|
||||
|
||||
buf := make([]byte, 5)
|
||||
|
||||
// First read (3 bytes + no error)
|
||||
n, err := pt.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, n)
|
||||
require.Equal(t, []byte{1, 2, 3}, buf[:n])
|
||||
|
||||
// Second read (0 bytes + EOF)
|
||||
n, err = pt.Read(buf)
|
||||
require.Equal(t, io.EOF, err)
|
||||
require.Equal(t, 0, n)
|
||||
|
||||
// Only first read sends progress
|
||||
require.Len(t, ch, 1)
|
||||
require.Equal(t, int64(3), <-ch)
|
||||
})
|
||||
|
||||
t.Run("Read with immediate EOF", func(t *testing.T) {
|
||||
// Custom reader that returns 0 and EOF immediately
|
||||
r := &eofReader{}
|
||||
ch := make(chan int64, 1)
|
||||
|
||||
pt := &progressTracker{
|
||||
Reader: r,
|
||||
progressCh: ch,
|
||||
}
|
||||
|
||||
buf := make([]byte, 5)
|
||||
n, err := pt.Read(buf)
|
||||
require.Equal(t, io.EOF, err)
|
||||
require.Equal(t, 0, n)
|
||||
|
||||
// No progress should be sent
|
||||
require.Len(t, ch, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// eofReader всегда возвращает EOF
|
||||
type eofReader struct{}
|
||||
|
||||
func (r *eofReader) Read(p []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
Reference in new issue
Block a user