Use mock for all tests
This commit is contained in:
parent
8019e67744
commit
55546487cf
@ -1,14 +1,10 @@
|
|||||||
language: go
|
language: go
|
||||||
dist: trusty
|
|
||||||
sudo: required
|
sudo: required
|
||||||
|
dist: xenial
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
- 1.10.x
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
env:
|
|
||||||
- FTP_SERVER=vsftpd
|
|
||||||
- FTP_SERVER=proftpd
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo $TRAVIS_BUILD_DIR/.travis/prepare.sh "$FTP_SERVER"
|
|
||||||
- sudo sysctl net.ipv6.conf.lo.disable_ipv6=0
|
- sudo sysctl net.ipv6.conf.lo.disable_ipv6=0
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- go get github.com/golang/lint/golint
|
- go get github.com/golang/lint/golint
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
#!/bin/sh -e
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
proftpd)
|
|
||||||
mkdir -p /etc/proftpd/conf.d/
|
|
||||||
cp $TRAVIS_BUILD_DIR/.travis/proftpd.conf /etc/proftpd/conf.d/
|
|
||||||
;;
|
|
||||||
vsftpd)
|
|
||||||
cp $TRAVIS_BUILD_DIR/.travis/vsftpd.conf /etc/vsftpd.conf
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown software: $1"
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
mkdir --mode 0777 -p /var/ftp/incoming
|
|
||||||
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -qq "$1"
|
|
@ -1,9 +0,0 @@
|
|||||||
<Anonymous /var/ftp>
|
|
||||||
User ftp
|
|
||||||
Group nogroup
|
|
||||||
MaxClients 2
|
|
||||||
# We want clients to be able to login with "anonymous" as well as "ftp"
|
|
||||||
UserAlias anonymous ftp
|
|
||||||
|
|
||||||
RequireValidShell off
|
|
||||||
</Anonymous>
|
|
@ -1,15 +0,0 @@
|
|||||||
# Used by Travis CI
|
|
||||||
|
|
||||||
listen=NO
|
|
||||||
listen_ipv6=YES
|
|
||||||
|
|
||||||
write_enable=YES
|
|
||||||
dirmessage_enable=YES
|
|
||||||
secure_chroot_dir=/var/run/vsftpd/empty
|
|
||||||
|
|
||||||
anonymous_enable=YES
|
|
||||||
anon_root=/var/ftp
|
|
||||||
anon_upload_enable=YES
|
|
||||||
anon_mkdir_write_enable=YES
|
|
||||||
anon_other_write_enable=YES
|
|
||||||
anon_umask=022
|
|
@ -1,107 +0,0 @@
|
|||||||
package ftp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/textproto"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ftpMock struct {
|
|
||||||
listener net.Listener
|
|
||||||
commands []string // list of received commands
|
|
||||||
sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFtpMock(t *testing.T, addresss string) *ftpMock {
|
|
||||||
var err error
|
|
||||||
mock := &ftpMock{}
|
|
||||||
mock.listener, err = net.Listen("tcp", addresss)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Listen for an incoming connection.
|
|
||||||
conn, err := mock.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.Add(1)
|
|
||||||
defer mock.Done()
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
proto := textproto.NewConn(conn)
|
|
||||||
proto.Writer.PrintfLine("220 FTP Server ready.")
|
|
||||||
|
|
||||||
for {
|
|
||||||
command, _ := proto.ReadLine()
|
|
||||||
|
|
||||||
// Strip the arguments
|
|
||||||
if i := strings.Index(command, " "); i > 0 {
|
|
||||||
command = command[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append to list of received commands
|
|
||||||
mock.commands = append(mock.commands, command)
|
|
||||||
|
|
||||||
// At least one command must have a multiline response
|
|
||||||
switch command {
|
|
||||||
case "FEAT":
|
|
||||||
proto.Writer.PrintfLine("211-Features:\r\nFEAT\r\nPASV\r\nSIZE\r\n211 End")
|
|
||||||
case "USER":
|
|
||||||
proto.Writer.PrintfLine("331 Please send your password")
|
|
||||||
case "PASS":
|
|
||||||
proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
|
|
||||||
case "TYPE":
|
|
||||||
proto.Writer.PrintfLine("200 Type set ok")
|
|
||||||
case "QUIT":
|
|
||||||
proto.Writer.PrintfLine("221 Goodbye.")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
t.Fatal("unknown command:", command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closes the listening socket
|
|
||||||
func (mock *ftpMock) Close() {
|
|
||||||
mock.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ftp.mozilla.org uses multiline 220 response
|
|
||||||
func TestMultiline(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
|
||||||
|
|
||||||
address := "localhost:2121"
|
|
||||||
mock := newFtpMock(t, address)
|
|
||||||
defer mock.Close()
|
|
||||||
|
|
||||||
c, err := Dial(address)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
|
|
||||||
// Wait for the connection to close
|
|
||||||
mock.Wait()
|
|
||||||
|
|
||||||
expected := []string{"FEAT", "USER", "PASS", "TYPE", "QUIT"}
|
|
||||||
if !reflect.DeepEqual(mock.commands, expected) {
|
|
||||||
t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected)
|
|
||||||
}
|
|
||||||
}
|
|
295
client_test.go
295
client_test.go
@ -23,21 +23,15 @@ func TestConnEPSV(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testConn(t *testing.T, disableEPSV bool) {
|
func testConn(t *testing.T, disableEPSV bool) {
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := DialTimeout("localhost:21", 5*time.Second)
|
mock, c := openConn(t, "127.0.0.1")
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if disableEPSV {
|
if disableEPSV {
|
||||||
delete(c.features, "EPSV")
|
delete(c.features, "EPSV")
|
||||||
c.DisableEPSV = true
|
c.DisableEPSV = true
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
err := c.Login("anonymous", "anonymous")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -52,6 +46,15 @@ func testConn(t *testing.T, disableEPSV bool) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dir, err := c.CurrentDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
if dir != "/incoming" {
|
||||||
|
t.Error("Wrong dir: " + dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data := bytes.NewBufferString(testData)
|
data := bytes.NewBufferString(testData)
|
||||||
err = c.Stor("test", data)
|
err = c.Stor("test", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,26 +118,12 @@ func testConn(t *testing.T, disableEPSV bool) {
|
|||||||
r.Close()
|
r.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fileSize, err := c.FileSize("tset")
|
fileSize, err := c.FileSize("magic-file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if fileSize != 14 {
|
if fileSize != 42 {
|
||||||
t.Errorf("file size %q, expected %q", fileSize, 14)
|
t.Errorf("file size %q, expected %q", fileSize, 42)
|
||||||
}
|
|
||||||
|
|
||||||
data = bytes.NewBufferString("")
|
|
||||||
err = c.Stor("tset", data)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileSize, err = c.FileSize("tset")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if fileSize != 0 {
|
|
||||||
t.Errorf("file size %q, expected %q", fileSize, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.FileSize("not-found")
|
_, err = c.FileSize("not-found")
|
||||||
@ -157,15 +146,6 @@ func testConn(t *testing.T, disableEPSV bool) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := c.CurrentDir()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else {
|
|
||||||
if dir != "/incoming/"+testDir {
|
|
||||||
t.Error("Wrong dir: " + dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDirToParent()
|
err = c.ChangeDirToParent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -195,7 +175,12 @@ func testConn(t *testing.T, disableEPSV bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Quit()
|
if err := c.Quit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the connection to close
|
||||||
|
mock.Wait()
|
||||||
|
|
||||||
err = c.NoOp()
|
err = c.NoOp()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -203,41 +188,23 @@ func testConn(t *testing.T, disableEPSV bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnIPv6(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := DialTimeout("[::1]:21", 5*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.List(".")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestConnect tests the legacy Connect function
|
// TestConnect tests the legacy Connect function
|
||||||
func TestConnect(t *testing.T) {
|
func TestConnect(t *testing.T) {
|
||||||
if testing.Short() {
|
mock, err := newFtpMock(t, "127.0.0.1")
|
||||||
t.Skip("skipping test in short mode.")
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
c, err := Connect("localhost:21")
|
c, err := Connect(mock.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Quit()
|
if err := c.Quit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mock.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeout(t *testing.T) {
|
func TestTimeout(t *testing.T) {
|
||||||
@ -253,11 +220,13 @@ func TestTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWrongLogin(t *testing.T) {
|
func TestWrongLogin(t *testing.T) {
|
||||||
if testing.Short() {
|
mock, err := newFtpMock(t, "127.0.0.1")
|
||||||
t.Skip("skipping test in short mode.")
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
c, err := DialTimeout("localhost:21", 5*time.Second)
|
c, err := DialTimeout(mock.Addr(), 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -270,189 +239,49 @@ func TestWrongLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteDirRecur(t *testing.T) {
|
func TestDeleteDirRecur(t *testing.T) {
|
||||||
if testing.Short() {
|
mock, c := openConn(t, "127.0.0.1")
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
err := c.RemoveDirRecur("testDir")
|
||||||
c, err := DialTimeout("localhost:21", 5*time.Second)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Quit(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
// Wait for the connection to close
|
||||||
if err != nil {
|
mock.Wait()
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.NoOp()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDir("incoming")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.MakeDir("testDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDir("testDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.MakeDir("anotherDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data := bytes.NewBufferString("test text")
|
|
||||||
err = c.Stor("fileTest", data)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDirToParent()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = c.RemoveDirRecur("testDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
dir, err := c.CurrentDir()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else {
|
|
||||||
if dir != "/incoming" {
|
|
||||||
t.Error("Wrong dir: " + dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDir("testDir")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error, got nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Logout()
|
|
||||||
if err != nil {
|
|
||||||
if protoErr := err.(*textproto.Error); protoErr != nil {
|
|
||||||
if protoErr.Code != StatusNotImplemented {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileDeleteDirRecur(t *testing.T) {
|
// func TestFileDeleteDirRecur(t *testing.T) {
|
||||||
if testing.Short() {
|
// mock, c := openConn(t, "127.0.0.1")
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := DialTimeout("localhost:21", 5*time.Second)
|
// err := c.RemoveDirRecur("testFile")
|
||||||
if err != nil {
|
// if err == nil {
|
||||||
t.Fatal(err)
|
// t.Fatal("expected error got nil")
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
// if err := c.Quit(); err != nil {
|
||||||
if err != nil {
|
// t.Fatal(err)
|
||||||
t.Fatal(err)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDir("incoming")
|
// // Wait for the connection to close
|
||||||
if err != nil {
|
// mock.Wait()
|
||||||
t.Error(err)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
data := bytes.NewBufferString(testData)
|
|
||||||
err = c.Stor("testFile", data)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.RemoveDirRecur("testFile")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error got nill")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := c.CurrentDir()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else {
|
|
||||||
if dir != "/incoming" {
|
|
||||||
t.Error("Wrong dir: " + dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Delete("testFile")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Logout()
|
|
||||||
if err != nil {
|
|
||||||
if protoErr := err.(*textproto.Error); protoErr != nil {
|
|
||||||
if protoErr.Code != StatusNotImplemented {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMissingFolderDeleteDirRecur(t *testing.T) {
|
func TestMissingFolderDeleteDirRecur(t *testing.T) {
|
||||||
if testing.Short() {
|
mock, c := openConn(t, "127.0.0.1")
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := DialTimeout("localhost:21", 5*time.Second)
|
err := c.RemoveDirRecur("missing-dir")
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Login("anonymous", "anonymous")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.ChangeDir("incoming")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.RemoveDirRecur("test")
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error got nill")
|
t.Fatal("expected error got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := c.CurrentDir()
|
if err := c.Quit(); err != nil {
|
||||||
if err != nil {
|
t.Fatal(err)
|
||||||
t.Error(err)
|
|
||||||
} else {
|
|
||||||
if dir != "/incoming" {
|
|
||||||
t.Error("Wrong dir: " + dir)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Logout()
|
// Wait for the connection to close
|
||||||
if err != nil {
|
mock.Wait()
|
||||||
if protoErr := err.(*textproto.Error); protoErr != nil {
|
|
||||||
if protoErr.Code != StatusNotImplemented {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Quit()
|
|
||||||
}
|
}
|
||||||
|
335
conn_test.go
Normal file
335
conn_test.go
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package ftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/textproto"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ftpMock struct {
|
||||||
|
address string
|
||||||
|
listener *net.TCPListener
|
||||||
|
proto *textproto.Conn
|
||||||
|
commands []string // list of received commands
|
||||||
|
rest int
|
||||||
|
dataConn *mockDataConn
|
||||||
|
sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFtpMock returns a mock implementation of a FTP server
|
||||||
|
// For simplication, a mock instance only accepts a signle connection and terminates afer
|
||||||
|
func newFtpMock(t *testing.T, address string) (*ftpMock, error) {
|
||||||
|
var err error
|
||||||
|
mock := &ftpMock{address: address}
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", address+":0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpListener, ok := l.(*net.TCPListener)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("listener is not a net.TCPListener")
|
||||||
|
}
|
||||||
|
mock.listener = tcpListener
|
||||||
|
|
||||||
|
go mock.listen(t)
|
||||||
|
|
||||||
|
return mock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *ftpMock) listen(t *testing.T) {
|
||||||
|
// Listen for an incoming connection.
|
||||||
|
conn, err := mock.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not accept: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not accept incoming connections anymore
|
||||||
|
mock.listener.Close()
|
||||||
|
|
||||||
|
mock.Add(1)
|
||||||
|
defer mock.Done()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
mock.proto = textproto.NewConn(conn)
|
||||||
|
mock.proto.Writer.PrintfLine("220 FTP Server ready.")
|
||||||
|
|
||||||
|
for {
|
||||||
|
fullCommand, _ := mock.proto.ReadLine()
|
||||||
|
|
||||||
|
cmdParts := strings.Split(fullCommand, " ")
|
||||||
|
|
||||||
|
// Append to list of received commands
|
||||||
|
mock.commands = append(mock.commands, cmdParts[0])
|
||||||
|
|
||||||
|
// At least one command must have a multiline response
|
||||||
|
switch cmdParts[0] {
|
||||||
|
case "FEAT":
|
||||||
|
mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n SIZE\r\n211 End")
|
||||||
|
case "USER":
|
||||||
|
if cmdParts[1] == "anonymous" {
|
||||||
|
mock.proto.Writer.PrintfLine("331 Please send your password")
|
||||||
|
} else {
|
||||||
|
mock.proto.Writer.PrintfLine("530 This FTP server is anonymous only")
|
||||||
|
}
|
||||||
|
case "PASS":
|
||||||
|
mock.proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
|
||||||
|
case "TYPE":
|
||||||
|
mock.proto.Writer.PrintfLine("200 Type set ok")
|
||||||
|
case "CWD":
|
||||||
|
if cmdParts[1] == "missing-dir" {
|
||||||
|
mock.proto.Writer.PrintfLine("550 %s: No such file or directory", cmdParts[1])
|
||||||
|
} else {
|
||||||
|
mock.proto.Writer.PrintfLine("250 Directory successfully changed.")
|
||||||
|
}
|
||||||
|
case "DELE":
|
||||||
|
mock.proto.Writer.PrintfLine("250 File successfully removed.")
|
||||||
|
case "MKD":
|
||||||
|
mock.proto.Writer.PrintfLine("257 Directory successfully created.")
|
||||||
|
case "RMD":
|
||||||
|
if cmdParts[1] == "missing-dir" {
|
||||||
|
mock.proto.Writer.PrintfLine("550 No such file or directory")
|
||||||
|
} else {
|
||||||
|
mock.proto.Writer.PrintfLine("250 Directory successfully removed.")
|
||||||
|
}
|
||||||
|
case "PWD":
|
||||||
|
mock.proto.Writer.PrintfLine("257 \"/incoming\"")
|
||||||
|
case "CDUP":
|
||||||
|
mock.proto.Writer.PrintfLine("250 CDUP command successful")
|
||||||
|
case "SIZE":
|
||||||
|
if cmdParts[1] == "magic-file" {
|
||||||
|
mock.proto.Writer.PrintfLine("213 42")
|
||||||
|
} else {
|
||||||
|
mock.proto.Writer.PrintfLine("550 Could not get file size.")
|
||||||
|
}
|
||||||
|
case "PASV":
|
||||||
|
p, err := mock.listenDataConn()
|
||||||
|
if err != nil {
|
||||||
|
mock.proto.Writer.PrintfLine("451 %s.", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p1 := int(p / 256)
|
||||||
|
p2 := p % 256
|
||||||
|
|
||||||
|
mock.proto.Writer.PrintfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2)
|
||||||
|
case "EPSV":
|
||||||
|
p, err := mock.listenDataConn()
|
||||||
|
if err != nil {
|
||||||
|
mock.proto.Writer.PrintfLine("451 %s.", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mock.proto.Writer.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", p)
|
||||||
|
case "STOR":
|
||||||
|
if mock.dataConn == nil {
|
||||||
|
mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mock.proto.Writer.PrintfLine("150 please send")
|
||||||
|
mock.recvDataConn()
|
||||||
|
case "LIST":
|
||||||
|
if mock.dataConn == nil {
|
||||||
|
mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.dataConn.Wait()
|
||||||
|
mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
|
||||||
|
mock.dataConn.conn.Write([]byte("-rw-r--r-- 1 ftp wheel 0 Jan 29 10:29 lo"))
|
||||||
|
mock.proto.Writer.PrintfLine("226 Transfer complete")
|
||||||
|
mock.closeDataConn()
|
||||||
|
case "NLST":
|
||||||
|
if mock.dataConn == nil {
|
||||||
|
mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.dataConn.Wait()
|
||||||
|
mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
|
||||||
|
mock.dataConn.conn.Write([]byte("/incoming"))
|
||||||
|
mock.proto.Writer.PrintfLine("226 Transfer complete")
|
||||||
|
mock.closeDataConn()
|
||||||
|
case "RETR":
|
||||||
|
if mock.dataConn == nil {
|
||||||
|
mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.dataConn.Wait()
|
||||||
|
mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
|
||||||
|
mock.dataConn.conn.Write([]byte(testData[mock.rest:]))
|
||||||
|
mock.rest = 0
|
||||||
|
mock.proto.Writer.PrintfLine("226 Transfer complete")
|
||||||
|
mock.closeDataConn()
|
||||||
|
case "RNFR":
|
||||||
|
mock.proto.Writer.PrintfLine("350 File or directory exists, ready for destination name")
|
||||||
|
case "RNTO":
|
||||||
|
mock.proto.Writer.PrintfLine("250 Rename successful")
|
||||||
|
case "REST":
|
||||||
|
if len(cmdParts) != 2 {
|
||||||
|
mock.proto.Writer.PrintfLine("500 wrong number of arguments")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rest, err := strconv.Atoi(cmdParts[1])
|
||||||
|
if err != nil {
|
||||||
|
mock.proto.Writer.PrintfLine("500 REST: %s", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mock.rest = rest
|
||||||
|
mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1])
|
||||||
|
case "NOOP":
|
||||||
|
mock.proto.Writer.PrintfLine("200 NOOP ok.")
|
||||||
|
case "REIN":
|
||||||
|
mock.proto.Writer.PrintfLine("220 Logged out")
|
||||||
|
case "QUIT":
|
||||||
|
mock.proto.Writer.PrintfLine("221 Goodbye.")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
mock.proto.Writer.PrintfLine("500 Unknown command %s.", cmdParts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *ftpMock) closeDataConn() (err error) {
|
||||||
|
if mock.dataConn != nil {
|
||||||
|
err = mock.dataConn.Close()
|
||||||
|
mock.dataConn = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockDataConn struct {
|
||||||
|
listener *net.TCPListener
|
||||||
|
conn net.Conn
|
||||||
|
// WaitGroup is done when conn is accepted and stored
|
||||||
|
sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *mockDataConn) Close() (err error) {
|
||||||
|
if d.listener != nil {
|
||||||
|
err = d.listener.Close()
|
||||||
|
}
|
||||||
|
if d.conn != nil {
|
||||||
|
err = d.conn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *ftpMock) listenDataConn() (int64, error) {
|
||||||
|
mock.closeDataConn()
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", mock.address+":0")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpListener, ok := l.(*net.TCPListener)
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("listener is not a net.TCPListener")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := tcpListener.Addr().String()
|
||||||
|
|
||||||
|
_, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := strconv.ParseInt(port, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dataConn := &mockDataConn{listener: tcpListener}
|
||||||
|
dataConn.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Listen for an incoming connection.
|
||||||
|
conn, err := dataConn.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
// t.Errorf("can not accept: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dataConn.conn = conn
|
||||||
|
dataConn.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
mock.dataConn = dataConn
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *ftpMock) recvDataConn() {
|
||||||
|
mock.dataConn.Wait()
|
||||||
|
io.Copy(ioutil.Discard, mock.dataConn.conn)
|
||||||
|
mock.proto.Writer.PrintfLine("226 Transfer Complete")
|
||||||
|
mock.closeDataConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *ftpMock) Addr() string {
|
||||||
|
return mock.listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the listening socket
|
||||||
|
func (mock *ftpMock) Close() {
|
||||||
|
mock.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to return a client connected to a mock server
|
||||||
|
func openConn(t *testing.T, addr string) (*ftpMock, *ServerConn) {
|
||||||
|
mock, err := newFtpMock(t, addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
|
c, err := Dial(mock.Addr())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Login("anonymous", "anonymous")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mock, c
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to close a client connected to a mock server
|
||||||
|
func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) {
|
||||||
|
expected := []string{"FEAT", "USER", "PASS", "TYPE"}
|
||||||
|
expected = append(expected, commands...)
|
||||||
|
expected = append(expected, "QUIT")
|
||||||
|
|
||||||
|
if err := c.Quit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the connection to close
|
||||||
|
mock.Wait()
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(mock.commands, expected) {
|
||||||
|
t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConn4(t *testing.T) {
|
||||||
|
mock, c := openConn(t, "127.0.0.1")
|
||||||
|
closeConn(t, mock, c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConn6(t *testing.T) {
|
||||||
|
mock, c := openConn(t, "[::1]")
|
||||||
|
closeConn(t, mock, c, nil)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user