| package main |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "fmt" |
| "net/http" |
| "net/http/httptest" |
| "testing" |
| "time" |
| |
| "github.com/google/uuid" |
| "github.com/gorilla/mux" |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| "go.skia.org/infra/go/baseapp" |
| "go.skia.org/infra/go/testutils" |
| "go.skia.org/infra/go/testutils/unittest" |
| "go.skia.org/infra/machine/go/machine" |
| "go.skia.org/infra/machine/go/machine/store" |
| "go.skia.org/infra/machine/go/machineserver/config" |
| "go.skia.org/infra/machine/go/switchboard" |
| switchboardMocks "go.skia.org/infra/machine/go/switchboard/mocks" |
| ) |
| |
| func setupForTest(t *testing.T) (context.Context, config.InstanceConfig) { |
| unittest.RequiresFirestoreEmulator(t) |
| cfg := config.InstanceConfig{ |
| Store: config.Store{ |
| Project: "test-project", |
| Instance: fmt.Sprintf("test-%s", uuid.New()), |
| }, |
| } |
| ctx := context.Background() |
| |
| // Use fake authentication. |
| *baseapp.Local = true |
| return ctx, cfg |
| } |
| |
| // responseTo tries an HTTP request of the given method and path against a server and returns the |
| // response. |
| func responseTo(s *server, method, path string) *httptest.ResponseRecorder { |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| r := httptest.NewRequest(method, path, nil) |
| w := httptest.NewRecorder() |
| router.ServeHTTP(w, r) |
| return w |
| } |
| |
| // assertJSONResponseWas asserts that the HTTP response w was a success (code 200) and has a body |
| // matching the expected string. Since JSON encoding adds a trailing newline, we expect it as well, |
| // though the caller should not include it in the expected string. |
| func assertJSONResponseWas(t *testing.T, expected string, w *httptest.ResponseRecorder) { |
| assert.Equal(t, 200, w.Code) |
| assert.Equal(t, []byte(expected+"\n"), w.Body.Bytes()) |
| } |
| |
| func TestMachineToggleModeHandler_Success(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| ctx, cfg := setupForTest(t) |
| store, err := store.NewFirestoreImpl(ctx, true, cfg) |
| require.NoError(t, err) |
| |
| // Create our server. |
| s := &server{ |
| store: store, |
| } |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/toggle_mode/someid", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| // Confirm the request was successful. |
| require.Equal(t, 200, w.Code) |
| machines, err := store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 1) |
| assert.Equal(t, machine.ModeMaintenance, machines[0].Mode) |
| assert.Contains(t, machines[0].Annotation.Message, "Changed mode to") |
| assert.Equal(t, machines[0].Annotation.User, "barney@example.org") |
| } |
| |
| func TestMachineToggleModeHandler_FailOnMissingID(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/toggle_mode/", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 404, w.Code) |
| } |
| |
| func TestMachineTogglePowerCycleHandler_Success(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| ctx, cfg := setupForTest(t) |
| store, err := store.NewFirestoreImpl(ctx, true, cfg) |
| require.NoError(t, err) |
| |
| const podName = "rpi-swarming-123456" |
| err = store.Update(ctx, "someid", func(in machine.Description) machine.Description { |
| ret := in.Copy() |
| ret.PodName = podName |
| return ret |
| }) |
| require.NoError(t, err) |
| |
| // Create our server. |
| s := &server{ |
| store: store, |
| } |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/toggle_powercycle/someid", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| // Confirm the request was successful. |
| require.Equal(t, 200, w.Code) |
| machines, err := store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 1) |
| assert.Equal(t, podName, machines[0].PodName) |
| assert.True(t, machines[0].PowerCycle) |
| assert.Contains(t, machines[0].Annotation.Message, "Requested powercycle for") |
| assert.Equal(t, machines[0].Annotation.User, "barney@example.org") |
| |
| // Now confirm we toggle back. |
| r = httptest.NewRequest("GET", "/_/machine/toggle_powercycle/someid", nil) |
| w = httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| // Confirm the request was successful. |
| require.Equal(t, 200, w.Code) |
| machines, err = store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 1) |
| assert.Equal(t, podName, machines[0].PodName) |
| assert.False(t, machines[0].PowerCycle) |
| assert.Contains(t, machines[0].Annotation.Message, "Requested powercycle for") |
| assert.Equal(t, machines[0].Annotation.User, "barney@example.org") |
| |
| } |
| |
| func TestMachineTogglePowerCycleHandler_FailOnMissingID(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/toggle_powercycle/", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 404, w.Code) |
| } |
| |
| func TestMachineRemoveDeviceHandler_Success(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| ctx, cfg := setupForTest(t) |
| store, err := store.NewFirestoreImpl(ctx, true, cfg) |
| require.NoError(t, err) |
| |
| const podName = "rpi-swarming-123456" |
| err = store.Update(ctx, "someid", func(in machine.Description) machine.Description { |
| ret := in.Copy() |
| ret.PodName = podName |
| ret.Dimensions = machine.SwarmingDimensions{ |
| "android_devices": {"1"}, |
| "device_os": {"Q", " QP1A.190711.020", "QP1A.190711.020_G980FXXU1ATB3"}, |
| "device_os_flavor": {"samsung"}, |
| } |
| return ret |
| }) |
| require.NoError(t, err) |
| |
| // Create our server. |
| s := &server{ |
| store: store, |
| } |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/remove_device/someid", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| // Confirm the request was successful. |
| require.Equal(t, 200, w.Code) |
| machines, err := store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 1) |
| assert.Equal(t, podName, machines[0].PodName) |
| // Confirm the dimensions were cleared. |
| assert.Empty(t, machines[0].Dimensions) |
| assert.Contains(t, machines[0].Annotation.Message, "Requested device removal") |
| assert.Equal(t, machines[0].Annotation.User, "barney@example.org") |
| } |
| |
| func TestMachineRemoveDeviceHandler_FailOnMissingID(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/remove_device/", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 404, w.Code) |
| } |
| |
| func TestMachineDeleteMachineHandler_Success(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| ctx, cfg := setupForTest(t) |
| store, err := store.NewFirestoreImpl(ctx, true, cfg) |
| require.NoError(t, err) |
| |
| const podName = "rpi-swarming-123456" |
| err = store.Update(ctx, "someid", func(in machine.Description) machine.Description { |
| ret := in.Copy() |
| ret.PodName = podName |
| return ret |
| }) |
| require.NoError(t, err) |
| |
| // Create our server. |
| s := &server{ |
| store: store, |
| } |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/delete_machine/someid", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| // Confirm the request was successful. |
| require.Equal(t, 200, w.Code) |
| machines, err := store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 0) |
| } |
| |
| func TestMachineDeleteMachineHandler_FailOnMissingID(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("GET", "/_/machine/delete_machine/", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 404, w.Code) |
| } |
| |
| func TestMachineSetNoteHandler_Success(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| ctx, cfg := setupForTest(t) |
| store, err := store.NewFirestoreImpl(ctx, true, cfg) |
| require.NoError(t, err) |
| |
| const podName = "rpi-swarming-123456" |
| err = store.Update(ctx, "someid", func(in machine.Description) machine.Description { |
| ret := in.Copy() |
| ret.PodName = podName |
| return ret |
| }) |
| require.NoError(t, err) |
| |
| // Create our server. |
| s := &server{ |
| store: store, |
| } |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| note := machine.Annotation{ |
| Message: "Hello World", |
| } |
| b, err := json.Marshal(note) |
| assert.NoError(t, err) |
| r := httptest.NewRequest("POST", "/_/machine/set_note/someid", bytes.NewReader(b)) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 200, w.Code) |
| machines, err := store.List(ctx) |
| require.NoError(t, err) |
| require.Len(t, machines, 1) |
| assert.Equal(t, machines[0].Note.Message, "Hello World") |
| } |
| |
| func TestMachineSetNoteHandler_FailOnMissingID(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("POST", "/_/machine/set_note/", nil) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, 404, w.Code) |
| } |
| |
| func TestMachineSetNoteHandler_FailOnInvalidJSON(t *testing.T) { |
| unittest.LargeTest(t) |
| |
| // Create our server. |
| s := &server{} |
| |
| // Put a mux.Router in place so the request path gets parsed. |
| router := mux.NewRouter() |
| s.AddHandlers(router) |
| |
| r := httptest.NewRequest("POST", "/_/machine/set_note/someid", bytes.NewReader([]byte("This isn't valid JSON."))) |
| w := httptest.NewRecorder() |
| |
| // Make the request. |
| router.ServeHTTP(w, r) |
| |
| assert.Equal(t, http.StatusBadRequest, w.Code) |
| } |
| |
| func TestPodsHandler_Success(t *testing.T) { |
| unittest.SmallTest(t) |
| expected := switchboard.Pod{ |
| Name: "switch-pod-3", |
| LastUpdated: time.Date(2001, 2, 3, 4, 5, 6, 789012345, time.UTC), |
| } |
| |
| // ListPods is already well-tested in switchboard/impl, so we can mock out the whole |
| // switchboard. |
| sw := switchboardMocks.Switchboard{} |
| sw.On("ListPods", testutils.AnyContext).Return([]switchboard.Pod{expected}, nil) |
| s := &server{ |
| switchboard: &sw, |
| } |
| |
| w := responseTo(s, "GET", "/_/pods") |
| assertJSONResponseWas(t, `[{"Name":"switch-pod-3","LastUpdated":"2001-02-03T04:05:06.789012345Z"}]`, w) |
| } |
| |
| func TestMeetingPointsHandler_Success(t *testing.T) { |
| unittest.SmallTest(t) |
| expected := switchboard.MeetingPoint{ |
| PodName: "somePod", |
| Port: 33, |
| Username: "someUser", |
| MachineID: "someMachine", |
| LastUpdated: time.Date(2001, 2, 3, 4, 5, 6, 789012345, time.UTC), |
| } |
| |
| // ListMeetingPoints is already well-tested in switchboard/impl, so we can mock out the whole |
| // switchboard. |
| sw := switchboardMocks.Switchboard{} |
| sw.On("ListMeetingPoints", testutils.AnyContext).Return([]switchboard.MeetingPoint{expected}, nil) |
| s := &server{ |
| switchboard: &sw, |
| } |
| |
| w := responseTo(s, "GET", "/_/meeting_points") |
| assertJSONResponseWas(t, `[{"PodName":"somePod","Port":33,"Username":"someUser","MachineID":"someMachine","LastUpdated":"2001-02-03T04:05:06.789012345Z"}]`, w) |
| } |