Files
athens/pkg/download/protocol_test.go
Marwan Sulaiman 76fb786324 downloadProtocol: support multi-proxy environments with DownloadFile (#1230)
* downloadProtocol: support multi-proxy environments with DownloadFile

* remove debugging lines

* update config tests

* download/mode: add tests for DownloadFile and friends

* add documentation to Download File
2019-06-08 00:30:07 -04:00

384 lines
8.4 KiB
Go

package download
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
"time"
"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/download/mode"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/module"
"github.com/gomods/athens/pkg/stash"
"github.com/gomods/athens/pkg/storage"
"github.com/gomods/athens/pkg/storage/mem"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
)
var (
testConfigPath = filepath.Join("..", "..", "config.dev.toml")
)
func getDP(t *testing.T) Protocol {
t.Helper()
conf, err := config.GetConf(testConfigPath)
if err != nil {
t.Fatalf("Unable to parse config file: %s", err.Error())
}
goBin := conf.GoBinary
fs := afero.NewOsFs()
mf, err := module.NewGoGetFetcher(goBin, fs)
if err != nil {
t.Fatal(err)
}
s, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
st := stash.New(mf, s)
return New(&Opts{s, st, NewVCSLister(goBin, fs), nil})
}
type listTest struct {
name string
path string
tags []string
}
var listTests = []listTest{
{
name: "happy tags",
path: "github.com/athens-artifacts/happy-path",
tags: []string{"v0.0.1", "v0.0.2", "v0.0.3"},
},
{
name: "no tags",
path: "github.com/athens-artifacts/no-tags",
tags: []string{},
},
}
func TestList(t *testing.T) {
dp := getDP(t)
ctx := context.Background()
for _, tc := range listTests {
t.Run(tc.name, func(t *testing.T) {
versions, err := dp.List(ctx, tc.path)
require.NoError(t, err)
require.EqualValues(t, tc.tags, versions)
})
}
}
func TestConcurrentLists(t *testing.T) {
dp := getDP(t)
ctx := context.Background()
pkg := "github.com/athens-artifacts/samplelib"
var pkgErr error
subPkg := "github.com/athens-artifacts/samplelib/types"
var subPkgErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
_, pkgErr = dp.List(ctx, pkg)
wg.Done()
}()
go func() {
_, subPkgErr = dp.List(ctx, subPkg)
wg.Done()
}()
wg.Wait()
if pkgErr != nil {
t.Fatalf("expected version listing of %v to succeed but got %v", pkg, pkgErr)
}
if subPkgErr == nil {
t.Fatalf("expected version listing of %v to fail because it's a subdirectory", subPkg)
}
}
type latestTest struct {
name string
path string
info *storage.RevInfo
err bool
}
var latestTests = []latestTest{
{
name: "happy path",
path: "github.com/athens-artifacts/no-tags",
info: &storage.RevInfo{
Version: "v0.0.0-20180803171426-1a540c5d67ab",
Time: time.Date(2018, 8, 3, 17, 14, 26, 0, time.UTC),
},
},
{
name: "tagged latest",
path: "github.com/athens-artifacts/happy-path",
info: &storage.RevInfo{
Version: "v0.0.3",
Time: time.Date(2018, 8, 3, 17, 16, 00, 0, time.UTC),
},
},
}
func TestLatest(t *testing.T) {
dp := getDP(t)
ctx := context.Background()
for _, tc := range latestTests {
t.Run(tc.name, func(t *testing.T) {
info, err := dp.Latest(ctx, tc.path)
if !tc.err && err != nil {
t.Fatal(err)
} else if tc.err && err == nil {
t.Fatalf("expected %v error but got nil", tc.err)
}
require.EqualValues(t, tc.info, info)
})
}
}
type infoTest struct {
name string
path string
version string
info *storage.RevInfo
}
var infoTests = []infoTest{
{
name: "happy path",
path: "github.com/athens-artifacts/happy-path",
version: "v0.0.2",
info: &storage.RevInfo{
Version: "v0.0.2",
Time: time.Date(2018, 8, 3, 3, 45, 19, 0, time.UTC),
},
},
{
name: "pseudo version",
path: "github.com/athens-artifacts/no-tags",
version: "v0.0.0-20180803035119-e4e0177efdb5",
info: &storage.RevInfo{
Version: "v0.0.0-20180803035119-e4e0177efdb5",
Time: time.Date(2018, 8, 3, 3, 51, 19, 0, time.UTC),
},
},
}
func TestInfo(t *testing.T) {
dp := getDP(t)
ctx := context.Background()
for _, tc := range infoTests {
t.Run(tc.name, func(t *testing.T) {
bts, err := dp.Info(ctx, tc.path, tc.version)
require.NoError(t, err)
var info storage.RevInfo
dec := json.NewDecoder(bytes.NewReader(bts))
dec.DisallowUnknownFields()
err = dec.Decode(&info)
require.NoError(t, err)
require.EqualValues(t, tc.info, &info)
})
}
}
type modTest struct {
name string
path string
version string
err bool
}
var modTests = []modTest{
{
name: "no mod file",
path: "github.com/athens-artifacts/no-tags",
version: "v0.0.0-20180803035119-e4e0177efdb5",
},
{
name: "upstream mod file",
path: "github.com/athens-artifacts/happy-path",
version: "v0.0.3",
},
{
name: "incorrect github repo",
path: "github.com/athens-artifacts/not-exists",
version: "v1.0.0",
err: true,
},
}
func rmNewLine(input string) string {
re := regexp.MustCompile(`\r?\n`)
return re.ReplaceAllString(input, "")
}
func TestGoMod(t *testing.T) {
dp := getDP(t)
ctx := context.Background()
for _, tc := range modTests {
t.Run(tc.name, func(t *testing.T) {
mod, err := dp.GoMod(ctx, tc.path, tc.version)
require.Equal(t, tc.err, err != nil, err)
if tc.err {
t.Skip()
}
expected := rmNewLine(string(getGoldenFile(t, tc.name)))
res := rmNewLine(string(mod))
require.Equal(t, expected, res)
})
}
}
func getGoldenFile(t *testing.T, name string) []byte {
t.Helper()
file := filepath.Join("test_data", strings.Replace(name, " ", "_", -1)+".golden")
bts, err := ioutil.ReadFile(file)
if err != nil {
t.Fatal(err)
}
return bts
}
type testMod struct {
mod, ver string
}
var mods = []testMod{
{"github.com/athens-artifacts/no-tags", "v0.0.2"},
{"github.com/athens-artifacts/happy-path", "v0.0.0-20180803035119-e4e0177efdb5"},
{"github.com/athens-artifacts/samplelib", "v1.0.0"},
}
func TestDownloadProtocol(t *testing.T) {
s, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
mp := &mockFetcher{}
st := stash.New(mp, s)
dp := New(&Opts{s, st, nil, nil})
ctx := context.Background()
var eg errgroup.Group
for i := 0; i < len(mods); i++ {
m := mods[i]
eg.Go(func() error {
_, err := dp.GoMod(ctx, m.mod, m.ver)
return err
})
}
err = eg.Wait()
if err != nil {
t.Fatal(err)
}
for _, m := range mods {
bts, err := dp.GoMod(ctx, m.mod, m.ver)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(bts, []byte(m.mod+"@"+m.ver)) {
t.Fatalf("unexpected gomod content: %s", bts)
}
}
}
type mockFetcher struct{}
func (m *mockFetcher) Fetch(ctx context.Context, mod, ver string) (*storage.Version, error) {
bts := []byte(mod + "@" + ver)
return &storage.Version{
Mod: bts,
Info: bts,
Zip: ioutil.NopCloser(bytes.NewReader(bts)),
}, nil
}
func TestDownloadProtocolWhenFetchFails(t *testing.T) {
s, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
fakeMod := testMod{"github.com/athens-artifacts/samplelib", "v1.0.0"}
bts := []byte(fakeMod.mod + "@" + fakeMod.ver)
err = s.Save(context.Background(), fakeMod.mod, fakeMod.ver, bts, ioutil.NopCloser(bytes.NewReader(bts)), bts)
if err != nil {
t.Fatal(err)
}
mp := &notFoundFetcher{}
st := stash.New(mp, s)
dp := New(&Opts{s, st, nil, nil})
ctx := context.Background()
_, err = dp.GoMod(ctx, fakeMod.mod, fakeMod.ver)
if err != nil {
t.Errorf("Download protocol should succeed, instead it gave error %s \n", err)
}
}
func TestAsyncRedirect(t *testing.T) {
s, err := mem.NewStorage()
require.NoError(t, err)
ms := &mockStasher{s, make(chan bool)}
dp := New(&Opts{
Stasher: ms,
Storage: s,
DownloadFile: &mode.DownloadFile{
Mode: mode.Async,
DownloadURL: "https://gomods.io",
},
})
mod, ver := "github.com/athens-artifacts/happy-path", "v0.0.1"
_, err = dp.Info(context.Background(), mod, ver)
if errors.Kind(err) != errors.KindNotFound {
t.Fatalf("expected async_redirect to enforce a 404 but got %v", errors.Kind(err))
}
<-ms.ch
info, err := dp.Info(context.Background(), mod, ver)
require.NoError(t, err)
require.Equal(t, string(info), "info", "expected async fetch to be successful")
}
type mockStasher struct {
s storage.Backend
ch chan bool
}
func (ms *mockStasher) Stash(ctx context.Context, mod string, ver string) (string, error) {
err := ms.s.Save(ctx, mod, ver, []byte("mod"), strings.NewReader("zip"), []byte("info"))
ms.ch <- true // signal async stashing is done
return ver, err
}
type notFoundFetcher struct{}
func (m *notFoundFetcher) Fetch(ctx context.Context, mod, ver string) (*storage.Version, error) {
const op errors.Op = "goGetFetcher.Fetch"
return nil, errors.E(op, "Fetcher error")
}