github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/daemon/daemon_test.go (about)

     1  package daemon
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math/rand"
    10  	"net/http"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/mitchellh/cli"
    15  	"github.com/replicatedhq/libyaml"
    16  	"github.com/replicatedhq/ship/pkg/api"
    17  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes"
    18  	"github.com/replicatedhq/ship/pkg/lifecycle/kustomize"
    19  	"github.com/replicatedhq/ship/pkg/testing/logger"
    20  	"github.com/spf13/afero"
    21  	"github.com/spf13/viper"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  type daemonAPITestCase struct {
    26  	name string
    27  	test func(t *testing.T)
    28  }
    29  
    30  func initTestDaemon(
    31  	t *testing.T,
    32  	release *api.Release,
    33  	v2 *NavcycleRoutes,
    34  ) (*ShipDaemon, int, context.CancelFunc, error) {
    35  	v := viper.New()
    36  
    37  	port := rand.Intn(10000) + 33000
    38  	viper.Set("api-port", port)
    39  	fs := afero.Afero{Fs: afero.NewMemMapFs()}
    40  	log := &logger.TestLogger{T: t}
    41  
    42  	v1 := &V1Routes{
    43  		Logger:           log,
    44  		Fs:               fs,
    45  		Viper:            v,
    46  		UI:               cli.NewMockUi(),
    47  		MessageConfirmed: make(chan string, 1),
    48  		OpenWebConsole:   func(ui cli.Ui, s string, b bool) error { return nil },
    49  	}
    50  
    51  	if v2 != nil {
    52  		v.Set("navcycle", true)
    53  		v2.Kustomizer = &kustomize.Kustomizer{}
    54  	}
    55  	daemon := &ShipDaemon{
    56  		Logger:         log,
    57  		WebUIFactory:   WebUIFactoryFactory(log),
    58  		Viper:          v,
    59  		V1Routes:       v1,
    60  		NavcycleRoutes: v2,
    61  	}
    62  
    63  	daemonCtx, daemonCancelFunc := context.WithCancel(context.Background())
    64  
    65  	log.Log("starting daemon")
    66  	go func() {
    67  		_ = daemon.Serve(daemonCtx, release)
    68  	}()
    69  
    70  	var daemonError error
    71  	for i := 0; i < 3; i++ {
    72  		time.Sleep(1 * time.Second)
    73  		resp, err := http.Get(fmt.Sprintf("http://localhost:%d/healthz", port))
    74  		if err == nil && resp.StatusCode == http.StatusOK {
    75  			daemonError = nil
    76  			resp.Body.Close()
    77  			break
    78  		}
    79  		daemonError = err
    80  	}
    81  
    82  	if daemonError != nil {
    83  		daemonCancelFunc()
    84  	}
    85  	return daemon, port, daemonCancelFunc, daemonError
    86  }
    87  
    88  func TestDaemonAPI(t *testing.T) {
    89  	step1 := api.Step{
    90  		Message: &api.Message{
    91  			Contents: "hello ship!",
    92  			Level:    "info",
    93  		},
    94  		Render: &api.Render{},
    95  	}
    96  
    97  	step2 := api.Step{
    98  		Message: &api.Message{
    99  			Contents: "bye ship!",
   100  			Level:    "warn",
   101  		},
   102  		Render: &api.Render{},
   103  	}
   104  
   105  	release := &api.Release{
   106  		Spec: api.Spec{
   107  			Lifecycle: api.Lifecycle{
   108  				V1: []api.Step{step1, step2},
   109  			},
   110  			Config: api.Config{
   111  				V1: []libyaml.ConfigGroup{},
   112  			},
   113  		},
   114  	}
   115  
   116  	daemon, port, daemonCancelFunc, err := initTestDaemon(t, release, &NavcycleRoutes{Shutdown: make(chan interface{})})
   117  	defer daemonCancelFunc()
   118  	require.New(t).NoError(err)
   119  
   120  	tests := []daemonAPITestCase{
   121  		{
   122  			name: "read message before steps",
   123  			test: func(t *testing.T) {
   124  				resp, err := http.Get(fmt.Sprintf("http://localhost:%d/api/v1/message/get", port))
   125  				require.New(t).NoError(err)
   126  				require.New(t).Equal(http.StatusBadRequest, resp.StatusCode)
   127  				bodyStr, err := ioutil.ReadAll(resp.Body)
   128  				require.New(t).NoError(err)
   129  				respMsg := struct {
   130  					Error string `json:"error"`
   131  				}{}
   132  				require.New(t).NoError(json.Unmarshal(bodyStr, &respMsg))
   133  				require.New(t).Equal("no steps taken", respMsg.Error)
   134  			},
   135  		},
   136  
   137  		{
   138  			name: "read message after 1st step",
   139  			test: func(t *testing.T) {
   140  				daemon.PushMessageStep(context.Background(), daemontypes.Message{
   141  					Contents: step1.Message.Contents,
   142  					Level:    step1.Message.Level,
   143  				}, MessageActions())
   144  
   145  				resp, err := http.Get(fmt.Sprintf("http://localhost:%d/api/v1/message/get", port))
   146  				require.New(t).NoError(err)
   147  				require.New(t).Equal(http.StatusOK, resp.StatusCode)
   148  				bodyStr, err := ioutil.ReadAll(resp.Body)
   149  				require.New(t).NoError(err)
   150  				respMsg := struct {
   151  					Message api.Message `json:"message"`
   152  				}{}
   153  				require.New(t).NoError(json.Unmarshal(bodyStr, &respMsg))
   154  				require.New(t).Equal(step1.Message, &respMsg.Message)
   155  			},
   156  		},
   157  
   158  		{
   159  			name: "confirm message that is not current",
   160  			test: func(t *testing.T) {
   161  				log := &logger.TestLogger{T: t}
   162  				daemon.PushMessageStep(context.Background(), daemontypes.Message{
   163  					Contents: step2.Message.Contents,
   164  					Level:    step2.Message.Level,
   165  				}, MessageActions())
   166  
   167  				reqBody := bytes.NewReader([]byte(`{"step_name": "wrong-name"}`))
   168  				log.Log("daemon.current", daemon.currentStepName)
   169  				resp, err := http.Post(fmt.Sprintf("http://localhost:%d/api/v1/message/confirm", port), "application/json", reqBody)
   170  				require.New(t).NoError(err)
   171  				require.New(t).Equal(http.StatusBadRequest, resp.StatusCode)
   172  				bodyStr, err := ioutil.ReadAll(resp.Body)
   173  				require.New(t).NoError(err)
   174  				respMsg := struct {
   175  					Error string `json:"error"`
   176  				}{}
   177  				require.New(t).NoError(json.Unmarshal(bodyStr, &respMsg))
   178  				require.New(t).Equal("not current step", respMsg.Error)
   179  			},
   180  		},
   181  
   182  		{
   183  			name: "confirm message that is current",
   184  			test: func(t *testing.T) {
   185  				log := &logger.TestLogger{T: t}
   186  				reqBody := bytes.NewReader([]byte(`{"step_name": "message"}`))
   187  				log.Log("daemon.current", daemon.currentStepName)
   188  				resp, err := http.Post(fmt.Sprintf("http://localhost:%d/api/v1/message/confirm", port), "application/json", reqBody)
   189  				require.New(t).NoError(err)
   190  				require.New(t).Equal(http.StatusOK, resp.StatusCode)
   191  				msg := <-daemon.MessageConfirmedChan()
   192  				require.New(t).Equal("message", msg)
   193  			},
   194  		},
   195  	}
   196  
   197  	for _, test := range tests {
   198  		t.Run(test.name, func(t *testing.T) {
   199  			test.test(t)
   200  		})
   201  	}
   202  }