109 lines
4.9 KiB
Go
109 lines
4.9 KiB
Go
package httpapi
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"data-terminal-cloud/internal/device"
|
||
|
|
"data-terminal-cloud/internal/deviceauth"
|
||
|
|
)
|
||
|
|
|
||
|
|
type fakeDeviceCommandService struct {
|
||
|
|
items []device.Command
|
||
|
|
pullErr error
|
||
|
|
ackErr error
|
||
|
|
pulledID deviceauth.Identity
|
||
|
|
pulledLimit int
|
||
|
|
ackedID deviceauth.Identity
|
||
|
|
ackedRef string
|
||
|
|
ackedInput device.CommandAcknowledgement
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeDeviceCommandService) PullCommands(_ context.Context, id deviceauth.Identity, limit int) ([]device.Command, error) {
|
||
|
|
f.pulledID, f.pulledLimit = id, limit
|
||
|
|
return f.items, f.pullErr
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeDeviceCommandService) AcknowledgeCommand(_ context.Context, id deviceauth.Identity, ref string, input device.CommandAcknowledgement) (device.Command, error) {
|
||
|
|
f.ackedID, f.ackedRef, f.ackedInput = id, ref, input
|
||
|
|
if f.ackErr != nil {
|
||
|
|
return device.Command{}, f.ackErr
|
||
|
|
}
|
||
|
|
return device.Command{ID: ref, Type: "reboot", Status: input.Status, Payload: json.RawMessage(`{}`), ExpiresAt: time.Now().UTC()}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func deviceCommandRequest(method, target, body string, authenticated bool) *http.Request {
|
||
|
|
request := httptest.NewRequest(method, target, strings.NewReader(body))
|
||
|
|
if body != "" {
|
||
|
|
request.Header.Set("Content-Type", "application/json")
|
||
|
|
}
|
||
|
|
if authenticated {
|
||
|
|
request = request.WithContext(deviceauth.WithIdentity(request.Context(), deviceauth.Identity{ID: "internal-device", PublicID: "device-1", TenantID: "tenant-1"}))
|
||
|
|
}
|
||
|
|
return request
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPullDeviceCommandsAuthenticationSuccessAndFailure(t *testing.T) {
|
||
|
|
service := &fakeDeviceCommandService{items: []device.Command{{ID: "command-1", Type: "apply_config", Status: "PENDING", Payload: json.RawMessage(`{}`)}}}
|
||
|
|
handler := PullDeviceCommandsHandler(service)
|
||
|
|
recorder := httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, deviceCommandRequest(http.MethodGet, "/device/v2/commands?limit=7", "", false))
|
||
|
|
if recorder.Code != http.StatusUnauthorized || !strings.Contains(recorder.Body.String(), "DEVICE_AUTHENTICATION_FAILED") {
|
||
|
|
t.Fatalf("unauthenticated status=%d body=%s", recorder.Code, recorder.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
recorder = httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, deviceCommandRequest(http.MethodGet, "/device/v2/commands?limit=7", "", true))
|
||
|
|
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "command-1") || service.pulledLimit != 7 || service.pulledID.PublicID != "device-1" {
|
||
|
|
t.Fatalf("success status=%d body=%s service=%+v", recorder.Code, recorder.Body.String(), service)
|
||
|
|
}
|
||
|
|
|
||
|
|
service.pullErr = errors.New("database unavailable")
|
||
|
|
recorder = httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, deviceCommandRequest(http.MethodGet, "/device/v2/commands", "", true))
|
||
|
|
if recorder.Code != http.StatusServiceUnavailable || !strings.Contains(recorder.Body.String(), "DEVICE_COMMANDS_UNAVAILABLE") || !strings.Contains(recorder.Body.String(), `"retryable":true`) {
|
||
|
|
t.Fatalf("failure status=%d body=%s", recorder.Code, recorder.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAcknowledgeDeviceCommandValidationSuccessAndConflict(t *testing.T) {
|
||
|
|
service := &fakeDeviceCommandService{}
|
||
|
|
handler := AcknowledgeDeviceCommandHandler(service)
|
||
|
|
recorder := httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, deviceCommandRequest(http.MethodPost, "/device/v2/commands/command-1/ack", `{"status":"SUCCEEDED"}`, false))
|
||
|
|
if recorder.Code != http.StatusUnauthorized {
|
||
|
|
t.Fatalf("unauthenticated status=%d", recorder.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
request := deviceCommandRequest(http.MethodPost, "/device/v2/commands/command-1/ack", `{"status":"unknown"}`, true)
|
||
|
|
request.SetPathValue("id", "command-1")
|
||
|
|
recorder = httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, request)
|
||
|
|
if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "VALIDATION_FAILED") {
|
||
|
|
t.Fatalf("validation status=%d body=%s", recorder.Code, recorder.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
request = deviceCommandRequest(http.MethodPost, "/device/v2/commands/command-1/ack", `{"status":" succeeded "}`, true)
|
||
|
|
request.SetPathValue("id", "command-1")
|
||
|
|
recorder = httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, request)
|
||
|
|
if recorder.Code != http.StatusOK || service.ackedRef != "command-1" || service.ackedInput.Status != "SUCCEEDED" || service.ackedInput.ResultSummary == nil {
|
||
|
|
t.Fatalf("success status=%d body=%s input=%+v", recorder.Code, recorder.Body.String(), service.ackedInput)
|
||
|
|
}
|
||
|
|
|
||
|
|
service.ackErr = errors.New("state conflict")
|
||
|
|
request = deviceCommandRequest(http.MethodPost, "/device/v2/commands/command-1/ack", `{"status":"FAILED","result_summary":{"result_code":"SIM_FAIL"}}`, true)
|
||
|
|
request.SetPathValue("id", "command-1")
|
||
|
|
recorder = httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(recorder, request)
|
||
|
|
if recorder.Code != http.StatusConflict || !strings.Contains(recorder.Body.String(), "DEVICE_COMMAND_STATE_CONFLICT") || service.ackedInput.ResultSummary["result_code"] != "SIM_FAIL" {
|
||
|
|
t.Fatalf("conflict status=%d body=%s input=%+v", recorder.Code, recorder.Body.String(), service.ackedInput)
|
||
|
|
}
|
||
|
|
}
|