github.com/equinox-io/equinox@v1.2.1-0.20200723040547-60ffe7f858fe/sdk_test.go (about)

     1  package equinox
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/sha256"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"os"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/equinox-io/equinox/proto"
    20  )
    21  
    22  const fakeAppID = "fake_app_id"
    23  
    24  var (
    25  	fakeBinary    = []byte{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1}
    26  	newFakeBinary = []byte{0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2}
    27  	ts            *httptest.Server
    28  	key           *ecdsa.PrivateKey
    29  	sha           string
    30  	newSHA        string
    31  	signature     string
    32  )
    33  
    34  func init() {
    35  	shaBytes := sha256.Sum256(fakeBinary)
    36  	sha = hex.EncodeToString(shaBytes[:])
    37  	newSHABytes := sha256.Sum256(newFakeBinary)
    38  	newSHA = hex.EncodeToString(newSHABytes[:])
    39  
    40  	var err error
    41  	key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
    42  	if err != nil {
    43  		panic(fmt.Sprintf("Failed to generate ecdsa key: %v", err))
    44  	}
    45  	sig, err := key.Sign(rand.Reader, newSHABytes[:], nil)
    46  	if err != nil {
    47  		panic(fmt.Sprintf("Failed to sign new binary: %v", err))
    48  	}
    49  	signature = hex.EncodeToString(sig)
    50  }
    51  
    52  func TestNotAvailable(t *testing.T) {
    53  	opts := setup(t, "TestNotAvailable", proto.Response{
    54  		Available: false,
    55  	})
    56  	defer cleanup(opts)
    57  
    58  	_, err := Check(fakeAppID, opts)
    59  	if err != NotAvailableErr {
    60  		t.Fatalf("Expected not available error, got: %v", err)
    61  	}
    62  }
    63  
    64  func TestEndToEnd(t *testing.T) {
    65  	opts := setup(t, "TestEndtoEnd", proto.Response{
    66  		Available: true,
    67  		Release: proto.Release{
    68  			Version:     "0.1.2.3",
    69  			Title:       "Release Title",
    70  			Description: "Release Description",
    71  			CreateDate:  time.Now(),
    72  		},
    73  		Checksum:  newSHA,
    74  		Signature: signature,
    75  	})
    76  	defer cleanup(opts)
    77  
    78  	resp, err := Check(fakeAppID, opts)
    79  	if err != nil {
    80  		t.Fatalf("Failed check: %v", err)
    81  	}
    82  	err = resp.Apply()
    83  	if err != nil {
    84  		t.Fatalf("Failed apply: %v", err)
    85  	}
    86  
    87  	buf, err := ioutil.ReadFile(opts.TargetPath)
    88  	if err != nil {
    89  		t.Fatalf("Failed to read file: %v", err)
    90  	}
    91  	if !bytes.Equal(buf, newFakeBinary) {
    92  		t.Fatalf("Binary did not update to new expected value. Got %v, expected %v", buf, newFakeBinary)
    93  	}
    94  }
    95  
    96  func TestInvalidPatch(t *testing.T) {
    97  	opts := setup(t, "TestInavlidPatch", proto.Response{
    98  		Available: true,
    99  		Release: proto.Release{
   100  			Version:     "0.1.2.3",
   101  			Title:       "Release Title",
   102  			Description: "Release Description",
   103  			CreateDate:  time.Now(),
   104  		},
   105  		DownloadURL: "bad-request",
   106  		Checksum:    newSHA,
   107  		Signature:   signature,
   108  		Patch:       proto.PatchBSDiff,
   109  	})
   110  	defer cleanup(opts)
   111  
   112  	resp, err := Check(fakeAppID, opts)
   113  	if err != nil {
   114  		t.Fatalf("Failed check: %v", err)
   115  	}
   116  	err = resp.Apply()
   117  	if err == nil {
   118  		t.Fatalf("Apply succeeded")
   119  	}
   120  	if err.Error() != "error downloading patch: bad-request" {
   121  		t.Fatalf("Expected a different error message: %s", err)
   122  	}
   123  }
   124  
   125  func setup(t *testing.T, name string, resp proto.Response) Options {
   126  	checkUserAgent := func(req *http.Request) {
   127  		if req.Header.Get("User-Agent") != userAgent {
   128  			t.Errorf("Expected user agent to be %s, not %s", userAgent, req.Header.Get("User-Agent"))
   129  		}
   130  	}
   131  
   132  	mux := http.NewServeMux()
   133  	mux.HandleFunc("/check", func(w http.ResponseWriter, r *http.Request) {
   134  		checkUserAgent(r)
   135  		var req proto.Request
   136  		err := json.NewDecoder(r.Body).Decode(&req)
   137  		if err != nil {
   138  			t.Fatalf("Failed to decode proto request: %v", err)
   139  		}
   140  		if resp.Available {
   141  			if req.AppID != fakeAppID {
   142  				t.Fatalf("Unexpected app ID. Got %v, expected %v", req.AppID, fakeAppID)
   143  			}
   144  			if req.CurrentSHA256 != sha {
   145  				t.Fatalf("Unexpected request SHA: %v", sha)
   146  			}
   147  		}
   148  		json.NewEncoder(w).Encode(resp)
   149  	})
   150  
   151  	// Keying off the download URL may not be the best idea...
   152  	if resp.DownloadURL == "bad-request" {
   153  		mux.HandleFunc("/bin", func(w http.ResponseWriter, r *http.Request) {
   154  			checkUserAgent(r)
   155  			http.Error(w, "bad-request", http.StatusBadRequest)
   156  		})
   157  	} else {
   158  		mux.HandleFunc("/bin", func(w http.ResponseWriter, r *http.Request) {
   159  			checkUserAgent(r)
   160  			w.Write(newFakeBinary)
   161  		})
   162  	}
   163  
   164  	ts = httptest.NewServer(mux)
   165  	resp.DownloadURL = ts.URL + "/bin"
   166  
   167  	var opts Options
   168  	opts.CheckURL = ts.URL + "/check"
   169  	opts.PublicKey = key.Public()
   170  
   171  	if name != "" {
   172  		opts.TargetPath = name
   173  		ioutil.WriteFile(name, fakeBinary, 0644)
   174  	}
   175  	return opts
   176  }
   177  
   178  func cleanup(opts Options) {
   179  	if opts.TargetPath != "" {
   180  		os.Remove(opts.TargetPath)
   181  	}
   182  	ts.Close()
   183  }