mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 00:01:14 +00:00
3837cd3898
Add request and mail-delivery timeouts around auth flows so login requests cannot hang indefinitely when notification delivery stalls. Allow the local admin dev origin through default CORS and update context-aware lint fixes so the repository lint gate passes.
109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/app/idempotency"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
)
|
|
|
|
type fakeIdempotencyService struct {
|
|
record *idempotency.Record
|
|
state idempotency.State
|
|
body []byte
|
|
}
|
|
|
|
func (s *fakeIdempotencyService) Begin(ctx context.Context, orgID uuid.UUID, key, method, path, requestHash string) (*idempotency.Record, idempotency.State, *errx.Error) {
|
|
if s.record == nil {
|
|
s.record = &idempotency.Record{ID: uuid.New(), StatusCode: http.StatusCreated}
|
|
}
|
|
return s.record, s.state, nil
|
|
}
|
|
|
|
func (s *fakeIdempotencyService) Complete(ctx context.Context, recordID uuid.UUID, statusCode int, responseBody []byte, contentType string) *errx.Error {
|
|
s.record.StatusCode = statusCode
|
|
s.body = append([]byte(nil), responseBody...)
|
|
return nil
|
|
}
|
|
|
|
func TestIdempotencyMiddlewareStoresResponse(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
orgID := uuid.New()
|
|
svc := &fakeIdempotencyService{state: idempotency.StateStarted}
|
|
h := &Handler{IdempotencyService: svc}
|
|
|
|
r := gin.New()
|
|
r.Use(RequestIDMiddleware())
|
|
r.POST("/contacts",
|
|
func(c *gin.Context) {
|
|
c.Set(OrganizationIDKey, orgID)
|
|
c.Next()
|
|
},
|
|
h.IdempotencyMiddleware(),
|
|
func(c *gin.Context) {
|
|
c.JSON(http.StatusCreated, gin.H{"id": "contact_123"})
|
|
},
|
|
)
|
|
|
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/contacts", nil)
|
|
req.Header.Set(IdempotencyKeyHeader, "idem_123")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusCreated {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusCreated)
|
|
}
|
|
if string(svc.body) == "" {
|
|
t.Fatal("expected response body to be stored")
|
|
}
|
|
}
|
|
|
|
func TestIdempotencyMiddlewareReplaysResponse(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
orgID := uuid.New()
|
|
contentType := "application/json; charset=utf-8"
|
|
svc := &fakeIdempotencyService{
|
|
state: idempotency.StateReplay,
|
|
record: &idempotency.Record{
|
|
ID: uuid.New(),
|
|
StatusCode: http.StatusCreated,
|
|
ResponseBody: []byte(`{"id":"contact_123"}`),
|
|
ContentType: &contentType,
|
|
},
|
|
}
|
|
h := &Handler{IdempotencyService: svc}
|
|
|
|
r := gin.New()
|
|
r.Use(RequestIDMiddleware())
|
|
r.POST("/contacts",
|
|
func(c *gin.Context) {
|
|
c.Set(OrganizationIDKey, orgID)
|
|
c.Next()
|
|
},
|
|
h.IdempotencyMiddleware(),
|
|
func(c *gin.Context) {
|
|
c.JSON(http.StatusTeapot, gin.H{"unexpected": true})
|
|
},
|
|
)
|
|
|
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/contacts", nil)
|
|
req.Header.Set(IdempotencyKeyHeader, "idem_123")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusCreated {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusCreated)
|
|
}
|
|
if rec.Header().Get(IdempotencyReplayedHeader) != "true" {
|
|
t.Fatalf("missing replay header")
|
|
}
|
|
if got := rec.Body.String(); got != `{"id":"contact_123"}` {
|
|
t.Fatalf("body = %q", got)
|
|
}
|
|
}
|