From caaa0696606a99153002148155449390d173d89c Mon Sep 17 00:00:00 2001 From: Peter Stuifzand Date: Wed, 20 Apr 2022 13:22:06 +0200 Subject: [PATCH] Problem: no sources in database Solution: add sources in database and tests --- .drone.yml | 15 ++- cmd/eksterd/database_test.go | 123 ++++++++++++++++++ .../000008_create_table_sources.down.sql | 1 + .../000008_create_table_sources.up.sql | 20 +++ cmd/eksterd/main.go | 8 +- cmd/eksterd/micropub.go | 17 ++- 6 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 cmd/eksterd/database_test.go create mode 100644 cmd/eksterd/db/migrations/000008_create_table_sources.down.sql create mode 100644 cmd/eksterd/db/migrations/000008_create_table_sources.up.sql diff --git a/.drone.yml b/.drone.yml index b9c58ec..03a9257 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,6 +7,17 @@ workspace: base: /go path: src/p83.nl/go/ekster +services: + - name: redis + image: redis:5 + - name: database + image: postgres:14 + environment: + POSTGRES_DB: ekster_testing + POSTGRES_USER: postgres + POSTGRES_PASSWORD: simple + POSTGRES_HOST_AUTH_METHOD: trust + steps: - name: testing image: golang:1.18-alpine @@ -18,11 +29,9 @@ steps: - go version - apk --no-cache add git - go get -d -t ./... - - go install honnef.co/go/tools/cmd/staticcheck@latest - - go build p83.nl/go/ekster/cmd/eksterd + - go build -buildvcs=false p83.nl/go/ekster/cmd/eksterd - go vet ./... - go test -v ./... - - staticcheck ./... - name: publish-personal image: plugins/docker diff --git a/cmd/eksterd/database_test.go b/cmd/eksterd/database_test.go new file mode 100644 index 0000000..38a9657 --- /dev/null +++ b/cmd/eksterd/database_test.go @@ -0,0 +1,123 @@ +/* + * Ekster is a microsub server + * Copyright (c) 2022 The Ekster authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "database/sql" + "log" + "net/http/httptest" + "os" + "testing" + + "github.com/gomodule/redigo/redis" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type DatabaseSuite struct { + suite.Suite + + URL string + Database *sql.DB + + RedisURL string + Redis redis.Conn +} + +func (s *DatabaseSuite) SetupSuite() { + db, err := sql.Open("postgres", s.URL) + if err != nil { + log.Fatal(err) + } + s.Database = db + + conn, err := redis.Dial("tcp", s.RedisURL) + if err != nil { + log.Fatal(err) + } + s.Redis = conn + _, err = s.Redis.Do("SELECT", "1") + if err != nil { + log.Fatal(err) + } +} + +func (s *DatabaseSuite) TearDownSuite() { + err := s.Database.Close() + if err != nil { + log.Fatal(err) + } + + err = s.Redis.Close() + if err != nil { + log.Fatal(err) + } +} + +type databaseSuite struct { + DatabaseSuite +} + +func (d *databaseSuite) TestGetChannelFromAuthorization() { + _, err := d.Database.Exec(`truncate "sources", "channels", "feeds", "subscriptions","items"`) + assert.NoError(d.T(), err, "truncate sources, channels, feeds") + row := d.Database.QueryRow(`INSERT INTO "channels" (uid, name, created_at, updated_at) VALUES ('abcdef', 'Channel', now(), now()) RETURNING "id"`) + var id int + err = row.Scan(&id) + assert.NoError(d.T(), err, "insert channel") + _, err = d.Database.Exec(`INSERT INTO "sources" (channel_id, auth_code, created_at, updated_at) VALUES ($1, '1234', now(), now())`, id) + assert.NoError(d.T(), err, "insert sources") + + // source_id found + r := httptest.NewRequest("POST", "/micropub?source_id=1234", nil) + c, err := getChannelFromAuthorization(r, d.Redis, d.Database) + assert.NoError(d.T(), err, "channel from source_id") + assert.Equal(d.T(), "abcdef", c, "channel uid found") + + // source_id not found + r = httptest.NewRequest("POST", "/micropub?source_id=1111", nil) + c, err = getChannelFromAuthorization(r, d.Redis, d.Database) + assert.Error(d.T(), err, "channel from authorization header") + assert.Equal(d.T(), "", c, "channel uid found") +} + +func TestDatabaseSuite(t *testing.T) { + if testing.Short() { + t.Skip("Skip test for database") + } + + databaseURL := os.Getenv("DATABASE_TEST_URL") + if databaseURL == "" { + databaseURL = "host=database user=postgres password=simple dbname=ekster_testing sslmode=disable" + } + databaseSuite := &databaseSuite{ + DatabaseSuite{ + URL: databaseURL, + RedisURL: "redis:6379", + }, + } + + databaseURL = "postgres://postgres@database/ekster_testing?sslmode=disable&user=postgres&password=simple" + err := runMigrations(databaseURL) + if err != nil { + log.Fatal(err) + } + + suite.Run(t, databaseSuite) +} diff --git a/cmd/eksterd/db/migrations/000008_create_table_sources.down.sql b/cmd/eksterd/db/migrations/000008_create_table_sources.down.sql new file mode 100644 index 0000000..55b3d1a --- /dev/null +++ b/cmd/eksterd/db/migrations/000008_create_table_sources.down.sql @@ -0,0 +1 @@ +DROP TABLE "sources"; diff --git a/cmd/eksterd/db/migrations/000008_create_table_sources.up.sql b/cmd/eksterd/db/migrations/000008_create_table_sources.up.sql new file mode 100644 index 0000000..be05e45 --- /dev/null +++ b/cmd/eksterd/db/migrations/000008_create_table_sources.up.sql @@ -0,0 +1,20 @@ +BEGIN; +CREATE OR REPLACE FUNCTION update_timestamp() + RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$ language 'plpgsql'; +COMMIT; + +CREATE TABLE "sources" ( + "id" int primary key generated always as identity, + "channel_id" int not null, + "auth_code" varchar(64) not null, + "created_at" timestamp DEFAULT current_timestamp, + "updated_at" timestamp DEFAULT current_timestamp +); + +CREATE TRIGGER sources_update_timestamp BEFORE INSERT OR UPDATE ON "sources" +FOR EACH ROW EXECUTE PROCEDURE update_timestamp(); diff --git a/cmd/eksterd/main.go b/cmd/eksterd/main.go index 534b6e6..5054084 100644 --- a/cmd/eksterd/main.go +++ b/cmd/eksterd/main.go @@ -167,8 +167,8 @@ func main() { // } // TODO(peter): automatically gather this information from login or otherwise - - err := runMigrations() + databaseURL := "postgres://postgres@database/ekster?sslmode=disable&user=postgres&password=simple" + err := runMigrations(databaseURL) if err != nil { log.Fatalf("Error with migrations: %s", err) } @@ -205,12 +205,12 @@ func (l Log) Verbose() bool { return false } -func runMigrations() error { +func runMigrations(databaseURL string) error { d, err := iofs.New(migrations, "db/migrations") if err != nil { return err } - m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://postgres@database/ekster?sslmode=disable&user=postgres&password=simple") + m, err := migrate.NewWithSourceInstance("iofs", d, databaseURL) if err != nil { return err } diff --git a/cmd/eksterd/micropub.go b/cmd/eksterd/micropub.go index 9db070c..7f87eac 100644 --- a/cmd/eksterd/micropub.go +++ b/cmd/eksterd/micropub.go @@ -20,6 +20,7 @@ package main import ( "crypto/sha1" + "database/sql" "encoding/json" "fmt" "log" @@ -65,7 +66,7 @@ func (h *micropubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { var channel string - channel, err = getChannelFromAuthorization(r, conn) + channel, err = getChannelFromAuthorization(r, conn, h.Backend.database) if err != nil { log.Println(err) http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -165,15 +166,21 @@ func parseIncomingItem(r *http.Request) (*microsub.Item, error) { return nil, fmt.Errorf("content-type %q is not supported", contentType) } -func getChannelFromAuthorization(r *http.Request, conn redis.Conn) (string, error) { +func getChannelFromAuthorization(r *http.Request, conn redis.Conn, database *sql.DB) (string, error) { // backward compatible sourceID := r.URL.Query().Get("source_id") if sourceID != "" { - channel, err := redis.String(conn.Do("HGET", "sources", sourceID)) - if err != nil { + row := database.QueryRow(` +SELECT c.uid +FROM "sources" AS "s" +INNER JOIN "channels" AS "c" ON s.channel_id = c.id +WHERE "auth_code" = $1 +`, sourceID) + + var channel string + if err := row.Scan(&channel); err == sql.ErrNoRows { return "", errors.Wrapf(err, "could not get channel for sourceID: %s", sourceID) } - return channel, nil }