feat(canary): manage page handlers + DTOs (GET + DELETE /m/{manage_id})
Adds the HTTP layer for spec §8.4. The UUID in the URL is the
capability — no other auth required, by design.
- token/dto.go: ManageTokenView (slimmer than Response — no manage_id,
no manage_url, no metadata, since the user is already on the manage
page and those would be redundant), ManagePage{NextCursor, HasMore},
ManageResponse{Token, Events, EventsTotal, EventsSilencedActive, Page}.
Token.ToManageView(triggerURL) builder. Events use the existing
event.Response (already matches spec shape exactly).
- token/handler.go: EventQuery + DedupCounter interfaces declared near
the existing EventRecorder + FingerprintRecorder; same dependency-
injection style. NewHandler signature gains the two new args (5 → 6;
test sites + main.go updated).
- GetManage(w, r): parses ?cursor= (int64, must be >= 0) and ?limit=
(defaults 20, capped at 100). Returns 404 envelope for unknown
manage_id (envelope, not bare http.NotFound, so frontend gets a
parseable error code). 400 BAD_CURSOR for non-numeric or negative.
Calls svc.GetByManageID + eventQuery.ListByToken + CountByToken +
dedupCounter.CountActiveDedup; assembles the manage payload.
- DeleteManage(w, r): 204 on success, 404 on miss. Cascade-delete is
via the existing tokens-events FK ON DELETE CASCADE.
- buildPage helper: NextCursor is the string of the last event's ID
iff len(events) == limit (matches spec example "next_cursor": "41").
Tests cover happy path, 404 on unknown id, 400 on bad/negative cursor,
?limit= respected, ?limit=999 capped at 100, DELETE happy + 404.
This commit is contained in:
parent
e3f9ff48f9
commit
593433fc03
|
|
@ -6,6 +6,8 @@ package token
|
|||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
|
||||
)
|
||||
|
||||
type CreateRequest struct {
|
||||
|
|
@ -53,3 +55,44 @@ func (t *Token) ToResponse(triggerURL, manageURL string) Response {
|
|||
Metadata: t.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
type ManageTokenView struct {
|
||||
ID string `json:"id"`
|
||||
Type Type `json:"type"`
|
||||
Memo string `json:"memo"`
|
||||
Filename *string `json:"filename"`
|
||||
AlertChannel AlertChannel `json:"alert_channel"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
TriggerCount int64 `json:"trigger_count"`
|
||||
LastTriggered *time.Time `json:"last_triggered"`
|
||||
Enabled bool `json:"enabled"`
|
||||
TriggerURL string `json:"trigger_url"`
|
||||
}
|
||||
|
||||
type ManagePage struct {
|
||||
NextCursor string `json:"next_cursor"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
type ManageResponse struct {
|
||||
Token ManageTokenView `json:"token"`
|
||||
Events []event.Response `json:"events"`
|
||||
EventsTotal int64 `json:"events_total"`
|
||||
EventsSilencedActive int64 `json:"events_silenced_active"`
|
||||
Page ManagePage `json:"page"`
|
||||
}
|
||||
|
||||
func (t *Token) ToManageView(triggerURL string) ManageTokenView {
|
||||
return ManageTokenView{
|
||||
ID: t.ID,
|
||||
Type: t.Type,
|
||||
Memo: t.Memo,
|
||||
Filename: t.Filename,
|
||||
AlertChannel: t.AlertChannel,
|
||||
CreatedAt: t.CreatedAt,
|
||||
TriggerCount: t.TriggerCount,
|
||||
LastTriggered: t.LastTriggered,
|
||||
Enabled: t.Enabled,
|
||||
TriggerURL: triggerURL,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
|
@ -19,7 +21,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
urlParamTokenID = "id"
|
||||
urlParamTokenID = "id"
|
||||
urlParamManageID = "manage_id"
|
||||
|
||||
headerContentType = "Content-Type"
|
||||
headerLocation = "Location"
|
||||
|
|
@ -30,17 +33,27 @@ const (
|
|||
errorCodeInternalError = "INTERNAL_ERROR"
|
||||
errorCodeUnknownType = "UNKNOWN_TYPE"
|
||||
errorCodeGenerateFailed = "GENERATE_FAILED"
|
||||
errorCodeNotFound = "NOT_FOUND"
|
||||
errorCodeBadCursor = "BAD_CURSOR"
|
||||
|
||||
respMessageValidation = "request validation failed"
|
||||
respMessageBadJSON = "invalid JSON body"
|
||||
respMessageInternalError = "internal server error"
|
||||
respMessageGenerateFailed = "artifact generation failed"
|
||||
respMessageUnknownType = "unknown token type"
|
||||
respMessageNotFound = "not found"
|
||||
respMessageBadCursor = "invalid cursor"
|
||||
|
||||
kubeconfigPathPrefix = "/k/"
|
||||
|
||||
createTokenBodyMaxBytes = 64 * 1024
|
||||
fingerprintBodyMaxBytes = 64 * 1024
|
||||
|
||||
manageDefaultPageSize = 20
|
||||
manageMaxPageSize = 100
|
||||
|
||||
queryParamCursor = "cursor"
|
||||
queryParamLimit = "limit"
|
||||
)
|
||||
|
||||
type EventRecorder interface {
|
||||
|
|
@ -55,10 +68,25 @@ type FingerprintRecorder interface {
|
|||
) error
|
||||
}
|
||||
|
||||
type EventQuery interface {
|
||||
ListByToken(
|
||||
ctx context.Context,
|
||||
tokenID string,
|
||||
opts event.ListOptions,
|
||||
) (event.ListResult, error)
|
||||
CountByToken(ctx context.Context, tokenID string) (int64, error)
|
||||
}
|
||||
|
||||
type DedupCounter interface {
|
||||
CountActiveDedup(ctx context.Context, tokenID string) (int64, error)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
svc *Service
|
||||
events EventRecorder
|
||||
fingerprintRecorder FingerprintRecorder
|
||||
eventQuery EventQuery
|
||||
dedupCounter DedupCounter
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +94,8 @@ func NewHandler(
|
|||
svc *Service,
|
||||
events EventRecorder,
|
||||
fingerprint FingerprintRecorder,
|
||||
eventQuery EventQuery,
|
||||
dedupCounter DedupCounter,
|
||||
logger *slog.Logger,
|
||||
) *Handler {
|
||||
if logger == nil {
|
||||
|
|
@ -75,6 +105,8 @@ func NewHandler(
|
|||
svc: svc,
|
||||
events: events,
|
||||
fingerprintRecorder: fingerprint,
|
||||
eventQuery: eventQuery,
|
||||
dedupCounter: dedupCounter,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +116,11 @@ func (h *Handler) RegisterAPIRoutes(r chi.Router) {
|
|||
r.Post("/tokens", h.CreateToken)
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterManageRoutes(r chi.Router) {
|
||||
r.Get("/m/{"+urlParamManageID+"}", h.GetManage)
|
||||
r.Delete("/m/{"+urlParamManageID+"}", h.DeleteManage)
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterTriggerRoutes(r chi.Router) {
|
||||
r.Get("/c/{"+urlParamTokenID+"}", h.HandleTrigger)
|
||||
r.Post("/c/{"+urlParamTokenID+"}/fingerprint", h.HandleFingerprint)
|
||||
|
|
@ -168,6 +205,158 @@ func (h *Handler) HandleTrigger(w http.ResponseWriter, r *http.Request) {
|
|||
h.writeTriggerResponse(w, r, resp)
|
||||
}
|
||||
|
||||
func (h *Handler) GetManage(w http.ResponseWriter, r *http.Request) {
|
||||
manageID := chi.URLParam(r, urlParamManageID)
|
||||
if manageID == "" {
|
||||
h.writeJSON(w, http.StatusNotFound, envelopeError(
|
||||
errorCodeNotFound, respMessageNotFound,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
cursor, err := parseCursor(r.URL.Query().Get(queryParamCursor))
|
||||
if err != nil {
|
||||
h.writeJSON(w, http.StatusBadRequest, envelopeError(
|
||||
errorCodeBadCursor, respMessageBadCursor,
|
||||
))
|
||||
return
|
||||
}
|
||||
limit := parseLimit(r.URL.Query().Get(queryParamLimit))
|
||||
|
||||
tok, err := h.svc.GetByManageID(r.Context(), manageID)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(r.Context(), "manage: get by manage id",
|
||||
"error", err, "manage_id", manageID)
|
||||
h.writeJSON(w, http.StatusInternalServerError, envelopeError(
|
||||
errorCodeInternalError, respMessageInternalError,
|
||||
))
|
||||
return
|
||||
}
|
||||
if tok == nil {
|
||||
h.writeJSON(w, http.StatusNotFound, envelopeError(
|
||||
errorCodeNotFound, respMessageNotFound,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
events, total, silenced := h.gatherManageData(r, tok.ID, cursor, limit)
|
||||
|
||||
resp := ManageResponse{
|
||||
Token: tok.ToManageView(h.svc.TriggerURL(tok.ID)),
|
||||
Events: events,
|
||||
EventsTotal: total,
|
||||
EventsSilencedActive: silenced,
|
||||
Page: buildPage(events, limit),
|
||||
}
|
||||
h.writeJSON(w, http.StatusOK, envelopeData(resp))
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteManage(w http.ResponseWriter, r *http.Request) {
|
||||
manageID := chi.URLParam(r, urlParamManageID)
|
||||
if manageID == "" {
|
||||
h.writeJSON(w, http.StatusNotFound, envelopeError(
|
||||
errorCodeNotFound, respMessageNotFound,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteByManageID(r.Context(), manageID); err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
h.writeJSON(w, http.StatusNotFound, envelopeError(
|
||||
errorCodeNotFound, respMessageNotFound,
|
||||
))
|
||||
return
|
||||
}
|
||||
h.logger.ErrorContext(r.Context(), "manage: delete",
|
||||
"error", err, "manage_id", manageID)
|
||||
h.writeJSON(w, http.StatusInternalServerError, envelopeError(
|
||||
errorCodeInternalError, respMessageInternalError,
|
||||
))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *Handler) gatherManageData(
|
||||
r *http.Request,
|
||||
tokenID string,
|
||||
cursor int64,
|
||||
limit int,
|
||||
) (events []event.Response, total, silenced int64) {
|
||||
if h.eventQuery == nil {
|
||||
return nil, 0, 0
|
||||
}
|
||||
list, err := h.eventQuery.ListByToken(
|
||||
r.Context(), tokenID, event.ListOptions{Cursor: cursor, Limit: limit},
|
||||
)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(r.Context(), "manage: list events",
|
||||
"error", err, "token_id", tokenID)
|
||||
}
|
||||
for i := range list.Events {
|
||||
events = append(events, list.Events[i].ToResponse())
|
||||
}
|
||||
|
||||
if total, err = h.eventQuery.CountByToken(
|
||||
r.Context(),
|
||||
tokenID,
|
||||
); err != nil {
|
||||
h.logger.ErrorContext(r.Context(), "manage: count events",
|
||||
"error", err, "token_id", tokenID)
|
||||
total = 0
|
||||
}
|
||||
|
||||
if h.dedupCounter != nil {
|
||||
var dErr error
|
||||
if silenced, dErr = h.dedupCounter.CountActiveDedup(
|
||||
r.Context(), tokenID,
|
||||
); dErr != nil {
|
||||
h.logger.WarnContext(r.Context(), "manage: count active dedup",
|
||||
"error", dErr, "token_id", tokenID)
|
||||
silenced = 0
|
||||
}
|
||||
}
|
||||
return events, total, silenced
|
||||
}
|
||||
|
||||
func buildPage(events []event.Response, limit int) ManagePage {
|
||||
if len(events) < limit || len(events) == 0 {
|
||||
return ManagePage{}
|
||||
}
|
||||
last := events[len(events)-1]
|
||||
return ManagePage{
|
||||
NextCursor: strconv.FormatInt(last.ID, 10),
|
||||
HasMore: true,
|
||||
}
|
||||
}
|
||||
|
||||
func parseCursor(raw string) (int64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return 0, nil
|
||||
}
|
||||
v, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil || v < 0 {
|
||||
return 0, fmt.Errorf("invalid cursor: %q", raw)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseLimit(raw string) int {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return manageDefaultPageSize
|
||||
}
|
||||
v, err := strconv.Atoi(raw)
|
||||
if err != nil || v <= 0 {
|
||||
return manageDefaultPageSize
|
||||
}
|
||||
if v > manageMaxPageSize {
|
||||
return manageMaxPageSize
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (h *Handler) HandleFingerprint(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, urlParamTokenID)
|
||||
if id == "" || h.fingerprintRecorder == nil {
|
||||
|
|
|
|||
|
|
@ -78,13 +78,20 @@ func newWebbugHandler(
|
|||
ManageURL: "https://canary.example.com",
|
||||
},
|
||||
)
|
||||
return token.NewHandler(svc, rec, nil, quietHandlerLogger()), repo, rec
|
||||
return token.NewHandler(
|
||||
svc,
|
||||
rec,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
quietHandlerLogger(),
|
||||
), repo, rec
|
||||
}
|
||||
|
||||
func TestGetTypes_Returns7Types(t *testing.T) {
|
||||
svc := token.NewService(newFakeRepo(), token.MapRegistry{},
|
||||
token.ServiceConfig{BaseURL: "https://x.test"})
|
||||
h := token.NewHandler(svc, nil, nil, quietHandlerLogger())
|
||||
h := token.NewHandler(svc, nil, nil, nil, nil, quietHandlerLogger())
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterAPIRoutes(r)
|
||||
|
|
@ -348,7 +355,7 @@ func exposeArtifactToJSON(a generators.Artifact) token.ArtifactJSON {
|
|||
repo := newFakeRepo()
|
||||
svc := token.NewService(repo, token.MapRegistry{token.TypeWebbug: gen},
|
||||
token.ServiceConfig{BaseURL: "https://x"})
|
||||
h := token.NewHandler(svc, nil, nil, quietHandlerLogger())
|
||||
h := token.NewHandler(svc, nil, nil, nil, nil, quietHandlerLogger())
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterAPIRoutes(r)
|
||||
|
|
@ -369,3 +376,268 @@ func exposeArtifactToJSON(a generators.Artifact) token.ArtifactJSON {
|
|||
}
|
||||
return resp.Data.Artifact
|
||||
}
|
||||
|
||||
type fakeEventQuery struct {
|
||||
listResult event.ListResult
|
||||
listErr error
|
||||
countN int64
|
||||
countErr error
|
||||
lastTokID string
|
||||
lastOpts event.ListOptions
|
||||
}
|
||||
|
||||
func (f *fakeEventQuery) ListByToken(
|
||||
_ context.Context,
|
||||
tokenID string,
|
||||
opts event.ListOptions,
|
||||
) (event.ListResult, error) {
|
||||
f.lastTokID = tokenID
|
||||
f.lastOpts = opts
|
||||
return f.listResult, f.listErr
|
||||
}
|
||||
|
||||
func (f *fakeEventQuery) CountByToken(
|
||||
_ context.Context,
|
||||
_ string,
|
||||
) (int64, error) {
|
||||
return f.countN, f.countErr
|
||||
}
|
||||
|
||||
type fakeDedupCounter struct {
|
||||
n int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeDedupCounter) CountActiveDedup(
|
||||
_ context.Context,
|
||||
_ string,
|
||||
) (int64, error) {
|
||||
return f.n, f.err
|
||||
}
|
||||
|
||||
func newManageHandler(
|
||||
t *testing.T,
|
||||
eq *fakeEventQuery,
|
||||
dc *fakeDedupCounter,
|
||||
) (*token.Handler, *fakeRepo) {
|
||||
t.Helper()
|
||||
repo := newFakeRepo()
|
||||
svc := token.NewService(repo, token.MapRegistry{},
|
||||
token.ServiceConfig{
|
||||
BaseURL: "https://canary.example.com",
|
||||
ManageURL: "https://canary.example.com",
|
||||
})
|
||||
return token.NewHandler(svc, nil, nil, eq, dc, quietHandlerLogger()), repo
|
||||
}
|
||||
|
||||
func seedToken(t *testing.T, repo *fakeRepo, manageID string) *token.Token {
|
||||
t.Helper()
|
||||
tok := &token.Token{
|
||||
ID: "tok" + manageID[:8],
|
||||
ManageID: manageID,
|
||||
Type: token.TypeWebbug,
|
||||
Memo: "manage-test",
|
||||
AlertChannel: token.ChannelWebhook,
|
||||
WebhookURL: strPtr("https://x/h"),
|
||||
CreatedIP: "1.1.1.1",
|
||||
CreatedFP: "fp",
|
||||
Metadata: json.RawMessage(`{}`),
|
||||
Enabled: true,
|
||||
TriggerCount: 5,
|
||||
}
|
||||
require.NoError(t, repo.Insert(context.Background(), tok))
|
||||
return tok
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
func decodeManage(t *testing.T, body []byte) struct {
|
||||
Success bool `json:"success"`
|
||||
Data token.ManageResponse `json:"data"`
|
||||
} {
|
||||
t.Helper()
|
||||
var resp struct {
|
||||
Success bool `json:"success"`
|
||||
Data token.ManageResponse `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(body, &resp))
|
||||
return resp
|
||||
}
|
||||
|
||||
func TestGetManage_HappyPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
eq := &fakeEventQuery{
|
||||
countN: 17,
|
||||
listResult: event.ListResult{
|
||||
Events: []event.Event{
|
||||
{ID: 42, TokenID: "x", SourceIP: "1.2.3.4"},
|
||||
{ID: 41, TokenID: "x", SourceIP: "5.6.7.8"},
|
||||
},
|
||||
HasMore: false,
|
||||
},
|
||||
}
|
||||
dc := &fakeDedupCounter{n: 3}
|
||||
h, repo := newManageHandler(t, eq, dc)
|
||||
tok := seedToken(t, repo, "11111111-1111-1111-1111-111111111111")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/m/"+tok.ManageID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code, "body=%s", w.Body.String())
|
||||
resp := decodeManage(t, w.Body.Bytes())
|
||||
require.True(t, resp.Success)
|
||||
require.Equal(t, tok.ID, resp.Data.Token.ID)
|
||||
require.Equal(
|
||||
t,
|
||||
"https://canary.example.com/c/"+tok.ID,
|
||||
resp.Data.Token.TriggerURL,
|
||||
)
|
||||
require.Equal(t, int64(5), resp.Data.Token.TriggerCount)
|
||||
require.Len(t, resp.Data.Events, 2)
|
||||
require.Equal(t, int64(17), resp.Data.EventsTotal)
|
||||
require.Equal(t, int64(3), resp.Data.EventsSilencedActive)
|
||||
}
|
||||
|
||||
func TestGetManage_404OnUnknownManageID(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, _ := newManageHandler(t, &fakeEventQuery{}, &fakeDedupCounter{})
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(
|
||||
w,
|
||||
httptest.NewRequest(http.MethodGet, "/m/does-not-exist", nil),
|
||||
)
|
||||
require.Equal(t, http.StatusNotFound, w.Code)
|
||||
require.Contains(t, w.Body.String(), "NOT_FOUND")
|
||||
}
|
||||
|
||||
func TestGetManage_400OnBadCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, repo := newManageHandler(t, &fakeEventQuery{}, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "22222222-2222-2222-2222-222222222222")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet,
|
||||
"/m/"+tok.ManageID+"?cursor=notanumber", nil))
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
require.Contains(t, w.Body.String(), "BAD_CURSOR")
|
||||
}
|
||||
|
||||
func TestGetManage_400OnNegativeCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, repo := newManageHandler(t, &fakeEventQuery{}, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "33333333-3333-3333-3333-333333333333")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet,
|
||||
"/m/"+tok.ManageID+"?cursor=-1", nil))
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestGetManage_PaginationCursorAndHasMore(t *testing.T) {
|
||||
t.Parallel()
|
||||
full := []event.Event{}
|
||||
for i := 20; i > 0; i-- {
|
||||
full = append(full, event.Event{ID: int64(i), SourceIP: "1.1.1.1"})
|
||||
}
|
||||
eq := &fakeEventQuery{
|
||||
listResult: event.ListResult{
|
||||
Events: full,
|
||||
HasMore: true,
|
||||
NextCursor: 1,
|
||||
},
|
||||
countN: 50,
|
||||
}
|
||||
h, repo := newManageHandler(t, eq, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "44444444-4444-4444-4444-444444444444")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/m/"+tok.ManageID, nil))
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := decodeManage(t, w.Body.Bytes())
|
||||
require.Len(t, resp.Data.Events, 20)
|
||||
require.True(t, resp.Data.Page.HasMore)
|
||||
require.Equal(t, "1", resp.Data.Page.NextCursor,
|
||||
"cursor is the ID of the last event returned")
|
||||
}
|
||||
|
||||
func TestGetManage_LimitParamRespected(t *testing.T) {
|
||||
t.Parallel()
|
||||
eq := &fakeEventQuery{}
|
||||
h, repo := newManageHandler(t, eq, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "55555555-5555-5555-5555-555555555555")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet,
|
||||
"/m/"+tok.ManageID+"?limit=5", nil))
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
require.Equal(t, 5, eq.lastOpts.Limit)
|
||||
}
|
||||
|
||||
func TestGetManage_LimitCappedAtMax(t *testing.T) {
|
||||
t.Parallel()
|
||||
eq := &fakeEventQuery{}
|
||||
h, repo := newManageHandler(t, eq, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "66666666-6666-6666-6666-666666666666")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet,
|
||||
"/m/"+tok.ManageID+"?limit=999", nil))
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
require.LessOrEqual(t, eq.lastOpts.Limit, 100,
|
||||
"limit should be capped at manageMaxPageSize")
|
||||
}
|
||||
|
||||
func TestDeleteManage_HappyPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, repo := newManageHandler(t, &fakeEventQuery{}, &fakeDedupCounter{})
|
||||
tok := seedToken(t, repo, "77777777-7777-7777-7777-777777777777")
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(
|
||||
w,
|
||||
httptest.NewRequest(http.MethodDelete, "/m/"+tok.ManageID, nil),
|
||||
)
|
||||
require.Equal(t, http.StatusNoContent, w.Code)
|
||||
}
|
||||
|
||||
func TestDeleteManage_404OnUnknownManageID(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, _ := newManageHandler(t, &fakeEventQuery{}, &fakeDedupCounter{})
|
||||
|
||||
r := chi.NewRouter()
|
||||
h.RegisterManageRoutes(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(
|
||||
w,
|
||||
httptest.NewRequest(http.MethodDelete, "/m/does-not-exist", nil),
|
||||
)
|
||||
require.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue