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