mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-03-17 20:47:50 +02:00
fix: add internal/http tests
This commit is contained in:
parent
9aa66e635b
commit
d41703227c
@ -28,6 +28,41 @@ const (
|
||||
ModeArchive = "archive"
|
||||
)
|
||||
|
||||
type asset struct {
|
||||
ReadCloser io.ReadCloser
|
||||
Size int64
|
||||
}
|
||||
|
||||
type assetOpenFunc func(string, *artifact.Artifact) (*asset, error)
|
||||
|
||||
var assetOpen assetOpenFunc
|
||||
|
||||
func init() {
|
||||
assetOpenReset()
|
||||
}
|
||||
|
||||
func assetOpenReset() {
|
||||
assetOpen = assetOpenDefault
|
||||
}
|
||||
|
||||
func assetOpenDefault(kind string, a *artifact.Artifact) (*asset, error) {
|
||||
f, err := os.Open(a.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.IsDir() {
|
||||
return nil, errors.Errorf("%s: upload failed: the asset to upload can't be a directory", kind)
|
||||
}
|
||||
return &asset{
|
||||
ReadCloser: f,
|
||||
Size: s.Size(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Defaults sets default configuration options on Put structs
|
||||
func Defaults(puts []config.Put) error {
|
||||
for i := range puts {
|
||||
@ -42,24 +77,28 @@ func defaults(put *config.Put) {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckConfig validates an HTTPUpload configuration returning a descriptive error when appropriate
|
||||
func CheckConfig(ctx *context.Context, upload *config.Put, kind string) error {
|
||||
// CheckConfig validates a Put configuration returning a descriptive error when appropriate
|
||||
func CheckConfig(ctx *context.Context, put *config.Put, kind string) error {
|
||||
|
||||
if upload.Target == "" {
|
||||
return misconfigured(kind, upload, "missing target")
|
||||
if put.Target == "" {
|
||||
return misconfigured(kind, put, "missing target")
|
||||
}
|
||||
|
||||
if upload.Username == "" {
|
||||
return misconfigured(kind, upload, "missing username")
|
||||
if put.Username == "" {
|
||||
return misconfigured(kind, put, "missing username")
|
||||
}
|
||||
|
||||
if upload.Name == "" {
|
||||
return misconfigured(kind, upload, "missing name")
|
||||
if put.Name == "" {
|
||||
return misconfigured(kind, put, "missing name")
|
||||
}
|
||||
|
||||
envName := fmt.Sprintf("%s_%s_SECRET", strings.ToUpper(kind), strings.ToUpper(upload.Name))
|
||||
if put.Mode != ModeArchive && put.Mode != ModeBinary {
|
||||
return misconfigured(kind, put, "mode must be 'binary' or 'archive'")
|
||||
}
|
||||
|
||||
envName := fmt.Sprintf("%s_%s_SECRET", strings.ToUpper(kind), strings.ToUpper(put.Name))
|
||||
if _, ok := ctx.Env[envName]; !ok {
|
||||
return misconfigured(kind, upload, fmt.Sprintf("missing %s environment variable", envName))
|
||||
return misconfigured(kind, put, fmt.Sprintf("missing %s environment variable", envName))
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -117,7 +156,7 @@ func Upload(ctx *context.Context, puts []config.Put, kind string, check Response
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPipeByFilter(ctx *context.Context, instance config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
|
||||
func runPipeByFilter(ctx *context.Context, put config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
|
||||
sem := make(chan bool, ctx.Parallelism)
|
||||
var g errgroup.Group
|
||||
for _, artifact := range ctx.Artifacts.Filter(filter).List() {
|
||||
@ -127,31 +166,31 @@ func runPipeByFilter(ctx *context.Context, instance config.Put, filter artifact.
|
||||
defer func() {
|
||||
<-sem
|
||||
}()
|
||||
return uploadAsset(ctx, instance, artifact, kind, check)
|
||||
return uploadAsset(ctx, put, artifact, kind, check)
|
||||
})
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// uploadAsset uploads file to target and logs all actions
|
||||
func uploadAsset(ctx *context.Context, instance config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
|
||||
envName := fmt.Sprintf("%s_%s_SECRET", strings.ToUpper(kind), strings.ToUpper(instance.Name))
|
||||
func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
|
||||
envName := fmt.Sprintf("%s_%s_SECRET", strings.ToUpper(kind), strings.ToUpper(put.Name))
|
||||
secret := ctx.Env[envName]
|
||||
|
||||
// Generate the target url
|
||||
targetURL, err := resolveTargetTemplate(ctx, instance, artifact)
|
||||
targetURL, err := resolveTargetTemplate(ctx, put, artifact)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: error while building the target url", kind)
|
||||
log.WithField("instance", instance.Name).WithError(err).Error(msg)
|
||||
log.WithField("instance", put.Name).WithError(err).Error(msg)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
// Handle the artifact
|
||||
file, err := os.Open(artifact.Path)
|
||||
asset, err := assetOpen(kind, &artifact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close() // nolint: errcheck
|
||||
defer asset.ReadCloser.Close() // nolint: errcheck
|
||||
|
||||
// The target url needs to contain the artifact name
|
||||
if !strings.HasSuffix(targetURL, "/") {
|
||||
@ -159,19 +198,19 @@ func uploadAsset(ctx *context.Context, instance config.Put, artifact artifact.Ar
|
||||
}
|
||||
targetURL += artifact.Name
|
||||
|
||||
location, _, err := uploadAssetToServer(ctx, targetURL, instance.Username, secret, file, check)
|
||||
location, _, err := uploadAssetToServer(ctx, targetURL, put.Username, secret, asset, check)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: upload failed", kind)
|
||||
log.WithError(err).WithFields(log.Fields{
|
||||
"instance": instance.Name,
|
||||
"username": instance.Username,
|
||||
"instance": put.Name,
|
||||
"username": put.Username,
|
||||
}).Error(msg)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"instance": instance.Name,
|
||||
"mode": instance.Mode,
|
||||
"instance": put.Name,
|
||||
"mode": put.Mode,
|
||||
"uri": location,
|
||||
}).Info("uploaded successful")
|
||||
|
||||
@ -179,16 +218,8 @@ func uploadAsset(ctx *context.Context, instance config.Put, artifact artifact.Ar
|
||||
}
|
||||
|
||||
// uploadAssetToServer uploads the asset file to target
|
||||
func uploadAssetToServer(ctx *context.Context, target, username, secret string, file *os.File, check ResponseChecker) (string, *h.Response, error) {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", nil, errors.New("the asset to upload can't be a directory")
|
||||
}
|
||||
|
||||
req, err := newUploadRequest(target, username, secret, file, stat.Size())
|
||||
func uploadAssetToServer(ctx *context.Context, target, username, secret string, a *asset, check ResponseChecker) (string, *h.Response, error) {
|
||||
req, err := newUploadRequest(target, username, secret, a)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@ -201,17 +232,17 @@ func uploadAssetToServer(ctx *context.Context, target, username, secret string,
|
||||
}
|
||||
|
||||
// newUploadRequest creates a new h.Request for uploading
|
||||
func newUploadRequest(target, username, secret string, reader io.Reader, size int64) (*h.Request, error) {
|
||||
func newUploadRequest(target, username, secret string, a *asset) (*h.Request, error) {
|
||||
u, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := h.NewRequest("PUT", u.String(), reader)
|
||||
req, err := h.NewRequest("PUT", u.String(), a.ReadCloser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.ContentLength = size
|
||||
req.ContentLength = a.Size
|
||||
req.SetBasicAuth(username, secret)
|
||||
|
||||
return req, err
|
||||
|
252
internal/http/http_test.go
Normal file
252
internal/http/http_test.go
Normal file
@ -0,0 +1,252 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
h "net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
)
|
||||
|
||||
var (
|
||||
mux *h.ServeMux
|
||||
srv *httptest.Server
|
||||
)
|
||||
|
||||
func setup() {
|
||||
mux = h.NewServeMux()
|
||||
srv = httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
type args struct {
|
||||
puts []config.Put
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
wantMode string
|
||||
}{
|
||||
{"set default", args{[]config.Put{{Name: "a", Target: "http://"}}}, false, ModeArchive},
|
||||
{"keep value", args{[]config.Put{{Name: "a", Target: "http://...", Mode: ModeBinary}}}, false, ModeBinary},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := Defaults(tt.args.puts); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Defaults() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.wantMode != tt.args.puts[0].Mode {
|
||||
t.Errorf("Incorrect Defaults() mode %q , wanted %q", tt.args.puts[0].Mode, tt.wantMode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckConfig(t *testing.T) {
|
||||
ctx := context.New(config.Project{ProjectName: "blah"})
|
||||
ctx.Env["TEST_A_SECRET"] = "x"
|
||||
type args struct {
|
||||
ctx *context.Context
|
||||
upload *config.Put
|
||||
kind string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false},
|
||||
{"secret missing", args{ctx, &config.Put{Name: "b", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
|
||||
{"target missing", args{ctx, &config.Put{Name: "a", Username: "pepe", Mode: ModeArchive}, "test"}, true},
|
||||
{"username missing", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Mode: ModeArchive}, "test"}, true},
|
||||
{"name missing", args{ctx, &config.Put{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
|
||||
{"mode missing", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true},
|
||||
{"mode invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr {
|
||||
t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func count(r io.Reader) (int64, error) {
|
||||
var (
|
||||
c int64
|
||||
b int64
|
||||
err error
|
||||
buf = make([]byte, 16)
|
||||
)
|
||||
for b >= 0 && err == nil {
|
||||
b, err := r.Read(buf)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c = c + int64(b)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type check struct {
|
||||
path string
|
||||
user string
|
||||
pass string
|
||||
content []byte
|
||||
}
|
||||
|
||||
func checks(checks ...check) func(rs []*h.Request) error {
|
||||
return func(rs []*h.Request) error {
|
||||
if len(rs) != len(checks) {
|
||||
return errors.New("expectations mismatch requests")
|
||||
}
|
||||
for _, r := range rs {
|
||||
found := false
|
||||
for _, c := range checks {
|
||||
if c.path == r.RequestURI {
|
||||
found = true
|
||||
err := doCheck(c, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return errors.Errorf("check not found for request %+v", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func doCheck(c check, r *h.Request) error {
|
||||
contentLength := int64(len(c.content))
|
||||
if r.ContentLength != contentLength {
|
||||
return errors.Errorf("request content-length header value %v unexpected, wanted %v", r.ContentLength, contentLength)
|
||||
}
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return errors.Errorf("reading request body: %v", err)
|
||||
}
|
||||
if !bytes.Equal(bs, c.content) {
|
||||
return errors.New("content does not match")
|
||||
}
|
||||
if int64(len(bs)) != contentLength {
|
||||
return errors.Errorf("request content length %v unexpected, wanted %v", int64(len(bs)), contentLength)
|
||||
}
|
||||
if r.RequestURI != c.path {
|
||||
return errors.Errorf("bad request uri %q, expecting %q", r.RequestURI, c.path)
|
||||
}
|
||||
if u, p, ok := r.BasicAuth(); !ok || u != c.user || p != c.pass {
|
||||
return errors.Errorf("bad basic auth credentials: %s/%s", u, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUpload(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
content := []byte("blah!")
|
||||
requests := []*h.Request{}
|
||||
var m sync.Mutex
|
||||
mux.Handle("/", h.HandlerFunc(func(w h.ResponseWriter, r *h.Request) {
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(h.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "reading request body: %v", err)
|
||||
return
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(bs))
|
||||
m.Lock()
|
||||
requests = append(requests, r)
|
||||
m.Unlock()
|
||||
w.WriteHeader(h.StatusCreated)
|
||||
w.Header().Set("Location", r.URL.RequestURI())
|
||||
}))
|
||||
assetOpen = func(k string, a *artifact.Artifact) (*asset, error) {
|
||||
return &asset{
|
||||
ReadCloser: ioutil.NopCloser(bytes.NewReader(content)),
|
||||
Size: int64(len(content)),
|
||||
}, nil
|
||||
}
|
||||
defer assetOpenReset()
|
||||
var is2xx ResponseChecker = func(r *h.Response) (string, error) {
|
||||
if r.StatusCode/100 == 2 {
|
||||
return r.Header.Get("Location"), nil
|
||||
}
|
||||
return "", errors.Errorf("unexpected http status code: %v", r.StatusCode)
|
||||
}
|
||||
ctx := context.New(config.Project{ProjectName: "blah"})
|
||||
ctx.Env["TEST_A_SECRET"] = "x"
|
||||
ctx.Version = "2.1.0"
|
||||
ctx.Artifacts = artifact.New()
|
||||
for _, a := range []struct {
|
||||
ext string
|
||||
typ artifact.Type
|
||||
}{
|
||||
{"---", artifact.DockerImage},
|
||||
{"deb", artifact.LinuxPackage},
|
||||
{"bin", artifact.Binary},
|
||||
{"tar", artifact.UploadableArchive},
|
||||
{"ubi", artifact.UploadableBinary},
|
||||
{"sum", artifact.Checksum},
|
||||
{"sig", artifact.Signature},
|
||||
} {
|
||||
ctx.Artifacts.Add(artifact.Artifact{Name: "a." + a.ext, Path: "/a/a." + a.ext, Type: a.typ})
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx *context.Context
|
||||
wantErr bool
|
||||
put config.Put
|
||||
check func(r []*h.Request) error
|
||||
}{
|
||||
{"archive", ctx, false,
|
||||
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u1"},
|
||||
checks(
|
||||
check{"/blah/2.1.0/a.deb", "u1", "x", content},
|
||||
check{"/blah/2.1.0/a.tar", "u1", "x", content},
|
||||
),
|
||||
},
|
||||
{"binary", ctx, false,
|
||||
config.Put{Mode: ModeBinary, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u2"},
|
||||
checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content}),
|
||||
},
|
||||
{"archive-with-checksum-and-signature", ctx, false,
|
||||
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u3", Checksum: true, Signature: true},
|
||||
checks(
|
||||
check{"/blah/2.1.0/a.deb", "u3", "x", content},
|
||||
check{"/blah/2.1.0/a.tar", "u3", "x", content},
|
||||
check{"/blah/2.1.0/a.sum", "u3", "x", content},
|
||||
check{"/blah/2.1.0/a.sig", "u3", "x", content},
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
requests = nil
|
||||
if err := Upload(tt.ctx, []config.Put{tt.put}, "test", is2xx); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Upload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if err := tt.check(requests); err != nil {
|
||||
t.Errorf("Upload() request invalid. Error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user