terraform/remote/http_test.go

332 lines
6.7 KiB
Go

package remote
import (
"bytes"
"crypto/md5"
"encoding/base64"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestHTTPRemote_Interface(t *testing.T) {
var client interface{} = &HTTPRemoteClient{}
if _, ok := client.(RemoteClient); !ok {
t.Fatalf("does not implement interface")
}
}
func TestHTTPRemote_Validate(t *testing.T) {
conf := map[string]string{}
if _, err := NewHTTPRemoteClient(conf); err == nil {
t.Fatalf("expect error")
}
conf["address"] = ""
if _, err := NewHTTPRemoteClient(conf); err == nil {
t.Fatalf("expect error")
}
conf["address"] = "*"
if _, err := NewHTTPRemoteClient(conf); err == nil {
t.Fatalf("expect error")
}
conf["address"] = "http://cool.com"
if _, err := NewHTTPRemoteClient(conf); err != nil {
t.Fatalf("err: %v", err)
}
}
func TestHTTPRemote_GetState(t *testing.T) {
type tcase struct {
Code int
Header http.Header
Body []byte
ExpectMD5 []byte
ExpectErr string
}
inp := []byte("testing")
inpMD5 := md5.Sum(inp)
hash := inpMD5[:16]
cases := []*tcase{
&tcase{
Code: http.StatusOK,
Body: inp,
ExpectMD5: hash,
},
&tcase{
Code: http.StatusNoContent,
},
&tcase{
Code: http.StatusNotFound,
},
&tcase{
Code: http.StatusInternalServerError,
ExpectErr: "Remote server reporting internal error",
},
&tcase{
Code: 418,
ExpectErr: "Unexpected HTTP response code 418",
},
}
for _, tc := range cases {
cb := func(resp http.ResponseWriter, req *http.Request) {
for k, v := range tc.Header {
resp.Header()[k] = v
}
resp.WriteHeader(tc.Code)
if tc.Body != nil {
resp.Write(tc.Body)
}
}
s := httptest.NewServer(http.HandlerFunc(cb))
defer s.Close()
remote := &terraform.RemoteState{
Type: "http",
Config: map[string]string{
"address": s.URL,
},
}
r, err := NewClientByState(remote)
if err != nil {
t.Fatalf("Err: %v", err)
}
payload, err := r.GetState()
errStr := ""
if err != nil {
errStr = err.Error()
}
if errStr != tc.ExpectErr {
t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr)
}
if tc.ExpectMD5 != nil {
if payload == nil || !bytes.Equal(payload.MD5, tc.ExpectMD5) {
t.Fatalf("bad: %#v", payload)
}
}
if tc.Body != nil {
if !bytes.Equal(payload.State, tc.Body) {
t.Fatalf("bad: %#v", payload)
}
}
}
}
func TestHTTPRemote_PutState(t *testing.T) {
type tcase struct {
Code int
Path string
Header http.Header
Body []byte
ExpectMD5 []byte
Force bool
ExpectErr string
}
inp := []byte("testing")
inpMD5 := md5.Sum(inp)
hash := inpMD5[:16]
cases := []*tcase{
&tcase{
Code: http.StatusOK,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
},
&tcase{
Code: http.StatusOK,
Path: "/foobar?force=true",
Body: inp,
Force: true,
ExpectMD5: hash,
},
&tcase{
Code: http.StatusConflict,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: ErrConflict.Error(),
},
&tcase{
Code: http.StatusPreconditionFailed,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: ErrServerNewer.Error(),
},
&tcase{
Code: http.StatusUnauthorized,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: ErrRequireAuth.Error(),
},
&tcase{
Code: http.StatusForbidden,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: ErrInvalidAuth.Error(),
},
&tcase{
Code: http.StatusInternalServerError,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: ErrRemoteInternal.Error(),
},
&tcase{
Code: 418,
Path: "/foobar",
Body: inp,
ExpectMD5: hash,
ExpectErr: "Unexpected HTTP response code 418",
},
}
for _, tc := range cases {
cb := func(resp http.ResponseWriter, req *http.Request) {
for k, v := range tc.Header {
resp.Header()[k] = v
}
resp.WriteHeader(tc.Code)
// Verify the body
buf := bytes.NewBuffer(nil)
io.Copy(buf, req.Body)
if !bytes.Equal(buf.Bytes(), tc.Body) {
t.Fatalf("bad body: %v", buf.Bytes())
}
// Verify the path
req.URL.Host = ""
if req.URL.String() != tc.Path {
t.Fatalf("Bad path: %v %v", req.URL.String(), tc.Path)
}
// Verify the content length
if req.ContentLength != int64(len(tc.Body)) {
t.Fatalf("bad content length: %d", req.ContentLength)
}
// Verify the Content-MD5
b64 := req.Header.Get("Content-MD5")
raw, _ := base64.StdEncoding.DecodeString(b64)
if !bytes.Equal(raw, tc.ExpectMD5) {
t.Fatalf("bad md5: %v", raw)
}
}
s := httptest.NewServer(http.HandlerFunc(cb))
defer s.Close()
remote := &terraform.RemoteState{
Type: "http",
Config: map[string]string{
"address": s.URL + "/foobar",
},
}
r, err := NewClientByState(remote)
if err != nil {
t.Fatalf("Err: %v", err)
}
err = r.PutState(tc.Body, tc.Force)
errStr := ""
if err != nil {
errStr = err.Error()
}
if errStr != tc.ExpectErr {
t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr)
}
}
}
func TestHTTPRemote_DeleteState(t *testing.T) {
type tcase struct {
Code int
Path string
Header http.Header
ExpectErr string
}
cases := []*tcase{
&tcase{
Code: http.StatusOK,
Path: "/foobar",
},
&tcase{
Code: http.StatusNoContent,
Path: "/foobar",
},
&tcase{
Code: http.StatusNotFound,
Path: "/foobar",
},
&tcase{
Code: http.StatusUnauthorized,
Path: "/foobar",
ExpectErr: ErrRequireAuth.Error(),
},
&tcase{
Code: http.StatusForbidden,
Path: "/foobar",
ExpectErr: ErrInvalidAuth.Error(),
},
&tcase{
Code: http.StatusInternalServerError,
Path: "/foobar",
ExpectErr: ErrRemoteInternal.Error(),
},
&tcase{
Code: 418,
Path: "/foobar",
ExpectErr: "Unexpected HTTP response code 418",
},
}
for _, tc := range cases {
cb := func(resp http.ResponseWriter, req *http.Request) {
for k, v := range tc.Header {
resp.Header()[k] = v
}
resp.WriteHeader(tc.Code)
// Verify the path
req.URL.Host = ""
if req.URL.String() != tc.Path {
t.Fatalf("Bad path: %v %v", req.URL.String(), tc.Path)
}
}
s := httptest.NewServer(http.HandlerFunc(cb))
defer s.Close()
remote := &terraform.RemoteState{
Type: "http",
Config: map[string]string{
"address": s.URL + "/foobar",
},
}
r, err := NewClientByState(remote)
if err != nil {
t.Fatalf("Err: %v", err)
}
err = r.DeleteState()
errStr := ""
if err != nil {
errStr = err.Error()
}
if errStr != tc.ExpectErr {
t.Fatalf("bad err: %v %v", errStr, tc.ExpectErr)
}
}
}