Files
story/internal/httpapi/device_command_handlers_test.go
T

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)
}
}