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 }