From f1bea1bd5301c85c9da434c46a6faf193321521c Mon Sep 17 00:00:00 2001 From: Allisson Azevedo Date: Tue, 2 Mar 2021 14:02:45 -0300 Subject: [PATCH] feat: Add WebhookRepository (#2) --- .github/workflows/verify.yml | 18 ++- .gitignore | 6 + Makefile | 11 +- .../000001_create_initial_schema.down.sql | 1 + .../000001_create_initial_schema.up.sql | 21 ++++ entity.go | 51 ++++---- entity_test.go | 24 +++- go.mod | 4 + go.sum | 20 ++- repository.go | 23 ++++ repository/util_test.go | 31 +++++ repository/webhook.go | 115 ++++++++++++++++++ repository/webhook_test.go | 103 ++++++++++++++++ 13 files changed, 398 insertions(+), 30 deletions(-) create mode 100644 db/migrations/000001_create_initial_schema.down.sql create mode 100644 db/migrations/000001_create_initial_schema.up.sql create mode 100644 repository.go create mode 100644 repository/util_test.go create mode 100644 repository/webhook.go create mode 100644 repository/webhook_test.go diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index a32ccda..7a3de1a 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -10,6 +10,20 @@ jobs: verify: name: Test runs-on: ubuntu-latest + services: + postgres: + image: postgres:12-alpine + env: + POSTGRES_DB: postmand + POSTGRES_USER: test + POSTGRES_PASSWORD: test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Set up Go 1.16 uses: actions/setup-go@v2 @@ -38,4 +52,6 @@ jobs: args: -E gosec - name: Run Tests - run: go test -covermode=count -coverprofile=count.out -v ./... + env: + POSTMAND_DATABASE_URL: "postgres://test:test@localhost:5432/postmand?sslmode=disable" + run: make db-migrate && go test -covermode=count -coverprofile=count.out -v ./... diff --git a/.gitignore b/.gitignore index 67540db..e6d0ee8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,10 @@ # vendor/ *.log + +# golangci-lint bin/ + +# golang migrate +migrate.darwin-amd64 +migrate.linux-amd64 diff --git a/Makefile b/Makefile index 45d4aa6..4111892 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,13 @@ lint: test: go test -covermode=count -coverprofile=count.out -v ./... -.PHONY: lint test +download-golang-migrate-binary: + if [ ! -f ./migrate.$(PLATFORM)-amd64 ] ; \ + then \ + curl -sfL https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.$(PLATFORM)-amd64.tar.gz | tar -xvz; \ + fi; + +db-migrate: download-golang-migrate-binary + ./migrate.$(PLATFORM)-amd64 -source file://db/migrations -database ${POSTMAND_DATABASE_URL} up + +.PHONY: lint test download-golang-migrate-binary db-migrate diff --git a/db/migrations/000001_create_initial_schema.down.sql b/db/migrations/000001_create_initial_schema.down.sql new file mode 100644 index 0000000..0457cf1 --- /dev/null +++ b/db/migrations/000001_create_initial_schema.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS webhooks; diff --git a/db/migrations/000001_create_initial_schema.up.sql b/db/migrations/000001_create_initial_schema.up.sql new file mode 100644 index 0000000..c279795 --- /dev/null +++ b/db/migrations/000001_create_initial_schema.up.sql @@ -0,0 +1,21 @@ +-- webhooks table + +CREATE TABLE IF NOT EXISTS webhooks( + id uuid PRIMARY KEY, + name VARCHAR NOT NULL, + url VARCHAR NOT NULL, + content_type VARCHAR NOT NULL, + valid_status_codes SMALLINT[] NOT NULL, + secret_token VARCHAR NOT NULL, + active BOOLEAN NOT NULL, + max_delivery_attempts SMALLINT NOT NULL, + delivery_attempt_timeout SMALLINT NOT NULL, + retry_min_backoff SMALLINT NOT NULL, + retry_max_backoff SMALLINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS webhooks_name_idx ON webhooks (name); +CREATE INDEX IF NOT EXISTS webhooks_active_idx ON webhooks (active); +CREATE INDEX IF NOT EXISTS webhooks_created_at_idx ON webhooks USING BRIN(created_at); diff --git a/entity.go b/entity.go index d7b655e..b092a6b 100644 --- a/entity.go +++ b/entity.go @@ -6,30 +6,38 @@ import ( validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" "github.com/google/uuid" + "github.com/lib/pq" ) const ( - // DeliveryStatusPending represents the delivery pending status - DeliveryStatusPending = "pending" + // DeliveryStatusTodo represents the delivery todo status + DeliveryStatusTodo = "todo" + // DeliveryStatusDoing represents the delivery doing status + DeliveryStatusDoing = "doing" + // DeliveryStatusSucceeded represents the delivery succeeded status + DeliveryStatusSucceeded = "succeeded" // DeliveryStatusFailed represents the delivery failed status DeliveryStatusFailed = "failed" - // DeliveryStatusCompleted represents the delivery completed status - DeliveryStatusCompleted = "completed" ) +// ID represents the primary key for all entities. +type ID = uuid.UUID + // Webhook represents a webhook in the system. type Webhook struct { - ID uuid.UUID `json:"id" db:"id"` - Name string `json:"name" db:"name"` - URL string `json:"url" db:"url"` - ContentType string `json:"content_type" db:"content_type"` - SecretToken string `json:"secret_token" db:"secret_token"` - MaxDeliveryAttempts int `json:"max_delivery_attempts" db:"max_delivery_attempts"` - DeliveryAttemptTimeout int `json:"delivery_attempt_timeout" db:"delivery_attempt_timeout"` - RetryMinBackoff int `json:"retry_min_backoff" db:"retry_min_backoff"` - RetryMaxBackoff int `json:"retry_max_backoff" db:"retry_max_backoff"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + ID ID `json:"id" db:"id"` + Name string `json:"name" db:"name"` + URL string `json:"url" db:"url"` + ContentType string `json:"content_type" db:"content_type"` + ValidStatusCodes pq.Int32Array `json:"valid_status_codes" db:"valid_status_codes"` + SecretToken string `json:"secret_token" db:"secret_token"` + Active bool `json:"active" db:"active"` + MaxDeliveryAttempts int `json:"max_delivery_attempts" db:"max_delivery_attempts"` + DeliveryAttemptTimeout int `json:"delivery_attempt_timeout" db:"delivery_attempt_timeout"` + RetryMinBackoff int `json:"retry_min_backoff" db:"retry_min_backoff"` + RetryMaxBackoff int `json:"retry_max_backoff" db:"retry_max_backoff"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } // Validate implements ozzo validation Validatable interface @@ -39,6 +47,7 @@ func (w Webhook) Validate() error { validation.Field(&w.Name, validation.Required, validation.Length(3, 255)), validation.Field(&w.URL, validation.Required, is.URL), validation.Field(&w.ContentType, validation.Required, validation.In("application/x-www-form-urlencoded", "application/json")), + validation.Field(&w.ValidStatusCodes, validation.Required), validation.Field(&w.MaxDeliveryAttempts, validation.Required, validation.Min(1)), validation.Field(&w.DeliveryAttemptTimeout, validation.Required, validation.Min(1)), validation.Field(&w.RetryMinBackoff, validation.Required, validation.Min(1)), @@ -48,8 +57,8 @@ func (w Webhook) Validate() error { // Delivery represents a payload that must be delivery using webhook context. type Delivery struct { - ID uuid.UUID `json:"id" db:"id"` - WebhookID uuid.UUID `json:"webhook_id" db:"webhook_id"` + ID ID `json:"id" db:"id"` + WebhookID ID `json:"webhook_id" db:"webhook_id"` Payload string `json:"payload" db:"payload"` ScheduledAt time.Time `json:"scheduled_at" db:"scheduled_at"` DeliveryAttempts int `json:"delivery_attempts" db:"delivery_attempts"` @@ -64,15 +73,15 @@ func (d Delivery) Validate() error { validation.Field(&d.ID, validation.Required, is.UUIDv4), validation.Field(&d.WebhookID, validation.Required, is.UUIDv4), validation.Field(&d.ScheduledAt, validation.Required), - validation.Field(&d.Status, validation.Required, validation.In("pending", "completed", "failed")), + validation.Field(&d.Status, validation.Required, validation.In("todo", "doing", "succeeded", "failed")), ) } // DeliveryAttempt represents a delivery attempt. type DeliveryAttempt struct { - ID uuid.UUID `json:"id" db:"id"` - WebhookID uuid.UUID `json:"destination_id" db:"destination_id"` - DeliveryID uuid.UUID `json:"delivery_id" db:"delivery_id"` + ID ID `json:"id" db:"id"` + WebhookID ID `json:"destination_id" db:"destination_id"` + DeliveryID ID `json:"delivery_id" db:"delivery_id"` ResponseHeaders string `json:"response_headers" db:"response_headers"` ResponseBody string `json:"response_body" db:"response_body"` ResponseStatusCode int `json:"response_status_code" db:"response_status_code"` diff --git a/entity_test.go b/entity_test.go index ded706b..00d24c2 100644 --- a/entity_test.go +++ b/entity_test.go @@ -7,6 +7,8 @@ import ( "time" "github.com/google/uuid" + "github.com/lib/pq" + _ "github.com/lib/pq" "github.com/stretchr/testify/assert" ) @@ -19,21 +21,21 @@ func TestWebhook(t *testing.T) { { "required fields", Webhook{}, - `{"content_type":"cannot be blank","delivery_attempt_timeout":"cannot be blank","id":"must be a valid UUID v4","max_delivery_attempts":"cannot be blank","name":"cannot be blank","retry_max_backoff":"cannot be blank","retry_min_backoff":"cannot be blank","url":"cannot be blank"}`, + `{"content_type":"cannot be blank","delivery_attempt_timeout":"cannot be blank","id":"must be a valid UUID v4","max_delivery_attempts":"cannot be blank","name":"cannot be blank","retry_max_backoff":"cannot be blank","retry_min_backoff":"cannot be blank","url":"cannot be blank","valid_status_codes":"cannot be blank"}`, }, { "Short name", - Webhook{ID: uuid.New(), Name: "A", URL: "https://httpbin.org/post", ContentType: "application/json", MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, + Webhook{ID: uuid.New(), Name: "A", URL: "https://httpbin.org/post", ContentType: "application/json", ValidStatusCodes: pq.Int32Array{200, 201}, MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, `{"name":"the length must be between 3 and 255"}`, }, { "Long name", - Webhook{ID: uuid.New(), Name: strings.Repeat("A", 300), URL: "https://httpbin.org/post", ContentType: "application/json", MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, + Webhook{ID: uuid.New(), Name: strings.Repeat("A", 300), URL: "https://httpbin.org/post", ContentType: "application/json", ValidStatusCodes: pq.Int32Array{200, 201}, MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, `{"name":"the length must be between 3 and 255"}`, }, { "Content type invalid option", - Webhook{ID: uuid.New(), Name: "AAA", URL: "https://httpbin.org/post", ContentType: "text/html", MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, + Webhook{ID: uuid.New(), Name: "AAA", URL: "https://httpbin.org/post", ContentType: "text/html", ValidStatusCodes: pq.Int32Array{200, 201}, MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1}, `{"content_type":"must be a valid value"}`, }, } @@ -47,7 +49,17 @@ func TestWebhook(t *testing.T) { }) } - webhook := Webhook{ID: uuid.New(), Name: "AAA", URL: "https://httpbin.org/post", ContentType: "application/json", MaxDeliveryAttempts: 1, DeliveryAttemptTimeout: 1, RetryMinBackoff: 1, RetryMaxBackoff: 1} + webhook := Webhook{ + ID: uuid.New(), + Name: "AAA", + URL: "https://httpbin.org/post", + ContentType: "application/json", + ValidStatusCodes: pq.Int32Array{200, 201}, + MaxDeliveryAttempts: 1, + DeliveryAttemptTimeout: 1, + RetryMinBackoff: 1, + RetryMaxBackoff: 1, + } err := webhook.Validate() assert.Nil(t, err) } @@ -84,7 +96,7 @@ func TestDelivery(t *testing.T) { WebhookID: uuid.New(), Payload: `{"success": true}`, ScheduledAt: time.Now().UTC(), - Status: "pending", + Status: "todo", CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } diff --git a/go.mod b/go.mod index 29fc8d2..b068683 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,11 @@ module github.com/allisson/postmand go 1.16 require ( + github.com/DATA-DOG/go-txdb v0.1.3 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/google/uuid v1.2.0 + github.com/huandu/go-sqlbuilder v1.12.0 + github.com/jmoiron/sqlx v1.3.1 + github.com/lib/pq v1.9.0 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 8d9378a..2d1b487 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,29 @@ +github.com/DATA-DOG/go-txdb v0.1.3 h1:R4v6OuOcy2O147e2zHxU0B4NDtF+INb5R9q/CV7AEMg= +github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-sqlbuilder v1.12.0 h1:QSmKkoIKaZTZBNROweq/c3wTxqXhuuAhbTWPtbpVsNA= +github.com/huandu/go-sqlbuilder v1.12.0/go.mod h1:LILlbQo0MOYjlIiGgOSR3UcWQpd5Y/oZ7HLNGyAUz0E= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= +github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/repository.go b/repository.go new file mode 100644 index 0000000..c266079 --- /dev/null +++ b/repository.go @@ -0,0 +1,23 @@ +package postmand + +// RepositoryGetOptions contains options used in the Get methods. +type RepositoryGetOptions struct { + Filters map[string]interface{} +} + +// RepositoryListOptions contains options used in the List methods. +type RepositoryListOptions struct { + Filters map[string]interface{} + Limit int + Offset int + OrderBy string +} + +// WebhookRepository is the interface that will be used to iterate with the Webhook data. +type WebhookRepository interface { + Get(getOptions *RepositoryGetOptions) (*Webhook, error) + List(listOptions *RepositoryListOptions) ([]*Webhook, error) + Create(webhook *Webhook) error + Update(webhook *Webhook) error + Delete(id ID) error +} diff --git a/repository/util_test.go b/repository/util_test.go new file mode 100644 index 0000000..e6678dc --- /dev/null +++ b/repository/util_test.go @@ -0,0 +1,31 @@ +package repository + +import ( + "fmt" + "math/rand" + "os" + "time" + + "github.com/DATA-DOG/go-txdb" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +func init() { + txdb.Register("pgx", "postgres", os.Getenv("POSTMAND_DATABASE_URL")) + rand.Seed(time.Now().UnixNano()) +} + +type testHelper struct { + db *sqlx.DB + webhookRepository *Webhook +} + +func newTestHelper() testHelper { + cName := fmt.Sprintf("connection_%d", time.Now().UnixNano()) + db, _ := sqlx.Open("pgx", cName) + return testHelper{ + db: db, + webhookRepository: NewWebhook(db), + } +} diff --git a/repository/webhook.go b/repository/webhook.go new file mode 100644 index 0000000..115eeb0 --- /dev/null +++ b/repository/webhook.go @@ -0,0 +1,115 @@ +package repository + +import ( + "github.com/allisson/postmand" + "github.com/huandu/go-sqlbuilder" + "github.com/jmoiron/sqlx" +) + +// Webhook implements postmand.WebhookRepository interface. +type Webhook struct { + db *sqlx.DB +} + +// Get returns postmand.Webhook by options filter. +func (w Webhook) Get(getOptions *postmand.RepositoryGetOptions) (*postmand.Webhook, error) { + webhook := postmand.Webhook{} + sb := sqlbuilder.PostgreSQL.NewSelectBuilder() + sb.Select("*").From("webhooks") + for key, value := range getOptions.Filters { + sb.Where(sb.Equal(key, value)) + } + sql, args := sb.Build() + err := w.db.Get(&webhook, sql, args...) + return &webhook, err +} + +// List returns a slice of postmand.Webhook by options filter. +func (w Webhook) List(listOptions *postmand.RepositoryListOptions) ([]*postmand.Webhook, error) { + webhooks := []*postmand.Webhook{} + sb := sqlbuilder.PostgreSQL.NewSelectBuilder() + sb.Select("*").From("webhooks").Limit(listOptions.Limit).Offset(listOptions.Offset) + for key, value := range listOptions.Filters { + sb.Where(sb.Equal(key, value)) + } + if listOptions.OrderBy != "" { + sb.OrderBy(listOptions.OrderBy) + } + sql, args := sb.Build() + err := w.db.Select(&webhooks, sql, args...) + return webhooks, err +} + +// Create webhook on database. +func (w Webhook) Create(webhook *postmand.Webhook) error { + sqlStatement := ` + INSERT INTO webhooks ( + "id", + "name", + "url", + "content_type", + "valid_status_codes", + "secret_token", + "active", + "max_delivery_attempts", + "delivery_attempt_timeout", + "retry_min_backoff", + "retry_max_backoff", + "created_at", + "updated_at" + ) + VALUES ( + :id, + :name, + :url, + :content_type, + :valid_status_codes, + :secret_token, + :active, + :max_delivery_attempts, + :delivery_attempt_timeout, + :retry_min_backoff, + :retry_max_backoff, + :created_at, + :updated_at + ) + ` + _, err := w.db.NamedExec(sqlStatement, webhook) + return err +} + +// Update webhook on database. +func (w Webhook) Update(webhook *postmand.Webhook) error { + sqlStatement := ` + UPDATE webhooks + SET name = :name, + url = :url, + content_type = :content_type, + valid_status_codes = :valid_status_codes, + secret_token = :secret_token, + active = :active, + max_delivery_attempts = :max_delivery_attempts, + delivery_attempt_timeout = :delivery_attempt_timeout, + retry_min_backoff = :retry_min_backoff, + retry_max_backoff = :retry_max_backoff, + created_at = :created_at, + updated_at = :updated_at + WHERE id = :id + ` + _, err := w.db.NamedExec(sqlStatement, webhook) + return err +} + +// Delete webhook on database. +func (w Webhook) Delete(id postmand.ID) error { + sqlStatement := ` + DELETE FROM webhooks WHERE id = $1 + ` + _, err := w.db.Exec(sqlStatement, id) + return err +} + +// NewWebhook returns Webhook with db connection. +func NewWebhook(db *sqlx.DB) *Webhook { + return &Webhook{db: db} +} diff --git a/repository/webhook_test.go b/repository/webhook_test.go new file mode 100644 index 0000000..d812898 --- /dev/null +++ b/repository/webhook_test.go @@ -0,0 +1,103 @@ +package repository + +import ( + "database/sql" + "testing" + + "github.com/allisson/postmand" + "github.com/google/uuid" + "github.com/lib/pq" + "github.com/stretchr/testify/assert" +) + +func makeWebhook() *postmand.Webhook { + return &postmand.Webhook{ + ID: uuid.New(), + Name: "Test", + URL: "https://httpbin.org/post", + ContentType: "application/json", + ValidStatusCodes: pq.Int32Array{200, 201}, + MaxDeliveryAttempts: 1, + DeliveryAttemptTimeout: 1, + RetryMinBackoff: 1, + RetryMaxBackoff: 1, + } +} + +func TestTransaction(t *testing.T) { + t.Run("Create webhook", func(t *testing.T) { + th := newTestHelper() + defer th.db.Close() + + webhook := makeWebhook() + err := th.webhookRepository.Create(webhook) + assert.Nil(t, err) + }) + + t.Run("Update webhook", func(t *testing.T) { + th := newTestHelper() + defer th.db.Close() + + webhook := makeWebhook() + err := th.webhookRepository.Create(webhook) + assert.Nil(t, err) + + webhook.ValidStatusCodes = pq.Int32Array{200, 201, 204} + err = th.webhookRepository.Update(webhook) + assert.Nil(t, err) + + options := postmand.RepositoryGetOptions{Filters: map[string]interface{}{"id": webhook.ID}} + webhookFromRepository, err := th.webhookRepository.Get(&options) + assert.Nil(t, err) + assert.Equal(t, pq.Int32Array{200, 201, 204}, webhookFromRepository.ValidStatusCodes) + }) + + t.Run("Delete webhook", func(t *testing.T) { + th := newTestHelper() + defer th.db.Close() + + webhook := makeWebhook() + err := th.webhookRepository.Create(webhook) + assert.Nil(t, err) + + err = th.webhookRepository.Delete(webhook.ID) + assert.Nil(t, err) + + options := postmand.RepositoryGetOptions{Filters: map[string]interface{}{"id": webhook.ID}} + _, err = th.webhookRepository.Get(&options) + assert.Equal(t, sql.ErrNoRows, err) + }) + + t.Run("Get webhook", func(t *testing.T) { + th := newTestHelper() + defer th.db.Close() + + webhook := makeWebhook() + err := th.webhookRepository.Create(webhook) + assert.Nil(t, err) + + options := postmand.RepositoryGetOptions{Filters: map[string]interface{}{"id": webhook.ID}} + webhookFromRepository, err := th.webhookRepository.Get(&options) + assert.Nil(t, err) + assert.Equal(t, webhook.ID, webhookFromRepository.ID) + }) + + t.Run("List webhook", func(t *testing.T) { + th := newTestHelper() + defer th.db.Close() + + webhook1 := makeWebhook() + err := th.webhookRepository.Create(webhook1) + assert.Nil(t, err) + + webhook2 := makeWebhook() + err = th.webhookRepository.Create(webhook2) + assert.Nil(t, err) + + options := postmand.RepositoryListOptions{Limit: 1, Offset: 1, OrderBy: "created_at DESC"} + webhooks, err := th.webhookRepository.List(&options) + assert.Nil(t, err) + assert.Len(t, webhooks, 1) + assert.Equal(t, webhook2.ID, webhooks[0].ID) + }) +}