88 lines
5.0 KiB
Go
88 lines
5.0 KiB
Go
package httpapi
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"data-terminal-cloud/internal/alerts"
|
||
|
|
"data-terminal-cloud/internal/billing"
|
||
|
|
"data-terminal-cloud/internal/device"
|
||
|
|
"data-terminal-cloud/internal/idempotency"
|
||
|
|
"data-terminal-cloud/internal/opsconfig"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestAlertBillingAndOpsConfigErrorsHaveStableMappings(t *testing.T) {
|
||
|
|
type errorWriter func(http.ResponseWriter, *http.Request, error)
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
writer errorWriter
|
||
|
|
err error
|
||
|
|
status int
|
||
|
|
code string
|
||
|
|
}{
|
||
|
|
{"alert invalid", writeAlertError, alerts.ErrInvalid, http.StatusBadRequest, "VALIDATION_FAILED"},
|
||
|
|
{"alert denied", writeAlertError, alerts.ErrAccessDenied, http.StatusForbidden, "OPS_ACCESS_DENIED"},
|
||
|
|
{"alert missing", writeAlertError, alerts.ErrNotFound, http.StatusNotFound, "ALERT_NOT_FOUND"},
|
||
|
|
{"alert version", writeAlertError, alerts.ErrVersionConflict, http.StatusPreconditionFailed, "VERSION_CONFLICT"},
|
||
|
|
{"alert state", writeAlertError, alerts.ErrStateConflict, http.StatusConflict, "ALERT_STATE_CONFLICT"},
|
||
|
|
{"alert key", writeAlertError, idempotency.ErrKeyConflict, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT"},
|
||
|
|
{"alert dependency", writeAlertError, errors.New("database unavailable"), http.StatusInternalServerError, "INTERNAL_ERROR"},
|
||
|
|
{"billing invalid", writeBillingError, billing.ErrInvalid, http.StatusBadRequest, "VALIDATION_FAILED"},
|
||
|
|
{"billing denied", writeBillingError, billing.ErrAccessDenied, http.StatusForbidden, "OPS_ACCESS_DENIED"},
|
||
|
|
{"billing missing", writeBillingError, billing.ErrNotFound, http.StatusNotFound, "RESOURCE_NOT_FOUND"},
|
||
|
|
{"billing version", writeBillingError, billing.ErrVersionConflict, http.StatusPreconditionFailed, "VERSION_CONFLICT"},
|
||
|
|
{"billing self approval", writeBillingError, billing.ErrSelfApproval, http.StatusConflict, "SELF_APPROVAL_FORBIDDEN"},
|
||
|
|
{"billing state", writeBillingError, billing.ErrStateConflict, http.StatusConflict, "BILLING_STATE_CONFLICT"},
|
||
|
|
{"billing key", writeBillingError, idempotency.ErrKeyConflict, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT"},
|
||
|
|
{"billing in progress", writeBillingError, idempotency.ErrInProgress, http.StatusConflict, "IDEMPOTENCY_IN_PROGRESS"},
|
||
|
|
{"billing dependency", writeBillingError, errors.New("database unavailable"), http.StatusInternalServerError, "INTERNAL_ERROR"},
|
||
|
|
{"config invalid", writeOpsConfigError, opsconfig.ErrInvalid, http.StatusBadRequest, "VALIDATION_FAILED"},
|
||
|
|
{"config denied", writeOpsConfigError, opsconfig.ErrAccessDenied, http.StatusForbidden, "OPS_ACCESS_DENIED"},
|
||
|
|
{"config missing", writeOpsConfigError, opsconfig.ErrNotFound, http.StatusNotFound, "RESOURCE_NOT_FOUND"},
|
||
|
|
{"config version", writeOpsConfigError, opsconfig.ErrVersionConflict, http.StatusPreconditionFailed, "VERSION_CONFLICT"},
|
||
|
|
{"config self approval", writeOpsConfigError, opsconfig.ErrSelfApproval, http.StatusConflict, "SELF_APPROVAL_FORBIDDEN"},
|
||
|
|
{"config state", writeOpsConfigError, opsconfig.ErrStateConflict, http.StatusConflict, "CONFIG_STATE_CONFLICT"},
|
||
|
|
{"config gate", writeOpsConfigError, opsconfig.ErrPublishGate, http.StatusConflict, "CONFIG_PUBLISH_GATE_FAILED"},
|
||
|
|
{"config key", writeOpsConfigError, idempotency.ErrKeyConflict, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT"},
|
||
|
|
{"config in progress", writeOpsConfigError, idempotency.ErrInProgress, http.StatusConflict, "IDEMPOTENCY_IN_PROGRESS"},
|
||
|
|
{"config dependency", writeOpsConfigError, errors.New("database unavailable"), http.StatusInternalServerError, "INTERNAL_ERROR"},
|
||
|
|
}
|
||
|
|
for _, test := range tests {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
recorder := httptest.NewRecorder()
|
||
|
|
request := httptest.NewRequest(http.MethodPost, "/api/v1/ops/test", nil)
|
||
|
|
test.writer(recorder, request, test.err)
|
||
|
|
if recorder.Code != test.status || !strings.Contains(recorder.Body.String(), test.code) {
|
||
|
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTelemetryErrorsHaveStableMappings(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
err error
|
||
|
|
status int
|
||
|
|
code string
|
||
|
|
}{
|
||
|
|
{&device.TelemetryConflictError{}, http.StatusConflict, "TELEMETRY_EVENT_CONFLICT"},
|
||
|
|
{device.ErrTelemetryConfigAhead, http.StatusConflict, "CONFIG_VERSION_CONFLICT"},
|
||
|
|
{device.ErrTelemetryConversationMissing, http.StatusNotFound, "TELEMETRY_CONVERSATION_NOT_FOUND"},
|
||
|
|
{device.ErrTelemetryCommandConflict, http.StatusConflict, "DEVICE_COMMAND_STATE_CONFLICT"},
|
||
|
|
{idempotency.ErrKeyConflict, http.StatusConflict, "IDEMPOTENCY_KEY_REUSED"},
|
||
|
|
{idempotency.ErrInProgress, http.StatusConflict, "REQUEST_IN_PROGRESS"},
|
||
|
|
{errors.New("database unavailable"), http.StatusServiceUnavailable, "DEVICE_TELEMETRY_UNAVAILABLE"},
|
||
|
|
}
|
||
|
|
for _, test := range tests {
|
||
|
|
recorder := httptest.NewRecorder()
|
||
|
|
request := httptest.NewRequest(http.MethodPost, "/device/v2/telemetry:batch", nil)
|
||
|
|
writeTelemetryError(recorder, request, test.err)
|
||
|
|
if recorder.Code != test.status || !strings.Contains(recorder.Body.String(), test.code) {
|
||
|
|
t.Fatalf("error=%v status=%d body=%s", test.err, recorder.Code, recorder.Body.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|