From 8358b4c9c75541d1c0627ce282e3a379c43d2270 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Tue, 14 Oct 2025 10:17:02 +0700 Subject: [PATCH 01/10] feat: idgen --- go.mod | 11 +- go.sum | 26 +-- internal/app/app.go | 13 ++ internal/config/config.go | 8 + internal/config/id_gen.go | 7 + internal/idgen/idgen.go | 114 +++++++++++ internal/idgen/idgen_test.go | 220 ++++++++++++++++++++++ internal/publishmq/messagehandler.go | 4 +- internal/services/api/publish_handlers.go | 4 +- 9 files changed, 386 insertions(+), 21 deletions(-) create mode 100644 internal/config/id_gen.go create mode 100644 internal/idgen/idgen.go create mode 100644 internal/idgen/idgen_test.go diff --git a/go.mod b/go.mod index 8dfa8a79..dcab9c94 100644 --- a/go.mod +++ b/go.mod @@ -32,9 +32,10 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hookdeck/outpost/sdks/outpost-go v0.4.0 - github.com/jackc/pgx/v5 v5.7.2 + github.com/jackc/pgx/v5 v5.7.6 github.com/jmespath/go-jmespath v0.4.0 github.com/joho/godotenv v1.5.1 + github.com/matoous/go-nanoid/v2 v2.1.0 github.com/mikestefanello/batcher v0.1.0 github.com/pkg/errors v0.9.1 github.com/rabbitmq/amqp091-go v1.10.0 @@ -72,7 +73,7 @@ require ( gocloud.dev v0.39.0 gocloud.dev/pubsub/rabbitpubsub v0.39.0 golang.org/x/oauth2 v0.22.0 - golang.org/x/sync v0.11.0 + golang.org/x/sync v0.13.0 google.golang.org/api v0.191.0 google.golang.org/grpc v1.65.0 gopkg.in/yaml.v3 v3.0.1 @@ -216,12 +217,12 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.35.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.36.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect diff --git a/go.sum b/go.sum index 57376c11..899b37c6 100644 --- a/go.sum +++ b/go.sum @@ -1068,8 +1068,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= -github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -1119,6 +1119,8 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= +github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -1404,8 +1406,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1584,8 +1586,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1676,8 +1678,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1689,8 +1691,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1709,8 +1711,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/app/app.go b/internal/app/app.go index 51d8cf4a..ab89aa87 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,6 +11,7 @@ import ( "time" "github.com/hookdeck/outpost/internal/config" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/infra" "github.com/hookdeck/outpost/internal/logging" "github.com/hookdeck/outpost/internal/migrator" @@ -56,6 +57,18 @@ func run(mainContext context.Context, cfg *config.Config) error { } logger.Info("starting outpost", logFields...) + // Initialize ID generators + logger.Debug("configuring ID generators", + zap.String("type", cfg.IDGen.Type), + zap.String("event_prefix", cfg.IDGen.EventPrefix)) + if err := idgen.Configure(idgen.IDGenConfig{ + Type: cfg.IDGen.Type, + EventPrefix: cfg.IDGen.EventPrefix, + }); err != nil { + logger.Error("failed to configure ID generators", zap.Error(err)) + return err + } + if err := runMigration(mainContext, cfg, logger); err != nil { return err } diff --git a/internal/config/config.go b/internal/config/config.go index e82b9142..1c206657 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -97,6 +97,9 @@ type Config struct { // Alert Alert AlertConfig `yaml:"alert"` + + // ID Generation + IDGen IDGenConfig `yaml:"id_gen"` } var ( @@ -184,6 +187,11 @@ func (c *Config) InitDefaults() { HookdeckSourceURL: "https://hkdk.events/yhk665ljz3rn6l", SentryDSN: "https://examplePublicKey@o0.ingest.sentry.io/0", } + + c.IDGen = IDGenConfig{ + Type: "uuidv4", + EventPrefix: "", + } } func (c *Config) parseConfigFile(flagPath string, osInterface OSInterface) error { diff --git a/internal/config/id_gen.go b/internal/config/id_gen.go new file mode 100644 index 00000000..12885055 --- /dev/null +++ b/internal/config/id_gen.go @@ -0,0 +1,7 @@ +package config + +// IDGenConfig is the configuration for ID generation +type IDGenConfig struct { + Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` + EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` +} diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go new file mode 100644 index 00000000..bb53cf18 --- /dev/null +++ b/internal/idgen/idgen.go @@ -0,0 +1,114 @@ +package idgen + +import ( + "fmt" + + "github.com/google/uuid" + gonanoid "github.com/matoous/go-nanoid/v2" +) + +var ( + globalGenerator *IDGenerator +) + +func init() { + // Initialize with default UUID v4 generator + globalGenerator = &IDGenerator{ + generator: &uuidv4Generator{}, + eventPrefix: "", + } +} + +type idGenerator interface { + generate() string +} + +type IDGenerator struct { + generator idGenerator + eventPrefix string +} + +func (g *IDGenerator) Event() string { + return g.generate(g.eventPrefix) +} + +func (g *IDGenerator) generate(prefix string) string { + id := g.generator.generate() + if prefix != "" { + return prefix + "_" + id + } + return id +} + +// newIDGenerator creates a new ID generator with the given type +func newIDGenerator(idType string) (idGenerator, error) { + if idType == "" { + idType = "uuidv4" + } + + // Select the appropriate generator implementation + switch idType { + case "uuidv4": + return &uuidv4Generator{}, nil + case "uuidv7": + return &uuidv7Generator{}, nil + case "nanoid": + return &nanoidGenerator{}, nil + default: + return nil, fmt.Errorf("invalid id type: %s (must be one of: uuidv4, uuidv7, nanoid)", idType) + } +} + +// Generator implementations + +type uuidv4Generator struct{} + +func (g *uuidv4Generator) generate() string { + return uuid.New().String() +} + +type uuidv7Generator struct{} + +func (g *uuidv7Generator) generate() string { + id, err := uuid.NewV7() + if err != nil { + return uuid.New().String() + } + return id.String() +} + +type nanoidGenerator struct{} + +func (g *nanoidGenerator) generate() string { + const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const length = 26 + + id, err := gonanoid.Generate(alphabet, length) + if err != nil { + return uuid.New().String() + } + return id +} + +type IDGenConfig struct { + Type string + EventPrefix string +} + +func Configure(cfg IDGenConfig) error { + gen, err := newIDGenerator(cfg.Type) + if err != nil { + return fmt.Errorf("failed to configure ID generator: %w", err) + } + + globalGenerator = &IDGenerator{ + generator: gen, + eventPrefix: cfg.EventPrefix, + } + + return nil +} + +func Event() string { + return globalGenerator.Event() +} diff --git a/internal/idgen/idgen_test.go b/internal/idgen/idgen_test.go new file mode 100644 index 00000000..23c25431 --- /dev/null +++ b/internal/idgen/idgen_test.go @@ -0,0 +1,220 @@ +package idgen + +import ( + "strings" + "testing" + + "github.com/google/uuid" +) + +func TestConfigure(t *testing.T) { + tests := []struct { + name string + idType string + wantErr bool + description string + }{ + { + name: "empty type uses default", + idType: "", + wantErr: false, + description: "should use default uuidv4", + }, + { + name: "valid uuidv4 type", + idType: "uuidv4", + wantErr: false, + description: "should accept uuidv4 type", + }, + { + name: "valid uuidv7 type", + idType: "uuidv7", + wantErr: false, + description: "should accept uuidv7 type", + }, + { + name: "valid nanoid type", + idType: "nanoid", + wantErr: false, + description: "should accept nanoid type", + }, + { + name: "invalid type", + idType: "invalid", + wantErr: true, + description: "should fail on invalid type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Configure(IDGenConfig{ + Type: tt.idType, + EventPrefix: "", + }) + if (err != nil) != tt.wantErr { + t.Errorf("Configure() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestEvent_Generate(t *testing.T) { + tests := []struct { + name string + idType string + prefix string + validate func(t *testing.T, id string) + }{ + { + name: "uuidv4 generates valid UUID", + idType: "uuidv4", + prefix: "", + validate: func(t *testing.T, id string) { + if _, err := uuid.Parse(id); err != nil { + t.Errorf("Generated ID is not a valid UUID: %s", id) + } + // UUIDv4 has version 4 in the correct position + if !strings.Contains(id, "-4") { + t.Errorf("Generated ID is not a UUID v4: %s", id) + } + }, + }, + { + name: "uuidv7 generates valid UUID", + idType: "uuidv7", + prefix: "", + validate: func(t *testing.T, id string) { + if _, err := uuid.Parse(id); err != nil { + t.Errorf("Generated ID is not a valid UUID: %s", id) + } + // UUIDv7 has version 7 in the correct position + parsed, _ := uuid.Parse(id) + if parsed.Version() != 7 { + t.Errorf("Generated ID is not a UUID v7: %s (version: %d)", id, parsed.Version()) + } + }, + }, + { + name: "nanoid generates valid ID", + idType: "nanoid", + prefix: "", + validate: func(t *testing.T, id string) { + if len(id) != 26 { + t.Errorf("Nanoid should be 26 characters, got %d: %s", len(id), id) + } + // Check it only contains valid characters + const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + for _, c := range id { + if !strings.ContainsRune(alphabet, c) { + t.Errorf("Nanoid contains invalid character: %c", c) + } + } + }, + }, + { + name: "uuidv4 with prefix", + idType: "uuidv4", + prefix: "evt", + validate: func(t *testing.T, id string) { + if !strings.HasPrefix(id, "evt_") { + t.Errorf("ID should have prefix 'evt_', got: %s", id) + } + uuidPart := strings.TrimPrefix(id, "evt_") + if _, err := uuid.Parse(uuidPart); err != nil { + t.Errorf("UUID part is not valid: %s", uuidPart) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Configure(IDGenConfig{ + Type: tt.idType, + EventPrefix: tt.prefix, + }) + if err != nil { + t.Fatalf("Configure() error = %v", err) + } + + id := Event() + if id == "" { + t.Error("Event() returned empty string") + } + + tt.validate(t, id) + }) + } +} + +func TestEvent_Uniqueness(t *testing.T) { + err := Configure(IDGenConfig{ + Type: "uuidv4", + EventPrefix: "", + }) + if err != nil { + t.Fatalf("Configure() error = %v", err) + } + + // Generate multiple IDs and ensure they're unique + ids := make(map[string]bool) + for i := 0; i < 100; i++ { + id := Event() + if ids[id] { + t.Errorf("Generated duplicate ID: %s", id) + } + ids[id] = true + } +} + +func TestEvent(t *testing.T) { + t.Run("generates UUID v4 by default", func(t *testing.T) { + id := Event() + if id == "" { + t.Error("Event() returned empty string") + } + if _, err := uuid.Parse(id); err != nil { + t.Errorf("Event() returned invalid UUID: %s", id) + } + }) + + t.Run("uses configured type and prefix", func(t *testing.T) { + err := Configure(IDGenConfig{ + Type: "uuidv4", + EventPrefix: "evt", + }) + if err != nil { + t.Fatalf("Configure() error = %v", err) + } + + id := Event() + if !strings.HasPrefix(id, "evt_") { + t.Errorf("Event() = %v, want prefix 'evt_'", id) + } + }) +} + +func BenchmarkEvent_UUIDv4(b *testing.B) { + Configure(IDGenConfig{Type: "uuidv4", EventPrefix: ""}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Event() + } +} + +func BenchmarkEvent_UUIDv7(b *testing.B) { + Configure(IDGenConfig{Type: "uuidv7", EventPrefix: ""}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Event() + } +} + +func BenchmarkEvent_Nanoid(b *testing.B) { + Configure(IDGenConfig{Type: "nanoid", EventPrefix: ""}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Event() + } +} diff --git a/internal/publishmq/messagehandler.go b/internal/publishmq/messagehandler.go index 557778b0..0f7c506a 100644 --- a/internal/publishmq/messagehandler.go +++ b/internal/publishmq/messagehandler.go @@ -5,8 +5,8 @@ import ( "encoding/json" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/consumer" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/mqs" ) @@ -52,7 +52,7 @@ type PublishedEvent struct { func (p *PublishedEvent) toEvent() models.Event { id := p.ID if id == "" { - id = uuid.New().String() + id = idgen.Event() } eventTime := p.Time if eventTime.IsZero() { diff --git a/internal/services/api/publish_handlers.go b/internal/services/api/publish_handlers.go index 3740b400..69400ddf 100644 --- a/internal/services/api/publish_handlers.go +++ b/internal/services/api/publish_handlers.go @@ -6,8 +6,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/idempotence" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/publishmq" @@ -78,7 +78,7 @@ type PublishedEvent struct { func (p *PublishedEvent) toEvent() models.Event { id := p.ID if id == "" { - id = uuid.New().String() + id = idgen.Event() } eventTime := p.Time if eventTime.IsZero() { From 8a15cc12467f425879456d89dd33370cdcf8a94a Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 10:29:47 +0700 Subject: [PATCH 02/10] feat: idgen destination --- internal/app/app.go | 8 ++++-- internal/config/id_gen.go | 5 ++-- internal/idgen/idgen.go | 28 +++++++++++++------ internal/services/api/destination_handlers.go | 3 +- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index ab89aa87..ba8e2f66 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -60,10 +60,12 @@ func run(mainContext context.Context, cfg *config.Config) error { // Initialize ID generators logger.Debug("configuring ID generators", zap.String("type", cfg.IDGen.Type), - zap.String("event_prefix", cfg.IDGen.EventPrefix)) + zap.String("event_prefix", cfg.IDGen.EventPrefix), + zap.String("destination_prefix", cfg.IDGen.DestinationPrefix)) if err := idgen.Configure(idgen.IDGenConfig{ - Type: cfg.IDGen.Type, - EventPrefix: cfg.IDGen.EventPrefix, + Type: cfg.IDGen.Type, + EventPrefix: cfg.IDGen.EventPrefix, + DestinationPrefix: cfg.IDGen.DestinationPrefix, }); err != nil { logger.Error("failed to configure ID generators", zap.Error(err)) return err diff --git a/internal/config/id_gen.go b/internal/config/id_gen.go index 12885055..295e89c3 100644 --- a/internal/config/id_gen.go +++ b/internal/config/id_gen.go @@ -2,6 +2,7 @@ package config // IDGenConfig is the configuration for ID generation type IDGenConfig struct { - Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` - EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` + Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` + EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` + DestinationPrefix string `yaml:"destination_prefix" env:"ID_GEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` } diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go index bb53cf18..fc706ace 100644 --- a/internal/idgen/idgen.go +++ b/internal/idgen/idgen.go @@ -14,8 +14,9 @@ var ( func init() { // Initialize with default UUID v4 generator globalGenerator = &IDGenerator{ - generator: &uuidv4Generator{}, - eventPrefix: "", + generator: &uuidv4Generator{}, + eventPrefix: "", + destinationPrefix: "", } } @@ -24,14 +25,19 @@ type idGenerator interface { } type IDGenerator struct { - generator idGenerator - eventPrefix string + generator idGenerator + eventPrefix string + destinationPrefix string } func (g *IDGenerator) Event() string { return g.generate(g.eventPrefix) } +func (g *IDGenerator) Destination() string { + return g.generate(g.destinationPrefix) +} + func (g *IDGenerator) generate(prefix string) string { id := g.generator.generate() if prefix != "" { @@ -91,8 +97,9 @@ func (g *nanoidGenerator) generate() string { } type IDGenConfig struct { - Type string - EventPrefix string + Type string + EventPrefix string + DestinationPrefix string } func Configure(cfg IDGenConfig) error { @@ -102,8 +109,9 @@ func Configure(cfg IDGenConfig) error { } globalGenerator = &IDGenerator{ - generator: gen, - eventPrefix: cfg.EventPrefix, + generator: gen, + eventPrefix: cfg.EventPrefix, + destinationPrefix: cfg.DestinationPrefix, } return nil @@ -112,3 +120,7 @@ func Configure(cfg IDGenConfig) error { func Event() string { return globalGenerator.Event() } + +func Destination() string { + return globalGenerator.Destination() +} diff --git a/internal/services/api/destination_handlers.go b/internal/services/api/destination_handlers.go index 474ebda7..ec493cf7 100644 --- a/internal/services/api/destination_handlers.go +++ b/internal/services/api/destination_handlers.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/telemetry" @@ -319,7 +320,7 @@ type CreateDestinationRequest struct { func (r *CreateDestinationRequest) ToDestination(tenantID string) models.Destination { if r.ID == "" { - r.ID = uuid.New().String() + r.ID = idgen.Destination() } if r.Config == nil { r.Config = make(map[string]string) From 7e473993439dbaef9276ff89b9fbf9b66b3cc3b2 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 10:33:21 +0700 Subject: [PATCH 03/10] feat: idgen delivery --- internal/app/app.go | 4 +++- internal/config/id_gen.go | 1 + internal/destregistry/registry.go | 3 ++- internal/idgen/idgen.go | 12 ++++++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index ba8e2f66..6a844c5d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -61,11 +61,13 @@ func run(mainContext context.Context, cfg *config.Config) error { logger.Debug("configuring ID generators", zap.String("type", cfg.IDGen.Type), zap.String("event_prefix", cfg.IDGen.EventPrefix), - zap.String("destination_prefix", cfg.IDGen.DestinationPrefix)) + zap.String("destination_prefix", cfg.IDGen.DestinationPrefix), + zap.String("delivery_prefix", cfg.IDGen.DeliveryPrefix)) if err := idgen.Configure(idgen.IDGenConfig{ Type: cfg.IDGen.Type, EventPrefix: cfg.IDGen.EventPrefix, DestinationPrefix: cfg.IDGen.DestinationPrefix, + DeliveryPrefix: cfg.IDGen.DeliveryPrefix, }); err != nil { logger.Error("failed to configure ID generators", zap.Error(err)) return err diff --git a/internal/config/id_gen.go b/internal/config/id_gen.go index 295e89c3..693dfd4d 100644 --- a/internal/config/id_gen.go +++ b/internal/config/id_gen.go @@ -5,4 +5,5 @@ type IDGenConfig struct { Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` DestinationPrefix string `yaml:"destination_prefix" env:"ID_GEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` + DeliveryPrefix string `yaml:"delivery_prefix" env:"ID_GEN_DELIVERY_PREFIX" desc:"Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix)" required:"N"` } diff --git a/internal/destregistry/registry.go b/internal/destregistry/registry.go index 98c3e09d..aa7a66bb 100644 --- a/internal/destregistry/registry.go +++ b/internal/destregistry/registry.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry/metadata" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" "github.com/hookdeck/outpost/internal/lru" "github.com/hookdeck/outpost/internal/models" @@ -142,7 +143,7 @@ func (r *registry) PublishEvent(ctx context.Context, destination *models.Destina } delivery := &models.Delivery{ - ID: uuid.New().String(), + ID: idgen.Delivery(), DestinationID: destination.ID, EventID: event.ID, } diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go index fc706ace..4c35d278 100644 --- a/internal/idgen/idgen.go +++ b/internal/idgen/idgen.go @@ -17,6 +17,7 @@ func init() { generator: &uuidv4Generator{}, eventPrefix: "", destinationPrefix: "", + deliveryPrefix: "", } } @@ -28,6 +29,7 @@ type IDGenerator struct { generator idGenerator eventPrefix string destinationPrefix string + deliveryPrefix string } func (g *IDGenerator) Event() string { @@ -38,6 +40,10 @@ func (g *IDGenerator) Destination() string { return g.generate(g.destinationPrefix) } +func (g *IDGenerator) Delivery() string { + return g.generate(g.deliveryPrefix) +} + func (g *IDGenerator) generate(prefix string) string { id := g.generator.generate() if prefix != "" { @@ -100,6 +106,7 @@ type IDGenConfig struct { Type string EventPrefix string DestinationPrefix string + DeliveryPrefix string } func Configure(cfg IDGenConfig) error { @@ -112,6 +119,7 @@ func Configure(cfg IDGenConfig) error { generator: gen, eventPrefix: cfg.EventPrefix, destinationPrefix: cfg.DestinationPrefix, + deliveryPrefix: cfg.DeliveryPrefix, } return nil @@ -124,3 +132,7 @@ func Event() string { func Destination() string { return globalGenerator.Destination() } + +func Delivery() string { + return globalGenerator.Delivery() +} From a36d58561bd8e1c166f3e85ed5894c62222389ee Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 10:46:31 +0700 Subject: [PATCH 04/10] feat: idgen event --- internal/app/app.go | 12 ++++++----- internal/config/id_gen.go | 9 ++++---- internal/idgen/idgen.go | 44 +++++++++++++++++++++++++-------------- internal/models/event.go | 4 ++-- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 6a844c5d..5879f20b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -62,12 +62,14 @@ func run(mainContext context.Context, cfg *config.Config) error { zap.String("type", cfg.IDGen.Type), zap.String("event_prefix", cfg.IDGen.EventPrefix), zap.String("destination_prefix", cfg.IDGen.DestinationPrefix), - zap.String("delivery_prefix", cfg.IDGen.DeliveryPrefix)) + zap.String("delivery_prefix", cfg.IDGen.DeliveryPrefix), + zap.String("delivery_event_prefix", cfg.IDGen.DeliveryEventPrefix)) if err := idgen.Configure(idgen.IDGenConfig{ - Type: cfg.IDGen.Type, - EventPrefix: cfg.IDGen.EventPrefix, - DestinationPrefix: cfg.IDGen.DestinationPrefix, - DeliveryPrefix: cfg.IDGen.DeliveryPrefix, + Type: cfg.IDGen.Type, + EventPrefix: cfg.IDGen.EventPrefix, + DestinationPrefix: cfg.IDGen.DestinationPrefix, + DeliveryPrefix: cfg.IDGen.DeliveryPrefix, + DeliveryEventPrefix: cfg.IDGen.DeliveryEventPrefix, }); err != nil { logger.Error("failed to configure ID generators", zap.Error(err)) return err diff --git a/internal/config/id_gen.go b/internal/config/id_gen.go index 693dfd4d..4e19b4b6 100644 --- a/internal/config/id_gen.go +++ b/internal/config/id_gen.go @@ -2,8 +2,9 @@ package config // IDGenConfig is the configuration for ID generation type IDGenConfig struct { - Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` - EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` - DestinationPrefix string `yaml:"destination_prefix" env:"ID_GEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` - DeliveryPrefix string `yaml:"delivery_prefix" env:"ID_GEN_DELIVERY_PREFIX" desc:"Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix)" required:"N"` + Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` + EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` + DestinationPrefix string `yaml:"destination_prefix" env:"ID_GEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` + DeliveryPrefix string `yaml:"delivery_prefix" env:"ID_GEN_DELIVERY_PREFIX" desc:"Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix)" required:"N"` + DeliveryEventPrefix string `yaml:"delivery_event_prefix" env:"ID_GEN_DELIVERY_EVENT_PREFIX" desc:"Prefix for delivery event IDs, prepended with underscore (e.g., 'dev_123'). Default: empty (no prefix)" required:"N"` } diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go index 4c35d278..040e3888 100644 --- a/internal/idgen/idgen.go +++ b/internal/idgen/idgen.go @@ -14,10 +14,11 @@ var ( func init() { // Initialize with default UUID v4 generator globalGenerator = &IDGenerator{ - generator: &uuidv4Generator{}, - eventPrefix: "", - destinationPrefix: "", - deliveryPrefix: "", + generator: &uuidv4Generator{}, + eventPrefix: "", + destinationPrefix: "", + deliveryPrefix: "", + deliveryEventPrefix: "", } } @@ -26,10 +27,11 @@ type idGenerator interface { } type IDGenerator struct { - generator idGenerator - eventPrefix string - destinationPrefix string - deliveryPrefix string + generator idGenerator + eventPrefix string + destinationPrefix string + deliveryPrefix string + deliveryEventPrefix string } func (g *IDGenerator) Event() string { @@ -44,6 +46,10 @@ func (g *IDGenerator) Delivery() string { return g.generate(g.deliveryPrefix) } +func (g *IDGenerator) DeliveryEvent() string { + return g.generate(g.deliveryEventPrefix) +} + func (g *IDGenerator) generate(prefix string) string { id := g.generator.generate() if prefix != "" { @@ -103,10 +109,11 @@ func (g *nanoidGenerator) generate() string { } type IDGenConfig struct { - Type string - EventPrefix string - DestinationPrefix string - DeliveryPrefix string + Type string + EventPrefix string + DestinationPrefix string + DeliveryPrefix string + DeliveryEventPrefix string } func Configure(cfg IDGenConfig) error { @@ -116,10 +123,11 @@ func Configure(cfg IDGenConfig) error { } globalGenerator = &IDGenerator{ - generator: gen, - eventPrefix: cfg.EventPrefix, - destinationPrefix: cfg.DestinationPrefix, - deliveryPrefix: cfg.DeliveryPrefix, + generator: gen, + eventPrefix: cfg.EventPrefix, + destinationPrefix: cfg.DestinationPrefix, + deliveryPrefix: cfg.DeliveryPrefix, + deliveryEventPrefix: cfg.DeliveryEventPrefix, } return nil @@ -136,3 +144,7 @@ func Destination() string { func Delivery() string { return globalGenerator.Delivery() } + +func DeliveryEvent() string { + return globalGenerator.DeliveryEvent() +} diff --git a/internal/models/event.go b/internal/models/event.go index 695ce0d2..9593cc61 100644 --- a/internal/models/event.go +++ b/internal/models/event.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/mqs" ) @@ -124,7 +124,7 @@ func (e *DeliveryEvent) GetRetryID() string { func NewDeliveryEvent(event Event, destinationID string) DeliveryEvent { return DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), DestinationID: destinationID, Event: event, Attempt: 0, From 0faa8199ecb40fbeb6e6ef928d1366d4c870e147 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 11:58:46 +0700 Subject: [PATCH 05/10] feat: idgen installation --- internal/app/installation.go | 4 ++-- internal/idgen/idgen.go | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/app/installation.go b/internal/app/installation.go index aefecf3c..9019e7ea 100644 --- a/internal/app/installation.go +++ b/internal/app/installation.go @@ -3,7 +3,7 @@ package app import ( "context" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/redis" "github.com/hookdeck/outpost/internal/telemetry" ) @@ -31,7 +31,7 @@ func getInstallation(ctx context.Context, redisClient redis.Cmdable, telemetryCo } // Installation ID doesn't exist, create one atomically - newInstallationID := uuid.New().String() + newInstallationID := idgen.Installation() // Use HSETNX to atomically set the installation ID only if it doesn't exist // This prevents race conditions when multiple Outpost instances start simultaneously diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go index 040e3888..9c884a61 100644 --- a/internal/idgen/idgen.go +++ b/internal/idgen/idgen.go @@ -50,6 +50,10 @@ func (g *IDGenerator) DeliveryEvent() string { return g.generate(g.deliveryEventPrefix) } +func (g *IDGenerator) Installation() string { + return g.generate("") +} + func (g *IDGenerator) generate(prefix string) string { id := g.generator.generate() if prefix != "" { @@ -148,3 +152,7 @@ func Delivery() string { func DeliveryEvent() string { return globalGenerator.DeliveryEvent() } + +func Installation() string { + return globalGenerator.Installation() +} From f64320554a05582ece6516fed25190f61e278779 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 12:02:21 +0700 Subject: [PATCH 06/10] fix: unused import --- internal/destregistry/registry.go | 1 - internal/services/api/destination_handlers.go | 1 - 2 files changed, 2 deletions(-) diff --git a/internal/destregistry/registry.go b/internal/destregistry/registry.go index aa7a66bb..02779914 100644 --- a/internal/destregistry/registry.go +++ b/internal/destregistry/registry.go @@ -8,7 +8,6 @@ import ( "strconv" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry/metadata" "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" diff --git a/internal/services/api/destination_handlers.go b/internal/services/api/destination_handlers.go index ec493cf7..ba581e03 100644 --- a/internal/services/api/destination_handlers.go +++ b/internal/services/api/destination_handlers.go @@ -7,7 +7,6 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry" "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" From 475e953e290af60cdd9d54e52737a11ac0869eff Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 12:23:53 +0700 Subject: [PATCH 07/10] test: remove uuid usage from test files --- cmd/e2e/alert_test.go | 10 +-- cmd/e2e/api_test.go | 24 +++--- cmd/e2e/configs/basic.go | 8 +- cmd/e2e/destwebhook_test.go | 26 +++---- internal/deliverymq/messagehandler_test.go | 74 +++++++++---------- internal/deliverymq/mock_test.go | 14 ++-- internal/deliverymq/retry_test.go | 18 ++--- .../destawskinesis_publish_test.go | 4 +- .../destawss3/destawss3_publish_test.go | 4 +- .../destrabbitmq/destrabbitmq_publish_test.go | 4 +- internal/idempotence/idempotence_test.go | 8 +- internal/idgen/idgen.go | 4 + internal/infra/infra_test.go | 10 +-- internal/models/entity_test.go | 8 +- internal/models/entitysuite_test.go | 34 ++++----- internal/mqinfra/mqinfra_test.go | 16 ++-- internal/publishmq/eventhandler_test.go | 6 +- internal/scheduler/scheduler_test.go | 20 ++--- .../services/api/publish_handlers_test.go | 8 +- .../api/requiretenant_middleware_test.go | 4 +- internal/services/api/router_test.go | 14 ++-- internal/services/api/tenant_handlers_test.go | 14 ++-- internal/services/delivery/delivery_test.go | 6 +- internal/util/testutil/destination.go | 6 +- internal/util/testutil/event.go | 14 ++-- 25 files changed, 181 insertions(+), 177 deletions(-) diff --git a/cmd/e2e/alert_test.go b/cmd/e2e/alert_test.go index 1f83fbf5..dba926b0 100644 --- a/cmd/e2e/alert_test.go +++ b/cmd/e2e/alert_test.go @@ -5,15 +5,15 @@ import ( "net/http" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/cmd/e2e/httpclient" + "github.com/hookdeck/outpost/internal/idgen" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func (suite *basicSuite) TestConsecutiveFailuresAlert() { - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() secret := "testsecret1234567890abcdefghijklmnop" tests := []APITest{ @@ -136,8 +136,8 @@ func (suite *basicSuite) TestConsecutiveFailuresAlert() { } func (suite *basicSuite) TestConsecutiveFailuresAlertReset() { - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() secret := "testsecret1234567890abcdefghijklmnop" // Setup phase - same as before diff --git a/cmd/e2e/api_test.go b/cmd/e2e/api_test.go index 1b75016e..782263ad 100644 --- a/cmd/e2e/api_test.go +++ b/cmd/e2e/api_test.go @@ -3,8 +3,8 @@ package e2e_test import ( "net/http" - "github.com/google/uuid" "github.com/hookdeck/outpost/cmd/e2e/httpclient" + "github.com/hookdeck/outpost/internal/idgen" ) func (suite *basicSuite) TestHealthzAPI() { @@ -26,8 +26,8 @@ func (suite *basicSuite) TestHealthzAPI() { } func (suite *basicSuite) TestTenantsAPI() { - tenantID := uuid.New().String() - sampleDestinationID := uuid.New().String() + tenantID := idgen.String() + sampleDestinationID := idgen.Destination() tests := []APITest{ { Name: "GET /:tenantID without auth header", @@ -272,8 +272,8 @@ func (suite *basicSuite) TestTenantsAPI() { } func (suite *basicSuite) TestDestinationsAPI() { - tenantID := uuid.New().String() - sampleDestinationID := uuid.New().String() + tenantID := idgen.String() + sampleDestinationID := idgen.Destination() tests := []APITest{ { Name: "PUT /:tenantID", @@ -582,7 +582,7 @@ func (suite *basicSuite) TestDestinationsAPI() { Name: "DELETE /:tenantID/destinations/:destinationID with invalid destination ID", Request: suite.AuthRequest(httpclient.Request{ Method: httpclient.MethodDELETE, - Path: "/" + tenantID + "/destinations/" + uuid.New().String(), + Path: "/" + tenantID + "/destinations/" + idgen.Destination(), }), Expected: APITestExpectation{ Match: &httpclient.Response{ @@ -629,7 +629,7 @@ func (suite *basicSuite) TestDestinationsAPI() { } func (suite *basicSuite) TestDestinationsListAPI() { - tenantID := uuid.New().String() + tenantID := idgen.String() tests := []APITest{ { Name: "PUT /:tenantID", @@ -795,8 +795,8 @@ func (suite *basicSuite) TestDestinationsListAPI() { } func (suite *basicSuite) TestDestinationEnableDisableAPI() { - tenantID := uuid.New().String() - sampleDestinationID := uuid.New().String() + tenantID := idgen.String() + sampleDestinationID := idgen.Destination() tests := []APITest{ { Name: "PUT /:tenantID", @@ -1046,8 +1046,8 @@ func (suite *basicSuite) TestDestinationTypesAPI() { func (suite *basicSuite) TestJWTAuthAPI() { // Step 1: Create tenant and get JWT token - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() // Create tenant first using admin auth createTenantTests := []APITest{ @@ -1212,7 +1212,7 @@ func (suite *basicSuite) TestJWTAuthAPI() { Name: "GET /wrong-tenant-id with JWT should fail", Request: suite.AuthJWTRequest(httpclient.Request{ Method: httpclient.MethodGET, - Path: "/" + uuid.New().String(), + Path: "/" + idgen.String(), }, token), Expected: APITestExpectation{ Match: &httpclient.Response{ diff --git a/cmd/e2e/configs/basic.go b/cmd/e2e/configs/basic.go index ae624ec8..65054bec 100644 --- a/cmd/e2e/configs/basic.go +++ b/cmd/e2e/configs/basic.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/config" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/infra" "github.com/hookdeck/outpost/internal/redis" "github.com/hookdeck/outpost/internal/util/testinfra" @@ -72,9 +72,9 @@ func Basic(t *testing.T, opts BasicOpts) config.Config { // MQ overrides c.MQs.RabbitMQ.ServerURL = rabbitmqServerURL - c.MQs.RabbitMQ.Exchange = uuid.New().String() - c.MQs.RabbitMQ.DeliveryQueue = uuid.New().String() - c.MQs.RabbitMQ.LogQueue = uuid.New().String() + c.MQs.RabbitMQ.Exchange = idgen.String() + c.MQs.RabbitMQ.DeliveryQueue = idgen.String() + c.MQs.RabbitMQ.LogQueue = idgen.String() // Test-specific overrides c.PublishMaxConcurrency = 3 diff --git a/cmd/e2e/destwebhook_test.go b/cmd/e2e/destwebhook_test.go index bae0d088..faba7f75 100644 --- a/cmd/e2e/destwebhook_test.go +++ b/cmd/e2e/destwebhook_test.go @@ -5,8 +5,8 @@ import ( "net/http" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/cmd/e2e/httpclient" + "github.com/hookdeck/outpost/internal/idgen" ) // TestingT is an interface wrapper around *testing.T @@ -15,13 +15,13 @@ type TestingT interface { } func (suite *basicSuite) TestDestwebhookPublish() { - tenantID := uuid.New().String() - sampleDestinationID := uuid.New().String() + tenantID := idgen.String() + sampleDestinationID := idgen.Destination() eventIDs := []string{ - uuid.New().String(), - uuid.New().String(), - uuid.New().String(), - uuid.New().String(), + idgen.Event(), + idgen.Event(), + idgen.Event(), + idgen.Event(), } secret := "testsecret1234567890abcdefghijklmnop" newSecret := "testsecret0987654321zyxwvutsrqponm" @@ -397,8 +397,8 @@ func (suite *basicSuite) TestDestwebhookPublish() { } func (suite *basicSuite) TestDestwebhookSecretRotation() { - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() // Setup tenant resp, err := suite.client.Do(suite.AuthRequest(httpclient.Request{ @@ -473,8 +473,8 @@ func (suite *basicSuite) TestDestwebhookSecretRotation() { } func (suite *basicSuite) TestDestwebhookTenantSecretManagement() { - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() // First create tenant and get JWT token createTenantTests := []APITest{ @@ -728,8 +728,8 @@ func (suite *basicSuite) TestDestwebhookTenantSecretManagement() { } func (suite *basicSuite) TestDestwebhookAdminSecretManagement() { - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() secret := "testsecret1234567890abcdefghijklmnop" newSecret := "testsecret0987654321zyxwvutsrqponm" diff --git a/internal/deliverymq/messagehandler_test.go b/internal/deliverymq/messagehandler_test.go index 5bb53438..16147ecc 100644 --- a/internal/deliverymq/messagehandler_test.go +++ b/internal/deliverymq/messagehandler_test.go @@ -6,11 +6,11 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/alert" "github.com/hookdeck/outpost/internal/backoff" "github.com/hookdeck/outpost/internal/deliverymq" "github.com/hookdeck/outpost/internal/destregistry" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testutil" "github.com/stretchr/testify/assert" @@ -28,7 +28,7 @@ func TestMessageHandler_DestinationGetterError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -62,7 +62,7 @@ func TestMessageHandler_DestinationGetterError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -91,7 +91,7 @@ func TestMessageHandler_DestinationNotFound(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -126,7 +126,7 @@ func TestMessageHandler_DestinationNotFound(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -152,7 +152,7 @@ func TestMessageHandler_DestinationDeleted(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -187,7 +187,7 @@ func TestMessageHandler_DestinationDeleted(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -213,7 +213,7 @@ func TestMessageHandler_PublishError_EligibleForRetry(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -258,7 +258,7 @@ func TestMessageHandler_PublishError_EligibleForRetry(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -287,7 +287,7 @@ func TestMessageHandler_PublishError_NotEligible(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -332,7 +332,7 @@ func TestMessageHandler_PublishError_NotEligible(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -360,7 +360,7 @@ func TestMessageHandler_EventGetterError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -396,7 +396,7 @@ func TestMessageHandler_EventGetterError(t *testing.T) { // Create and handle message simulating a retry deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Attempt: 2, // Retry attempt DestinationID: destination.ID, Event: models.Event{ @@ -427,7 +427,7 @@ func TestMessageHandler_RetryFlow(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -462,7 +462,7 @@ func TestMessageHandler_RetryFlow(t *testing.T) { // Create and handle message simulating a retry deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Attempt: 2, // Retry attempt DestinationID: destination.ID, Event: models.Event{ @@ -495,7 +495,7 @@ func TestMessageHandler_Idempotency(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -530,7 +530,7 @@ func TestMessageHandler_Idempotency(t *testing.T) { ) // Create message with fixed ID for idempotency check - messageID := uuid.New().String() + messageID := idgen.DeliveryEvent() deliveryEvent := models.DeliveryEvent{ ID: messageID, Event: event, @@ -562,7 +562,7 @@ func TestMessageHandler_IdempotencyWithSystemError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -599,7 +599,7 @@ func TestMessageHandler_IdempotencyWithSystemError(t *testing.T) { // Create retry message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Attempt: 2, DestinationID: destination.ID, Event: models.Event{ @@ -638,7 +638,7 @@ func TestMessageHandler_DestinationDisabled(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -676,7 +676,7 @@ func TestMessageHandler_DestinationDisabled(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -704,7 +704,7 @@ func TestMessageHandler_LogPublisherError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -739,7 +739,7 @@ func TestMessageHandler_LogPublisherError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -766,7 +766,7 @@ func TestMessageHandler_PublishAndLogError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -801,7 +801,7 @@ func TestMessageHandler_PublishAndLogError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -828,7 +828,7 @@ func TestManualDelivery_Success(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -864,7 +864,7 @@ func TestManualDelivery_Success(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, Manual: true, // Manual delivery @@ -894,7 +894,7 @@ func TestManualDelivery_PublishError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -938,7 +938,7 @@ func TestManualDelivery_PublishError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, Manual: true, // Manual delivery @@ -967,7 +967,7 @@ func TestManualDelivery_CancelError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), ) @@ -1004,7 +1004,7 @@ func TestManualDelivery_CancelError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, Manual: true, // Manual delivery @@ -1035,7 +1035,7 @@ func TestManualDelivery_DestinationDisabled(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithTenantID(tenant.ID), testutil.DestinationFactory.WithDisabledAt(time.Now()), @@ -1072,7 +1072,7 @@ func TestManualDelivery_DestinationDisabled(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, Manual: true, // Manual delivery @@ -1100,7 +1100,7 @@ func TestMessageHandler_PublishSuccess(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -1145,7 +1145,7 @@ func TestMessageHandler_PublishSuccess(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -1169,7 +1169,7 @@ func TestMessageHandler_AlertMonitorError(t *testing.T) { t.Parallel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -1206,7 +1206,7 @@ func TestMessageHandler_AlertMonitorError(t *testing.T) { // Create and handle message deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } diff --git a/internal/deliverymq/mock_test.go b/internal/deliverymq/mock_test.go index 3784e92b..c627ce08 100644 --- a/internal/deliverymq/mock_test.go +++ b/internal/deliverymq/mock_test.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/alert" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" mqs "github.com/hookdeck/outpost/internal/mqs" "github.com/hookdeck/outpost/internal/scheduler" @@ -37,8 +37,8 @@ func (m *mockPublisher) PublishEvent(ctx context.Context, destination *models.De if m.current >= len(m.responses) { m.current++ return &models.Delivery{ - ID: uuid.New().String(), - DeliveryEventID: uuid.New().String(), + ID: idgen.Delivery(), + DeliveryEventID: idgen.DeliveryEvent(), EventID: event.ID, DestinationID: destination.ID, Status: models.DeliveryStatusSuccess, @@ -52,8 +52,8 @@ func (m *mockPublisher) PublishEvent(ctx context.Context, destination *models.De m.current++ if resp == nil { return &models.Delivery{ - ID: uuid.New().String(), - DeliveryEventID: uuid.New().String(), + ID: idgen.Delivery(), + DeliveryEventID: idgen.DeliveryEvent(), EventID: event.ID, DestinationID: destination.ID, Status: models.DeliveryStatusSuccess, @@ -63,8 +63,8 @@ func (m *mockPublisher) PublishEvent(ctx context.Context, destination *models.De }, nil } return &models.Delivery{ - ID: uuid.New().String(), - DeliveryEventID: uuid.New().String(), + ID: idgen.Delivery(), + DeliveryEventID: idgen.DeliveryEvent(), EventID: event.ID, DestinationID: destination.ID, Status: models.DeliveryStatusFailed, diff --git a/internal/deliverymq/retry_test.go b/internal/deliverymq/retry_test.go index 334d4ee8..8c4dfb8b 100644 --- a/internal/deliverymq/retry_test.go +++ b/internal/deliverymq/retry_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/backoff" "github.com/hookdeck/outpost/internal/deliverymq" "github.com/hookdeck/outpost/internal/destregistry" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/mqs" "github.com/hookdeck/outpost/internal/util/testutil" @@ -102,7 +102,7 @@ func TestDeliveryMQRetry_EligibleForRetryFalse(t *testing.T) { defer cancel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -137,7 +137,7 @@ func TestDeliveryMQRetry_EligibleForRetryFalse(t *testing.T) { defer suite.TeardownTest(t) deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -159,7 +159,7 @@ func TestDeliveryMQRetry_EligibleForRetryTrue(t *testing.T) { defer cancel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -199,7 +199,7 @@ func TestDeliveryMQRetry_EligibleForRetryTrue(t *testing.T) { defer suite.TeardownTest(t) deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -239,7 +239,7 @@ func TestDeliveryMQRetry_SystemError(t *testing.T) { defer cancel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -269,7 +269,7 @@ func TestDeliveryMQRetry_SystemError(t *testing.T) { defer suite.TeardownTest(t) deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } @@ -291,7 +291,7 @@ func TestDeliveryMQRetry_RetryMaxCount(t *testing.T) { defer cancel() // Setup test data - tenant := models.Tenant{ID: uuid.New().String()} + tenant := models.Tenant{ID: idgen.String()} destination := testutil.DestinationFactory.Any( testutil.DestinationFactory.WithType("webhook"), testutil.DestinationFactory.WithTenantID(tenant.ID), @@ -338,7 +338,7 @@ func TestDeliveryMQRetry_RetryMaxCount(t *testing.T) { defer suite.TeardownTest(t) deliveryEvent := models.DeliveryEvent{ - ID: uuid.New().String(), + ID: idgen.DeliveryEvent(), Event: event, DestinationID: destination.ID, } diff --git a/internal/destregistry/providers/destawskinesis/destawskinesis_publish_test.go b/internal/destregistry/providers/destawskinesis/destawskinesis_publish_test.go index fc308a4c..9891df10 100644 --- a/internal/destregistry/providers/destawskinesis/destawskinesis_publish_test.go +++ b/internal/destregistry/providers/destawskinesis/destawskinesis_publish_test.go @@ -13,9 +13,9 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/kinesis" "github.com/aws/aws-sdk-go-v2/service/kinesis/types" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry/providers/destawskinesis" testsuite "github.com/hookdeck/outpost/internal/destregistry/testing" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testinfra" "github.com/hookdeck/outpost/internal/util/testutil" @@ -301,7 +301,7 @@ func (s *AWSKinesisSuite) SetupSuite() { t.Cleanup(testinfra.Start(t)) // Create a unique stream name for the test - s.streamName = "test-stream-" + uuid.New().String() + s.streamName = "test-stream-" + idgen.String() // Setup AWS config and client localstackEndpoint := testinfra.EnsureLocalStack() diff --git a/internal/destregistry/providers/destawss3/destawss3_publish_test.go b/internal/destregistry/providers/destawss3/destawss3_publish_test.go index ad36e896..c3e9172f 100644 --- a/internal/destregistry/providers/destawss3/destawss3_publish_test.go +++ b/internal/destregistry/providers/destawss3/destawss3_publish_test.go @@ -12,8 +12,8 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry/providers/destawss3" + "github.com/hookdeck/outpost/internal/idgen" testsuite "github.com/hookdeck/outpost/internal/destregistry/testing" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testinfra" @@ -189,7 +189,7 @@ func (s *S3PublishSuite) SetupSuite() { }) // Create a unique bucket for this test - s.bucket = fmt.Sprintf("test-bucket-%s", uuid.New().String()) + s.bucket = fmt.Sprintf("test-bucket-%s", idgen.String()) _, err = s.client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: aws.String(s.bucket), }) diff --git a/internal/destregistry/providers/destrabbitmq/destrabbitmq_publish_test.go b/internal/destregistry/providers/destrabbitmq/destrabbitmq_publish_test.go index 45ab0bc1..bf4e0857 100644 --- a/internal/destregistry/providers/destrabbitmq/destrabbitmq_publish_test.go +++ b/internal/destregistry/providers/destrabbitmq/destrabbitmq_publish_test.go @@ -3,9 +3,9 @@ package destrabbitmq_test import ( "testing" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/destregistry/providers/destrabbitmq" testsuite "github.com/hookdeck/outpost/internal/destregistry/testing" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testinfra" "github.com/hookdeck/outpost/internal/util/testutil" @@ -167,7 +167,7 @@ func (s *RabbitMQPublishSuite) SetupSuite() { t := s.T() t.Cleanup(testinfra.Start(t)) rabbitURL := testinfra.EnsureRabbitMQ() - exchange := uuid.New().String() + exchange := idgen.String() provider, err := destrabbitmq.New(testutil.Registry.MetadataLoader(), nil) require.NoError(t, err) diff --git a/internal/idempotence/idempotence_test.go b/internal/idempotence/idempotence_test.go index 7b97ff0f..2528eabe 100644 --- a/internal/idempotence/idempotence_test.go +++ b/internal/idempotence/idempotence_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/idempotence" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/mqs" "github.com/hookdeck/outpost/internal/util/testinfra" "github.com/hookdeck/outpost/internal/util/testutil" @@ -241,7 +241,7 @@ func TestIntegrationIdempotence_WithUnackedFailures(t *testing.T) { } }() - id := uuid.New().String() + id := idgen.String() err = mq.Publish(ctx, &MockMsg{ID: id}) require.Nil(t, err) @@ -322,7 +322,7 @@ func TestIntegrationIdempotence_WithConcurrentHandlerAndSuccess(t *testing.T) { } }() - id := uuid.New().String() + id := idgen.String() err = mq.Publish(ctx, &MockMsg{ID: id}) require.Nil(t, err) err = mq.Publish(ctx, &MockMsg{ID: id}) @@ -398,7 +398,7 @@ func TestIntegrationIdempotence_WithConcurrentHandlerAndFailedExecution(t *testi go consumerFn("1") go consumerFn("2") - id := uuid.New().String() + id := idgen.String() err = mq.Publish(ctx, &MockMsg{ID: id}) require.Nil(t, err) err = mq.Publish(ctx, &MockMsg{ID: id}) diff --git a/internal/idgen/idgen.go b/internal/idgen/idgen.go index 9c884a61..894466f5 100644 --- a/internal/idgen/idgen.go +++ b/internal/idgen/idgen.go @@ -156,3 +156,7 @@ func DeliveryEvent() string { func Installation() string { return globalGenerator.Installation() } + +func String() string { + return globalGenerator.generate("") +} diff --git a/internal/infra/infra_test.go b/internal/infra/infra_test.go index 5537b15b..d6aa66da 100644 --- a/internal/infra/infra_test.go +++ b/internal/infra/infra_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/alicebob/miniredis/v2" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/infra" "github.com/hookdeck/outpost/internal/redis" "github.com/hookdeck/outpost/internal/util/testutil" @@ -81,7 +81,7 @@ func TestInfra_SingleNode(t *testing.T) { ctx := context.Background() mockProvider := &mockInfraProvider{} - lockKey := "test:lock:" + uuid.New().String() + lockKey := "test:lock:" + idgen.String() infra := newTestInfra(t, mockProvider, lockKey) @@ -103,7 +103,7 @@ func TestInfra_InfrastructureAlreadyExists(t *testing.T) { ctx := context.Background() mockProvider := &mockInfraProvider{} mockProvider.exists.Store(true) // Infrastructure already exists - lockKey := "test:lock:" + uuid.New().String() + lockKey := "test:lock:" + idgen.String() infra := newTestInfra(t, mockProvider, lockKey) @@ -123,7 +123,7 @@ func TestInfra_ConcurrentNodes(t *testing.T) { mockProvider := &mockInfraProvider{ declareDelay: 100 * time.Millisecond, // Simulate slow declaration } - lockKey := "test:lock:" + uuid.New().String() + lockKey := "test:lock:" + idgen.String() redisConfig := testutil.CreateTestRedisConfig(t) client, err := redis.New(ctx, redisConfig) @@ -165,7 +165,7 @@ func TestInfra_LockExpiry(t *testing.T) { ctx := context.Background() mockProvider := &mockInfraProvider{} - lockKey := "test:lock:" + uuid.New().String() + lockKey := "test:lock:" + idgen.String() mr := miniredis.RunT(t) t.Cleanup(func() { diff --git a/internal/models/entity_test.go b/internal/models/entity_test.go index 7c89980f..b4221a28 100644 --- a/internal/models/entity_test.go +++ b/internal/models/entity_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testutil" "github.com/stretchr/testify/assert" @@ -90,7 +90,7 @@ func TestMaxDestinationsPerTenant(t *testing.T) { ) tenant := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } require.NoError(t, limitedStore.UpsertTenant(ctx, tenant)) @@ -146,8 +146,8 @@ func TestDeploymentIsolation(t *testing.T) { ) // Use the SAME tenant ID and destination ID for both deployments - tenantID := uuid.New().String() - destinationID := uuid.New().String() + tenantID := idgen.String() + destinationID := idgen.Destination() // Create tenant in both deployments tenant := models.Tenant{ diff --git a/internal/models/entitysuite_test.go b/internal/models/entitysuite_test.go index 01756ade..6991a30f 100644 --- a/internal/models/entitysuite_test.go +++ b/internal/models/entitysuite_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/redis" "github.com/hookdeck/outpost/internal/util/testutil" @@ -52,7 +52,7 @@ func (s *EntityTestSuite) SetupTest() { func (s *EntityTestSuite) TestTenantCRUD() { t := s.T() input := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } @@ -120,7 +120,7 @@ func (s *EntityTestSuite) TestTenantCRUD() { func (s *EntityTestSuite) TestDestinationCRUD() { t := s.T() input := models.Destination{ - ID: uuid.New().String(), + ID: idgen.Destination(), Type: "rabbitmq", Topics: []string{"user.created", "user.updated"}, Config: map[string]string{ @@ -133,7 +133,7 @@ func (s *EntityTestSuite) TestDestinationCRUD() { }, CreatedAt: time.Now(), DisabledAt: nil, - TenantID: uuid.New().String(), + TenantID: idgen.String(), } t.Run("gets empty", func(t *testing.T) { @@ -191,19 +191,19 @@ func (s *EntityTestSuite) TestDestinationCRUD() { } func (s *EntityTestSuite) TestListDestinationEmpty() { - destinations, err := s.entityStore.ListDestinationByTenant(s.ctx, uuid.New().String()) + destinations, err := s.entityStore.ListDestinationByTenant(s.ctx, idgen.String()) require.NoError(s.T(), err) assert.Empty(s.T(), destinations) } func (s *EntityTestSuite) TestDeleteTenantAndAssociatedDestinations() { tenant := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } // Arrange require.NoError(s.T(), s.entityStore.UpsertTenant(s.ctx, tenant)) - destinationIDs := []string{uuid.New().String(), uuid.New().String(), uuid.New().String()} + destinationIDs := []string{idgen.Destination(), idgen.Destination(), idgen.Destination()} for _, id := range destinationIDs { require.NoError(s.T(), s.entityStore.UpsertDestination(s.ctx, testutil.DestinationFactory.Any( testutil.DestinationFactory.WithID(id), @@ -230,7 +230,7 @@ type multiDestinationData struct { func (s *EntityTestSuite) setupMultiDestination() multiDestinationData { data := multiDestinationData{ tenant: models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), }, destinations: make([]models.Destination, 5), @@ -245,7 +245,7 @@ func (s *EntityTestSuite) setupMultiDestination() multiDestinationData { {"user.created", "user.updated"}, } for i := 0; i < 5; i++ { - id := uuid.New().String() + id := idgen.Destination() data.destinations[i] = testutil.DestinationFactory.Any( testutil.DestinationFactory.WithID(id), testutil.DestinationFactory.WithTenantID(data.tenant.ID), @@ -255,7 +255,7 @@ func (s *EntityTestSuite) setupMultiDestination() multiDestinationData { } // Insert & Delete destination to ensure it's cleaned up properly - toBeDeletedID := uuid.New().String() + toBeDeletedID := idgen.Destination() require.NoError(s.T(), s.entityStore.UpsertDestination(s.ctx, testutil.DestinationFactory.Any( testutil.DestinationFactory.WithID(toBeDeletedID), @@ -387,7 +387,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { t.Run("match by topic", func(t *testing.T) { event := models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.created", Time: time.Now(), TenantID: data.tenant.ID, @@ -405,7 +405,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { t.Run("match by topic & destination", func(t *testing.T) { event := models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.created", Time: time.Now(), TenantID: data.tenant.ID, @@ -422,7 +422,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { t.Run("destination not found", func(t *testing.T) { event := models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.created", Time: time.Now(), TenantID: data.tenant.ID, @@ -438,7 +438,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { t.Run("destination topic is invalid", func(t *testing.T) { event := models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.created", Time: time.Now(), TenantID: data.tenant.ID, @@ -469,7 +469,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { // Match user.created event := models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.created", Time: time.Now(), TenantID: data.tenant.ID, @@ -485,7 +485,7 @@ func (s *EntityTestSuite) TestMultiDestinationMatchEvent() { // Match user.updated event = models.Event{ - ID: uuid.New().String(), + ID: idgen.Event(), Topic: "user.updated", Time: time.Now(), TenantID: data.tenant.ID, @@ -594,7 +594,7 @@ func (s *EntityTestSuite) TestDeleteDestination() { }) t.Run("should return error when deleting non-existent destination", func(t *testing.T) { - err := s.entityStore.DeleteDestination(s.ctx, destination.TenantID, uuid.New().String()) + err := s.entityStore.DeleteDestination(s.ctx, destination.TenantID, idgen.Destination()) assert.ErrorIs(s.T(), err, models.ErrDestinationNotFound) }) diff --git a/internal/mqinfra/mqinfra_test.go b/internal/mqinfra/mqinfra_test.go index 8f25ce63..57230f06 100644 --- a/internal/mqinfra/mqinfra_test.go +++ b/internal/mqinfra/mqinfra_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/mqinfra" "github.com/hookdeck/outpost/internal/mqs" "github.com/hookdeck/outpost/internal/util/testinfra" @@ -70,7 +70,7 @@ func testMQInfra(t *testing.T, mqConfig *Config, dlqConfig *Config) { } }() - msg := &testutil.MockMsg{ID: uuid.New().String()} + msg := &testutil.MockMsg{ID: idgen.String()} require.NoError(t, mq.Publish(ctx, msg)) var receivedMsg *testutil.MockMsg @@ -130,7 +130,7 @@ func testMQInfra(t *testing.T, mqConfig *Config, dlqConfig *Config) { } }() - msg := &testutil.MockMsg{ID: uuid.New().String()} + msg := &testutil.MockMsg{ID: idgen.String()} require.NoError(t, mq.Publish(ctx, msg)) msgCount := 0 @@ -183,8 +183,8 @@ func testMQInfra(t *testing.T, mqConfig *Config, dlqConfig *Config) { } func TestIntegrationMQInfra_RabbitMQ(t *testing.T) { - exchange := uuid.New().String() - queue := uuid.New().String() + exchange := idgen.String() + queue := idgen.String() testMQInfra(t, &Config{ @@ -226,7 +226,7 @@ func TestIntegrationMQInfra_RabbitMQ(t *testing.T) { } func TestIntegrationMQInfra_AWSSQS(t *testing.T) { - q := uuid.New().String() + q := idgen.String() testMQInfra(t, &Config{ @@ -276,7 +276,7 @@ func TestIntegrationMQInfra_GCPPubSub(t *testing.T) { // Set PUBSUB_EMULATOR_HOST environment variable testinfra.EnsureGCP() - topicID := "test-" + uuid.New().String() + topicID := "test-" + idgen.String() subscriptionID := topicID + "-subscription" testMQInfra(t, @@ -326,7 +326,7 @@ func TestIntegrationMQInfra_GCPPubSub(t *testing.T) { func TestIntegrationMQInfra_AzureServiceBus(t *testing.T) { t.Skip("skip TestIntegrationMQInfra_AzureServiceBus integration test for now since the emulator doesn't support managing resources") - topic := uuid.New().String() + topic := idgen.String() subscription := topic + "-subscription" const ( diff --git a/internal/publishmq/eventhandler_test.go b/internal/publishmq/eventhandler_test.go index 70bcc8d1..951cc17e 100644 --- a/internal/publishmq/eventhandler_test.go +++ b/internal/publishmq/eventhandler_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/deliverymq" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/publishmq" "github.com/hookdeck/outpost/internal/util/testinfra" @@ -41,7 +41,7 @@ func TestIntegrationPublishMQEventHandler_Concurrency(t *testing.T) { ) tenant := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(ctx, tenant) @@ -105,7 +105,7 @@ func TestEventHandler_WildcardTopic(t *testing.T) { ) tenant := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(ctx, tenant) diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go index 8ef07f15..11647fb8 100644 --- a/internal/scheduler/scheduler_test.go +++ b/internal/scheduler/scheduler_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" iredis "github.com/hookdeck/outpost/internal/redis" "github.com/hookdeck/outpost/internal/rsmq" "github.com/hookdeck/outpost/internal/scheduler" @@ -44,9 +44,9 @@ func TestScheduler_Basic(t *testing.T) { // Act ids := []string{ - uuid.New().String(), - uuid.New().String(), - uuid.New().String(), + idgen.String(), + idgen.String(), + idgen.String(), } s.Schedule(ctx, ids[0], 1*time.Second) s.Schedule(ctx, ids[1], 2*time.Second) @@ -89,9 +89,9 @@ func TestScheduler_ParallelMonitor(t *testing.T) { // Act ids := []string{ - uuid.New().String(), - uuid.New().String(), - uuid.New().String(), + idgen.String(), + idgen.String(), + idgen.String(), } s.Schedule(ctx, ids[0], 1*time.Second) s.Schedule(ctx, ids[1], 2*time.Second) @@ -131,7 +131,7 @@ func TestScheduler_VisibilityTimeout(t *testing.T) { go s.Monitor(ctx) - id := uuid.New().String() + id := idgen.String() s.Schedule(ctx, id, 1*time.Second) <-ctx.Done() @@ -155,7 +155,7 @@ func TestScheduler_CustomID(t *testing.T) { } rsmqClient := createRSMQClient(t, redisConfig) - s := scheduler.New(uuid.New().String(), rsmqClient, exec) + s := scheduler.New(idgen.String(), rsmqClient, exec) require.NoError(t, s.Init(ctx)) go s.Monitor(ctx) @@ -260,7 +260,7 @@ func TestScheduler_Cancel(t *testing.T) { } rsmqClient := createRSMQClient(t, redisConfig) - s := scheduler.New(uuid.New().String(), rsmqClient, exec) + s := scheduler.New(idgen.String(), rsmqClient, exec) require.NoError(t, s.Init(ctx)) go s.Monitor(ctx) diff --git a/internal/services/api/publish_handlers_test.go b/internal/services/api/publish_handlers_test.go index 186686b7..07d3d1b1 100644 --- a/internal/services/api/publish_handlers_test.go +++ b/internal/services/api/publish_handlers_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/stretchr/testify/assert" ) @@ -24,9 +24,9 @@ func TestPublishHandlers(t *testing.T) { w := httptest.NewRecorder() testEvent := models.Event{ - ID: uuid.New().String(), - TenantID: uuid.New().String(), - DestinationID: uuid.New().String(), + ID: idgen.Event(), + TenantID: idgen.String(), + DestinationID: idgen.Destination(), Topic: "user.created", Time: time.Now(), Metadata: map[string]string{"key": "value"}, diff --git a/internal/services/api/requiretenant_middleware_test.go b/internal/services/api/requiretenant_middleware_test.go index 11907a9b..e5681284 100644 --- a/internal/services/api/requiretenant_middleware_test.go +++ b/internal/services/api/requiretenant_middleware_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +30,7 @@ func TestRequireTenantMiddleware(t *testing.T) { t.Parallel() tenant := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), } entityStore := setupTestEntityStore(t, redisClient, nil) err := entityStore.UpsertTenant(context.Background(), tenant) diff --git a/internal/services/api/router_test.go b/internal/services/api/router_test.go index 495fb256..895eff4a 100644 --- a/internal/services/api/router_test.go +++ b/internal/services/api/router_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/clickhouse" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/deliverymq" "github.com/hookdeck/outpost/internal/eventtracer" "github.com/hookdeck/outpost/internal/logging" @@ -111,7 +111,7 @@ func TestRouterWithAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) @@ -121,7 +121,7 @@ func TestRouterWithAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) req.Header.Set("Authorization", "Bearer "+validToken) router.ServeHTTP(w, req) @@ -132,7 +132,7 @@ func TestRouterWithAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) req.Header.Set("Authorization", "Bearer "+apiKey) router.ServeHTTP(w, req) @@ -224,7 +224,7 @@ func TestRouterWithoutAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) @@ -234,7 +234,7 @@ func TestRouterWithoutAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) req.Header.Set("Authorization", "Bearer "+validToken) router.ServeHTTP(w, req) @@ -245,7 +245,7 @@ func TestRouterWithoutAPIKey(t *testing.T) { t.Parallel() w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", baseAPIPath+"/"+uuid.New().String(), nil) + req, _ := http.NewRequest("PUT", baseAPIPath+"/"+idgen.String(), nil) req.Header.Set("Authorization", "Bearer "+apiKey) router.ServeHTTP(w, req) diff --git a/internal/services/api/tenant_handlers_test.go b/internal/services/api/tenant_handlers_test.go index c78f76ac..f9a6e065 100644 --- a/internal/services/api/tenant_handlers_test.go +++ b/internal/services/api/tenant_handlers_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/stretchr/testify/assert" ) @@ -24,7 +24,7 @@ func TestDestinationUpsertHandler(t *testing.T) { w := httptest.NewRecorder() - id := uuid.New().String() + id := idgen.String() req, _ := http.NewRequest("PUT", baseAPIPath+"/"+id, nil) router.ServeHTTP(w, req) @@ -41,7 +41,7 @@ func TestDestinationUpsertHandler(t *testing.T) { // Setup existingResource := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(context.Background(), existingResource) @@ -88,7 +88,7 @@ func TestTenantRetrieveHandler(t *testing.T) { // Setup existingResource := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(context.Background(), existingResource) @@ -135,7 +135,7 @@ func TestTenantDeleteHandler(t *testing.T) { // Setup existingResource := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(context.Background(), existingResource) @@ -157,7 +157,7 @@ func TestTenantDeleteHandler(t *testing.T) { // Setup existingResource := models.Tenant{ - ID: uuid.New().String(), + ID: idgen.String(), CreatedAt: time.Now(), } entityStore.UpsertTenant(context.Background(), existingResource) @@ -169,7 +169,7 @@ func TestTenantDeleteHandler(t *testing.T) { } ids := make([]string, 5) for i := 0; i < 5; i++ { - ids[i] = uuid.New().String() + ids[i] = idgen.String() inputDestination.ID = ids[i] inputDestination.CreatedAt = time.Now() entityStore.UpsertDestination(context.Background(), inputDestination) diff --git a/internal/services/delivery/delivery_test.go b/internal/services/delivery/delivery_test.go index e8fdc8b0..2f7c8782 100644 --- a/internal/services/delivery/delivery_test.go +++ b/internal/services/delivery/delivery_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/hookdeck/outpost/internal/consumer" "github.com/hookdeck/outpost/internal/deliverymq" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/mqs" "github.com/hookdeck/outpost/internal/util/testutil" @@ -87,8 +87,8 @@ func TestDeliveryService(t *testing.T) { // Act time.Sleep(time.Second / 5) // wait for service to start - expectedID := uuid.New().String() - deliveryMQ.Publish(ctx, models.NewDeliveryEvent(models.Event{ID: expectedID}, uuid.New().String())) + expectedID := idgen.Event() + deliveryMQ.Publish(ctx, models.NewDeliveryEvent(models.Event{ID: expectedID}, idgen.Destination())) // Assert // wait til service has stopped diff --git a/internal/util/testutil/destination.go b/internal/util/testutil/destination.go index 20ec866a..ae47d6f2 100644 --- a/internal/util/testutil/destination.go +++ b/internal/util/testutil/destination.go @@ -3,7 +3,7 @@ package testutil import ( "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" ) @@ -16,13 +16,13 @@ type mockDestinationFactory struct { func (f *mockDestinationFactory) Any(opts ...func(*models.Destination)) models.Destination { destination := models.Destination{ - ID: uuid.New().String(), + ID: idgen.Destination(), Type: "webhook", Topics: []string{"*"}, Config: map[string]string{"url": "http://host.docker.internal:4444"}, Credentials: map[string]string{}, CreatedAt: time.Now(), - TenantID: uuid.New().String(), + TenantID: "test-tenant", DisabledAt: nil, } diff --git a/internal/util/testutil/event.go b/internal/util/testutil/event.go index ee85a1d8..4c69cb56 100644 --- a/internal/util/testutil/event.go +++ b/internal/util/testutil/event.go @@ -3,7 +3,7 @@ package testutil import ( "time" - "github.com/google/uuid" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" ) @@ -16,8 +16,8 @@ type mockEventFactory struct { func (f *mockEventFactory) Any(opts ...func(*models.Event)) models.Event { event := models.Event{ - ID: uuid.New().String(), - TenantID: uuid.New().String(), + ID: idgen.Event(), + TenantID: "test-tenant", DestinationID: "", Topic: TestTopics[0], EligibleForRetry: true, @@ -105,10 +105,10 @@ type mockDeliveryFactory struct { func (f *mockDeliveryFactory) Any(opts ...func(*models.Delivery)) models.Delivery { delivery := models.Delivery{ - ID: uuid.New().String(), - DeliveryEventID: uuid.New().String(), - EventID: uuid.New().String(), - DestinationID: uuid.New().String(), + ID: idgen.Delivery(), + DeliveryEventID: idgen.DeliveryEvent(), + EventID: idgen.Event(), + DestinationID: idgen.Destination(), Status: "success", Time: time.Now(), } From 5aad525e099e5a1eb05a04a705bbcf6433ceeda0 Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 12:32:43 +0700 Subject: [PATCH 08/10] chore: rename idgen config --- internal/config/config.go | 2 +- internal/config/id_gen.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 1c206657..13ee0cce 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -99,7 +99,7 @@ type Config struct { Alert AlertConfig `yaml:"alert"` // ID Generation - IDGen IDGenConfig `yaml:"id_gen"` + IDGen IDGenConfig `yaml:"idgen"` } var ( diff --git a/internal/config/id_gen.go b/internal/config/id_gen.go index 4e19b4b6..790b4c02 100644 --- a/internal/config/id_gen.go +++ b/internal/config/id_gen.go @@ -2,9 +2,9 @@ package config // IDGenConfig is the configuration for ID generation type IDGenConfig struct { - Type string `yaml:"type" env:"ID_GEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` - EventPrefix string `yaml:"event_prefix" env:"ID_GEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` - DestinationPrefix string `yaml:"destination_prefix" env:"ID_GEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` - DeliveryPrefix string `yaml:"delivery_prefix" env:"ID_GEN_DELIVERY_PREFIX" desc:"Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix)" required:"N"` - DeliveryEventPrefix string `yaml:"delivery_event_prefix" env:"ID_GEN_DELIVERY_EVENT_PREFIX" desc:"Prefix for delivery event IDs, prepended with underscore (e.g., 'dev_123'). Default: empty (no prefix)" required:"N"` + Type string `yaml:"type" env:"IDGEN_TYPE" desc:"ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4" required:"N"` + EventPrefix string `yaml:"event_prefix" env:"IDGEN_EVENT_PREFIX" desc:"Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix)" required:"N"` + DestinationPrefix string `yaml:"destination_prefix" env:"IDGEN_DESTINATION_PREFIX" desc:"Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix)" required:"N"` + DeliveryPrefix string `yaml:"delivery_prefix" env:"IDGEN_DELIVERY_PREFIX" desc:"Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix)" required:"N"` + DeliveryEventPrefix string `yaml:"delivery_event_prefix" env:"IDGEN_DELIVERY_EVENT_PREFIX" desc:"Prefix for delivery event IDs, prepended with underscore (e.g., 'dev_123'). Default: empty (no prefix)" required:"N"` } From ac55cec242f1df513072eeb385d2f8f349f6fe4c Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 12:34:52 +0700 Subject: [PATCH 09/10] docs: generate config --- docs/pages/references/configuration.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/pages/references/configuration.mdx b/docs/pages/references/configuration.mdx index f9d98cee..0840d605 100644 --- a/docs/pages/references/configuration.mdx +++ b/docs/pages/references/configuration.mdx @@ -67,6 +67,11 @@ Global configurations are provided through env variables or a YAML file. ConfigM | `GCP_PUBSUB_SERVICE_ACCOUNT_CREDENTIALS` | JSON string or path to a file containing GCP service account credentials for Pub/Sub. Required if GCP Pub/Sub is the chosen MQ provider and not running in an environment with implicit credentials (e.g., GCE, GKE). | `nil` | Conditional | | `GIN_MODE` | Sets the Gin framework mode (e.g., 'debug', 'release', 'test'). See Gin documentation for details. | `release` | No | | `HTTP_USER_AGENT` | Custom HTTP User-Agent string for outgoing webhook deliveries. If unset, a default (OrganizationName/Version) is used. | `nil` | No | +| `IDGEN_DELIVERY_EVENT_PREFIX` | Prefix for delivery event IDs, prepended with underscore (e.g., 'dev_123'). Default: empty (no prefix) | `nil` | No | +| `IDGEN_DELIVERY_PREFIX` | Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix) | `nil` | No | +| `IDGEN_DESTINATION_PREFIX` | Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix) | `nil` | No | +| `IDGEN_EVENT_PREFIX` | Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix) | `nil` | No | +| `IDGEN_TYPE` | ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4 | `uuidv4` | No | | `LOG_BATCH_SIZE` | Maximum number of log entries to batch together before writing to storage. | `1000` | No | | `LOG_BATCH_THRESHOLD_SECONDS` | Maximum time in seconds to buffer logs before flushing them to storage, if batch size is not reached. | `10` | No | | `LOG_LEVEL` | Defines the verbosity of application logs. Common values: 'trace', 'debug', 'info', 'warn', 'error'. | `info` | No | @@ -227,6 +232,23 @@ gin_mode: "release" # Custom HTTP User-Agent string for outgoing webhook deliveries. If unset, a default (OrganizationName/Version) is used. http_user_agent: "" +idgen: + # Prefix for delivery event IDs, prepended with underscore (e.g., 'dev_123'). Default: empty (no prefix) + delivery_event_prefix: "" + + # Prefix for delivery IDs, prepended with underscore (e.g., 'dlv_123'). Default: empty (no prefix) + delivery_prefix: "" + + # Prefix for destination IDs, prepended with underscore (e.g., 'dst_123'). Default: empty (no prefix) + destination_prefix: "" + + # Prefix for event IDs, prepended with underscore (e.g., 'evt_123'). Default: empty (no prefix) + event_prefix: "" + + # ID generation type for all entities: uuidv4, uuidv7, nanoid. Default: uuidv4 + type: "uuidv4" + + # Maximum number of log entries to batch together before writing to storage. log_batch_size: 1000 From 5d34b2e3518d20df25433082d30ea03b385cae9b Mon Sep 17 00:00:00 2001 From: Alex Luong Date: Mon, 20 Oct 2025 20:23:15 +0700 Subject: [PATCH 10/10] chore: gofmt --- .../destregistry/providers/destawss3/destawss3_publish_test.go | 2 +- internal/services/api/router_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/destregistry/providers/destawss3/destawss3_publish_test.go b/internal/destregistry/providers/destawss3/destawss3_publish_test.go index c3e9172f..5b5e8a03 100644 --- a/internal/destregistry/providers/destawss3/destawss3_publish_test.go +++ b/internal/destregistry/providers/destawss3/destawss3_publish_test.go @@ -13,8 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/hookdeck/outpost/internal/destregistry/providers/destawss3" - "github.com/hookdeck/outpost/internal/idgen" testsuite "github.com/hookdeck/outpost/internal/destregistry/testing" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/models" "github.com/hookdeck/outpost/internal/util/testinfra" "github.com/hookdeck/outpost/internal/util/testutil" diff --git a/internal/services/api/router_test.go b/internal/services/api/router_test.go index 895eff4a..efaccfa8 100644 --- a/internal/services/api/router_test.go +++ b/internal/services/api/router_test.go @@ -8,9 +8,9 @@ import ( "github.com/gin-gonic/gin" "github.com/hookdeck/outpost/internal/clickhouse" - "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/deliverymq" "github.com/hookdeck/outpost/internal/eventtracer" + "github.com/hookdeck/outpost/internal/idgen" "github.com/hookdeck/outpost/internal/logging" "github.com/hookdeck/outpost/internal/logstore" "github.com/hookdeck/outpost/internal/models"