688 lines
18 KiB
Go
688 lines
18 KiB
Go
// ©AngelaMos | 2026
|
|
// handler_test.go
|
|
|
|
package token_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/event"
|
|
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token"
|
|
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/token/generators"
|
|
)
|
|
|
|
type triggerGen struct {
|
|
tokenType token.Type
|
|
artifact generators.Artifact
|
|
resp *generators.TriggerResponse
|
|
evt *event.Event
|
|
}
|
|
|
|
func (g *triggerGen) Type() token.Type { return g.tokenType }
|
|
|
|
func (g *triggerGen) Generate(
|
|
_ context.Context,
|
|
_ *token.Token,
|
|
_ string,
|
|
) (generators.Artifact, error) {
|
|
return g.artifact, nil
|
|
}
|
|
|
|
func (g *triggerGen) Trigger(
|
|
_ context.Context,
|
|
_ *token.Token,
|
|
_ *http.Request,
|
|
) (*event.Event, *generators.TriggerResponse, error) {
|
|
return g.evt, g.resp, nil
|
|
}
|
|
|
|
type recordingEvents struct {
|
|
events []*event.Event
|
|
}
|
|
|
|
func (r *recordingEvents) Record(
|
|
_ context.Context,
|
|
_ *token.Token,
|
|
e *event.Event,
|
|
) error {
|
|
r.events = append(r.events, e)
|
|
return nil
|
|
}
|
|
|
|
func quietHandlerLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
}
|
|
|
|
func newWebbugHandler(
|
|
t *testing.T,
|
|
gen token.Generator,
|
|
) (*token.Handler, *fakeRepo, *recordingEvents) {
|
|
t.Helper()
|
|
repo := newFakeRepo()
|
|
rec := &recordingEvents{}
|
|
svc := token.NewService(
|
|
repo,
|
|
token.MapRegistry{token.TypeWebbug: gen},
|
|
token.ServiceConfig{
|
|
BaseURL: "https://canary.example.com",
|
|
ManageURL: "https://canary.example.com",
|
|
},
|
|
)
|
|
return token.NewHandler(
|
|
svc,
|
|
rec,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
quietHandlerLogger(),
|
|
false,
|
|
), 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, nil, nil, quietHandlerLogger(), false)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterAPIRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/tokens/types", nil))
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
var body struct {
|
|
Success bool `json:"success"`
|
|
Data []token.TypeDescriptor `json:"data"`
|
|
}
|
|
require.NoError(t, json.NewDecoder(w.Body).Decode(&body))
|
|
require.True(t, body.Success)
|
|
require.Len(t, body.Data, 7)
|
|
}
|
|
|
|
func TestCreateToken_HappyPath(t *testing.T) {
|
|
gen := &triggerGen{
|
|
tokenType: token.TypeWebbug,
|
|
artifact: generators.Artifact{
|
|
Kind: generators.KindURL, URL: "https://canary.example.com/c/x",
|
|
},
|
|
}
|
|
h, _, _ := newWebbugHandler(t, gen)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterAPIRoutes(r)
|
|
|
|
body := strings.NewReader(`{
|
|
"type":"webbug","memo":"m","alert_channel":"webhook",
|
|
"webhook_url":"https://example.com/h","cf_turnstile_response":"t",
|
|
"metadata":{}
|
|
}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/tokens", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusCreated, w.Code)
|
|
|
|
var resp struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Token token.Response `json:"token"`
|
|
Artifact token.ArtifactJSON `json:"artifact"`
|
|
} `json:"data"`
|
|
}
|
|
require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
|
|
require.True(t, resp.Success)
|
|
require.Equal(t, token.TypeWebbug, resp.Data.Token.Type)
|
|
require.NotEmpty(t, resp.Data.Token.ID)
|
|
require.Contains(t, resp.Data.Token.TriggerURL, "/c/")
|
|
require.Contains(t, resp.Data.Token.ManageURL, "/m/")
|
|
require.Equal(t, "url", resp.Data.Artifact.Kind)
|
|
}
|
|
|
|
func TestCreateToken_BadJSON(t *testing.T) {
|
|
gen := &triggerGen{tokenType: token.TypeWebbug}
|
|
h, _, _ := newWebbugHandler(t, gen)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterAPIRoutes(r)
|
|
|
|
req := httptest.NewRequest(
|
|
http.MethodPost,
|
|
"/tokens",
|
|
strings.NewReader(`{not json`),
|
|
)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
require.Contains(t, w.Body.String(), "BAD_JSON")
|
|
}
|
|
|
|
func TestCreateToken_ValidationFailure(t *testing.T) {
|
|
gen := &triggerGen{tokenType: token.TypeWebbug}
|
|
h, _, _ := newWebbugHandler(t, gen)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterAPIRoutes(r)
|
|
|
|
body := strings.NewReader(
|
|
`{"type":"webbug","memo":"m","cf_turnstile_response":"t"}`,
|
|
)
|
|
req := httptest.NewRequest(http.MethodPost, "/tokens", body)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
require.Contains(t, w.Body.String(), "VALIDATION_ERROR")
|
|
}
|
|
|
|
func TestHandleTrigger_KnownTokenReturnsResponseAndRecordsEvent(t *testing.T) {
|
|
gen := &triggerGen{
|
|
tokenType: token.TypeWebbug,
|
|
artifact: generators.Artifact{Kind: generators.KindURL},
|
|
resp: &generators.TriggerResponse{
|
|
StatusCode: 200,
|
|
ContentType: "image/gif",
|
|
Body: []byte{0x47, 0x49, 0x46, 0x38},
|
|
},
|
|
evt: &event.Event{SourceIP: "1.2.3.4"},
|
|
}
|
|
h, repo, rec := newWebbugHandler(t, gen)
|
|
|
|
tok := &token.Token{
|
|
ID: "abcdef012345", ManageID: "m", Type: token.TypeWebbug,
|
|
AlertChannel: token.ChannelWebhook, Enabled: true,
|
|
Metadata: json.RawMessage(`{}`),
|
|
}
|
|
require.NoError(t, repo.Insert(context.Background(), tok))
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterTriggerRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/c/abcdef012345", nil)
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Equal(t, "image/gif", w.Header().Get("Content-Type"))
|
|
require.Equal(t, []byte{0x47, 0x49, 0x46, 0x38}, w.Body.Bytes())
|
|
require.Len(t, rec.events, 1)
|
|
}
|
|
|
|
func TestHandleTrigger_TrimsTrailingUnderscoresFromPaddedID(t *testing.T) {
|
|
gen := &triggerGen{
|
|
tokenType: token.TypeWebbug,
|
|
artifact: generators.Artifact{Kind: generators.KindURL},
|
|
resp: &generators.TriggerResponse{
|
|
StatusCode: 200,
|
|
ContentType: "image/gif",
|
|
Body: []byte{0x47},
|
|
},
|
|
evt: &event.Event{SourceIP: "1.2.3.4"},
|
|
}
|
|
h, repo, rec := newWebbugHandler(t, gen)
|
|
|
|
tok := &token.Token{
|
|
ID: "abcdef012345", ManageID: "m", Type: token.TypeWebbug,
|
|
AlertChannel: token.ChannelWebhook, Enabled: true,
|
|
Metadata: json.RawMessage(`{}`),
|
|
}
|
|
require.NoError(t, repo.Insert(context.Background(), tok))
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterTriggerRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
padded := "/c/abcdef012345____________________"
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, padded, nil))
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Len(t, rec.events, 1,
|
|
"trailing-underscore padded id must still resolve to the real token "+
|
|
"so PDFs already in the field (carrying padded URLs) record events "+
|
|
"instead of silently no-opping (audit finding F2)")
|
|
}
|
|
|
|
func TestHandleTrigger_UnknownTokenStillReturnsArtifactShape(t *testing.T) {
|
|
gen := &triggerGen{
|
|
tokenType: token.TypeWebbug,
|
|
resp: &generators.TriggerResponse{
|
|
StatusCode: 200,
|
|
ContentType: "image/gif",
|
|
Body: []byte{0x47, 0x49, 0x46},
|
|
},
|
|
}
|
|
h, _, rec := newWebbugHandler(t, gen)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterTriggerRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/c/nonexistent1", nil))
|
|
|
|
require.Equal(t, http.StatusOK, w.Code,
|
|
"unknown tokens still return artifact shape (defense in depth)")
|
|
require.Empty(t, rec.events, "no event recorded for unknown token")
|
|
}
|
|
|
|
func TestHandleTrigger_DisabledTokenIsTreatedAsUnknown(t *testing.T) {
|
|
gen := &triggerGen{
|
|
tokenType: token.TypeWebbug,
|
|
resp: &generators.TriggerResponse{
|
|
StatusCode: 200,
|
|
ContentType: "image/gif",
|
|
Body: []byte{0x47},
|
|
},
|
|
evt: &event.Event{SourceIP: "1.2.3.4"},
|
|
}
|
|
h, repo, rec := newWebbugHandler(t, gen)
|
|
|
|
tok := &token.Token{
|
|
ID: "disabled1234", ManageID: "m", Type: token.TypeWebbug,
|
|
AlertChannel: token.ChannelWebhook, Enabled: false,
|
|
Metadata: json.RawMessage(`{}`),
|
|
}
|
|
require.NoError(t, repo.Insert(context.Background(), tok))
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterTriggerRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/c/disabled1234", nil))
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
require.Empty(t, rec.events, "disabled token must not record events")
|
|
}
|
|
|
|
func TestHandleFingerprint_Returns204WithNoRecorder(t *testing.T) {
|
|
gen := &triggerGen{tokenType: token.TypeWebbug}
|
|
h, _, _ := newWebbugHandler(t, gen)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterTriggerRoutes(r)
|
|
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost,
|
|
"/c/anything/fingerprint", strings.NewReader(`{"x":1}`)))
|
|
|
|
require.Equal(t, http.StatusNoContent, w.Code)
|
|
}
|
|
|
|
func TestArtifactToJSON_Kinds(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in generators.Artifact
|
|
want token.ArtifactJSON
|
|
}{
|
|
{
|
|
"url",
|
|
generators.Artifact{
|
|
Kind: generators.KindURL,
|
|
URL: "u",
|
|
DestinationURL: "d",
|
|
},
|
|
token.ArtifactJSON{Kind: "url", URL: "u", DestinationURL: "d"},
|
|
},
|
|
{
|
|
"file",
|
|
generators.Artifact{
|
|
Kind: generators.KindFile,
|
|
Filename: "f.docx",
|
|
ContentType: "x",
|
|
Content: []byte("hi"),
|
|
},
|
|
token.ArtifactJSON{
|
|
Kind: "file",
|
|
Filename: "f.docx",
|
|
ContentType: "x",
|
|
ContentB64: "aGk=",
|
|
},
|
|
},
|
|
{
|
|
"text",
|
|
generators.Artifact{
|
|
Kind: generators.KindText,
|
|
Filename: ".env",
|
|
ContentType: "text/plain",
|
|
Content: []byte("KEY=v"),
|
|
},
|
|
token.ArtifactJSON{
|
|
Kind: "text",
|
|
Filename: ".env",
|
|
ContentType: "text/plain",
|
|
Content: "KEY=v",
|
|
},
|
|
},
|
|
{
|
|
"conn",
|
|
generators.Artifact{
|
|
Kind: generators.KindConnectionString,
|
|
ConnectionString: "mysql://x",
|
|
},
|
|
token.ArtifactJSON{
|
|
Kind: "connection_string",
|
|
ConnectionString: "mysql://x",
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
c := c
|
|
t.Run(c.name, func(t *testing.T) {
|
|
require.Equal(t, c.want, exposeArtifactToJSON(c.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func exposeArtifactToJSON(a generators.Artifact) token.ArtifactJSON {
|
|
gen := &triggerGen{tokenType: token.TypeWebbug, artifact: a}
|
|
repo := newFakeRepo()
|
|
svc := token.NewService(repo, token.MapRegistry{token.TypeWebbug: gen},
|
|
token.ServiceConfig{BaseURL: "https://x"})
|
|
h := token.NewHandler(svc, nil, nil, nil, nil, quietHandlerLogger(), false)
|
|
|
|
r := chi.NewRouter()
|
|
h.RegisterAPIRoutes(r)
|
|
|
|
body := strings.NewReader(
|
|
`{"type":"webbug","alert_channel":"webhook","webhook_url":"https://x/h","cf_turnstile_response":"t","metadata":{}}`,
|
|
)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/tokens", body))
|
|
|
|
var resp struct {
|
|
Data struct {
|
|
Artifact token.ArtifactJSON `json:"artifact"`
|
|
} `json:"data"`
|
|
}
|
|
if jsonErr := json.NewDecoder(w.Body).Decode(&resp); jsonErr != nil {
|
|
panic(jsonErr)
|
|
}
|
|
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(),
|
|
false,
|
|
),
|
|
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)
|
|
}
|